* {
    border: 0;
    margin: 0;
    padding: 0;
}

body {
    overflow: hidden;
}
#game {
    position:absolute;
    top:0;
    left:0;
    z-index:2;
    overflow:hidden;
    display:block;
    background-color:#2a2a2a;

    height: 100vh;
    width: 100vw;
    display: grid;
    place-items: center;
    position: fixed;
    /* TASK-2A-427: the canvas-surface selectors carry no explicit
       `color`, so under <meta name="color-scheme" content="light dark">
       (web/index.html) Chromium inherits the UA's scheme-dependent
       default — rgb(255,255,255) under prefers-color-scheme: dark and
       rgb(0,0,0) under light. The painted-surface guard in
       tests/color-scheme.test.js captures the computed color on every
       canvas-surface element and asserts it is byte-identical between
       the dark and light runs, so each base rule pins a fixed value
       the media-query block below cannot touch. The actual value is
       invisible (#game's only child is the <canvas id="canvas"> and
       the overlays paint via Canvas2D, never via inherited color),
       so #fff matches the rest of the canvas chrome and keeps the
       file's existing hex vocabulary. */
    color: #fff;
    /* TASK-24-96: the phone painter pans with a single finger and
       places with a tap. The browser would otherwise interpret a
       one-finger drag as a scroll/overscroll on the body (which is
       overflow:hidden but still receives touch events that bubble
       up from #game), and a two-finger pinch as a page zoom. The
       viewport meta already disables user-scalable, so we just need
       to tell the browser the canvas itself is not a scrollable
       surface. touch-action: none stops scroll, pinch-zoom, double-
       tap-to-zoom, and the iOS rubber-band on this element. The
       pointer-events: none overlays on top of the canvas do not
       need it (they never receive touches in the first place). */
    touch-action: none;
}
#canvas {
    position:absolute;
    /* TASK-2A-427: see #game. The <canvas> element itself does not
       render text (pixels are pushed through Canvas2D), so pinning
       `color` here only affects the computed-style value the guard
       reads, not anything the painter sees. */
    color: #fff;
    /* Same reasoning as #game: this is the surface the painter
       drags and taps. Without touch-action: none, Chrome on
       Android may swallow the first touchmove as a scroll
       attempt. */
    touch-action: none;
}
#gridOverlay {
    position:absolute;
    z-index: 200;
    pointer-events: none;
    /* TASK-2A-427: see #game. <canvas> overlay that draws the grid
       lines; `color` is unused for rendering, the declaration only
       exists so the painted-surface guard sees a byte-identical value
       across schemes. */
    color: #fff;
}
#axisOverlay {
    /* TASK-2A-429: numbered edge-tick ruler overlay (TASK-2A-423
       part 2). Chrome only — a separate canvas so it is never part
       of the saved PNG (Screenshot reads #canvas / fullCanvas, not
       #axisOverlay). Sits above #gridOverlay z=200 and
       #cursorOverlay z=150 so the numbers win the painter's eye;
       below #minimap z=300 and the HUD panels z=500 so a ruler on
       the canvas's bottom-right does not occlude chrome. top:0/
       left:0 anchors it to the same viewport origin the other
       absolute overlays use, so a tick's screen x lines up byte-
       for-byte with the grid cell the label numbers. pointer-
       events:none is non-negotiable: a tap on the ruler must fall
       through to #canvas and never eat a paint. */
    position:absolute;
    top:0;
    left:0;
    z-index: 250;
    pointer-events: none;
}
#minimap {
    /* Client minimap of the whole canvas (TASK-22-397). Bottom-left
       so it does not collide with the action buttons (top:5px;right)
       or the stats panel (bottom:5px;right). z-index higher than
       #game (2) and #gridOverlay (200) so the painter can read it
       while the grid overlay is in front of the main canvas, but
       lower than the connection-status overlay (1000). Pointer
       events are intentionally ON so a click on the minimap moves
       the view — do NOT copy pointer-events:none from #gridOverlay. */
    position:absolute;
    bottom:5px;
    left:5px;
    z-index:300;
    width:200px;
    height:200px;
    border:1px solid #fff;
    /* TASK-2A-427: see #game. */
    color: #fff;
}
#cursorOverlay {
    /* TASK-22-167: remote-cursor overlay. Sits above the canvas
       (#game, z=2) and the grid overlay (#gridOverlay, z=200) so
       other painters' cursors are visible at every pan/zoom; sits
       below the in-canvas hover box (#hover, z=200 -- the local
       cursor's drawn frame) so the local hover is not occluded by
       a remote cursor sitting on the same cell; sits below the
       user-list panel (z=50) and stats (z=50) and operator notice
       (z=100) so the chrome stays readable. pointer-events:none
       keeps the overlay out of the input path so it never blocks
       a click on the canvas underneath. */
    position:absolute;
    z-index: 150;
    pointer-events: none;
    /* TASK-2A-427: see #game. */
    color: #fff;
}
#pulseOverlay {
    /* TASK-28-436: per-cell pulse marker overlay. inset:0 anchors
       it to the same viewport origin as #cursorOverlay (no
       positioned ancestor between body and the canvas overlays),
       so the absolute left/top math in syncPulseMarkers (gx*scale
       + view.position.x) lines up with the painted pixels. z=151
       sits just above #cursorOverlay (z=150) and below #hover /
       #gridOverlay (z=200) and #focus (z=400) so a fresh pulse
       does not occlude the keyboard-focus outline or the local
       hover frame. pointer-events:none keeps it out of the
       input path so a tap on a pulsing cell still reaches the
       canvas underneath (no missed paints). */
    position: absolute;
    inset: 0;
    z-index: 151;
    pointer-events: none;
    /* TASK-2A-427: see #game. */
    color: #fff;
}
.pulse-marker {
    /* TASK-28-436: a single .pulse-marker is one rectangle in
       screen space, positioned/sized by syncPulseMarkers() in
       web/js/ws.js (gx*scale+pos.x/y, size=Math.max(1,scale)).
       The bright RGBA tint (warm yellow on dark cells, still
       visible on light palette entries) reads on every operator-
       palette colour without obscuring the underlying pixel. The
       per-element animation-duration is set inline from
       pulse.PULSE_WINDOW_MS so the same @keyframes plays for
       however long the configured window is (3 s today).
       animation-fill-mode: forwards leaves the marker at its
       end-state once the fade has finished, which is the visual
       cue that the placement is "no longer fresh" — the ws.js
       sync loop also removes the DOM node on the same instant,
       so a still-visible end-state is a no-op. box-sizing:
       border-box keeps the size in sync with the cell the
       freshness overlay paints, even if a future change adds a
       border. pointer-events:none is the same input-path guard
       as the parent. */
    position: absolute;
    box-sizing: border-box;
    background: rgba(255, 235, 120, 0.55);
    pointer-events: none;
    animation-name: pulse-fade;
    animation-timing-function: linear;
    animation-fill-mode: forwards;
    animation-duration: 3000ms;
}
@keyframes pulse-fade {
    /* TASK-28-436: the fade curve. A freshly placed pixel starts
       at full intensity and linearly drops to 0 by the end of the
       window; the ws.js sync loop removes the DOM node on the
       next frame, so the end-state is never observed in practice.
       The keyframe name is referenced by .pulse-marker. */
    from { opacity: 1; }
    to   { opacity: 0; }
}
#hover {
    position:absolute;
    z-index: 200;
    pointer-events: none;
    scale: 1;
    width: 1px;
    height: 1px;
    background-color: #fff;
}
#pixelInfo {
    /* TASK-26-695: provenance tooltip for the hovered cell
       (TASK-26-684 Part 3, module: web/js/pixelinfo.js, page hook:
       web/js/input.js). Sits above the hover box (#hover, z=200)
       so the painter/placement-time text is not occluded by the
       cell-square outline, and below the focus outline (#focus,
       z=400) and any action/menu overlays (#action z=500,
       #connectionStatus z=1000) -- the tooltip should never cover
       a clickable surface. pointer-events:none keeps it out of
       the mouse-event path so a move over the tooltip still
       reaches the canvas underneath. The literal fallbacks on
       the var() calls keep the rule self-consistent even if the
       chrome.css variables are ever removed or renamed; the
       chrome values (--font-mono, --fg-text, --border-mid,
       --radius-sm) win on the index page, where chrome.css is
       loaded alongside style.css. Hidden by default via the
       `hidden` attribute the pixelinfo module toggles; the
       `[hidden]` rule below makes the browser honour it even
       when other styles (e.g. z-index) are present. */
    position: absolute;
    z-index: 300;
    pointer-events: none;
    max-width: 240px;
    padding: 2px 6px;
    font: 12px/1.35 var(--font-mono, ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace);
    color: var(--fg-text, #ffffff);
    background: rgba(0, 0, 0, 0.82);
    border: 1px solid var(--border-mid, #555555);
    border-radius: var(--radius-sm, 3px);
    white-space: nowrap;
}
#pixelInfo[hidden] { display: none; }
#focus {
    /* Keyboard-focus outline (TASK-23-81). Sits on top of the
       canvas (#game, z=2) and the hover square (#hover, z=200) so a
       keyboard-only painter can still see which cell they will
       paint at when their mouse also happens to be over a cell;
       below the action buttons (z=500) and the connection-status
       overlay (z=1000) so neither hides the focus. The outline is
       rendered as a CSS outline (drawn outside the box) so the
       highlight is one cell wide without painting over the cell's
       pixel. pointer-events:none keeps the outline out of the
       mouse-event path so a click on the focused cell still
       reaches the canvas. */
    position:absolute;
    box-sizing:border-box;
    outline:2px solid #ffd400;
    pointer-events:none;
    z-index:400;
}
#activeColor {
    width: 2em;
    height: 2em;
}
#stats {
    pointer-events: none;
    position: absolute;
    right: 5px;
    bottom: 5px;
    color: #fff;
    z-index: 50;
    font-family: monospace;
    font-size: 1em;
}
#connectionStatus {
    display: none;
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background-color: rgba(42, 42, 42, 0.95);
    color: #fff;
    padding: 20px 40px;
    border-radius: 8px;
    font-family: monospace;
    font-size: 1.2em;
    z-index: 1000;
    border: 2px solid #555;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
}
#connectionStatus.error {
    border-color: #ff6b6b;
    background-color: rgba(60, 30, 30, 0.95);
}
#cooldownStatus {
    /* TASK-22-84: small pill near the top of the page that shows the
       remaining per-account placement cooldown ("Next pixel in Ns").
       Sits below the user-list panel (top:5px;left:5px) and the action
       buttons (top:5px;right:5px) and the operator banner (top:12px);
       centred so it stays out of the way of those widgets while still
       being clearly visible while the painter is reaching for the next
       pixel. z-index:100 matches the operator notice and is below the
       connection-status overlay (z=1000) so a reconnecting status
       message can still cover it. pointer-events:none keeps the pill
       out of the input path so it never blocks a click on the canvas. */
    display: none;
    position: fixed;
    top: 50px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 100;
    color: #fff;
    background-color: rgba(42, 42, 42, 0.92);
    padding: 6px 14px;
    border-radius: 6px;
    font-family: monospace;
    font-size: 0.95em;
    border: 1px solid #888;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
    pointer-events: none;
}
#userList {
    /* Top-left "who else is on the canvas" panel. Sits above the
       canvas (#game, z=2) so the painter can read the names without
       hiding their work area, and below the connection status
       overlay (z=1000) so a reconnecting status message can cover
       it during a network outage. */
    position: absolute;
    top: 5px;
    left: 5px;
    z-index: 50;
    color: #fff;
    background-color: rgba(42, 42, 42, 0.85);
    padding: 8px 12px;
    border-radius: 6px;
    font-family: monospace;
    font-size: 0.9em;
    min-width: 140px;
    max-width: 240px;
    border: 1px solid #555;
    pointer-events: none;
}
#userList .userListHeading {
    font-weight: bold;
    margin-bottom: 4px;
    color: #fff;
    letter-spacing: 0.05em;
}
#userList ul {
    list-style: none;
    margin: 0;
    padding: 0;
}
#userList li {
    padding: 1px 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
#operatorNotice {
    /* Operator-composed banner (TASK-1X-633). Sits above the canvas
       (#game, z=2) so the painter can read it without having to dig
       through panels; below the connection-status overlay (z=1000)
       so a reconnecting status message can still cover it. Centered
       near the top so it does not collide with the user-list panel
       (top:5px;left:5px) or the action buttons (top:5px;right:5px).
       pointer-events:none keeps the banner out of the input path so
       it never blocks a click on the canvas. */
    position: fixed;
    top: 12px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 100;
    color: #fff;
    background-color: rgba(42, 42, 42, 0.92);
    padding: 8px 18px;
    border-radius: 6px;
    font-family: monospace;
    font-size: 0.95em;
    max-width: 60%;
    text-align: center;
    border: 1px solid #888;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
    pointer-events: none;
}
#placementRefusal {
    /* Paint-side rejection banner (TASK-22-259). Shown when the
       server replies to a Claim with a Wrapper_Error — the painter
       sent a pixel and the server refused it (cooldown, banned,
       protected region, palette, maintenance, etc.). Modelled on
       #operatorNotice so the two banners share the same panel
       language (centred, monospace, pointer-events:none) but
       stacked BELOW it (top:48px vs top:12px) so both can be on
       screen at once without colliding. The red border + red text
       tint flags the message as a refusal rather than an
       operator-composed neutral notice. */
    position: fixed;
    top: 48px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 100;
    color: #ffb3b3;
    background-color: rgba(60, 20, 20, 0.92);
    padding: 6px 16px;
    border-radius: 6px;
    font-family: monospace;
    font-size: 0.9em;
    max-width: 60%;
    text-align: center;
    border: 1px solid #ff6b6b;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
    pointer-events: none;
}
#cooldownStatus {
    /* Per-account placement cooldown readout (TASK-22-332). Sits just
       below the operator-notice banner so the two stacked banners
       don't collide, but above the canvas (#game, z=2) so the painter
       can read the "Next pixel in Ns" countdown without losing sight
       of their work area. Below the connection-status overlay
       (z=1000) so a reconnecting status message can still cover it.
       pointer-events:none keeps the readout out of the input path so
       it never blocks a click on the canvas. JS toggles the inline
       `display` so the element only takes screen space while a
       cooldown is actually running. */
    position: fixed;
    top: 52px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 100;
    color: #fff;
    background-color: rgba(42, 42, 42, 0.92);
    padding: 6px 16px;
    border-radius: 6px;
    font-family: monospace;
    font-size: 0.95em;
    text-align: center;
    border: 1px solid #888;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
    pointer-events: none;
}
#templateOverlay {
    /* Translucent template overlay (TASK-22-358). A 1000x1000 image
       the painter can lay over the canvas to coordinate a group
       draw, in the spirit of r/place templates. Sits between the
       canvas (#game, z=2) and the cursor / grid overlays
       (#gridOverlay / #hover, both z=200) at z=100 so the painter
       can read the template outline AND see their cursor on top.
       pointer-events:none is the part that does not intercept
       clicks — clicks and drags fall through to the canvas
       underneath, so picking up the overlay never blocks a paint
       or a pan. transform-origin:0 0 keeps the
       translate(<pos>) scale(<scale>) transform on this layer in
       the same coordinate frame the canvas uses for its own
       ctx.setTransform, so pan/zoom stays aligned. The image
       starts display:none so a returning painter without a
       "show on load" choice never sees a surprise overlay. */
    position: absolute;
    top: 0;
    left: 0;
    width: 1000px;
    height: 1000px;
    transform-origin: 0 0;
    z-index: 100;
    pointer-events: none;
    opacity: 0.5;
    display: none;
    image-rendering: pixelated;
    user-select: none;
}
#templateSelect {
    /* Template picker (TASK-22-358). Sits inside the top-right
       control div next to the action buttons so the painter
       doesn't have to leave the canvas to swap templates. The
       white text on a dark background keeps the option labels
       legible against the canvas; the surrounding div already sets
       z-index:500 so this is well above everything else. */
    font-family: monospace;
    font-size: 0.9em;
    padding: 2px 4px;
    color: #fff;
    background-color: rgba(42, 42, 42, 0.85);
    border: 1px solid #555;
    border-radius: 3px;
}
.paletteSwatch {
    /* Operator-configured palette legend (TASK-24-199). One entry
       per palette colour, rendered by web/js/palette.js →
       renderLegend. The button's background is the entry's
       "#RRGGBB" colour; the visible label is the matching
       "#RRGGBB" index so the painter can read the index off the
       swatch. width / height / margin set the legend row's tile
       size so 16 entries (DefaultPalette) fit horizontally on
       the canvas's bottom-left strip. */
    width: 2em;
    height: 2em;
    margin: 2px;
    padding: 0;
    border: 1px solid #555;
    border-radius: 3px;
    color: #fff;
    font-family: monospace;
    font-size: 0.7em;
    line-height: 1em;
    cursor: pointer;
}
#recentPlacements {
    /* TASK-26-577: recent-placements list. The list-style reset and
       the swatch size both belong to the HUD chrome, so they live in
       style.css rather than the inline-style convention of the
       surrounding #operatorNotice / #cooldownStatus panels. The
       module only writes inline style.background on each <span>
       (web/js/recent.js → render); without an explicit display /
       width / height the swatch is 0×0 and the colour is invisible.
       The border keeps the swatch readable on light palette colours
       (e.g. #FFF200 against a dark page background). */
    list-style: none;
    margin: 0;
    padding: 0;
}
#recentPlacements li span {
    display: inline-block;
    width: 0.75em;
    height: 0.75em;
    margin-left: 0.4em;
    vertical-align: middle;
    border: 1px solid #444;
}
.paletteSwatchActive {
    /* "Last colour placed" outline (TASK-24-284). The matching
       swatch in #paletteContainer carries both `paletteSwatch`
       and `paletteSwatchActive` (web/js/palette.js →
       renderLegend, 5th arg `activeIndex`) so the painter sees
       which swatch the page remembers without losing the
       background-colour tile. The outline is rendered with the
       same offset on every side as the keyboard-focus outline
       (#focus) so the two are visually consistent across the
       canvas; the bright colour contrasts against any palette
       entry (including the dark default entries in
       DefaultPalette). outline:2px solid sticks outside the
       button box so the highlight does not eat into the
       2em × 2em tile and the surrounding tiles do not shift. */
    outline: 2px solid #ffd400;
    outline-offset: 1px;
}
.paletteSwatch:focus-visible {
    /* TASK-2A-508: keyboard-focus ring on the palette swatch strip.
       Same colour and 1px offset as #focus (line 226) and
       .paletteSwatchActive (the "last colour placed" ring) so the
       three read as one chrome. outline:2px solid draws outside the
       button box, so the 2em x 2em tile does not shrink and the
       neighbouring tiles do not shift. The literal #ffd400 (not
       var(--focus)) matches #focus and .paletteSwatchActive, which
       stay gold under prefers-color-scheme: light as well. */
    outline: 2px solid #ffd400;
    outline-offset: 1px;
}
.paletteSwatch:focus {
    /* TASK-2A-508 fallback: a headless run that focuses a swatch
       programmatically (element.focus()) may not match :focus-visible,
       which would leave the ring non-deterministic. Same declaration
       as the :focus-visible rule so the ring is identical either way;
       a pointer click also focuses the button, which then shows the
       same gold ring as the .paletteSwatchActive state (no double
       ring, no reflow — an outline never affects layout). */
    outline: 2px solid #ffd400;
    outline-offset: 1px;
}
#paletteColorblindToggle {
    /* TASK-28-433: colour-blind-safe toggle next to the palette
       row. Sits as a sibling of #paletteContainer in web/index.html
       (NOT inside it — renderLegend clears paletteContainer's
       children on every render, which would wipe the control). The
       chrome mirrors .paletteSwatch so the control reads as another
       palette chrome element rather than a different widget, and
       vertical-align:middle lines its baseline up with the swatch
       row so the painter's eye doesn't jump between the two. */
    display: inline-block;
    vertical-align: middle;
    margin: 2px;
    padding: 2px 6px;
    border: 1px solid #555;
    border-radius: 3px;
    color: #fff;
    background-color: rgba(42, 42, 42, 0.85);
    font-family: monospace;
    font-size: 0.7em;
    cursor: pointer;
}

/* The project chrome (tokens, header, footer, about panel, document
 * styles for /help /404 /profile) lives in web/chrome.css
 * (TASK-26-34). Each public page links it explicitly so the file is
 * only fetched by the pages that actually need it; the canvas page
 * still loads just /style.css — the chrome rules only kick in on the
 * new elements and on the .sitePage opt-in. */

/* ----------------------------------------------------------------------
 * Light colour scheme for the canvas HUD / toolbar chrome (TASK-2A-426).
 *
 * web/style.css paints the canvas HUD with hard-coded dark values
 * (rgba(42,42,42,*) / #fff / #555), so a browser that asks for a light
 * scheme would keep dark chrome. This block re-declares ONLY those
 * chrome selectors with the light panel set the rest of the project
 * chrome already uses under prefers-color-scheme: light
 * (web/chrome.css): panel rgba(255,255,255,.92), text #1a1a1a,
 * border #b8b8b8; #placementRefusal keeps its refusal signal with the
 * light red set. The canvas (#game), palette swatches and overlays are
 * deliberately untouched so painted pixels keep their palette colours.
 * #connectionStatus.error and #userList .userListHeading are re-declared
 * here because their base rules are more specific than the panel-id
 * rules above; everything else wins by source order (this block is last).
 */
@media (prefers-color-scheme: light) {
    #userList {
        color: #1a1a1a;
        background-color: rgba(255, 255, 255, 0.92);
        border-color: #b8b8b8;
    }
    #userList .userListHeading { color: #1a1a1a; }
    #stats {
        color: #1a1a1a;
        background-color: rgba(255, 255, 255, 0.92);
    }
    #cooldownStatus {
        color: #1a1a1a;
        background-color: rgba(255, 255, 255, 0.92);
        border-color: #b8b8b8;
    }
    #operatorNotice {
        color: #1a1a1a;
        background-color: rgba(255, 255, 255, 0.92);
        border-color: #b8b8b8;
    }
    #connectionStatus {
        color: #1a1a1a;
        background-color: rgba(255, 255, 255, 0.92);
        border-color: #b8b8b8;
    }
    #connectionStatus.error {
        color: #1a1a1a;
        background-color: rgba(255, 255, 255, 0.92);
        border-color: #b8b8b8;
    }
    #placementRefusal {
        color: #8a1c1c;
        background-color: rgba(255, 240, 240, 0.97);
        border-color: #c0392b;
    }
    #pixelInfo {
        color: #1a1a1a;
        background: rgba(255, 255, 255, 0.92);
        border-color: #b8b8b8;
    }
    #templateSelect {
        color: #1a1a1a;
        background-color: rgba(255, 255, 255, 0.92);
        border-color: #b8b8b8;
    }
    #paletteColorblindToggle {
        color: #1a1a1a;
        background-color: rgba(255, 255, 255, 0.92);
        border-color: #b8b8b8;
    }
}