@layer layout {
/**
 * Layout - Grid and Structure
 * PixULA
 */

/* The page itself never scrolls — the app fills the viewport exactly and
   scrolling lives ONLY inside the regions that own it (#toolbar, #color-rail,
   #panels, #canvas-viewport). Anything that would overflow the page
   (fractional `zoom` rounding, transient off-screen nodes) is clipped instead
   of surfacing as a browser scrollbar. */
html,
body {
    height: 100%;
    overflow: hidden;
}

/* Main Application Grid */
#app {
    display: grid;
    grid-template-areas:
        "header   header   header"
        "toolbar  colorbar panels"
        "toolbar  canvas   panels"
        "status   status   status";
    /* Fixed tracks scale with --ui-scale so the chrome regions (which are
       `zoom`-scaled by the same factor) get the room their content needs.
       The colour bar row is `auto` — sized to its content — and only spans
       the canvas column between the toolbar and #panels. The 1fr canvas row
       takes the remaining space.

       The colour rail is NOT a column here (it was, 2026-08-25 to
       2026-08-27) — see #color-rail below for why: a fixed-width column
       taken out of a fixed budget (toolbar + rail + panels) is width
       #panels never gets back, and on a narrow-enough window that pushed
       #panels off the right edge of the viewport entirely, since the 1fr
       canvas track cannot shrink past its content's min-content size. Only
       three fixed consumers now (toolbar, panels, and whatever the canvas
       genuinely needs), so #panels only loses ground to the canvas itself
       shrinking, never to the rail. */
    grid-template-rows: calc(var(--header-height) * var(--ui-scale)) auto 1fr calc(var(--status-height) * var(--ui-scale));
    grid-template-columns:
        calc(var(--toolbar-width) * var(--ui-scale))
        1fr
        calc(var(--panel-width) * var(--ui-scale));
    /* 100% (not 100vh): body's height chain is pinned to the real viewport
       above, and 100vh can exceed it on mobile UA chrome. */
    height: 100%;
    overflow: hidden;
}

/* UI scale: `zoom` uniformly scales every descendant (text, icons, swatches,
   spacing, borders) of each chrome region. The canvas viewport/iframe is
   deliberately excluded so the drawing surface and its coordinate math are
   never affected — the canvas has its own separate zoom control. #color-rail
   joins this plain block (2026-08-25) — its swatches scale only with
   --ui-scale, unlike #color-bar below, which ColorBarFit multiplies by a
   second, independent scale of its own. */
#header,
#toolbar,
#color-rail,
#panels,
#status-bar,
#canvas-controls {
    zoom: var(--ui-scale);
}

/* #color-bar scales by --ui-scale too, but multiplied by --colorbar-scale
   (default 1, css/components.css) — the one region ColorBarFit
   (js/ui/components/colorbar-fit.js) can dial back on its own, shrinking
   this bar's icons independently of every other chrome region, specifically
   to keep its content fitting ONE row (always — never two, since 2026-08-25)
   at interface sizes where it otherwise would not. See the row-count
   rationale further down this file. */
#color-bar {
    zoom: calc(var(--ui-scale) * var(--colorbar-scale));
}

#header {
    grid-area: header;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0 var(--space-md);
    background: var(--bg-secondary);
    border-bottom: 1px solid var(--border-color);
    z-index: var(--z-header);
}

#toolbar {
    grid-area: toolbar;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-sm);
    background: var(--bg-secondary);
    border-right: 1px solid var(--border-color);
    overflow-y: auto;
    z-index: var(--z-toolbar);
}

/* Vertical colour rail — every screen mode's palette swatches (ClutBar),
   FLOATING on top of the canvas rather than a grid column of its own
   (2026-08-27; was one from 2026-08-25 — see the #app comment above for
   why that pushed #panels off-screen on a narrow window). A CHILD of
   #canvas-area (index.html), not a grid item at all — a grid item taken
   out of flow with position: absolute does NOT reliably get its assigned
   grid area as its containing block in practice (tried first; measured
   the rail landing at the viewport's own top-left corner instead, full
   page height, over the header and toolbar both). Nesting it inside
   #canvas-area, which #canvas-area's own position: relative (below) makes
   the containing block, is the unambiguous version of the same idea: it
   costs the canvas track no width, and is confined to exactly the canvas
   row's box — never the colorbar row above it, since that is a sibling
   grid area #canvas-area was never part of (the rail must never cover
   #color-bar's own icons — same reasoning as the collapse tab's placement
   below). Anchored to the LEFT edge of #canvas-area, i.e. flush against
   #toolbar, exactly where the old grid column used to start. Fixed width
   (--colorrail-width, RAW — not multiplied by --ui-scale, unlike every
   fixed grid track in this file), sized for ONE swatch column: content
   stacks single-file (css/components.css) rather than being auto-scaled.
   z-index lifts it above the canvas content it now overlaps.

   The raw width matters: every fixed grid TRACK here is pre-multiplied by
   --ui-scale because the track lives in #app's own (unzoomed) coordinate
   system, and the zoomed region placed into it (#toolbar etc.) gets its
   OUTER size from the track, not from its own `width` — zoom then only
   rescales that region's CONTENT to visually fill the already-scaled box,
   one multiplication in total. #color-rail has no such track anymore: its
   own `width` IS what determines its outer size, and it ALSO carries
   `zoom: var(--ui-scale)` below — multiplying by --ui-scale in the width
   too would scale it TWICE (confirmed: measured the 256-entry dense grid
   overflowing #color-rail-content at 85% interface size, tracked back to
   exactly this). Left raw, zoom alone correctly scales both this box (as
   its parent #canvas-area, which is not zoomed, sees it) and everything
   inside it by the same single factor. */
#color-rail {
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: var(--colorrail-width);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-sm);
    background: var(--bg-secondary);
    border-right: 1px solid var(--border-color);
    z-index: var(--z-toolbar);
    /* Collapsing (below) slides the whole box left by its own width rather
       than resizing anything, so the rail's own overflow must stay
       visible — a scrolling INNER wrapper (#color-rail-content) owns
       overflow-y/x instead, or the collapse tab (its child, positioned to
       poke out past this box's own right edge — css/components.css)
       would be clipped exactly where it needs to show, the same problem
       a separate host existed to dodge before this box could safely
       overflow on its own. */
    overflow: visible;
    transform: translateX(0);
}

/* The scrolling swatch area proper: fills #color-rail below its fixed
   width/height (top/bottom: 0 above gives #color-rail a definite height,
   so height: 100% here resolves), and owns the clipping #color-rail used
   to (scrollbar-width: thin keeps that one column from having to share
   its already-narrow width with a full-size scrollbar). Also the single
   element ColorRailToggle hides wholesale (below) regardless of what the
   active mode built inside it. */
#color-rail-content {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-sm);
    width: 100%;
    height: 100%;
    overflow-y: auto;
    overflow-x: hidden;
    scrollbar-width: thin;
}

/* Collapsing the colour rail (ColorRailToggle, js/ui/components/
   colorrail-toggle.js — a chrome-only, non-persisted-to-document toggle
   like a panel section's collapsed flag): slide the whole floating panel
   left by its own width, off the canvas and over the toolbar it sits
   beside — which costs nothing to undo since #color-rail-content
   underneath is left fully built, just not visible while covered. Because
   this is a transform on an overlay with no grid track of its own, it can
   never affect #panels or any other column's width, expanded or
   collapsed. The collapse tab (css/components.css .rail-collapse-tab) is
   a child of #color-rail positioned at left: 100% of ITS box — sliding the
   whole box moves the tab along with it, landing back at the toolbar/
   canvas boundary exactly where it belongs when collapsed, with no
   separate positioning rule needed for that state. */
#app.colorrail-collapsed #color-rail {
    transform: translateX(-100%);
}

#app.colorrail-collapsed #color-rail-content {
    display: none;
}

/* Top colour bar — a slim strip spanning the canvas column only (between the
   two rails), directly under the header. Since 2026-08-25 (docs/superpowers/
   specs/2026-08-25-colour-rail-design.md) every screen mode's palette
   swatches live in the vertical #color-rail instead, and since 2026-08-26
   Border lives in the header (#header-controls) — this strip now carries
   only the marks controls (#color-bar-controls: draw modes, Mirror,
   Swap/Recolour), its one and only child.

   Guaranteed to render as exactly ONE row, always, by ColorBarFit
   (js/ui/components/colorbar-fit.js), which shrinks this bar's own icon
   scale (--colorbar-scale below) independently of every other chrome region
   until the content fits — see the class comment there for the search. Before
   the palette moved out, this bar targeted TWO rows and could grow as well as
   shrink; both needs left with the swatches, and so did the reason to scroll
   sideways — with only one group of icon buttons left, the `overflow-x: auto`
   below is a safety net for pathological cases rather than something this
   bar's ordinary content ever reaches. */
#color-bar {
    grid-area: colorbar;
    display: flex;
    flex-direction: row;
    /* WRAP, never scroll out of sight: on a narrow window the global draw-
       mode selector would otherwise sit off the right edge behind an
       auto-hiding scrollbar. A mode you cannot see is a trap: leave it on
       Paper Recolour and every tool silently paints paper on paper, which
       reads as "the app is broken". */
    flex-wrap: wrap;
    align-items: flex-end;
    /* Centres the bar's one child (#color-bar-controls) as a whole — that
       box's own children get their own justify-content: center too, for the
       same reason (its own flex:1 1 auto, css/components.css, already fills
       the line before this could otherwise act). */
    justify-content: center;
    /* One palette-icon pitch between every pair of groups, whether they land
       on the same row or not — the same unit #clut-cluster uses between ink
       and paper (css/components.css), so the whole bar reads as one rhythm. */
    gap: var(--space-sm) var(--clut-btn-size);
    padding: var(--space-xs) var(--space-sm);
    background: var(--bg-secondary);
    border-bottom: 1px solid var(--border-color);
    overflow-x: auto;
    overflow-y: hidden;
    z-index: var(--z-toolbar);
}

/* The marks group: one "Drawing Modes" caption over the draw modes, Mirror
   and Swap/Recolour, captioned as a whole the way Ink/Paper caption their own
   swatch groups (_captionGroup, css/components.css) — a column of [label,
   icon row], centred. It is #color-bar's only child now (Border moved to the
   header 2026-08-26), so it always has the whole bar to itself. */
#color-bar-controls {
    display: flex;
    flex-direction: column;
    /* This box is the bar's ONLY flex-grow item (css/components.css), so it
       is usually wider than its own children need - without this the label
       and icon row cluster at ITS left edge, leaving the same off-centre
       look the outer #color-bar rule above fixes for a line that has no
       grown item. */
    align-items: center;
    gap: 2px 0;
}

/* The icon-only run itself (draw modes, Mirror, Swap/Recolour) — its own
   wrapping row, sized to its content rather than stretched to the label's
   column width. */
#marks-icons-row {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: center;
    gap: var(--space-sm) 4px;
}


/* A mode with no attribute bits leaves #colour-bits empty (ClutBar clears it
   for every mode except classic fixed16 — 9 of 14 screen modes); without this
   rule it would still spend a layout gap on nothing. Every .toolbar-section
   in the app lives in either #color-bar or #color-rail, so one unscoped
   selector covers both regions rather than needing a rule per region. */
.toolbar-section:empty {
    display: none;
}

#canvas-area {
    grid-area: canvas;
    display: flex;
    flex-direction: column;
    /* Match the surrounding panels/toolbar so the canvas viewport
       reads as a single themed surface rather than a lighter island. */
    background: var(--bg-secondary);
    overflow: hidden;
    /* Containing block for #color-rail, which floats over this area as a
       child rather than a grid column of its own — see the #color-rail
       comment. */
    position: relative;
}

/* Wrapper for canvas-container to enable centering. Its own theme background
   keeps the thin frame around the iframe on the UI theme colour — the selected
   ZX border colour lives INSIDE the iframe (the mirrored body backdrop), not
   in this padding. */
#canvas-viewport {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: auto;
    padding: var(--space-md);
    background: var(--bg-secondary);
}

#panels {
    grid-area: panels;
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
    padding: var(--space-sm);
    background: var(--bg-secondary);
    border-left: 1px solid var(--border-color);
    overflow-y: auto;
    z-index: var(--z-panels);
}

/* Sidebar section order — most common use first.
   Creation order in app.js _initUI is the canonical order (Layers (with Stamps
   nested) -> Tool Options -> Transform -> Reference), so keyboard/AT order matches
   the visual order without flex `order` overrides. All panels stack and scroll
   together inside #panels (no sticky/fixed sections — panels must never
   overlap or slide behind one another). */

#status-bar {
    grid-area: status;
    display: flex;
    gap: var(--space-lg);
    padding: 0 var(--space-md);
    align-items: center;
    background: var(--bg-secondary);
    border-top: 1px solid var(--border-color);
    font-size: var(--font-size-sm);
    color: var(--text-secondary);
}

/* Canvas iframe */
#canvas-frame {
    width: 100%;
    height: 100%;
    border: none;
    background: transparent;
}

/* Canvas Controls */
#canvas-controls {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    padding: var(--space-xs) var(--space-sm);
    background: var(--bg-secondary);
    border-top: 1px solid var(--border-color);
}

#zoom-controls {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
}

/* min-width/min-height, not width/height: the +/- buttons stay square, but
   the text-bearing Fit button must grow with its localised label — including
   VERTICALLY when it wraps at large font scales (UI-schema: labels never clip) */
#zoom-controls button {
    min-width: 28px;
    min-height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0 var(--space-xs);
    background: var(--bg-tertiary);
    border-radius: var(--radius-sm);
    color: var(--text-primary);
    font-weight: var(--font-weight-bold);
    transition: background var(--transition-fast);
}

#zoom-controls button:hover {
    background: var(--bg-hover);
}

#zoom-level {
    min-width: 70px;
    height: 28px;
    padding: 0 var(--space-xs);
    font-size: var(--font-size-sm);
    color: var(--text-primary);
    background: var(--bg-tertiary);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: background var(--transition-fast), border-color var(--transition-fast);
}

#zoom-level:hover {
    background: var(--bg-hover);
    border-color: var(--border-light);
}

#zoom-level:focus {
    outline: none;
    border-color: var(--accent-primary);
}

#grid-controls {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    justify-self: center;
}

#grid-controls .control-label {
    font-size: var(--font-size-sm);
    color: var(--text-secondary);
    margin-right: var(--space-xs);
}

.grid-toggle {
    padding: var(--space-xs) var(--space-sm);
    font-size: var(--font-size-sm);
    background: var(--bg-tertiary);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-sm);
    color: var(--text-primary);
    cursor: pointer;
    transition: background var(--transition-fast), border-color var(--transition-fast);
}

.grid-toggle:hover {
    background: var(--bg-hover);
}

.grid-toggle[aria-pressed="true"] {
    background: var(--accent-primary);
    border-color: var(--accent-primary);
    color: var(--on-accent);
}

#canvas-info {
    display: flex;
    gap: var(--space-md);
    font-size: var(--font-size-sm);
    color: var(--text-muted);
    justify-self: end;
    font-variant-numeric: tabular-nums;
}

#cursor-position,
#cell-position {
    display: inline-block;
    text-align: right;
}

/* Responsive Breakpoints */

/* Tablet: Collapse panels to slide-out drawer */
@media (max-width: 1024px) {
    #app {
        /* No "colorrail" column here either (removed 2026-08-27, main #app
           rule above) — #color-rail floats over the canvas at every width
           now, so reserving a column for it here would waste exactly the
           space this breakpoint exists to protect. */
        grid-template-areas:
            "header   header"
            "toolbar  colorbar"
            "toolbar  canvas"
            "status   status";
        grid-template-rows: calc(var(--header-height) * var(--ui-scale)) auto 1fr calc(var(--status-height) * var(--ui-scale));
        grid-template-columns:
            calc(var(--toolbar-width) * var(--ui-scale))
            1fr;
    }

    #panels {
        position: fixed;
        right: -280px;
        top: var(--header-height);
        bottom: var(--status-height);
        width: 280px;
        transition: right 0.3s ease;
        box-shadow: -2px 0 8px rgba(0, 0, 0, 0.3);
    }

    #panels.visible {
        right: 0;
    }

    /* Add panel toggle button in header */
    .panel-toggle-btn {
        display: flex;
    }
}

/* Small tablet / large phone */
@media (max-width: 768px) {
    #app {
        /* Room for the 2-wide tool grid + the vertical scrollbar. */
        --toolbar-width: 120px;
    }

    #panels {
        width: 240px;
        right: -240px;
    }

    #panels.visible {
        right: 0;
    }

    #toolbar {
        padding: var(--space-xs);
    }

    .tool-btn {
        width: 32px;
        height: 32px;
    }

    #status-bar {
        gap: var(--space-sm);
        font-size: 10px;
    }

    #canvas-info {
        display: none;
    }
}

/* Hide panel toggle button on larger screens */
@media (min-width: 1025px) {
    .panel-toggle-btn {
        display: none;
    }
}

/* Accessibility: skip-to-canvas link, hidden until keyboard-focused */
.skip-link {
    position: absolute;
    top: 0;
    left: 0;
    z-index: var(--z-loading);
    padding: var(--space-sm) var(--space-md);
    background: var(--accent-primary);
    color: var(--on-accent);
    border-radius: 0 0 var(--radius-sm) 0;
    transform: translateY(-200%);
    transition: transform var(--transition-fast);
}

.skip-link:focus {
    transform: translateY(0);
    outline: 2px solid var(--on-accent);
}

/* Baseline visible keyboard focus for controls that don't define their own. */
a:focus-visible,
select:focus-visible,
input:focus-visible,
[role="radio"]:focus-visible,
[role="button"]:focus-visible {
    outline: 2px solid var(--accent-primary);
    outline-offset: 2px;
}

/* ZX border preview: while the Border dropdown has a colour selected,
   #canvas-area takes the document's border colour (set by BorderControl via
   --zx-border-preview). It sits behind #canvas-viewport's theme background, so
   it isn't directly visible — it's the source CanvasSystem.syncBackdropColor
   mirrors into the iframe body. Without the class it stays the theme colour. */
#canvas-area.border-preview {
    background-color: var(--zx-border-preview);
}
}
