diff --git a/index.html b/index.html index c842d2a..27140c0 100644 --- a/index.html +++ b/index.html @@ -7,7 +7,7 @@ name="description" content="amerc is an agent mercenary layer for embedding, bringing, and hosting agents across vertical software boundaries." /> - + diff --git a/package.json b/package.json index b74ecc8..e92c04e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "amerc-site", - "version": "0.21.1-pool", + "version": "0.22.1-tavern", "private": true, "type": "module", "scripts": { diff --git a/src/Scene2D.jsx b/src/Scene2D.jsx index 09b1317..d2f045b 100644 --- a/src/Scene2D.jsx +++ b/src/Scene2D.jsx @@ -5,7 +5,7 @@ import { AgentBrowse, BoothDashboard } from './AgentPlatform.jsx'; import TopDownTavern from './TopDownTavern.jsx'; import { api } from './api.js'; -const RELEASE = '0.21.0-pool'; +const RELEASE = '0.22.1-tavern'; const NAV = [ { id: 'home', label: 'Tavern' }, diff --git a/src/TopDownTavern.jsx b/src/TopDownTavern.jsx index df1befa..22a3335 100644 --- a/src/TopDownTavern.jsx +++ b/src/TopDownTavern.jsx @@ -1,114 +1,130 @@ import React, { useEffect, useRef, useState } from 'react'; -// 3/4 top-down RPG tavern, drawn procedurally on a canvas at a fixed virtual size. -// Pixel-chunky: rendered at TILE px then scaled up with image-rendering:pixelated. -const COLS = 20, ROWS = 13, TILE = 16; // virtual room = 320x208, scaled up -const W = COLS * TILE, H = ROWS * TILE; +// Painterly 3/4 top-down RPG tavern — smooth high-res canvas, warm lighting, +// cohesive with the painted character sprites. +const W = 1200, H = 760; +const rnd = (s) => { let x = Math.sin(s * 127.1) * 43758.5453; return x - Math.floor(x); }; -// tile map legend: . floor # wall = bar T table o stool b barrel r rug f fireplace s shelf c crate p plant w window -const MAP = [ - '####################', - '#wwww##ffff##wwww##s#', - '#..................s#', - '#..bb....rr....cc...#', - '#..bb..rrrrrr..cc...#', - '#......rrrrrr.......#', - '#..T...rrrrrr...T...#', - '#.ooo..........ooo..#', - '#..................##', - '#===================#', - '#...=...=...=...=...##', - '#..................##', - '####################', -]; +function roundRect(ctx, x, y, w, h, r) { ctx.beginPath(); ctx.moveTo(x + r, y); ctx.arcTo(x + w, y, x + w, y + h, r); ctx.arcTo(x + w, y + h, x, y + h, r); ctx.arcTo(x, y + h, x, y, r); ctx.arcTo(x, y, x + w, y, r); ctx.closePath(); } +function ellipse(ctx, x, y, rx, ry, c) { ctx.fillStyle = c; ctx.beginPath(); ctx.ellipse(x, y, rx, ry, 0, 0, 7); ctx.fill(); } -const PAL = { - floorA: '#6b4a2b', floorB: '#5e4126', grain: '#4f361f', - wall: '#3a2a3f', wallTop: '#52405a', mortar: '#241a28', - rug: '#7a1f2b', rugEdge: '#d8a843', rugMid: '#9c2a38', - bar: '#7a4a24', barTop: '#a06a36', barEdge: '#3c220f', - wood: '#8a5a2e', woodHi: '#a87a44', woodLo: '#4a2e16', - barrel: '#6b4524', barrelBand: '#caa54a', barrelTop: '#a8732f', - stone: '#4a4452', stoneHi: '#6a6276', - fire: '#ff9f38', fireHot: '#ffd66b', glass: '#2aa9d8', -}; - -function px(ctx, x, y, w, h, c) { ctx.fillStyle = c; ctx.fillRect(x, y, w, h); } - -function drawFloor(ctx, x, y) { - const a = ((x / TILE) + (y / TILE)) % 2 === 0; - px(ctx, x, y, TILE, TILE, a ? PAL.floorA : PAL.floorB); - ctx.fillStyle = PAL.grain; - ctx.fillRect(x, y + 5, TILE, 1); ctx.fillRect(x, y + 11, TILE, 1); - ctx.fillRect(x + (a ? 4 : 11), y, 1, TILE); -} -function drawWall(ctx, x, y) { - px(ctx, x, y, TILE, TILE, PAL.wall); - px(ctx, x, y, TILE, 4, PAL.wallTop); // 3/4 top cap - ctx.fillStyle = PAL.mortar; - ctx.fillRect(x, y + 9, TILE, 1); - ctx.fillRect(x + ((y / TILE) % 2 ? 8 : 0), y + 4, 1, 5); - ctx.fillRect(x + ((y / TILE) % 2 ? 0 : 8), y + 9, 1, 7); -} -function drawRug(ctx, x, y, edge, midOnly) { - px(ctx, x, y, TILE, TILE, PAL.rug); - if (midOnly) { px(ctx, x + 3, y + 3, TILE - 6, TILE - 6, PAL.rugMid); } - ctx.fillStyle = PAL.rugEdge; - if (edge.t) ctx.fillRect(x, y, TILE, 2); - if (edge.b) ctx.fillRect(x, y + TILE - 2, TILE, 2); - if (edge.l) ctx.fillRect(x, y, 2, TILE); - if (edge.r) ctx.fillRect(x + TILE - 2, y, 2, TILE); -} - -function drawTile(ctx, ch, x, y, neigh) { - switch (ch) { - case '#': drawWall(ctx, x, y); break; - case 'w': drawWall(ctx, x, y); px(ctx, x + 3, y + 5, TILE - 6, TILE - 8, '#0a1830'); px(ctx, x + 4, y + 6, TILE - 8, TILE - 10, PAL.glass); px(ctx, x + 7, y + 6, 1, TILE - 10, '#0a1830'); break; - case 's': drawWall(ctx, x, y); px(ctx, x + 2, y + 4, TILE - 4, 2, PAL.wood); px(ctx, x + 2, y + 8, TILE - 4, 2, PAL.wood); px(ctx, x + 2, y + 12, TILE - 4, 2, PAL.wood); px(ctx, x + 4, y + 1, 2, 3, PAL.woodHi); px(ctx, x + 9, y + 5, 2, 3, PAL.fire); break; - case 'f': drawWall(ctx, x, y); px(ctx, x + 2, y + 6, TILE - 4, TILE - 6, '#1a1014'); break; - default: drawFloor(ctx, x, y); - if (ch === 'r') { - const e = { t: neigh.t !== 'r', b: neigh.b !== 'r', l: neigh.l !== 'r', r: neigh.r !== 'r' }; - drawRug(ctx, x, y, e, true); - } - if (ch === '=') { // bar counter - px(ctx, x, y + 3, TILE, TILE - 3, PAL.bar); px(ctx, x, y, TILE, 4, PAL.barTop); px(ctx, x, y + 3, TILE, 1, PAL.barEdge); px(ctx, x, y + TILE - 1, TILE, 1, PAL.barEdge); - } - if (ch === 'T') { px(ctx, x + 1, y + 2, TILE - 2, TILE - 4, PAL.woodLo); px(ctx, x + 1, y + 1, TILE - 2, 4, PAL.wood); px(ctx, x + 3, y + 2, TILE - 6, 1, PAL.woodHi); } - if (ch === 'o') { px(ctx, x + 5, y + 6, 6, 6, PAL.woodLo); px(ctx, x + 5, y + 5, 6, 2, PAL.wood); } - if (ch === 'b') { px(ctx, x + 3, y + 2, TILE - 6, TILE - 3, PAL.barrel); px(ctx, x + 3, y + 1, TILE - 6, 3, PAL.barrelTop); px(ctx, x + 3, y + 6, TILE - 6, 1, PAL.barrelBand); px(ctx, x + 3, y + 11, TILE - 6, 1, PAL.barrelBand); } - if (ch === 'c') { px(ctx, x + 2, y + 3, TILE - 4, TILE - 4, PAL.wood); px(ctx, x + 2, y + 2, TILE - 4, 2, PAL.woodHi); ctx.fillStyle = PAL.woodLo; ctx.fillRect(x + 2, y + 8, TILE - 4, 1); ctx.fillRect(x + 8, y + 3, 1, TILE - 5); } +function buildScene(buf) { + const ctx = buf.getContext('2d'); + // ---- floor: warm wood with radial light + planks ---- + const fg = ctx.createLinearGradient(0, 180, 0, H); + fg.addColorStop(0, '#5a3a20'); fg.addColorStop(0.5, '#6e4727'); fg.addColorStop(1, '#4a2e18'); + ctx.fillStyle = fg; ctx.fillRect(0, 180, W, H - 180); + for (let y = 200; y < H; y += 46) { // plank seams (perspective: wider toward bottom) + ctx.strokeStyle = 'rgba(40,24,12,0.55)'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke(); + ctx.strokeStyle = 'rgba(150,100,60,0.10)'; ctx.beginPath(); ctx.moveTo(0, y + 2); ctx.lineTo(W, y + 2); ctx.stroke(); + for (let x = 0; x < W; x += 150) { ctx.strokeStyle = 'rgba(40,24,12,0.4)'; ctx.beginPath(); ctx.moveTo(x + rnd(x + y) * 60, y); ctx.lineTo(x + rnd(x + y) * 60, y + 46); ctx.stroke(); } + } + // ---- rug (center, patterned) ---- + const cx = W / 2, ry = 470; + for (const [rw, rh, col] of [[420, 220, '#5a1a24'], [400, 205, '#7a2330'], [300, 150, '#5a1a24']]) { ctx.fillStyle = col; ctx.beginPath(); ctx.ellipse(cx, ry, rw / 2, rh / 2, 0, 0, 7); ctx.fill(); } + ctx.strokeStyle = '#d6a846'; ctx.lineWidth = 4; ctx.beginPath(); ctx.ellipse(cx, ry, 195, 98, 0, 0, 7); ctx.stroke(); + ctx.lineWidth = 3; ctx.beginPath(); ctx.moveTo(cx, ry - 70); ctx.lineTo(cx + 150, ry); ctx.lineTo(cx, ry + 70); ctx.lineTo(cx - 150, ry); ctx.closePath(); ctx.stroke(); + // ---- back wall (top) stone with gradient + rail ---- + const wg = ctx.createLinearGradient(0, 0, 0, 200); + wg.addColorStop(0, '#241c30'); wg.addColorStop(1, '#3a2f48'); + ctx.fillStyle = wg; ctx.fillRect(0, 0, W, 190); + for (let y = 0; y < 180; y += 36) for (let x = (Math.floor(y / 36) % 2 ? 0 : -40); x < W; x += 80) { ctx.strokeStyle = 'rgba(18,12,26,0.7)'; ctx.lineWidth = 2; ctx.strokeRect(x, y, 80, 36); ctx.strokeStyle = 'rgba(120,108,140,0.15)'; ctx.beginPath(); ctx.moveTo(x + 2, y + 2); ctx.lineTo(x + 78, y + 2); ctx.stroke(); } + // wainscot rail + const rg = ctx.createLinearGradient(0, 175, 0, 200); rg.addColorStop(0, '#8a5a2e'); rg.addColorStop(1, '#5a3a1c'); + ctx.fillStyle = rg; ctx.fillRect(0, 175, W, 22); ctx.fillStyle = 'rgba(180,130,80,0.5)'; ctx.fillRect(0, 175, W, 3); + ctx.fillStyle = 'rgba(0,0,0,0.35)'; ctx.fillRect(0, 197, W, 8); // shadow below rail + // windows (night sky) + for (const wx of [150, W - 230]) { + ctx.fillStyle = '#5a3a1c'; roundRect(ctx, wx - 6, 30, 92, 110, 8); ctx.fill(); + const sg = ctx.createLinearGradient(0, 36, 0, 132); sg.addColorStop(0, '#16243f'); sg.addColorStop(1, '#0c1428'); + ctx.fillStyle = sg; ctx.fillRect(wx, 36, 80, 98); + ctx.fillStyle = '#cfe0ff'; for (let i = 0; i < 9; i++) ctx.fillRect(wx + rnd(i + wx) * 76, 38 + rnd(i * 3) * 92, 2, 2); + ctx.fillStyle = '#5a3a1c'; ctx.fillRect(wx + 38, 36, 4, 98); ctx.fillRect(wx, 82, 80, 4); + } + // hearth (center back) + const hx = cx - 110; + ctx.fillStyle = '#2a2333'; roundRect(ctx, hx, 8, 220, 188, 10); ctx.fill(); + ctx.fillStyle = '#1a1422'; roundRect(ctx, hx + 24, 50, 172, 130, 8); ctx.fill(); + ctx.fillStyle = '#120a08'; roundRect(ctx, hx + 36, 66, 148, 110, 6); ctx.fill(); // fire cavity (fire animated) + const mg = ctx.createLinearGradient(0, 188, 0, 210); mg.addColorStop(0, '#9a6a36'); mg.addColorStop(1, '#5a3a1c'); + ctx.fillStyle = mg; ctx.fillRect(hx - 14, 188, 248, 18); + // back-bar shelf with bottles (under rail, sides of hearth) + for (const sx of [40, hx + 250]) { + ctx.fillStyle = '#4a3018'; ctx.fillRect(sx, 150, 230, 30); + ctx.fillStyle = 'rgba(0,0,0,0.4)'; ctx.fillRect(sx, 176, 230, 5); + const cols = ['#4a9a64', '#b46a52', '#6a82c8', '#caa54a', '#8a5ab0']; + for (let i = 0; i < 11; i++) { const bx = sx + 8 + i * 20, bc = cols[i % 5]; ctx.fillStyle = bc; roundRect(ctx, bx, 124, 9, 30, 3); ctx.fill(); ctx.fillStyle = 'rgba(255,255,255,0.25)'; ctx.fillRect(bx + 1, 126, 2, 22); } + } + // ---- bar counter (front of bartender) ---- + const barY = 360, barW = 520, barX = cx - barW / 2; + ctx.fillStyle = 'rgba(0,0,0,0.4)'; ctx.beginPath(); ctx.ellipse(cx, barY + 70, barW / 2 + 10, 30, 0, 0, 7); ctx.fill(); + const bg = ctx.createLinearGradient(0, barY, 0, barY + 64); bg.addColorStop(0, '#a06a38'); bg.addColorStop(0.18, '#7a4a24'); bg.addColorStop(1, '#5a3418'); + ctx.fillStyle = bg; roundRect(ctx, barX, barY, barW, 64, 10); ctx.fill(); + ctx.fillStyle = 'rgba(200,150,90,0.6)'; roundRect(ctx, barX + 6, barY + 4, barW - 12, 8, 4); ctx.fill(); // top sheen + for (let i = 1; i < 7; i++) { ctx.strokeStyle = 'rgba(40,24,12,0.5)'; ctx.beginPath(); ctx.moveTo(barX + i * barW / 7, barY + 16); ctx.lineTo(barX + i * barW / 7, barY + 60); ctx.stroke(); } + for (const mx of [barX + 70, barX + barW - 90]) { ctx.fillStyle = '#caa54a'; roundRect(ctx, mx, barY + 14, 22, 26, 4); ctx.fill(); ctx.fillStyle = '#e8dca0'; roundRect(ctx, mx + 2, barY + 10, 18, 8, 3); ctx.fill(); } + // ---- tables + stools (sides) ---- + for (const [tx, ty] of [[230, 600], [W - 230, 600]]) { + ctx.fillStyle = 'rgba(0,0,0,0.4)'; ellipse(ctx, tx, ty + 22, 70, 24, 'rgba(0,0,0,0.4)'); + ellipse(ctx, tx, ty, 64, 40, '#5a3418'); ellipse(ctx, tx, ty - 4, 60, 36, '#7a4a24'); ellipse(ctx, tx - 12, ty - 10, 30, 16, '#9a6a3a'); + ctx.fillStyle = '#e8dca0'; ctx.fillRect(tx - 4, ty - 14, 8, 14); // candle + } + // ---- barrels (left) + crates (right) ---- + for (const [bx, by] of [[70, 580], [120, 612], [92, 660]]) { + ellipse(ctx, bx + 22, by + 50, 26, 8, 'rgba(0,0,0,0.4)'); + const bgr = ctx.createLinearGradient(bx, 0, bx + 44, 0); bgr.addColorStop(0, '#6a4422'); bgr.addColorStop(0.5, '#8a5a30'); bgr.addColorStop(1, '#5a3618'); + ctx.fillStyle = bgr; roundRect(ctx, bx, by, 44, 54, 8); ctx.fill(); + ctx.fillStyle = '#c8a040'; ctx.fillRect(bx, by + 10, 44, 5); ctx.fillRect(bx, by + 38, 44, 5); + ellipse(ctx, bx + 22, by + 4, 22, 7, '#9a6a3a'); + } + for (const [cx2, cy2] of [[W - 130, 600], [W - 78, 632]]) { + ellipse(ctx, cx2 + 26, cy2 + 52, 30, 8, 'rgba(0,0,0,0.4)'); + ctx.fillStyle = '#7a4a24'; roundRect(ctx, cx2, cy2, 52, 50, 4); ctx.fill(); + ctx.fillStyle = 'rgba(180,130,80,0.5)'; ctx.fillRect(cx2 + 3, cy2 + 3, 46, 4); + ctx.strokeStyle = '#5a3418'; ctx.lineWidth = 3; ctx.strokeRect(cx2 + 3, cy2 + 3, 46, 44); ctx.beginPath(); ctx.moveTo(cx2 + 4, cy2 + 4); ctx.lineTo(cx2 + 48, cy2 + 46); ctx.stroke(); + } + // banners + for (const [bx, col] of [[60, '#3cc8f0'], [W - 96, '#d6a846']]) { + ctx.fillStyle = col; ctx.globalAlpha = 0.9; ctx.beginPath(); ctx.moveTo(bx, 6); ctx.lineTo(bx + 36, 6); ctx.lineTo(bx + 36, 110); ctx.lineTo(bx + 18, 124); ctx.lineTo(bx, 110); ctx.closePath(); ctx.fill(); ctx.globalAlpha = 1; + ctx.fillStyle = 'rgba(0,0,0,0.25)'; ctx.fillRect(bx + 24, 6, 12, 104); + ctx.fillStyle = '#d6a846'; roundRect(ctx, bx + 12, 48, 12, 12, 2); ctx.fill(); } } -export default function TopDownTavern({ stage = {}, onHotspot }) { +const LIGHTS = [[W / 2, 90, 150, 0.55, '255,170,90'], [W / 2, 360, 220, 0.22, '255,180,100'], [150, 60, 80, 0.3, '255,200,120'], [W - 190, 60, 80, 0.3, '255,200,120'], [230, 586, 70, 0.32, '255,190,110'], [W - 230, 586, 70, 0.32, '255,190,110']]; + +export default function TopDownTavern({ onHotspot }) { const canvasRef = useRef(null); - const [t, setT] = useState(0); useEffect(() => { + const buf = document.createElement('canvas'); buf.width = W; buf.height = H; buildScene(buf); const ctx = canvasRef.current.getContext('2d'); - ctx.imageSmoothingEnabled = false; - let raf, frame = 0; - const at = (c, r) => (MAP[r] && MAP[r][c]) || '.'; + let raf, f = 0; const render = () => { - frame++; - // base map - for (let r = 0; r < ROWS; r++) for (let c = 0; c < COLS; c++) { - const ch = at(c, r); - drawTile(ctx, ch, c * TILE, r * TILE, { t: at(c, r - 1), b: at(c, r + 1), l: at(c - 1, r), r: at(c + 1, r) }); + f++; + ctx.drawImage(buf, 0, 0); + // hearth fire + const hx = W / 2 - 74; + for (let i = 0; i < 6; i++) { + const fl = 0.55 + 0.45 * Math.sin(f * 0.16 + i * 1.3), bx = hx + 8 + i * 22; + const fgr = ctx.createLinearGradient(0, 176, 0, 100); fgr.addColorStop(0, '#ff8a2a'); fgr.addColorStop(1, 'rgba(255,210,90,0)'); + ctx.fillStyle = fgr; ctx.beginPath(); ctx.moveTo(bx, 178); ctx.quadraticCurveTo(bx + 8, 150 - fl * 30, bx + 11, 120 - fl * 26); ctx.quadraticCurveTo(bx + 14, 150 - fl * 30, bx + 22, 178); ctx.fill(); } - // fireplace fire (animated) under the 'f' wall tiles row1 cols6-9 - const flick = 0.6 + 0.4 * Math.sin(frame * 0.2); - for (let c = 6; c <= 9; c++) { - const x = c * TILE, y = 1 * TILE; - px(ctx, x + 3, y + 8 - flick * 3, TILE - 6, 6 + flick * 3, PAL.fire); - px(ctx, x + 5, y + 10 - flick * 2, TILE - 10, 4, PAL.fireHot); + // warm lighting + ctx.globalCompositeOperation = 'lighter'; + for (const [lx, ly, lr, la, rgb] of LIGHTS) { + const a = la * (0.85 + 0.15 * Math.sin(f * 0.09 + lx)); + const g = ctx.createRadialGradient(lx, ly, 0, lx, ly, lr); g.addColorStop(0, `rgba(${rgb},${a})`); g.addColorStop(1, `rgba(${rgb},0)`); + ctx.fillStyle = g; ctx.beginPath(); ctx.arc(lx, ly, lr, 0, 7); ctx.fill(); } - // wall lantern glow (animated) near windows - ctx.globalAlpha = 0.10 + 0.05 * Math.sin(frame * 0.15); - [[3, 2], [16, 2], [10, 1]].forEach(([c, r]) => { ctx.fillStyle = PAL.fire; ctx.beginPath(); ctx.arc(c * TILE + 8, r * TILE + 10, 14, 0, 7); ctx.fill(); }); - ctx.globalAlpha = 1; + ctx.globalCompositeOperation = 'source-over'; + // vignette + const vg = ctx.createRadialGradient(W / 2, H * 0.5, H * 0.28, W / 2, H * 0.5, H * 0.82); + vg.addColorStop(0, 'rgba(0,0,0,0)'); vg.addColorStop(1, 'rgba(8,4,12,0.7)'); + ctx.fillStyle = vg; ctx.fillRect(0, 0, W, H); + // dust + ctx.fillStyle = 'rgba(255,220,160,0.45)'; + for (let i = 0; i < 22; i++) { const dx = (i * 67 + f * 0.35) % W, dy = (i * 53 + Math.sin(f * 0.018 + i) * 24 + 220) % H; ctx.fillRect(dx, dy, 2, 2); } raf = requestAnimationFrame(render); - if (frame % 30 === 0) setT(frame); }; render(); return () => cancelAnimationFrame(raf); @@ -118,16 +134,13 @@ export default function TopDownTavern({ stage = {}, onHotspot }) {
- {/* characters (sprites as NPCs over the floor) */} - onHotspot?.('agents')} /> - onHotspot?.('agents')} /> - onHotspot?.('booth')} /> - {/* hotspots */} - - +
); diff --git a/src/styles.css b/src/styles.css index 070fd5d..10053cc 100644 --- a/src/styles.css +++ b/src/styles.css @@ -922,14 +922,14 @@ button { /* ===================================================================== */ .td-app { background: #0a0710; } .td-home { position: relative; flex: 1; display: flex; align-items: center; justify-content: center; padding: 18px; overflow: hidden; } -.td-stage { position: relative; aspect-ratio: 320 / 208; width: min(96vw, calc((100vh - 150px) * 1.538)); box-shadow: 0 16px 50px rgba(0,0,0,0.6), inset 0 0 0 4px #1a0f08; } +.td-stage { position: relative; aspect-ratio: 1200 / 760; width: min(96vw, calc((100vh - 150px) * 1.579)); border-radius: 8px; overflow: hidden; box-shadow: 0 18px 60px rgba(0,0,0,0.7), inset 0 0 0 4px #1a0f08, inset 0 0 0 6px #3a2410; } .td-room { position: absolute; inset: 0; } -.td-canvas { width: 100%; height: 100%; display: block; image-rendering: pixelated; } +.td-canvas { width: 100%; height: 100%; display: block; image-rendering: auto; } .td-overlay { position: absolute; inset: 0; } -.td-npc { position: absolute; transform: translateX(-50%); bottom: auto; background: none; cursor: pointer; padding: 0; filter: drop-shadow(2px 3px 0 rgba(0,0,0,0.5)); } -.td-npc img { height: 100%; image-rendering: pixelated; display: block; animation: td-bob 3.6s ease-in-out infinite; } -.td-npc:hover { filter: drop-shadow(0 0 8px rgba(255,200,120,0.7)) drop-shadow(2px 3px 0 rgba(0,0,0,0.5)); z-index: 5; } +.td-npc { position: absolute; transform: translateX(-50%); bottom: auto; background: none; cursor: pointer; padding: 0; filter: drop-shadow(0 6px 6px rgba(0,0,0,0.55)); } +.td-npc img { height: 100%; display: block; animation: td-bob 4s ease-in-out infinite; } +.td-npc:hover { filter: drop-shadow(0 0 12px rgba(255,200,120,0.85)) drop-shadow(0 6px 6px rgba(0,0,0,0.55)); z-index: 5; } @keyframes td-bob { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-4%)} } .td-npc-shadow { position: absolute; left: 50%; bottom: -4px; width: 60%; height: 8%; transform: translateX(-50%); background: radial-gradient(ellipse, rgba(0,0,0,0.5), transparent 70%); } .td-say { position: absolute; bottom: 102%; left: 50%; transform: translateX(-50%); width: max-content; max-width: 180px; padding: 6px 8px; font-size: 8px; line-height: 1.6; color: #1c1206; background: var(--cream); border: 2px solid #000; box-shadow: 2px 2px 0 #000; text-align: center; z-index: 6; } @@ -942,7 +942,8 @@ button { .td-hot-door { padding: 8px 14px; font-size: 10px; color: #36f0b0; background: rgba(8,24,18,0.82); border: 2px solid #36f0b0; box-shadow: 0 0 16px rgba(54,240,176,0.35), 3px 3px 0 #000; } .td-hot-door:hover { background: rgba(12,34,26,0.92); } -.td-hero { position: absolute; left: 3%; top: 8%; width: min(42%, 360px); z-index: 4; font-family: "Press Start 2P", monospace; } +.td-hero { position: absolute; left: 2.5%; top: 6%; width: min(40%, 380px); z-index: 4; font-family: "Press Start 2P", monospace; + padding: 16px 18px; background: linear-gradient(180deg, rgba(12,6,18,0.72), rgba(12,6,18,0.45)); border-radius: 10px; backdrop-filter: blur(2px); } .td-hero h1 { font-size: clamp(13px, 2.1vw, 22px); line-height: 1.5; color: #fff; text-shadow: 2px 2px 0 #000, 0 0 18px rgba(0,0,0,0.8); margin: 0 0 10px; } .td-hero p { font-family: Inter, sans-serif; font-size: clamp(10px, 1.3vw, 13px); line-height: 1.7; color: #e7d4b6; text-shadow: 1px 1px 2px #000; margin: 0 0 12px; } .td-hero-cta { display: flex; gap: 8px; flex-wrap: wrap; }