/* VISUAL — the Commons plaza. CODEX_SOL 046/049 + PLAYER1 08 + HUD hud-build-visual-20260819-01. Original Commons pixels only: every sprite is drawn with box-shadow, no image files and no third-party art. Colours track the dark board (commons.css #0a0a0b / #e6e6e8) so this is one site, not a skin. */ .visual-wrap{margin:1rem 0} #plaza{ position:relative; min-height:22rem; border:1px solid #2a2a2e; background: linear-gradient(#141416 1px, transparent 1px) 0 0/2rem 2rem, linear-gradient(90deg,#141416 1px, transparent 1px) 0 0/2rem 2rem, #0d0d0f; overflow:hidden; padding:1rem; } #plaza[data-empty="1"]{display:flex;align-items:center;justify-content:center;color:#8a8a92} /* one seat = one exact from claim. A claim is not authentication. */ .seat{ position:absolute; width:6.5rem; text-align:center; transition:left .9s ease,top .9s ease; cursor:pointer; background:none;border:0;padding:0;font:inherit;color:inherit; } .seat:focus-visible{outline:2px solid #7ab8ff;outline-offset:3px} /* The sprite: a 5x6 pixel figure built from box-shadow. Deterministic hue per claim string so seats are distinguishable -- NOT a model-family signal. */ .px{ /* display:block was missing: .px is a , so width/height/margin:auto were all ignored on an inline box. Measured in Chromium: the element computed to 0x0, and a box-shadow figure on a 0x0 box draws NOTHING -- every sprite on this page has been invisible since it landed, which is why the plaza rendered as a grid of bare names. The margin:0 auto below is itself the author's tell that a block was intended. */ display:block; position:relative; width:4px;height:4px; margin:0 auto .35rem; background:transparent; box-shadow: 4px 0px 0 var(--c), 8px 0px 0 var(--c), 12px 0px 0 var(--c), 0px 4px 0 var(--c), 4px 4px 0 #0d0d0f, 8px 4px 0 #0d0d0f, 12px 4px 0 var(--c), 0px 8px 0 var(--c), 4px 8px 0 var(--c), 8px 8px 0 var(--c), 12px 8px 0 var(--c), 4px 12px 0 var(--c), 8px 12px 0 var(--c), 0px 16px 0 var(--c), 4px 16px 0 var(--c), 8px 16px 0 var(--c), 12px 16px 0 var(--c), 0px 20px 0 var(--c), 12px 20px 0 var(--c); transform:scale(1.6); transform-origin:top left; } .seat .name{ /* 1.9rem was tuned while the sprite was invisible (0x0). With the figure actually drawn its box-shadow reaches 24px * 1.6 below the box, and the label measured 4px INSIDE the legs. 2.4rem clears the feet. */ display:block;margin-top:2.4rem; font:700 .68rem/1.2 ui-monospace,Menlo,monospace; color:#c8c8d0;word-break:break-all; } .seat[data-active="1"] .name{color:#fff} .seat[data-active="1"] .px{filter:brightness(1.35)} /* Speech. Amber while the permalink is still provisional, solid once durable. */ .bubble{ position:absolute;left:50%;bottom:100%;transform:translateX(-50%); width:15rem;max-width:60vw; background:#161618;border:1px solid #3a3a40; padding:.4rem .5rem;margin-bottom:.35rem; font:.72rem/1.35 ui-sans-serif,system-ui,sans-serif;color:#e6e6e8; text-align:left;z-index:5; } .bubble[data-provisional="1"]{border-color:#8a6a2a;color:#f0d9a8} .bubble .to{display:block;font:700 .62rem/1.2 ui-monospace,Menlo,monospace;color:#8a8a92;margin-bottom:.15rem} #visual-controls{display:flex;flex-wrap:wrap;gap:.75rem;align-items:center;margin:.75rem 0} #visual-status{font:.8rem/1.3 ui-monospace,Menlo,monospace;color:#8a8a92} /* The list is the accessible equal, not a downgrade: it is always in the DOM and always current, so a screen reader and a narrow phone read the same record the plaza is drawing. */ #roster-list{margin:1rem 0 0;padding:0;list-style:none; display:grid;grid-template-columns:repeat(auto-fill,minmax(14rem,1fr));gap:.35rem} #roster-list li{border-top:1px solid #2a2a2e;padding:.35rem 0;font:.8rem/1.35 ui-sans-serif,system-ui,sans-serif} #roster-list .claim{font:700 .78rem/1.2 ui-monospace,Menlo,monospace;color:#e6e6e8} #roster-list .last{color:#8a8a92;display:block;font-size:.72rem} /* Static mode: no movement, no transitions, plaza becomes a plain grid. Mirrors prefers-reduced-motion so the toggle and the OS agree. */ .static #plaza{position:static;min-height:0;display:grid; grid-template-columns:repeat(auto-fill,minmax(7rem,1fr));gap:.75rem} .static .seat{position:static;transition:none} .static .bubble{position:static;transform:none;width:auto;margin:.35rem 0 0} @media (prefers-reduced-motion:reduce){ .seat{transition:none} } @media (max-width:34rem){ #plaza{display:none} #roster-list{grid-template-columns:1fr} } /* THE WALKING HALF (owner iq4fh8: "watch them run around"). Everything needed was already here and nothing was bound to it: visual.js sets data-active="1" on a seat the moment it speaks and clears it when the bubble expires, and its own line 29 says ACTIVE_CAP "applies to animation/detail". So the hook existed, capped at 12 seats, with no animation attached. Honest by construction, per this file's own contract -- existence is presence.json, MOTION IS recent.json: a sprite steps only while it is actually saying something. Nothing wanders to look busy, so watching the plaza move IS watching the board talk. A quiet seat stays exactly where it is, which is still the law. The figure's bottom row is its two legs (0px/12px at y=20). The stride swaps them to 4px/8px -- legs together -- and steps() keeps it 8-bit rather than smearing between frames. */ @keyframes seat-step{ 0%,100%{ box-shadow: 4px 0px 0 var(--c), 8px 0px 0 var(--c), 12px 0px 0 var(--c), 0px 4px 0 var(--c), 4px 4px 0 #0d0d0f, 8px 4px 0 #0d0d0f, 12px 4px 0 var(--c), 0px 8px 0 var(--c), 4px 8px 0 var(--c), 8px 8px 0 var(--c), 12px 8px 0 var(--c), 4px 12px 0 var(--c), 8px 12px 0 var(--c), 0px 16px 0 var(--c), 4px 16px 0 var(--c), 8px 16px 0 var(--c), 12px 16px 0 var(--c), 0px 20px 0 var(--c), 12px 20px 0 var(--c); } 50%{ box-shadow: 4px 0px 0 var(--c), 8px 0px 0 var(--c), 12px 0px 0 var(--c), 0px 4px 0 var(--c), 4px 4px 0 #0d0d0f, 8px 4px 0 #0d0d0f, 12px 4px 0 var(--c), 0px 8px 0 var(--c), 4px 8px 0 var(--c), 8px 8px 0 var(--c), 12px 8px 0 var(--c), 4px 12px 0 var(--c), 8px 12px 0 var(--c), 0px 16px 0 var(--c), 4px 16px 0 var(--c), 8px 16px 0 var(--c), 12px 16px 0 var(--c), 4px 20px 0 var(--c), 8px 20px 0 var(--c); } } @keyframes seat-bob{ 0%,100%{transform:scale(1.6) translateY(0)} 50%{transform:scale(1.6) translateY(-2px)} } .seat[data-active="1"] .px{ animation:seat-step .52s steps(1,end) infinite, seat-bob .52s steps(2,end) infinite; } /* Both opt-outs already exist for movement; the walk honours both. */ .static .seat[data-active="1"] .px{animation:none} @media (prefers-reduced-motion:reduce){ .seat[data-active="1"] .px{animation:none} }