From 6a1b3ec572b056543c737cab184ba6ca6f92b7aa Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Mon, 20 Apr 2026 21:46:21 +0700 Subject: [PATCH] feat: add design canvas assets from claude design --- public/assets/Night Ninja Assets.html | 23 + public/assets/design-canvas.jsx | 253 ++++++++++ .../sketch-2026-04-20T14-24-41-j058tw.napkin | 6 + public/assets/src/canvas.jsx | 445 ++++++++++++++++++ public/assets/src/characters.jsx | 391 +++++++++++++++ public/assets/src/pixel.jsx | 122 +++++ public/assets/src/scenes.jsx | 250 ++++++++++ public/assets/src/tiles-ui.jsx | 338 +++++++++++++ 8 files changed, 1828 insertions(+) create mode 100644 public/assets/Night Ninja Assets.html create mode 100644 public/assets/design-canvas.jsx create mode 100644 public/assets/scraps/sketch-2026-04-20T14-24-41-j058tw.napkin create mode 100644 public/assets/src/canvas.jsx create mode 100644 public/assets/src/characters.jsx create mode 100644 public/assets/src/pixel.jsx create mode 100644 public/assets/src/scenes.jsx create mode 100644 public/assets/src/tiles-ui.jsx diff --git a/public/assets/Night Ninja Assets.html b/public/assets/Night Ninja Assets.html new file mode 100644 index 0000000..515f21d --- /dev/null +++ b/public/assets/Night Ninja Assets.html @@ -0,0 +1,23 @@ + + + + +Night Ninja : Twilight Voyage — Assets + + + +
+ + + + + + + + + + + diff --git a/public/assets/design-canvas.jsx b/public/assets/design-canvas.jsx new file mode 100644 index 0000000..1806221 --- /dev/null +++ b/public/assets/design-canvas.jsx @@ -0,0 +1,253 @@ + +// DesignCanvas.jsx — Figma-ish design canvas wrapper +// Warm gray grid bg + Sections + Artboards + PostIt notes. +// No assets, no deps. + +const DC = { + bg: '#f0eee9', + grid: 'rgba(0,0,0,0.06)', + label: 'rgba(60,50,40,0.7)', + title: 'rgba(40,30,20,0.85)', + subtitle: 'rgba(60,50,40,0.6)', + postitBg: '#fef4a8', + postitText: '#5a4a2a', + font: '-apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif', +}; + +// ───────────────────────────────────────────────────────────── +// Main canvas — transform-based pan/zoom viewport +// +// Input mapping (Figma-style): +// • trackpad pinch → zoom (ctrlKey wheel; Safari gesture* events) +// • trackpad scroll → pan (two-finger) +// • mouse wheel → zoom (notched; distinguished from trackpad scroll) +// • middle-drag / primary-drag-on-bg → pan +// +// Transform state lives in a ref and is written straight to the DOM +// (translate3d + will-change) so wheel ticks don't go through React — +// keeps pans at 60fps on dense canvases. +// ───────────────────────────────────────────────────────────── +function DesignCanvas({ children, minScale = 0.1, maxScale = 8, style = {} }) { + const vpRef = React.useRef(null); + const worldRef = React.useRef(null); + const tf = React.useRef({ x: 0, y: 0, scale: 1 }); + + const apply = React.useCallback(() => { + const { x, y, scale } = tf.current; + const el = worldRef.current; + if (el) el.style.transform = `translate3d(${x}px, ${y}px, 0) scale(${scale})`; + }, []); + + React.useEffect(() => { + const vp = vpRef.current; + if (!vp) return; + + const zoomAt = (cx, cy, factor) => { + const r = vp.getBoundingClientRect(); + const px = cx - r.left, py = cy - r.top; + const t = tf.current; + const next = Math.min(maxScale, Math.max(minScale, t.scale * factor)); + const k = next / t.scale; + // keep the world point under the cursor fixed + t.x = px - (px - t.x) * k; + t.y = py - (py - t.y) * k; + t.scale = next; + apply(); + }; + + // Mouse-wheel vs trackpad-scroll heuristic. A physical wheel sends + // line-mode deltas (Firefox) or large integer pixel deltas with no X + // component (Chrome/Safari, typically multiples of 100/120). Trackpad + // two-finger scroll sends small/fractional pixel deltas, often with + // non-zero deltaX. ctrlKey is set by the browser for trackpad pinch. + const isMouseWheel = (e) => + e.deltaMode !== 0 || + (e.deltaX === 0 && Number.isInteger(e.deltaY) && Math.abs(e.deltaY) >= 40); + + const onWheel = (e) => { + e.preventDefault(); + if (isGesturing) return; // Safari: gesture* owns the pinch — discard concurrent wheels + if (e.ctrlKey) { + // trackpad pinch (or explicit ctrl+wheel) + zoomAt(e.clientX, e.clientY, Math.exp(-e.deltaY * 0.01)); + } else if (isMouseWheel(e)) { + // notched mouse wheel — fixed-ratio step per click + zoomAt(e.clientX, e.clientY, Math.exp(-Math.sign(e.deltaY) * 0.18)); + } else { + // trackpad two-finger scroll — pan + tf.current.x -= e.deltaX; + tf.current.y -= e.deltaY; + apply(); + } + }; + + // Safari sends native gesture* events for trackpad pinch with a smooth + // e.scale; preferring these over the ctrl+wheel fallback gives a much + // better feel there. No-ops on other browsers. Safari also fires + // ctrlKey wheel events during the same pinch — isGesturing makes + // onWheel drop those entirely so they neither zoom nor pan. + let gsBase = 1; + let isGesturing = false; + const onGestureStart = (e) => { e.preventDefault(); isGesturing = true; gsBase = tf.current.scale; }; + const onGestureChange = (e) => { + e.preventDefault(); + zoomAt(e.clientX, e.clientY, (gsBase * e.scale) / tf.current.scale); + }; + const onGestureEnd = (e) => { e.preventDefault(); isGesturing = false; }; + + // Drag-pan: middle button anywhere, or primary button starting on the + // canvas background (not inside an artboard). + let drag = null; + const onPointerDown = (e) => { + const onBg = e.target === vp || e.target === worldRef.current; + if (!(e.button === 1 || (e.button === 0 && onBg))) return; + e.preventDefault(); + vp.setPointerCapture(e.pointerId); + drag = { id: e.pointerId, lx: e.clientX, ly: e.clientY }; + vp.style.cursor = 'grabbing'; + }; + const onPointerMove = (e) => { + if (!drag || e.pointerId !== drag.id) return; + tf.current.x += e.clientX - drag.lx; + tf.current.y += e.clientY - drag.ly; + drag.lx = e.clientX; drag.ly = e.clientY; + apply(); + }; + const onPointerUp = (e) => { + if (!drag || e.pointerId !== drag.id) return; + vp.releasePointerCapture(e.pointerId); + drag = null; + vp.style.cursor = ''; + }; + + vp.addEventListener('wheel', onWheel, { passive: false }); + vp.addEventListener('gesturestart', onGestureStart, { passive: false }); + vp.addEventListener('gesturechange', onGestureChange, { passive: false }); + vp.addEventListener('gestureend', onGestureEnd, { passive: false }); + vp.addEventListener('pointerdown', onPointerDown); + vp.addEventListener('pointermove', onPointerMove); + vp.addEventListener('pointerup', onPointerUp); + vp.addEventListener('pointercancel', onPointerUp); + return () => { + vp.removeEventListener('wheel', onWheel); + vp.removeEventListener('gesturestart', onGestureStart); + vp.removeEventListener('gesturechange', onGestureChange); + vp.removeEventListener('gestureend', onGestureEnd); + vp.removeEventListener('pointerdown', onPointerDown); + vp.removeEventListener('pointermove', onPointerMove); + vp.removeEventListener('pointerup', onPointerUp); + vp.removeEventListener('pointercancel', onPointerUp); + }; + }, [apply, minScale, maxScale]); + + const gridSvg = `url("data:image/svg+xml,%3Csvg width='120' height='120' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M120 0H0v120' fill='none' stroke='${encodeURIComponent(DC.grid)}' stroke-width='1'/%3E%3C/svg%3E")`; + return ( +
+
+ {children} +
+
+ ); +} + +// ───────────────────────────────────────────────────────────── +// Section — title + subtitle + h-stack of artboards (no wrap) +// ───────────────────────────────────────────────────────────── +function DCSection({ title, subtitle, children, gap = 48 }) { + return ( +
+
+
{title}
+ {subtitle && ( +
{subtitle}
+ )} +
+ {/* h-stack — clips offscreen, never wraps */} +
+ {children} +
+
+ ); +} + +// ───────────────────────────────────────────────────────────── +// Artboard — labeled card +// ───────────────────────────────────────────────────────────── +function DCArtboard({ label, children, width, height, style = {} }) { + return ( +
+ {label && ( +
{label}
+ )} +
+ {children} +
+
+ ); +} + +// ───────────────────────────────────────────────────────────── +// Post-it — absolute-positioned sticky note +// ───────────────────────────────────────────────────────────── +function DCPostIt({ children, top, left, right, bottom, rotate = -2, width = 180 }) { + return ( +
{children}
+ ); +} + +Object.assign(window, { DesignCanvas, DCSection, DCArtboard, DCPostIt }); + diff --git a/public/assets/scraps/sketch-2026-04-20T14-24-41-j058tw.napkin b/public/assets/scraps/sketch-2026-04-20T14-24-41-j058tw.napkin new file mode 100644 index 0000000..6023f37 --- /dev/null +++ b/public/assets/scraps/sketch-2026-04-20T14-24-41-j058tw.napkin @@ -0,0 +1,6 @@ +{ + "version": 1, + "created": "2026-04-20T14:24:41.949Z", + "modified": "2026-04-20T14:24:41.949Z", + "objects": [] +} \ No newline at end of file diff --git a/public/assets/src/canvas.jsx b/public/assets/src/canvas.jsx new file mode 100644 index 0000000..884d7dc --- /dev/null +++ b/public/assets/src/canvas.jsx @@ -0,0 +1,445 @@ +// Main canvas — assembles all NNTV pixel assets onto a single design canvas. +const { useState } = React; + +function Card({ title, subtitle, children, bg = NNTV.twilight, width = 'auto', pad = 20 }) { + return ( +
+ {title &&
{title}
} + {subtitle &&
{subtitle}
} + {children} +
+ ); +} + +function SpriteCard({ sprite, scale = 5, border = NNTV.shadow, colorDot }) { + return ( +
+
+ +
+
+ {colorDot && } + {sprite.label} +
+
{sprite.desc}
+
+ ); +} + +// ── 1. LOGO + PALETTE ────────────────────────────────────────────────── +function LogoSection() { + return ( + + +
+ {/* starfield */} +
+ {[[80,50],[600,40],[120,280],[680,260],[380,30]].map(([x,y],i)=>( +
+ ))} + +
NIGHT NINJA : TWILIGHT VOYAGE
+
+ — a turn-based stealth puzzle — +
+
+ + + +
+
GUARD COLORS · STRICT
+ {[ + ['static', NNTV.guardStatic, 'tomato · red'], + ['rotating', NNTV.guardRotating, 'blueberry · blue'], + ['blinking', NNTV.guardBlinking, 'corn · yellow'], + ['patrolling', NNTV.guardPatrolling, 'eggplant · purple'], + ['mirror', NNTV.guardMirror, 'lettuce · green'], + ['chaser', NNTV.guardChaser, 'pumpkin · orange'], + ].map(([k, c, label]) => ( +
+ + {k} + {c} + {label} +
+ ))} +
WORLD · TWILIGHT
+ {[ + ['midnight', NNTV.midnight], + ['twilight', NNTV.twilight], + ['dusk', NNTV.dusk], + ['plum', NNTV.plum], + ['moonlight', NNTV.moonlight], + ['moonGlow', NNTV.moonGlow], + ['cream', NNTV.cream], + ['carrot', NNTV.carrot], + ].map(([k, c]) => ( +
+ + {k} + {c} +
+ ))} +
+
+ + Guard colors match the engine's color tokens exactly — so a player reading the board sees the mechanic, not just the character. + + + ); +} + +// ── 2. CHARACTER SPRITES ─────────────────────────────────────────────── +function CharactersSection() { + return ( + + +
+ +
+
+ + +
+ + + + + + +
+
+ + +
+ + +
+
+ + + Princess sits on a carrot throne color. Tiara echoes the gold palace trim — foreshadowing Act 5 before you ever reach it. + +
+ ); +} + +// ── 3. TILES ──────────────────────────────────────────────────────────── +function TilesSection() { + const tiles = [ + ['Empty', TILE_EMPTY, TILE_EMPTY_PAL, 'dark safe cell with subtle dither'], + ['Wall', TILE_WALL, TILE_WALL_PAL, 'dressed stone block'], + ['Goal', TILE_GOAL, TILE_GOAL_PAL, 'grass patch — safe end'], + ['Lit (danger)', TILE_LIT, TILE_LIT_PAL, 'pulsing inner rings'], + ['Mirror', TILE_MIRROR, TILE_MIRROR_PAL, 'diamond — deflects beams'], + ['Preview', TILE_PREVIEW, TILE_PREVIEW_PAL, 'press V · corner markers'], + ]; + return ( + + +
+ {tiles.map(([n, a, p, d]) => ( +
+
+
{n}
+
{d}
+
+ ))} +
+
+ + {/* live board mock */} + +
+ +
+
+
+ ); +} + +function BoardMock() { + // 7x7 level layout. W=wall, .=empty, G=goal, L=lit, M=mirror + // guards/player rendered as overlays + const rows = [ + 'P......', + '.......', + '..WWW..', + '..W.W..', + '..WbW..', // b = blue guard inside wall box + '.......', + '......G', + ]; + const tileFor = (ch) => { + if (ch === 'W') return { art: TILE_WALL, pal: TILE_WALL_PAL }; + if (ch === 'G') return { art: TILE_GOAL, pal: TILE_GOAL_PAL }; + return { art: TILE_EMPTY, pal: TILE_EMPTY_PAL }; + }; + const SIZE = 48; + return ( +
+ {rows.map((row, r) => ( +
+ {[...row].map((ch, c) => ( +
+ +
+ ))} +
+ ))} + {/* lit cells — beam pointing right from blue guard */} + {[[4,3],[4,4]].map(([r,c],i)=>( +
+ ))} + {/* player at 0,0 */} +
+ +
+ {/* blueberry guard at 4,3 */} +
+ +
+
+ ); +} + +// ── 4. UI / HUD / ICONS ──────────────────────────────────────────────── +function UISection() { + const [livesState] = useState(2); + return ( + + +
+ {/* level */} +
+
LEVEL
+
07/12
+
+ {/* lives */} +
+
LIVES
+
+ {[0,1,2].map(i => ( + + ))} +
+
+ {/* turns */} +
+
TURN
+
014
+
+ {/* divider */} +
+ {/* icon buttons */} +
+ {[['undo', ICON_UNDO, 'Z'], ['redo', ICON_REDO, 'Y'], ['preview', ICON_EYE, 'V'], ['pause', ICON_PAUSE, 'Esc']].map(([k, art, key]) => ( +
+
+ +
+
{key}
+
+ ))} +
+
+ + + +
+ {['PLAY', 'LEVEL SELECT', 'CONTINUE'].map((t, i) => ( + + ))} + +
+
+ + +
+ {[['undo', ICON_UNDO], ['redo', ICON_REDO], ['eye', ICON_EYE], ['pause', ICON_PAUSE], ['gear', ICON_SETTINGS], ['lang', ICON_LANG], ['move', ICON_ARROW]].map(([name, art]) => ( +
+
+ +
+
{name}
+
+ ))} +
+
+ + ); +} + +// ── 5. POPUPS / SCREENS ──────────────────────────────────────────────── +function PopupsSection() { + return ( + + +
+
+ +
TWILIGHT VOYAGE
+
+ +
+
+ {['START GAME', 'LEVEL SELECT', 'GUIDE', 'SETTINGS'].map((t, i) => ( + + ))} +
+
+
+ + +
+
+
+ + +
+
DETECTED!
+
– 1 life · restarting level
+ +
+
+
+ + +
+
+
+ {[...'★★★'].map((s, i) => {s})} +
+
LEVEL CLEAR
+
14 turns · 0 detections
+ +
+
+
+ + +
+
+
+ +
"SOME RESCUES WERE NEVER MEANT TO BE…"
+
— the princess remains beyond your reach
+
+
+ + + ); +} + +// ── 6. ACT BACKGROUNDS ────────────────────────────────────────────────── +function BackgroundsSection() { + const acts = [ + ['garden', SCENES.garden], + ['walls', SCENES.walls], + ['fortress', SCENES.fortress], + ['underground', SCENES.underground], + ['palace', SCENES.palace], + ['chamber', SCENES.chamber], + ]; + return ( + + {acts.map(([k, s]) => ( + +
+
+ +
+
{s.desc}
+
+
+ ))} +
+ ); +} + +// ── 7. LEVEL INTRO MOCK ──────────────────────────────────────────────── +function LevelIntroSection() { + const story = [ + { act: SCENES.garden, name: 'Level 1 · Garden Path', body: 'You slip past the outskirts of the Vegetable Kingdom. Moonlight silvers the hedges. Nothing stirs — yet.' }, + { act: SCENES.fortress, name: 'Level 5 · Fortress Gate', body: 'A corn sentry blinks its torch on and off. Count the beats. Move with the dark.' }, + { act: SCENES.chamber, name: 'Level 12 · The Princess Chamber', body: 'The air feels watchful. She already knows you are here…' }, + ]; + return ( + + {story.map((s, i) => ( + +
+
+ +
+
+
+
{s.act.label.toUpperCase()}
+
{s.name}
+
{s.body}
+ +
+
+ + ))} + + ); +} + +// ── APP ───────────────────────────────────────────────────────────────── +function App() { + return ( + +
+
NIGHT NINJA : TWILIGHT VOYAGE
+
+ Pixel-art asset set for tiennm99/nntv. 32×32 characters, 16×16 tiles, 80×40 act backgrounds. + Strict guard-color readability preserved. Palette: evolution of the original indigo theme toward twilight plum + moonlight. +
+
+ + + + + + + +
+ ); +} + +ReactDOM.createRoot(document.getElementById('root')).render(); diff --git a/public/assets/src/characters.jsx b/public/assets/src/characters.jsx new file mode 100644 index 0000000..cb623b8 --- /dev/null +++ b/public/assets/src/characters.jsx @@ -0,0 +1,391 @@ +// Character sprites for NNTV — 32×32 pixel art. +// Design rules: +// • ink outline in rabbit/ink tones +// • each guard's primary color matches its mechanic (strict readability) +// • guards: tomato=static(red), blueberry=rotating(blue), corn=blinking(yellow), +// eggplant=patrolling(purple), lettuce=mirror(green), pumpkin=chaser(orange) +// • readable silhouette even at 32px + +// ── NINJA RABBIT (32×32) ────────────────────────────────────────────── +// Scrappy apprentice. Dark ninja wrap, wide eyes, one ear flicked. +const RABBIT_ART = [ + '................................', + '................................', + '.......KK........KKKK...........', + '......KFFK......KFFFFK..........', + '......KFFK......KFFFFK..........', + '......KFCK......KFFCFK..........', + '......KFCK......KFFCFK..........', + '.......KFK......KFFFK...........', + '.......KKKKKKKKKFFFFK...........', + '......KFFFFFFFFFFFFFFK..........', + '.....KFFFFFFFFFFFFFFFFK.........', + '....KFSSSSSSSSSSSSSSSSFK........', + '...KFSSSSSSSSSSSSSSSSSSFK.......', + '...KFSKKKKSSSSSSSSKKKKSFK.......', + '...KFSKWWKSSSSSSSSKWWKSFK.......', + '...KFSKWEKSSSSSSSSKWEKSFK.......', + '...KFSKKKKSSSSSSSSKKKKSFK.......', + '...KFSSSSSSSKRRKSSSSSSSFK.......', + '...KFSSSSSSKRRRRKSSSSSSFK.......', + '....KFSSSSSSSRRSSSSSSSFK........', + '.....KFSSSSSSSSSSSSSSFK.........', + '......KFFSSSSSSSSSSFFK..........', + '.......KKFFSSSSSSFFKK...........', + '.........KFKKKKKKFK.............', + '.........KFK....KFK.............', + '........KFFK....KFFK............', + '.......KFMMK....KMMFK...........', + '.......KMMMK....KMMMK...........', + '........KKK......KKK............', + '................................', + '................................', + '................................', +]; +const RABBIT_PAL = { + K: NNTV.ink, + F: NNTV.furLight, + C: NNTV.furMid, // inner ear + S: NNTV.cloth, // ninja wrap + W: NNTV.eyeShine, // eye white + E: NNTV.ink, // pupil + R: NNTV.scarfRed, // scarf knot + M: NNTV.furMid, // paw shadow +}; + +// ── TOMATO — Static Guard (RED, circle shape) 32×32 ─────────────────── +const TOMATO_ART = [ + '................................', + '................................', + '................................', + '..............GGG...............', + '.............GLLLG..............', + '............GLDDLG..............', + '...........GLDLLDLG.............', + '..........GGLLLLLLGG............', + '.........KKKKKKKKKKKK...........', + '.......KKRRRRRRRRRRRRKK.........', + '......KRRHHHHRRRRHHHHRRK........', + '.....KRHHHHHHRRRRHHHHHRRK.......', + '....KRRHWWHHRRRRRRHWWHRRRK......', + '....KRRHWWHHRRRRRRHWWHRRRK......', + '...KRRRHHHHRRRRRRRRHHHHRRRK.....', + '...KRRRRRRRRRRRDDRRRRRRRRRRK....', + '...KRRRRRRRRRRDDDDRRRRRRRRRK....', + '...KRRRRRRRRRRRRRRRRRRRRRRRK....', + '...KRRRRHHRRRRRRRRRRRRHHRRRK....', + '....KRRRHHHRRRRRRRRRRHHHRRK.....', + '....KRRRRHHHHRRRRRRHHHHRRRK.....', + '.....KRRRRRHHHHHHHHHHRRRRRK.....', + '......KRRRRRRRRRRRRRRRRRRK......', + '.......KKRRRRRRRRRRRRRRKK.......', + '.........KKRRRRRRRRRRKK.........', + '...........KKKKKKKKKK...........', + '................................', + '................................', + '................................', + '................................', + '................................', + '................................', +]; +const TOMATO_PAL = { + K: NNTV.ink, + R: NNTV.tomatoRed, // primary = red (static guard color) + H: NNTV.tomatoDark, // shadow + W: NNTV.eyeShine, + D: NNTV.cream, // highlight + G: NNTV.leafGreen, // leaf + L: NNTV.leafDark, // leaf shadow +}; + +// ── BLUEBERRY — Rotating Guard (BLUE, circle + direction) 32×32 ──────── +const BLUEBERRY_ART = [ + '................................', + '................................', + '................................', + '..............GGG...............', + '.............GGSG...............', + '..............GGG...............', + '.........KKKKKKKKKKKK...........', + '.......KKBBBBBBBBBBBBKK.........', + '......KBBBLLLBBBBLLLBBBK........', + '.....KBBLLLLBBBBBBLLLLBBK.......', + '....KBBBLLLLBBBBBBBLLLLBBBK.....', + '....KBBBBBBBBBBBBBBBBBBBBBK.....', + '....KBBBBBBBBBBBBBBBBBBBBBK.....', + '...KBBBBHHHBBBBBBBBBHHHBBBBK....', + '...KBBBHWWHBBBBBBBBHWWHBBBBK....', + '...KBBBHWWHBBBBBBBBHWWHBBBBK....', + '...KBBBBHHHBBBBBBBBBHHHBBBBK....', + '...KBBBBBBBBBBBBBBBBBBBBBBBK....', + '...KBBBBBBBBBBBBBBBBBBBBBBBK....', + '...KBBBBBBBBBBNNNBBBBBBBBBBK....', + '....KBBBBBBBBBNNNBBBBBBBBBK.....', + '....KBBBBBBBBBBNBBBBBBBBBBK.....', + '....KBBBBBBBBBBBBBBBBBBBBBK.....', + '.....KBBBBBBBBBBBBBBBBBBBK......', + '......KBBBBBBBBBBBBBBBBBK.......', + '.......KKBBBBBBBBBBBBBKK........', + '.........KKBBBBBBBBBKK..........', + '...........KKKKKKKKK............', + '................................', + '................................', + '................................', + '................................', +]; +const BLUEBERRY_PAL = { + K: NNTV.ink, + B: NNTV.guardRotating, // strict guard blue + L: NNTV.cream, // dusting highlight + H: NNTV.blueberryDark, + W: NNTV.eyeShine, + N: NNTV.cream, // mouth/whisker + G: NNTV.leafGreen, + S: NNTV.leafDark, +}; + +// ── CORN — Blinking Guard (YELLOW, dims when off) 32×32 ─────────────── +const CORN_ART = [ + '................................', + '................................', + '...........GGG....GGG...........', + '..........GHHHG..GHHHG..........', + '..........GHHHGGGHHHG...........', + '...........GHHHGHHHG............', + '............KGGKGGK.............', + '..........KKCCCCCCCCKK..........', + '........KKCCYCCYCCYCCKK.........', + '........KCYYCCYCCYYCCYCK........', + '.......KCCYYCCYCCYYCCYCCK.......', + '.......KCYYYYCCYCCYYYYYCK.......', + '.......KCCHHCCYYCCCHHCCCCK......', + '.......KCHWWHCCYCCHWWHCCCK......', + '.......KCHWWHCCYCCHWWHCCCK......', + '.......KCCHHCCYCCCHHCCCCCK......', + '.......KCYYYYCCYCCYYYYCCCK......', + '.......KCCYYCCYCCCCYCCYCCK......', + '.......KCYYYCYYCYCYYCCYYCK......', + '.......KCCYCCYCCCYCCYYYCCK......', + '........KCYYYNNNNCYYYCCCK.......', + '........KCCYCNNNNCCYCCYCK.......', + '........KCYYCCNNCCCYCYYCK.......', + '........KCCYYCCCCYYYCCCCK.......', + '.........KCCYYYYYCYYYCCK........', + '.........KKCCCCCCCCCCCKK........', + '...........KKCCCCCCCKK..........', + '.............KKKKKKKK...........', + '................................', + '................................', + '................................', + '................................', +]; +const CORN_PAL = { + K: NNTV.ink, + C: NNTV.guardBlinking, // kernel yellow + Y: NNTV.cornDark, // kernel shadow + H: NNTV.cornDark, + W: NNTV.eyeShine, + N: NNTV.ink, // mouth + G: NNTV.cornHusk, // husk +}; +// OFF variant: swap C→dim +const CORN_OFF_PAL = { ...CORN_PAL, C: NNTV.guardBlinkingOff, Y: '#5a4410' }; + +// ── EGGPLANT — Patrolling Guard (PURPLE, circle + direction) 32×32 ───── +const EGGPLANT_ART = [ + '................................', + '................................', + '..............GG................', + '.............GLLG...............', + '............GLLLG...............', + '...........GLLLLG...............', + '...........KLLLLK...............', + '..........KKKLLKKK..............', + '.........KKPPPPPPKK.............', + '........KPPHHPPPPPPK............', + '.......KPPPHPPPPPHPPK...........', + '......KPPPPPPPPPPPPPPK..........', + '......KPPPPPPPPPPPPPPK..........', + '.....KPPHHPPPPPPPPHHPPK.........', + '.....KPHWWHPPPPPPHWWHPK.........', + '.....KPHWWHPPPPPPHWWHPK.........', + '.....KPPHHPPPPPPPPHHPPK.........', + '.....KPPPPPPPPPPPPPPPPK.........', + '.....KPPPPPPPPPPPPPPPPK.........', + '......KPPPPPPNNNNPPPPK..........', + '......KPPPPPPNNNNPPPPK..........', + '......KPPPPPPPPPPPPPPK..........', + '.......KPPPPPPPPPPPPK...........', + '.......KPPPPPPPPPPPPK...........', + '........KPPPPPPPPPPK............', + '........KKPPPPPPPPKK............', + '.........KKPPPPPPKK.............', + '..........KKPPPPKK..............', + '...........KKKKKK...............', + '................................', + '................................', + '................................', +]; +const EGGPLANT_PAL = { + K: NNTV.ink, + P: NNTV.guardPatrolling, // strict purple + H: NNTV.eggplantDark, + W: NNTV.eyeShine, + N: NNTV.cream, + G: NNTV.leafGreen, + L: NNTV.leafDark, +}; + +// ── LETTUCE — Mirror Guard (GREEN, diamond-ish) 32×32 ────────────────── +const LETTUCE_ART = [ + '................................', + '................................', + '................................', + '..............KKK...............', + '............KKMMMKK.............', + '..........KKMMMMMMMKK...........', + '.........KMMMMLLLMMMMK..........', + '........KMMLLLLLLLLMMMK.........', + '.......KMMLLLLLLLLLLLMMK........', + '......KMMLLLMMMMMMMLLLMMK.......', + '.....KMMLLMMLLLLLLLMMLLMMK......', + '....KMMLLMLLLLLLLLLLLMLLMMK.....', + '....KMMLMLLLHHLLLLHHLLMLMMK.....', + '...KMMLLMLHWWHLLHWWHLMLLMMK.....', + '...KMLLMMLHWWHLLHWWHLMMLLMK.....', + '...KMLLMMLLHHLLLLHHLLMMLLMK.....', + '....KMMLMLLLLLLLLLLLLMLMMK......', + '....KMMLLMLLLNNNNNLLLMLLMMK.....', + '.....KMMLLMLLNNNNNLLMLLMMK......', + '......KMMLLMMLLLLLMMLLMMK.......', + '.......KMMLLLMMMMMMMLLMMK.......', + '........KMMLLLLLLLLLLMMK........', + '.........KMMLLLLLLLLMMK.........', + '..........KKMMMMMMMMKK..........', + '............KKMMMMKK............', + '..............KKKK..............', + '................................', + '................................', + '................................', + '................................', + '................................', + '................................', +]; +const LETTUCE_PAL = { + K: NNTV.ink, + L: NNTV.guardMirror, // strict green + M: NNTV.lettuceDark, // leaf vein + H: NNTV.lettuceDark, + W: NNTV.eyeShine, + N: NNTV.cream, +}; + +// ── PUMPKIN — Chaser Guard (ORANGE, angry) 32×32 ────────────────────── +const PUMPKIN_ART = [ + '................................', + '................................', + '................................', + '..............SS................', + '.............SSSS...............', + '.............SSSS...............', + '...........GGGSSGGG.............', + '...........KGGSSGGK.............', + '.........KKPPKKKKPPKK...........', + '........KPPOOPPPPOOPPK..........', + '.......KPPOOOPPPPOOOPPK.........', + '......KPPOOOPPPPPPOOOPPK........', + '.....KPOOOPPPPPPPPPPOOOPK.......', + '.....KPOOPPPPPPPPPPPPOOPK.......', + '....KPPOOPHHPPPPPPHHPPOOPPK.....', + '....KPOOPPHWHPPPPPHWHPPOOOPK....', + '....KPOOPPHWHPPPPPHWHPPOOOPK....', + '....KPOOPPPHHPPPPPPHHPPPOOPK....', + '....KPOOPPPPPPPPPPPPPPPPOOPK....', + '....KPOOPPPPKKPPPPKKPPPPOOPK....', + '....KPPOOPPKNNKPPKNNKPPPOOPK....', + '.....KPOOPKNNNNKKNNNNKPPOOPK....', + '.....KPPOOPKNNNNNNNNKPPOOPPK....', + '......KPOOOPKKNNNNKKPOOOPK......', + '.......KPOOOOPPPPPPOOOOPK.......', + '........KPPOOOOOOOOOOPPK........', + '.........KKPPOOOOOOPPKK.........', + '...........KKPPPPPPKK...........', + '.............KKKKKK.............', + '................................', + '................................', + '................................', +]; +const PUMPKIN_PAL = { + K: NNTV.ink, + P: NNTV.guardChaser, // strict orange + O: NNTV.pumpkinDark, + H: NNTV.eyeRed, // glowing angry eye + W: NNTV.cream, + N: NNTV.ink, // jagged mouth + G: NNTV.leafGreen, // stem leaf + S: NNTV.leafDark, // stem +}; + +// ── CARROT PRINCESS — 32×32 ──────────────────────────────────────────── +const PRINCESS_ART = [ + '................................', + '................................', + '............SSSSS...............', + '...........SLLLLLS..............', + '..........SLLHLLLLS.............', + '..........SLLLHLLLS.............', + '.........SSLLLLHLLSS............', + '........KKKGGGGGGGKK............', + '.......KYYYYCCCCCYYYK...........', + '......KYYCCCDCCDCCCYYK..........', + '......KYCCCCCDCDCCCCYK..........', + '......KYCCCCCCCCCCCCYK..........', + '......KYCCHHCCCCCHHCYK..........', + '......KYCHWWHCCCHWWHCK..........', + '......KYCHWEHCCCHWEHCK..........', + '......KYCCHHCCCCCHHCCK..........', + '......KYCCCCCCKKCCCCCK..........', + '......KYCCCCCKPPKCCCCK..........', + '......KYCCCCCKKKKCCCCK..........', + '......KYCCCCCCCCCCCCCK..........', + '.......KYCCCCCCCCCCCK...........', + '........KYCCCCCCCCCK............', + '.........KYCCCCCCCK.............', + '..........KYCCCCCK..............', + '...........KYCCCK...............', + '............KYCK................', + '.............KK.................', + '................................', + '................................', + '................................', + '................................', + '................................', +]; +const PRINCESS_PAL = { + K: NNTV.ink, + C: NNTV.carrot, // carrot body + D: NNTV.cream, // highlight streaks + H: NNTV.carrotDark, + W: NNTV.eyeShine, + E: NNTV.ink, + P: NNTV.scarfRed, // lips + Y: NNTV.carrotDark, // outline carrot + L: NNTV.leafGreen, // leafy crown + S: NNTV.leafDark, + G: NNTV.gold, // tiara band +}; + +// Export all +const SPRITES = { + rabbit: { art: RABBIT_ART, pal: RABBIT_PAL, label: 'Ninja Rabbit', desc: 'Player. Scrappy apprentice, dark wrap, red scarf.' }, + tomato: { art: TOMATO_ART, pal: TOMATO_PAL, label: 'Tomato — Static', desc: 'Red · fixed light pattern', color: NNTV.guardStatic }, + blueberry: { art: BLUEBERRY_ART, pal: BLUEBERRY_PAL, label: 'Blueberry — Rotating', desc: 'Blue · rotates beam 90° / turn', color: NNTV.guardRotating }, + corn: { art: CORN_ART, pal: CORN_PAL, label: 'Corn — Blinking (on)', desc: 'Yellow · toggles lights on/off', color: NNTV.guardBlinking }, + cornOff: { art: CORN_ART, pal: CORN_OFF_PAL, label: 'Corn — Blinking (off)', desc: 'Dimmed variant', color: NNTV.guardBlinkingOff }, + eggplant: { art: EGGPLANT_ART, pal: EGGPLANT_PAL, label: 'Eggplant — Patrolling', desc: 'Purple · walks a path', color: NNTV.guardPatrolling }, + lettuce: { art: LETTUCE_ART, pal: LETTUCE_PAL, label: 'Lettuce — Mirror', desc: 'Green · reflects beams 90°', color: NNTV.guardMirror }, + pumpkin: { art: PUMPKIN_ART, pal: PUMPKIN_PAL, label: 'Pumpkin — Chaser', desc: 'Orange · hunts via pathfinding', color: NNTV.guardChaser }, + princess: { art: PRINCESS_ART, pal: PRINCESS_PAL, label: 'Carrot Princess', desc: 'Objective. Royal. Unreachable.' }, +}; + +Object.assign(window, { SPRITES }); diff --git a/public/assets/src/pixel.jsx b/public/assets/src/pixel.jsx new file mode 100644 index 0000000..662bee6 --- /dev/null +++ b/public/assets/src/pixel.jsx @@ -0,0 +1,122 @@ +// Pixel art renderer — converts string art into inline SVG s. +// Each character maps to a palette index; '.' or ' ' = transparent. +// +// Usage: +// +// Pixel(art, palette, scale) → SVG element +// +// Palette keys are 1-char strings. Keep sprites small (≤64px) for readability. + +const NNTV = { + // Game semantic colors (from theme.css) — preserved exactly for gameplay readability + guardStatic: '#ff4444', + guardRotating: '#4488ff', + guardBlinking: '#ffdd44', + guardBlinkingOff:'#887722', + guardPatrolling: '#bb44ff', + guardMirror: '#44ddaa', + guardChaser: '#ff6622', + gridEmpty: '#1a1a2e', + gridWall: '#4a4a5e', + gridGoal: '#00c853', + gridLit: '#ffea00', + gridBorder: '#2a2a3e', + playerInk: '#111111', + + // Expanded palette for richer pixel work — evolution of current indigo theme + midnight: '#0a0a1a', // deepest bg + twilight: '#141428', // bg panel deep + dusk: '#1f1b3a', // panel mid + plum: '#2d2150', // panel warm + moonlight: '#e8e4ff', // light text / highlight + silver: '#9a9ac0', // mid text + shadow: '#05050f', // near-black + ink: '#0b0a18', // outline ink + cream: '#fff4d6', // moon / whites + moonGlow: '#ffe88a', // warm light halo + carrot: '#ff8844', // princess warm + carrotDark:'#cc5a1f', // princess shadow + leafGreen: '#3ea85c', // princess leaf + leafDark: '#1e6b38', + + // Rabbit fur/cloth tones + furLight: '#f5eeff', + furMid: '#b8b0d0', + furShadow: '#6a607a', + cloth: '#1a1a2e', + clothShadow:'#05050f', + eyeShine: '#ffffff', + eyeRed: '#ff3355', // determined glint + scarfRed: '#c73e3a', + + // Veggie tones + tomatoRed: '#e23a3a', + tomatoDark: '#991f1f', + tomatoLeaf: '#3ea85c', + eggplantPurp: '#8a3fc9', + eggplantDark: '#4a1a6e', + cornYellow: '#ffd84a', + cornDark: '#b88a1a', + cornHusk: '#6aa84a', + lettuceGreen: '#8fd66a', + lettuceDark: '#3e7a3e', + pumpkinOrange:'#ff7722', + pumpkinDark: '#aa4411', + blueberry: '#4a7acc', + blueberryDark:'#2a4a8c', + onionPurp: '#a968c9', + onionDark: '#5a2a7a', + + // Tile tones + stone: '#5a5a6e', + stoneDark: '#2a2a3e', + stoneLight: '#7a7a9a', + dirt: '#4a3a2a', + dirtDark: '#2a1a1a', + grass: '#3e7a3e', + grassDark: '#2a5a2a', + grassLight: '#5aaa5a', + mossGreen: '#2a5a3a', + brick: '#6a3a2a', + brickDark: '#3a1a0a', + wood: '#7a5a2a', + woodDark: '#3a2a1a', + gold: '#ffcc44', + goldDark: '#aa7722', +}; + +// ---- Pixel renderer ------------------------------------------------------ +function Pixel({ art, palette, scale = 6, bg = null, style = {}, pixelated = true }) { + // art: array of strings, all same length. palette: { [char]: color } + const rows = art.length; + const cols = art[0]?.length ?? 0; + const rects = []; + for (let r = 0; r < rows; r++) { + let c = 0; + while (c < cols) { + const ch = art[r][c]; + if (ch === '.' || ch === ' ') { c++; continue; } + // run-length merge same color horizontally + let run = 1; + while (c + run < cols && art[r][c + run] === ch) run++; + const color = palette[ch]; + if (color) { + rects.push(); + } + c += run; + } + } + return ( + + {bg && } + {rects} + + ); +} + +Object.assign(window, { NNTV, Pixel }); diff --git a/public/assets/src/scenes.jsx b/public/assets/src/scenes.jsx new file mode 100644 index 0000000..34752a2 --- /dev/null +++ b/public/assets/src/scenes.jsx @@ -0,0 +1,250 @@ +// Act backgrounds & level intro illustrations — wider pixel art scenes. +// Each act: 80×40 pixel strip evoking the environment. + +// ── ACT 1: GARDEN (Levels 1-2) — cabbages, carrots in rows, moon ────── +const BG_GARDEN = [ + 'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmmmmm........smmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmmm....OOOOO....smmmmmmmmmmmmmmmmmmmmmm..s..s...mmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmm...OO..........OOmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmm..OO............OOmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmm..O..............Omm.s.mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmm..O..............Ommmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmmm..OO..........OOmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmmmm..OOOOOOOOOOOOmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmmmmmm........smmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + '..LL......LL......LL......LL......LL......LL......LL......LL......LL......LL...', + '.LLLL....LLLL....LLLL....LLLL....LLLL....LLLL....LLLL....LLLL....LLLL....LLLL...', + '.LDDL....LDDL....LDDL....LDDL....LDDL....LDDL....LDDL....LDDL....LDDL....LDDL...', + 'gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg', + 'GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG', + 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', + 'ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', + '...CC..........CC..........CC..........CC..........CC..........CC..........CC..', + '..CCCC........CCCC........CCCC........CCCC........CCCC........CCCC........CCCC.', + '..CCCC........CCCC........CCCC........CCCC........CCCC........CCCC........CCCC.', + '..CCCC........CCCC........CCCC........CCCC........CCCC........CCCC........CCCC.', + '..OOOO........OOOO........OOOO........OOOO........OOOO........OOOO........OOOO.', + '..OOOO........OOOO........OOOO........OOOO........OOOO........OOOO........OOOO.', + '..ooooo......ooooo........oooo........ooooo.......oooo.......ooooo........ooo..', + 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', + 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'.slice(0,80), + 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'.slice(0,80), + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', +]; +const BG_GARDEN_PAL = { + m: NNTV.midnight, // sky + s: NNTV.moonlight, // stars + O: NNTV.cream, // moon edge + o: NNTV.moonGlow, // moon inner + L: NNTV.leafDark, // hedge + D: NNTV.mossGreen, // hedge shadow + g: NNTV.grass, + G: NNTV.grassDark, + d: NNTV.dirt, + C: NNTV.leafGreen, // cabbage leaves + f: NNTV.dirtDark, // deep soil +}; + +// ── ACT 2: VEGETABLE GARDEN WALLS (3-4) — brick wall + ivy ───────────── +const BG_WALLS = [ + 'ppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp', + 'ppppppppppppppppp..sppppppppppppppppppppppppps.ppppppppppppppppppp..spppppppppp', + 'pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp', + 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + 'bBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBB', + 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + 'bbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbb', + 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + 'bBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBB', + 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + 'bbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbb', + 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + 'bBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBB', + 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + 'bbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbbBBBBbbb', + '..vvbbb.bbbvvbbbb..vvbb.bbbvv..bbbvvb..bbbbvvb..vvbbbb..vvbbbbb..vvbbbb..vvbbbbb', + 'vvllvvvvbbbllvvvbbllvvbbbbllv..bllvvbbvvllvvbvvvllvbvvvvllvbvvvllvvbbvvllvvvbbbb', + 'lllllvvvvvlllllvvvlllvvvvllll.lllllvllllllvllllllvllllllvllllllvlllllllvllllllv', + 'gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg', + 'GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG', + 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'.slice(0,80), + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'.slice(0,80), + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'.slice(0,80), +]; +const BG_WALLS_PAL = { + p: NNTV.midnight, + s: NNTV.cream, + b: NNTV.brick, + B: NNTV.brickDark, + v: NNTV.leafDark, + l: NNTV.leafGreen, + g: NNTV.grass, + G: NNTV.grassDark, + d: NNTV.dirt, + f: NNTV.dirtDark, +}; + +// ── ACT 3: FORTRESS (5-6) — stone battlements + torch ───────────────── +const BG_FORTRESS = [ + 'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmsmmmmmmmmmmmsmmmmmmmmmmmmmmmmmmmmmsmmmmmmmmmmmmmmsmmmmmmmmmmmmmmmmmmmmmmmm', + 'sssssssssssssssssssssssssssssssssssssssss.....ssssssssssssssssssssssssssssssssss', + 's.....ss.....ss.....ss.....ss.....ss...ss.....ss...ss.....ss.....ss.....ss....ss', + 's.....ss.....ss.....ss.....ss.....ss...ss.....ss...ss.....ss.....ss.....ss....ss', + 'SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS', + 'sSSsSSssSsSSssSSsSSssSsSSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSsSSS', + 'SssSSsSSssSsSSssSSsSSssSsSSssSsSSssSSsSSssSSsSSssSsSSssSSsSSssSSsSSssSsSSssSSsSSS', + 'sSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSSsSSssSsSSssSSsSSssSsSS', + 'SssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSSsSSssSsSSssSSsSSS', + 'sSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSSsSSssSsSSssSSsSSssSsSS', + 'ssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSSsSSssSsSSssSSsSSss', + 'sSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSSsSSssSsSSssSSsSSssSsSS', + 'ssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSSsSSssSsSSssSSsSSss', + 'sSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSsSSssSSsSSssSSsSSssSsSSssSSsSSssSsSS', + '..........ff..................................ff...........................ff..', + '.........fFFf................................fFFf.........................fFFf.', + '........fFFFFf..............................fFFFFf........................fFFFFf', + '.........ffff................................ffff..........................ffff', + '..........kk..................................kk...........................kk..', + 'ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss', + 'SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS', + 'ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd.', +]; +const BG_FORTRESS_PAL = { + m: NNTV.midnight, + s: NNTV.stone, + S: NNTV.stoneDark, + f: NNTV.guardBlinking, // torch flame + F: NNTV.pumpkinOrange, + k: NNTV.ink, + d: NNTV.dirtDark, +}; + +// ── ACT 4: UNDERGROUND (7-8) — tunnel ────────────────────────────────── +const BG_UNDERGROUND = [ + 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', + 'dDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDddd', + 'dddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDd', + 'dDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDddd', + 'sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss'.slice(0,80), + 'sSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSs', + 'sSmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmSSs'.slice(0,80), + 'sSmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmSSs'.slice(0,80), + 'sSmmmmmmfmmmmmmmmmmmmmmmmmmmmmmmfmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmSSs'.slice(0,80), + 'sSmmmmmFFmmmmmmmmmmmmmmmmmmmmmmFFmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmSSs'.slice(0,80), + 'sSmmmmmffmmmmmmmmmmmmmmmmmmmmmmffmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmSSs'.slice(0,80), + 'sSmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmSSs'.slice(0,80), + 'sSmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmSSs'.slice(0,80), + 'sSmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmSSs'.slice(0,80), + 'sSmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmSSs'.slice(0,80), + 'sSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSs', + 'sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss'.slice(0,80), + 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd', + 'dDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDddd', + 'dddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDdddDDd', +]; +const BG_UNDERGROUND_PAL = { + d: NNTV.dirt, + D: NNTV.dirtDark, + s: NNTV.stoneDark, + S: NNTV.stoneDark, + m: NNTV.midnight, + f: NNTV.guardBlinking, + F: NNTV.pumpkinOrange, +}; + +// ── ACT 5: ROYAL PALACE (9-11) — pillars + banner ────────────────────── +const BG_PALACE = [ + 'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmsmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGmmm', + 'mmGCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCGmm', + 'mGC..........pp.......................pp.......................pp............CGm'.slice(0,80), + 'mGC.........pPPp.....................pPPp.....................pPPp...........CGm', + 'mGC........pPPPPp...................pPPPPp...................pPPPPp..........CGm', + 'mGC........pPPPPp...................pPPPPp...................pPPPPp..........CGm', + 'mGC........pPPPPp...................pPPPPp...................pPPPPp..........CGm', + 'mGC........pPPPPp...................pPPPPp...................pPPPPp..........CGm', + 'mGC........pPPPPp...................pPPPPp...................pPPPPp..........CGm', + 'mGC........pPPPPp...................pPPPPp...................pPPPPp..........CGm', + 'mGC........pPPPPp...................pPPPPp...................pPPPPp..........CGm', + 'mGC........pPPPPp...................pPPPPp...................pPPPPp..........CGm', + 'mGC........pPPPPp...................pPPPPp...................pPPPPp..........CGm', + 'mGC........pPPPPp...................pPPPPp...................pPPPPp..........CGm', + 'mGC........pPPPPp...................pPPPPp...................pPPPPp..........CGm', + 'mGC........pPPPPp...................pPPPPp...................pPPPPp..........CGm', + 'mGCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCGm', + 'mmGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGmm.', + 'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + '................................................................................', + '................................................................................', +]; +const BG_PALACE_PAL = { + m: NNTV.midnight, + s: NNTV.cream, + G: NNTV.gold, + C: NNTV.goldDark, + p: NNTV.moonlight, + P: NNTV.silver, +}; + +// ── ACT 6: PRINCESS CHAMBER (12) — ominous light halo ───────────────── +const BG_CHAMBER = [ + 'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmyyyyymmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmyyyYYYyymmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmyyyYYYYYYymmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmyyyyYYYCCYYYymmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmyyyyYYYYCCCCYYYyymmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmmmmmmmmmmmmmyyyyYYYYYYCCCCCYYYyymmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmmmmmmmmmmmyyyyYYYYYYYYCCCCCYYYYyymmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmmmmmmmmmyyyyYYYYYYYYYYCCCCYYYYYyymmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmmmmmmmyyyyYYYYYYYYYYYYCCCCYYYYYYyymmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmmmmmyyyyyYYYYYYYYYYYYYCCYYYYYYYYyyymmmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmmmyyyyYYYYYYYYYYYYYYYYCCYYYYYYYYYYyymmmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmmmyyyyyYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYyymmmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmmyyyyyYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYyyymmmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmmmyyyyYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYyymmmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmmyyyyYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYyymmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmmmyyyyYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYymmmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmmyyyyyYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYyymmmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmmyyyyYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYyymmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmmyyyyYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYymmmmmmmmmmmmmmmmmmmmmmm', + 'mmmmyyyyYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYymmmmmmmmmmmmmmmmmmmmmm', + 'mmmyyyYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYymmmmmmmmmmmmmmmmmmmmmm', +]; +const BG_CHAMBER_PAL = { + m: NNTV.shadow, + y: NNTV.guardBlinkingOff, + Y: NNTV.gridLit, + C: NNTV.carrot, +}; + +const SCENES = { + garden: { art: BG_GARDEN, pal: BG_GARDEN_PAL, label: 'Act 1 · The Outskirts', desc: 'Levels 1–2 · moonlit garden hedge' }, + walls: { art: BG_WALLS, pal: BG_WALLS_PAL, label: 'Act 2 · The Vegetable Garden', desc: 'Levels 3–4 · brick walls with ivy' }, + fortress: { art: BG_FORTRESS, pal: BG_FORTRESS_PAL, label: 'Act 3 · The Fortress', desc: 'Levels 5–6 · battlements, flickering torches' }, + underground: { art: BG_UNDERGROUND, pal: BG_UNDERGROUND_PAL, label: 'Act 4 · The Underground', desc: 'Levels 7–8 · tunnel, distant lamp' }, + palace: { art: BG_PALACE, pal: BG_PALACE_PAL, label: 'Act 5 · The Royal Palace', desc: 'Levels 9–11 · gilded hall, silver pillars' }, + chamber: { art: BG_CHAMBER, pal: BG_CHAMBER_PAL, label: 'Act 6 · The Princess Chamber', desc: 'Level 12 · expanding halo — unwinnable' }, +}; + +Object.assign(window, { SCENES }); diff --git a/public/assets/src/tiles-ui.jsx b/public/assets/src/tiles-ui.jsx new file mode 100644 index 0000000..2b37cc5 --- /dev/null +++ b/public/assets/src/tiles-ui.jsx @@ -0,0 +1,338 @@ +// Tile set, UI elements, icons, HUD, popups — all pixel art. +// 16×16 tiles for board cells, larger for UI pieces. + +// ── TILES 16×16 ──────────────────────────────────────────────────────── +const TILE_EMPTY = [ + 'aaaaaaaaaaaaaaaa', + 'abbbbbbbbbbbbbba', + 'abccccccccccccba', + 'abcccccccccccccb', + 'abcccdddccccdccb', + 'abccdcccccccccbb', + 'abcccccccccccccb', + 'abcccccccccddcbb', + 'abccdddcccccccbb', + 'abccccccccccccbb', + 'abccccdcccccccbb', + 'abcccccccccccccb', + 'abccccccccdccccb', + 'abbbbbbbbbbbbbbb', + 'aaaaaaaaaaaaaaaa', + 'aaaaaaaaaaaaaaaa', +]; +const TILE_EMPTY_PAL = { a: NNTV.gridBorder, b: NNTV.twilight, c: NNTV.gridEmpty, d: NNTV.dusk }; + +const TILE_WALL = [ + 'kkkkkkkkkkkkkkkk', + 'kssssssstsssssrk', + 'ksllllllstlllsrk', + 'kslmmmmlstlmmsrk', + 'ksllllllstlllsrk', + 'ksssssssstssssrk', + 'ktttttttttttttrk', + 'kssssstsssssssrk', + 'kslmmstlmmmlssrk', + 'kslllstllllllsrk', + 'kssssstsssssssrk', + 'ksssssstssssssrk', + 'kslmmlstmmlssrk.', + 'ksllllstlllssrk.', + 'ksssssstssssrrk.', + 'kkkkkkkkkkkkkkkk', +]; +const TILE_WALL_PAL = { + k: NNTV.ink, + s: NNTV.stone, + l: NNTV.stoneLight, + m: NNTV.stoneDark, + r: NNTV.stoneDark, + t: NNTV.ink, +}; + +const TILE_GOAL = [ + 'kkkkkkkkkkkkkkkk', + 'kggggggggggggggk', + 'kghhghhhghhhgggk', + 'kgghghhhghhhhggk', + 'kgggghhghhhhhggk', + 'kgghghhhghhhhggk', + 'kggghhhhhhhhhggk', + 'kggghhwhwhhhhggk', + 'kggghhhhhwhhhggk', + 'kggghwhhhhhhhggk', + 'kgghghhhwhhhhggk', + 'kggghhhhhhhhhggk', + 'kgghghhhhhhhhggk', + 'kggghwhhhhwhhggk', + 'kggggggggggggggk', + 'kkkkkkkkkkkkkkkk', +]; +const TILE_GOAL_PAL = { k: NNTV.ink, g: NNTV.gridGoal, h: '#00e763', w: '#7affb0' }; + +const TILE_LIT = [ + 'yyyyyyyyyyyyyyyy', + 'ywwwwwwwwwwwwwwy', + 'ywyyyyyyyyyyyywy', + 'ywyyfffffffffywy', + 'ywyfyyyyyyyyfywy', + 'ywyfyffffffyfywy', + 'ywyfyfyyyyyfyfwy', + 'ywyfyfyffyyfyfwy', + 'ywyfyfyffyyfyfwy', + 'ywyfyfyyyyyfyfwy', + 'ywyfyffffffyfywy', + 'ywyfyyyyyyyyfywy', + 'ywyyfffffffffywy', + 'ywyyyyyyyyyyyywy', + 'ywwwwwwwwwwwwwwy', + 'yyyyyyyyyyyyyyyy', +]; +const TILE_LIT_PAL = { y: NNTV.gridLit, w: '#fff88a', f: NNTV.gridLit }; + +const TILE_MIRROR = [ + '........kk......', + '.......kmmk.....', + '......kmllmk....', + '.....kmllllmk...', + '....kmllwwllmk..', + '...kmllwllwllmk.', + '..kmllwlllwllmk.', + '.kmllwlllllwllmk', + 'kmlwlllllllwllmk', + '.kmllwlllllwllmk', + '..kmllwlllwllmk.', + '...kmllwwwllmk..', + '....kmllllmk....', + '.....kmllmk.....', + '......kmmk......', + '.......kk.......', +]; +const TILE_MIRROR_PAL = { k: NNTV.ink, l: NNTV.guardMirror, m: NNTV.lettuceDark, w: NNTV.cream }; + +const TILE_PREVIEW = [ + 'yyyy........yyyy', + 'y..y........y..y', + 'y..y........y..y', + 'yyyy........yyyy', + '................', + '................', + '................', + '................', + '................', + '................', + '................', + '................', + 'yyyy........yyyy', + 'y..y........y..y', + 'y..y........y..y', + 'yyyy........yyyy', +]; +const TILE_PREVIEW_PAL = { y: NNTV.gridLit }; + +// ── HEART (lives) 16×16 ──────────────────────────────────────────────── +const HEART_ART = [ + '................', + '..kkkk....kkkk..', + '.krrrhk..krrrhk.', + 'krrwrrrkkrrrrhk.', + 'krwrrrrrrrrrrhk.', + 'krrrrrrrrrrrrhk.', + 'krrrrrrrrrrrhhk.', + 'krrrrrrrrrrrhhk.', + '.krrrrrrrrrrhk..', + '.khrrrrrrrrhhk..', + '..khrrrrrrhhk...', + '...khrrrrhhk....', + '....khrrhhk.....', + '.....khhhk......', + '......khk.......', + '.......k........', +]; +const HEART_ART_EMPTY = HEART_ART.map(r => r.replace(/[rwh]/g, 'e')); +const HEART_PAL = { k: NNTV.ink, r: NNTV.scarfRed, h: '#8a1a22', w: NNTV.cream, e: NNTV.dusk }; + +// ── LOGO / wordmark — NNTV stylized 80×24 ───────────────────────────── +const LOGO_ART = [ + '................................................................................', + '...kk.............kk..................kkkkkkk.........kk................kk......', + '..kccck..........kccck..............kkcccccccckk......kccck............kcccck...', + '..kcc.ck.........kcc.ck.............kcc.......kcck....kccccck..........kcc.cck..', + '..kcc.cck........kcc.cck............kcc.........ck....kcc..cck........kcc...ck..', + '..kcc..cck.......kcc..cck...........kcc.........ck....kcc...ck........kcc...ck..', + '..kcc..cck.......kcc..cck...........kcc.........ck....kcc...ck........kcc...ck..', + '..kcc...cck......kcc...cck..........kcc.........ck....kcc...ck........kcc..cck..', + '..kcc...ccck.....kcc...ccck.........kcc.........ck....kcc..cck........kcccccck..', + '..kcc....cck.....kcc....cck.........kcc.........ck....kccccck.........kcc..cck..', + '..kcc....ccck....kcc....ccck........kcc.........ck....kcccck..........kcc..cck..', + '..kcc.....cck....kcc.....cck........kcc.........ck....kcc.ck..........kcc...ck..', + '..kcc.....ccck...kcc.....ccck.......kcc.........ck....kcc.cck.........kcc...ck..', + '..kcc......cck...kcc......cck.......kcc.........ck....kcc..ck.........kcc...ck..', + '..kcc......ccck..kcc......ccck......kcc.........ck....kcc..cck........kcc..cck..', + '..kcc.......cck..kcc.......cck......kcc.......kcck....kcc...ck........kcc.cck...', + '..kcc.......cccckcc.......cccck.....kkcccccccckkk.....kcc...cck........kcccck...', + '..kcc........ccckcc........ccck......kkkkkkkkk........kcc....ck.........kcck....', + '..kcc.........ckkcc.........ckk......................kcc....ck..........kck.....', + '..kcc..........kcc..........k........................kcc....cck..........k......', + '..kkk...........kkk.................................kkkk....kkk..................', + '................................................................................', + '................................................................................', + '................................................................................', +]; +const LOGO_PAL = { k: NNTV.ink, c: NNTV.moonlight }; + +// ── MOON 20×20 ───────────────────────────────────────────────────────── +const MOON_ART = [ + '........kkkkk.......', + '......kkggghhk......', + '.....kgggghhhhk.....', + '....kggghhhhhhhk....', + '...kggghhhhhhhhhk...', + '...kgghhhhhhhhhhk...', + '..kggghhhhhhhhhhhk..', + '..kgghhhhhhhhhhhhk..', + '..kgghhhhhhhhhhhhk..', + '..kgghhhhhhhhhhhhk..', + '..kgghhhhhhhhhhhhk..', + '..kgghhhhhhhhhhhhk..', + '..kggghhhhhhhhhhhk..', + '...kgghhhhhhhhhhk...', + '...kggghhhhhhhhhk...', + '....kggghhhhhhhk....', + '.....kgggghhhhk.....', + '......kkggghhk......', + '........kkkkk.......', + '....................', +]; +const MOON_PAL = { k: NNTV.ink, g: NNTV.cream, h: NNTV.moonGlow }; + +// ── ICONS 16×16 ──────────────────────────────────────────────────────── +const mkIcon = (s) => s.split('\n').filter(Boolean).map(r => r.padEnd(16, '.').slice(0,16)); +const ICON_PAL = { k: NNTV.ink, w: NNTV.moonlight, a: NNTV.scarfRed }; + +const ICON_UNDO = mkIcon(` +................ +................ +.....wwww....... +....w....w...... +...w......w..... +..w...wwww.w.... +..w..w.....w.... +..w..w.......... +.wwww.w......... +.wwwww.......... +..www........... +...w............ +................ +................ +................ +................`); +const ICON_REDO = mkIcon(` +................ +................ +.......wwww..... +......w....w.... +.....w......w... +....w.wwww...w.. +....w.....w..w.. +..........w..w.. +.........w.wwww. +..........wwwww. +...........www.. +............w... +................ +................ +................ +................`); +const ICON_EYE = mkIcon(` +................ +................ +...wwwwwwwww.... +..w.........w... +.w...wwwww...w.. +w...w.....w...w. +w...w.www.w...w. +w...w.www.w...w. +w...w.....w...w. +.w...wwwww...w.. +..w.........w... +...wwwwwwwww.... +................ +................ +................ +................`); +const ICON_PAUSE = mkIcon(` +................ +...ww.....ww.... +...ww.....ww.... +...ww.....ww.... +...ww.....ww.... +...ww.....ww.... +...ww.....ww.... +...ww.....ww.... +...ww.....ww.... +...ww.....ww.... +...ww.....ww.... +...ww.....ww.... +................ +................ +................ +................`); +const ICON_SETTINGS = mkIcon(` +................ +.....wwww....... +....w....w...... +...w..ww..w..... +..w..wwww..w.... +..w..wwww..w.... +.w....ww....w... +.w..........w... +.w..........w... +.w....ww....w... +..w..wwww..w.... +..w..wwww..w.... +...w..ww..w..... +....w....w...... +.....wwww....... +................`); +const ICON_LANG = mkIcon(` +................ +.....wwww....... +....wwwwww...... +...ww....ww..... +..ww..ww..ww.... +.ww..w..w..ww... +.w..ww..ww..w... +.w..ww..ww..w... +.w..ww..ww..w... +.ww..w..w..ww... +..ww..ww..ww.... +...ww....ww..... +....wwwwww...... +.....wwww....... +................ +................`); +const ICON_ARROW = mkIcon(` +................ +................ +.......w........ +......ww........ +.....www........ +....wwww........ +...wwwww........ +..wwwwwwwwww.... +...wwwww........ +....wwww........ +.....www........ +......ww........ +.......w........ +................ +................ +................`); + +Object.assign(window, { + TILE_EMPTY, TILE_EMPTY_PAL, TILE_WALL, TILE_WALL_PAL, TILE_GOAL, TILE_GOAL_PAL, + TILE_LIT, TILE_LIT_PAL, TILE_MIRROR, TILE_MIRROR_PAL, TILE_PREVIEW, TILE_PREVIEW_PAL, + HEART_ART, HEART_ART_EMPTY, HEART_PAL, + LOGO_ART, LOGO_PAL, MOON_ART, MOON_PAL, + ICON_UNDO, ICON_REDO, ICON_EYE, ICON_PAUSE, ICON_SETTINGS, ICON_LANG, ICON_ARROW, ICON_PAL, +});