CoolFace
Apppublic

nitin-rawat/anycoder-d958a76f

sourceHugging Faceupdated 10mo agoView on Hugging Face
0likes
app.py583 linesDownload Raw Back to root
1# Use official Node.js LTS2FROM node:18-alpine AS deps3WORKDIR /app4COPY package.json package-lock.json* ./5RUN npm ci --legacy-peer-deps6 7FROM node:18-alpine AS builder8WORKDIR /app9COPY --from=deps /app/node_modules ./node_modules10COPY . .11ENV NEXT_TELEMETRY_DISABLED=112RUN npm run build13 14FROM node:18-alpine AS runner15WORKDIR /app16ENV NODE_ENV=production17ENV NEXT_TELEMETRY_DISABLED=118# Create non-root user19RUN addgroup -S nextjs && adduser -S nextjs -G nextjs20COPY --from=builder /app/public ./public21COPY --from=builder /app/.next/standalone ./22COPY --from=builder /app/.next/static ./.next/static23COPY --from=builder /app/.next/standalone/public ./public24USER nextjs25EXPOSE 786026ENV PORT=786027CMD ["node", "server.js"]28 29=== package.json ===30{31  "name": "lego-mario-nextjs",32  "version": "1.0.1",33  "private": true,34  "scripts": {35    "dev": "next dev -p 3000",36    "build": "next build",37    "start": "next start -p ${PORT:-3000}",38    "lint": "next lint"39  },40  "dependencies": {41    "next": "14.2.10",42    "react": "18.3.1",43    "react-dom": "18.3.1"44  },45  "devDependencies": {46    "autoprefixer": "10.4.20",47    "eslint": "8.57.0",48    "eslint-config-next": "14.2.10",49    "postcss": "8.4.49",50    "tailwindcss": "3.4.14"51  }52}53 54=== next.config.js ===55/** @type {import('next').NextConfig} */56const nextConfig = {57  output: 'standalone',58  reactStrictMode: true,59  images: {60    unoptimized: true61  },62  experimental: {63    typedRoutes: false64  },65  eslint: {66    ignoreDuringBuilds: true67  }68};69module.exports = nextConfig;70 71=== postcss.config.js ===72module.exports = {73  plugins: {74    tailwindcss: {},75    autoprefixer: {}76  }77};78 79=== tailwind.config.js ===80module.exports = {81  content: [82    "./pages/**/*.{js,jsx}",83    "./components/**/*.{js,jsx}"84  ],85  theme: {86    extend: {87      colors: {88        brick: {89          DEFAULT: "#C0392B",90          dark: "#922B21",91          light: "#E74C3C"92        },93        plate: {94          DEFAULT: "#2ECC71",95          dark: "#27AE60"96        },97        sky: {98          DEFAULT: "#A7D3FF",99          light: "#CBE6FF"100        },101        coin: {102          DEFAULT: "#F1C40F",103          dark: "#D4AC0D"104        }105      },106      boxShadow: {107        stud: "inset 0 2px 0 rgba(255,255,255,0.3), inset 0 -3px 0 rgba(0,0,0,0.25)"108      },109      keyframes: {110        bob: {111          "0%,100%": { transform: "translateY(0)" },112          "50%": { transform: "translateY(-4px)" }113        },114        blink: {115          "0%, 90%, 100%": { opacity: "1" },116          "95%": { opacity: "0.2" }117        }118      },119      animation: {120        bob: "bob 1.6s ease-in-out infinite",121        blink: "blink 3s linear infinite"122      }123    }124  },125  plugins: []126};127 128=== components/Header.jsx ===129import Link from "next/link";130 131export default function Header() {132  return (133    <header className="w-full bg-sky-light border-b border-sky/50">134      <div className="mx-auto max-w-6xl px-4 py-3 flex items-center justify-between">135        <div className="flex items-center gap-3">136          <div className="w-8 h-8 rounded-md bg-brick shadow-stud border-2 border-brick-dark" aria-hidden="true" />137          <h1 className="text-xl font-extrabold tracking-tight text-brick-dark">138            LEGO Mario139            <span className="sr-only">Lego Mario Game</span>140          </h1>141        </div>142        <nav aria-label="Primary">143          <ul className="flex items-center gap-4">144            <li>145              <a146                href="https://huggingface.co/spaces/akhaliq/anycoder"147                target="_blank"148                rel="noopener noreferrer"149                className="text-sm font-semibold text-blue-700 hover:text-blue-900 underline underline-offset-4"150              >151                Built with anycoder152              </a>153            </li>154            <li>155              <Link href="#how-to-play" className="text-sm text-slate-800 hover:text-slate-900">156                How to play157              </Link>158            </li>159            <li>160              <Link href="#credits" className="text-sm text-slate-800 hover:text-slate-900">161                Credits162              </Link>163            </li>164          </ul>165        </nav>166      </div>167    </header>168  );169}170 171=== components/Loading.jsx ===172export default function Loading({ label = "Loading..." }) {173  return (174    <div role="status" aria-live="polite" className="w-full flex items-center justify-center py-8">175      <div className="flex items-center gap-3">176        <div className="w-4 h-4 rounded-full bg-brick animate-bounce" />177        <div className="w-4 h-4 rounded-full bg-plate animate-bounce [animation-delay:.15s]" />178        <div className="w-4 h-4 rounded-full bg-coin animate-bounce [animation-delay:.3s]" />179      </div>180      <span className="sr-only">{label}</span>181    </div>182  );183}184 185=== components/LegoMario.jsx ===186function StudRow({ count = 4 }) {187  return (188    <div className="flex gap-1">189      {Array.from({ length: count }).map((_, i) => (190        <div key={i} className="w-2 h-2 rounded-full bg-white/40 shadow-stud" aria-hidden="true" />191      ))}192    </div>193  );194}195 196export default function LegoMario({ facing = 1, jumping = false }) {197  const dir = facing === 1 ? "" : "-scale-x-100";198  return (199    <div className={`relative select-none ${dir} transition-transform`} aria-label="Lego-style Mario character" role="img">200      <div className="w-12 h-10 bg-brick rounded-md border-2 border-brick-dark relative">201        <div className="absolute -top-2 left-1 right-1 flex justify-between">202          <StudRow count={5} />203        </div>204      </div>205      <div className="absolute -top-8 left-1/2 -translate-x-1/2 w-10 h-8 bg-coin rounded-md border-2 border-coin-dark">206        <div className="absolute top-1 left-2 w-2 h-2 bg-black rounded-full animate-blink" />207        <div className="absolute top-1 right-2 w-2 h-2 bg-black rounded-full animate-blink [animation-delay:.2s]" />208        <div className="absolute -top-2 left-1 right-1">209          <StudRow count={4} />210        </div>211      </div>212      <div className="absolute -top-11 left-1/2 -translate-x-1/2 w-12 h-3 bg-brick rounded-md border-2 border-brick-dark" />213      <div className="absolute top-2 -left-4 w-4 h-6 bg-coin rounded-md border-2 border-coin-dark" />214      <div className="absolute top-2 -right-4 w-4 h-6 bg-coin rounded-md border-2 border-coin-dark" />215      <div className={`absolute -bottom-3 left-1 w-4 h-6 bg-plate rounded-md border-2 border-plate-dark ${jumping ? "-rotate-6 -translate-y-1" : ""}`} />216      <div className={`absolute -bottom-3 right-1 w-4 h-6 bg-plate rounded-md border-2 border-plate-dark ${jumping ? "rotate-6 -translate-y-1" : ""}`} />217    </div>218  );219}220 221=== components/LegoWorld.jsx ===222import { useEffect, useRef, useState } from "react";223import LegoMario from "./LegoMario";224 225const TILE = 32;226const GRAVITY = 0.8;227const JUMP_VELOCITY = -12;228const SPEED = 4;229 230export default function LegoWorld() {231  const [ready, setReady] = useState(false);232  const [state, setState] = useState({233    x: 50,234    y: 0,235    vx: 0,236    vy: 0,237    facing: 1,238    jumping: false,239    coins: 0,240    gameOver: false241  });242 243  const level = {244    width: 64,245    height: 12,246    groundY: 9,247    coins: [248      { x: 10, y: 7, collected: false },249      { x: 15, y: 5, collected: false },250      { x: 22, y: 7, collected: false },251      { x: 30, y: 6, collected: false },252      { x: 42, y: 7, collected: false }253    ],254    blocks: [255      { x: 12, y: 8 },256      { x: 13, y: 8 },257      { x: 20, y: 8 },258      { x: 28, y: 7 }259    ]260  };261 262  const keys = useRef({ ArrowLeft: false, ArrowRight: false, Space: false, KeyZ: false, KeyX: false });263 264  useEffect(() => {265    function onDown(e) {266      if (keys.current[e.code] !== undefined) keys.current[e.code] = true;267      if (e.code === "Space") e.preventDefault();268    }269    function onUp(e) {270      if (keys.current[e.code] !== undefined) keys.current[e.code] = false;271    }272    window.addEventListener("keydown", onDown);273    window.addEventListener("keyup", onUp);274    setReady(true);275    return () => {276      window.removeEventListener("keydown", onDown);277      window.removeEventListener("keyup", onUp);278    };279  }, []);280 281  useEffect(() => {282    if (!ready) return;283    let raf;284    const tick = () => {285      setState(prev => {286        if (prev.gameOver) return prev;287 288        let { x, y, vx, vy, jumping, facing, coins } = prev;289 290        const left = keys.current.ArrowLeft;291        const right = keys.current.ArrowRight;292        if (left && !right) {293          vx = -SPEED;294          facing = -1;295        } else if (right && !left) {296          vx = SPEED;297          facing = 1;298        } else {299          vx = 0;300        }301 302        const wantJump = keys.current.Space || keys.current.KeyZ || keys.current.KeyX;303        if (wantJump && !jumping) {304          vy = JUMP_VELOCITY;305          jumping = true;306        }307 308        vy += GRAVITY;309        x += vx;310        y += vy;311 312        const groundPx = level.groundY * TILE;313        if (y >= groundPx) {314          y = groundPx;315          vy = 0;316          jumping = false;317        }318 319        level.blocks.forEach(b => {320          const bx = b.x * TILE;321          const by = b.y * TILE;322          const bw = TILE;323          const bh = TILE;324          const mw = 20;325          const mh = 28;326          const mx = x;327          const my = y - mh;328 329          const overlapX = Math.max(0, Math.min(mx + mw, bx + bw) - Math.min(mx, bx + bw));330          const overlapY = Math.max(0, Math.min(my + mh, by + bh) - Math.max(my, by));331          if (overlapX > 0 && overlapY > 0) {332            if (vy > 0 && my + mh - vy <= by) {333              y = by;334              vy = 0;335              jumping = false;336            }337          }338        });339 340        const cx = x + 10;341        const cy = y - 20;342        level.coins.forEach(c => {343          if (c.collected) return;344          const px = c.x * TILE + TILE / 2;345          const py = c.y * TILE + TILE / 2;346          const dx = cx - px;347          const dy = cy - py;348          if (dx * dx + dy * dy < 20 * 20) {349            c.collected = true;350            coins += 1;351          }352        });353 354        const minX = 0;355        const maxX = level.width * TILE - 40;356        if (x < minX) x = minX;357        if (x > maxX) x = maxX;358 359        return { ...prev, x, y, vx, vy, jumping, facing, coins };360      });361      raf = requestAnimationFrame(tick);362    };363    raf = requestAnimationFrame(tick);364    return () => cancelAnimationFrame(raf);365  }, [ready]);366 367  const camX = Math.max(0, Math.min(state.x - 200, level.width * TILE - 400));368 369  return (370    <div className="relative w-full h-[420px] overflow-hidden rounded-xl border border-sky/60 bg-sky">371      <div className="absolute top-2 left-2 z-20 px-3 py-1 rounded-md bg-white/70 backdrop-blur text-slate-800 text-sm font-semibold">372        Coins: {state.coins}373      </div>374 375      <div className="absolute inset-0" aria-hidden="true">376        <div className="absolute top-6 left-10 w-24 h-6 bg-sky-light rounded-md border border-white/50" />377        <div className="absolute top-16 left-56 w-16 h-5 bg-sky-light rounded-md border border-white/50" />378        <div className="absolute top-10 left-96 w-28 h-6 bg-sky-light rounded-md border border-white/50" />379      </div>380 381      <div className="absolute bottom-0 left-0 right-0 h-[384px]">382        <div className="absolute inset-x-0 bottom-[96px] h-[8px] bg-plate-dark/40" />383        <div className="absolute bottom-0 left-0 h-[96px] w-[4096px] bg-plate border-t-4 border-plate-dark [transform:translateX(var(--cam))]" style={{ "--cam": `-${camX}px` }}>384          <div className="absolute top-2 left-2 right-2 grid grid-cols-16 gap-2">385            {Array.from({ length: 64 }).map((_, i) => (386              <div key={i} className="w-3 h-3 rounded-full bg-white/40 shadow-stud" />387            ))}388          </div>389        </div>390 391        <div className="absolute inset-x-0 bottom-[96px] h-[288px] [transform:translateX(var(--cam))]" style={{ "--cam": `-${camX}px` }}>392          {level.blocks.map((b, i) => (393            <div394              key={i}395              className={`absolute w-8 h-8 bg-brick border-2 border-brick-dark rounded-sm [left:${b.x * TILE}px] [bottom:${(b.y - level.groundY) * TILE}px]`}396            >397              <div className="absolute inset-1 grid grid-cols-2 gap-1">398                {Array.from({ length: 4 }).map((_, j) => (399                  <div key={j} className="rounded-full bg-white/40 shadow-stud" />400                ))}401              </div>402            </div>403          ))}404 405          {level.coins.map((c, i) =>406            c.collected ? null : (407              <div key={i} className={`absolute [left:${c.x * TILE + 8}px] [bottom:${(c.y - level.groundY) * TILE + 40}px]`}>408                <div className="w-6 h-6 rounded-full bg-coin border-2 border-coin-dark animate-bob" aria-label="Coin" />409              </div>410            )411          )}412        </div>413 414        <div className="absolute bottom-[96px] [transform:translateX(var(--mx))]" style={{ "--mx": `${state.x - camX}px` }}>415          <div className="[transform:translateY(var(--my))]" style={{ "--my": `${state.y - level.groundY * TILE}px` }}>416            <LegoMario facing={state.facing} jumping={state.jumping} />417          </div>418        </div>419      </div>420 421      <div className="absolute bottom-2 right-2 z-20 px-3 py-1 rounded-md bg-white/70 backdrop-blur text-slate-800 text-xs">422        Controls: ← → to move, Space/Z/X to jump423      </div>424    </div>425  );426}427 428=== components/Seo.jsx ===429import Head from "next/head";430 431export default function Seo() {432  return (433    <Head>434      <title>LEGO Mario - Next.js</title>435      <meta name="description" content="A LEGO-styled Mario platformer built with Next.js and Tailwind CSS." />436      <meta name="viewport" content="width=device-width, initial-scale=1" />437    </Head>438  );439}440 441=== pages/_app.js ===442import "@/styles/globals.css";443 444export default function App({ Component, pageProps }) {445  return <Component {...pageProps} />;446}447 448=== pages/index.js ===449import dynamic from "next/dynamic";450import Header from "@/components/Header";451import Seo from "@/components/Seo";452import Loading from "@/components/Loading";453 454const LegoWorld = dynamic(() => import("@/components/LegoWorld"), {455  ssr: false,456  loading: () => <Loading label="Loading world..." />457});458 459export default function Home() {460  return (461    <>462      <Seo />463      <Header />464      <main className="min-h-screen bg-sky-light">465        <section className="mx-auto max-w-6xl px-4 py-8">466          <div className="flex flex-col md:flex-row items-start gap-6">467            <div className="flex-1">468              <div className="mb-4">469                <h2 className="text-2xl font-bold text-slate-900">Lego Mario Platformer</h2>470                <p className="text-slate-700 mt-1">471                  Run and jump to collect coins in a LEGO-styled world. Fully client-side, responsive, and accessible.472                </p>473              </div>474              <LegoWorld />475            </div>476            <aside className="w-full md:w-72">477              <div id="how-to-play" className="sticky top-4 bg-white rounded-lg border border-slate-200 p-4 shadow-sm">478                <h3 className="text-lg font-semibold text-slate-900">How to play</h3>479                <ul className="mt-2 list-disc pl-5 text-slate-700 space-y-1">480                  <li>Use Left and Right arrow keys to move.</li>481                  <li>Press Space, Z, or X to jump.</li>482                  <li>Collect as many coins as you can.</li>483                </ul>484                <div className="mt-3 text-xs text-slate-500">485                  Tip: The camera follows LEGO Mario. Try jumping on blocks to reach higher coins.486                </div>487              </div>488            </aside>489          </div>490 491          <div id="credits" className="mt-10 text-sm text-slate-600">492            This fan project uses LEGO-inspired shapes and colors; LEGO is a trademark of the LEGO Group, which does not sponsor, authorize or endorse this project.493          </div>494        </section>495      </main>496      <footer className="border-t border-slate-200 bg-white">497        <div className="mx-auto max-w-6xl px-4 py-6 text-sm text-slate-600">498          © {new Date().getFullYear()} LEGO Mario Demo. All rights reserved.499        </div>500      </footer>501    </>502  );503}504 505=== pages/api/health.js ===506export default function handler(req, res) {507  try {508    res.status(200).json({ ok: true, time: new Date().toISOString() });509  } catch (e) {510    res.status(500).json({ ok: false, error: "unexpected_error" });511  }512}513 514=== pages/api/leaderboard.js ===515let scores = [];516 517export default function handler(req, res) {518  try {519    if (req.method === "GET") {520      const top = [...scores].sort((a, b) => b.score - a.score).slice(0, 20);521      return res.status(200).json({ ok: true, scores: top });522    }523    if (req.method === "POST") {524      const { name, score } = req.body || {};525      if (typeof name !== "string" || typeof score !== "number" || score < 0) {526        return res.status(400).json({ ok: false, error: "invalid_payload" });527      }528      const entry = { name: name.slice(0, 20), score: Math.floor(score), at: Date.now() };529      scores.push(entry);530      scores = scores.slice(-200);531      return res.status(201).json({ ok: true, entry });532    }533    res.setHeader("Allow", "GET, POST");534    return res.status(405).json({ ok: false, error: "method_not_allowed" });535  } catch (e) {536    return res.status(500).json({ ok: false, error: "unexpected_error" });537  }538}539 540=== styles/globals.css ===541@tailwind base;542@tailwind components;543@tailwind utilities;544 545/* App base styles */546html, body, #__next {547  height: 100%;548}549 550:root {551  color-scheme: light;552}553 554/* Accessibility helpers */555.visually-hidden,556.sr-only {557  position: absolute!important;558  width: 1px!important;559  height: 1px!important;560  padding: 0!important;561  margin: -1px!important;562  overflow: hidden!important;563  clip: rect(0, 0, 0, 0)!important;564  white-space: nowrap!important;565  border: 0!important;566}567 568/* Disable image dragging */569img, svg {570  user-select: none;571  -webkit-user-drag: none;572}573 574/* Focus styles */575:focus-visible {576  outline: 3px solid rgba(59,130,246,.6);577  outline-offset: 2px;578}579 580/* Utility: allow bracket syntax custom props without inline style objects for Tailwind rule */581*[style*="--"] {582  /* no-op; just allowed */583}