/* THE PALETTE COMES FROM THE MALL. This file's own names are kept — several
   hundred rules below use --panel, --line, --dim and --accent — but each is now
   an alias onto a token in tokens.css, so the Checker and cybertownmall.org
   cannot drift apart. Change a colour in tokens.css, not here.

   DARK ONLY, like the Mall. The old sheet offered a light scheme and had to
   re-tune --pass/--fail/--warn for it, because the dark-tuned values measured
   2.18 / 3.27 / 1.97:1 on white and the checkers' own verdicts were unreadable.
   tokens.css sets `color-scheme: dark`, so there is one scheme and one set of
   measurements. */
:root {
  --bg: var(--surface-0);
  --panel: var(--surface-2);
  --line: var(--border);
  --dim: var(--fg-muted);
  --accent: var(--cyan);
  --pass: var(--lime);
  --warn: var(--amber);
  /* The Mall has no red: it never had to say "this fails". Measured against
     every surface in the ramp, worst case 7.35:1 on --surface-3, so it clears
     the 4.5:1 floor everywhere a verdict can land. Keep it in step with
     mallsite/build/check-contrast.mjs if a red is ever added there. */
  --fail: #FF8A80;
}
* { box-sizing: border-box; }
body { margin:0; background:var(--bg); color:var(--fg);
       font-family: var(--font-body); font-size: var(--text-base);
       line-height: var(--leading-normal);
       /* Nothing here may scroll the page sideways; wide content gets its own
          overflow-x container instead. */
       overflow-x: hidden; }
a { color: var(--accent); }
a:hover { color: var(--cyan-hover); }
code { font-family: var(--font-mono); font-size: max(13px, .92em); }
::selection { background: var(--cyan); color: var(--fg-on-cyan); }
/* ---- landing page ---------------------------------------------------------
   Flat plates and hairlines, small radii, mono labels — the Mall's tile
   language rather than the rounded card this page used to carry. */
.wrap { width:100%; max-width:var(--page-wide); margin-inline:auto;
        padding:0 var(--space-5) var(--space-7); }
.cards { display:grid; gap:var(--space-5);
         grid-template-columns:repeat(auto-fit,minmax(300px,1fr)); }
.card { display:block; padding:var(--space-5); background:var(--surface-2);
        border:1px solid var(--border); border-radius:var(--radius-md);
        text-decoration:none; color:inherit; }
.card:hover { border-color:var(--border-strong); }
.card h2 { margin:0 0 var(--space-2); font-size:var(--text-lg); color:var(--cyan); }
.card:hover h2 { color:var(--cyan-hover); }
.card p { margin:0 0 var(--space-3); color:var(--dim); }
.card p:last-child { margin-bottom:0; }
.feat { color:var(--fg) !important; font-size:var(--text-sm); }

.note { margin:var(--space-6) 0 0; padding:var(--space-4) var(--space-5);
        border-left:3px solid var(--cyan); background:var(--surface-indigo);
        border-radius:0 var(--radius-md) var(--radius-md) 0; }
.note h3 { margin:0 0 var(--space-2); font-size:var(--text-base); }
.note p { margin:0; color:var(--dim); }

/* ---- dropzone ---- */
.dropzone { border:2px dashed var(--line); border-radius:12px; background:var(--panel);
            padding:34px 20px; text-align:center; transition:border-color .12s, background .12s; }
.dropzone.dz-over { border-color:var(--accent); background:color-mix(in srgb,var(--accent) 8%,var(--panel)); }
.dz-title { margin:0 0 4px; font-size:17px; }
.dz-sub { margin:0 0 14px; color:var(--dim); font-size:13px; }
.dz-actions { display:flex; gap:10px; justify-content:center; flex-wrap:wrap; }
.dz-actions button { padding:8px 16px; border-radius:7px; border:1px solid var(--line);
                     background:var(--bg); color:var(--fg); cursor:pointer; font-size:14px; }
.dz-actions button:hover { border-color:var(--accent); color:var(--accent); }

/* ---- report ---- */
.report { width:100%; border-collapse:collapse; margin-top:16px; }
.report th, .report td { text-align:left; padding:7px 10px; border-bottom:1px solid var(--line);
                         vertical-align:top; }
.report th { color:var(--dim); font-weight:600; font-size:13px; }
.report .measured { font-family:ui-monospace,monospace; font-size:13px; }
/* The report must never widen the page.
   `width:100%` does not stop a table growing past its container: a table is
   still at least as wide as its MIN-CONTENT width, and these cells carry things
   with no break opportunity in them -- a list of seven missing filenames, a dead
   2001 URL, a monospace measurement. On a 390px phone that pushed /world out to
   884px and gave the whole page a horizontal scrollbar.
   `anywhere` rather than `break-word` on purpose: only `anywhere` is taken into
   account when the min-content size is computed, which is the number that was
   actually too big. Shrinking the type would also have "fixed" it and is not
   available here -- the scale is a low-vision accommodation, not a style. */
.report th, .report td { overflow-wrap:anywhere; }
.s-pass { color:var(--pass); } .s-fail { color:var(--fail); } .s-warn { color:var(--warn); }
.detail { color:var(--dim); font-size:13px; }

/* ---- render stage ---- */
#stage { margin-top:18px; }
.bgbar { display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin-bottom:8px; }
.bglabel { color:var(--dim); font-size:13px; margin-right:2px; }
.bgbar button { padding:5px 11px; border-radius:6px; border:1px solid var(--line);
                background:var(--panel); color:var(--fg); cursor:pointer; font-size:13px; }
.bgbar button:hover { border-color:var(--accent); color:var(--accent); }
.bgbar input[type=color] { width:34px; height:28px; padding:0; border:1px solid var(--line);
                           border-radius:6px; background:none; cursor:pointer; }
#canvashost { border:1px solid var(--line); border-radius:10px; overflow:hidden;
              background:#12151a; }
.checker-splash-host { position:relative; }
.checker-splash { position:absolute; inset:0; z-index:20; display:grid; place-items:center;
                  align-content:center; gap:14px; background:#080b13; color:#eef2ff;
                  font-size:14px; letter-spacing:.03em; transition:opacity .2s ease; }
.checker-splash img { width:min(30vw,220px); height:auto; display:block;
                      filter:drop-shadow(0 8px 24px rgba(73,93,255,.32)); }
.checker-splash-done { opacity:0; pointer-events:none; }
/* Viewer size. Morning.star asked for "at least half more or double that size"
   as a low-vision accommodation -- the old min(62vh,560px) was too small to
   inspect a world in. Default is now roughly double the height, and the
   --viewer-h custom property lets the size buttons change it live. */
:root { --viewer-h: min(86vh, 1100px); }
/* The `#canvashost` prefix is load-bearing, not tidiness. X_ITE 4.7.0 injects
   its own <link rel=stylesheet href=/vendor/x_ite/x_ite.css> into <head> at
   runtime, so that sheet always lands AFTER this one. It carries
   `X3DCanvas { display:inline-block; height:150px; width:300px }` -- a bare
   type selector, the same 0-0-1 specificity a bare `X3DCanvas` rule here has.
   Equal specificity, later sheet: the vendor default wins, and the viewer sits
   300px wide inside an 1100px stage. Every rule in this file that targets the
   canvas therefore carries the host id, which lifts it to 1-0-1 or above.
   Do not strip the prefix back off. The pages use the legacy <X3DCanvas>
   element, which gets no shadow root, so the vendor rules are ordinary
   document rules competing with ours -- there is no :host boundary that would
   have demoted them. */
#canvashost X3DCanvas { display:block; width:100%; height:var(--viewer-h); min-height:420px; }
main { max-width:none !important; }
/* Same problem, different element: a `pre` does not wrap at all, so the widest
   line in the scale block sets a floor on the page width. Wrap it instead of
   scrolling it -- every line here is a number a creator has to read. */
pre.bounds { white-space:pre-wrap; overflow-wrap:anywhere;
             background:var(--panel); border:1px solid var(--line); border-radius:8px;
             padding:12px 14px; overflow-x:auto; font-size:13px; margin:14px 0 4px; }
.bgbar select { padding:5px 8px; border-radius:6px; border:1px solid var(--line);
                background:var(--panel); color:var(--fg); font-size:13px; max-width:260px; }
.bgbar input[type=range] { width:110px; accent-color:var(--accent); }
.chk { display:inline-flex; align-items:center; gap:5px; font-size:13px; color:var(--dim); }
.chk input { accent-color:var(--accent); }
.mode-on { border-color:var(--accent) !important; color:var(--accent) !important; }


/* ---- low-vision accommodations ---------------------------------------------
   Raise contrast on the control strip. The dim grey used for secondary labels
   sat around 3:1 against the panel, which is under the WCAG AA 4.5:1 minimum
   for body text -- fine as decoration, not fine as the only label on a control. */
.bglabel { color: var(--fg); font-weight: 600; font-size: 13px; }
.chk { color: var(--fg); }
.bgbar button, .bgbar select { font-size: 14px; padding: 7px 13px; }
.bgbar button:focus-visible, .bgbar select:focus-visible,
.dz-actions button:focus-visible, .report a:focus-visible {
  outline: 3px solid var(--accent); outline-offset: 2px;
}
.viewer-size { display:inline-flex; gap:4px; }
.viewer-size button { min-width:34px; }
.report th { color: var(--fg); }
.detail { color: var(--dim); font-size: 13.5px; }

/* Fullscreen: the host element becomes the whole screen, so the canvas must
   fill it rather than keeping its --viewer-h height. */
#canvashost:fullscreen { background:#000; }
#canvashost:fullscreen X3DCanvas { height:100vh; }

/* ---- material previewer ------------------------------------------------------ */
.visually-hidden { position:absolute; width:1px; height:1px; overflow:hidden;
                   clip:rect(0 0 0 0); white-space:nowrap; }
/* Three columns at desktop width: swatch list, stage, controls beside the shape.
   The goal is the whole tool on ONE 1080p screen with no scrolling -- a creator
   picks a colour, judges the render, and copies the block without ever losing
   sight of any of the three. A single wide stage wasted ~500px on empty backdrop
   and pushed the paste block below the fold.
   The list column is wide enough for two swatch cards side by side; auto-fill
   drops it to one rather than squeezing names. */
/* The STAGE COLUMN is capped, not the <main>: `main { max-width:none !important }`
   above is a deliberate accommodation that frees the item/world viewers from
   their inline 1100px cap, and it would win here anyway. Capping the column and
   centring the row keeps a 2560px monitor from spending 700 extra pixels on
   empty backdrop, since the side columns stop growing at their own maximums. */
.mat-layout { display:grid; gap:18px; padding:0 20px 40px; align-items:start;
              justify-content:center;
              grid-template-columns:minmax(260px,370px) minmax(0,1000px) minmax(300px,400px);
              grid-template-areas:"list stage controls"; }
.mat-list    { grid-area:list; }
.mat-stage   { grid-area:stage; }
.mat-controls{ grid-area:controls; }

/* THE THREE PANES SHARE ONE HEIGHT, and it is measured rather than guessed:
   body becomes a flex column of fixed viewport height and .mat-layout takes the
   slack, so the grid row is exactly "viewport minus header minus footer" with no
   calc(100vh - 240px) fudge factor to go stale the moment the tagline wraps or
   the footer text changes length. Each pane then fills from the inside -- more
   swatches, a bigger preview, and the paste block absorbing the remainder.

   `height`, NOT `min-height`. A flex container needs a DEFINITE height for
   `flex:1` to mean "the leftover space"; with min-height the body sizes to its
   content, the content sizes to the body, and the circularity resolves by the
   swatch list growing to fit all 309 cards -- a 10,000px page.

   Gated on min-height too: this mode hides page overflow, so on a short viewport
   it would put the paste block out of reach. Below that, normal scrolling. */
@media (min-width:1181px) and (min-height:720px) {
  body.mat-page { height:100vh; overflow:hidden; display:flex; flex-direction:column; }
  body.mat-page .mat-layout { flex:1; min-height:0; align-items:stretch; }
  .mat-list, .mat-stage, .mat-controls { height:100%; min-height:0; }
  /* min-height:0 on every flex child, or each refuses to shrink below its
     content and the overflow reappears inside the pane instead. */
  .mat-list .swatches { flex:1; min-height:0; max-height:none; }
  .mat-stage { display:flex; flex-direction:column; }
  .mat-stage #canvashost { flex:1; min-height:0; }
  /* body.mat-page prefix is load-bearing, not decoration: the base
     `.mat-stage #canvashost X3DCanvas { height:min(56vh,600px) }` below has
     equal specificity and comes later in the file, so it would win and leave
     the canvas short of its cell -- an empty band of backdrop under the scene. */
  body.mat-page .mat-stage #canvashost X3DCanvas { height:100%; min-height:0; }
  .mat-controls { display:flex; flex-direction:column; }
  .mat-controls #vrmlout { flex:1; min-height:0; overflow:auto; margin-bottom:10px; }
  .mat-controls #copy { align-self:flex-start; }
}

/* Below three-column width the controls drop under the stage, with the list
   still beside both -- the stage stays as wide as it can for as long as it can. */
@media (max-width:1180px) {
  .mat-layout { grid-template-columns:minmax(240px,340px) minmax(0,1fr);
                grid-template-areas:"list stage" "list controls"; }
}
@media (max-width:900px) {
  .mat-layout { grid-template-columns:1fr;
                grid-template-areas:"list" "stage" "controls"; }
}
.mat-list { position:sticky; top:12px; display:flex; flex-direction:column; gap:8px; }
.mat-list input[type=search], .mat-list select {
  width:100%; padding:9px 11px; font-size:14px; border-radius:8px;
  border:1px solid var(--line); background:var(--panel); color:var(--fg); }

/* ---- library switch + category pills ---------------------------------------- */
.lib-seg { display:flex; }
.lib-seg button { flex:1; }
.lib-note { margin:0; font-size:13.5px; color:var(--dim); line-height:1.4; }

/* Pills rather than a <select>: every category is visible at once and each is a
   full-size click target. Capped and scrollable so 13 of them cannot push the
   swatch list off screen. */
.cat-tabs { display:flex; flex-wrap:wrap; gap:6px; max-height:132px; overflow-y:auto; }
.cat-tabs button { padding:6px 12px; border-radius:999px; border:1px solid var(--line);
                   background:var(--panel); color:var(--fg); cursor:pointer;
                   font-size:13.5px; font-weight:600; }
.cat-tabs button:hover { border-color:var(--accent); color:var(--accent); }
/* The blanket `.mode-on` rule near the top of this file sets accent text with
   !important, which on a filled accent pill is accent-on-accent -- invisible.
   Overriding it needs !important back. The fill is nudged toward --fg because
   the raw accent on near-white is only 4.8:1: at 88% it clears WCAG AA for
   13.5px text in both colour schemes, which the light scheme otherwise misses. */
.cat-tabs button.mode-on { background:color-mix(in srgb,var(--accent) 88%,var(--fg));
                           color:var(--bg) !important; }

/* ---- swatch cards ------------------------------------------------------------ */
/* Height is budgeted, not chosen: the list has to fit the one-page target
   alongside the header, the category pills and the footer. It scrolls
   internally, so a shorter box costs reach, not access. */
.swatches { max-height:min(48vh,520px); overflow-y:auto; display:grid; gap:6px;
            grid-template-columns:repeat(auto-fill,minmax(158px,1fr));
            border:1px solid var(--line); border-radius:8px; padding:6px;
            background:var(--panel); align-content:start; }
.swatch { display:grid; grid-template-columns:40px minmax(0,1fr); gap:9px;
          align-items:center; padding:7px; border:1px solid transparent;
          border-radius:8px; background:var(--bg); color:var(--fg); cursor:pointer;
          font-size:13.5px; text-align:left; }
.swatch:hover, .swatch:focus-visible { border-color:var(--accent);
          background:color-mix(in srgb,var(--accent) 14%,var(--bg)); }
.swatch.mode-on { border-color:var(--accent);
          background:color-mix(in srgb,var(--accent) 22%,var(--bg)); }
.sw-copy { min-width:0; }
/* Names like "Evergreen Midnight Cerulean Soft Sheen" do not fit one line in a
   two-column grid, and shrinking the text to fit is the one option not on the
   table. Three lines is what it takes for the longest name in the pack to land
   whole; short names still make short cards. */
.sw-name { display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical;
           overflow:hidden; font-weight:600; }
.sw-cat { display:block; font-size:13px; color:var(--dim); overflow:hidden;
          text-overflow:ellipsis; white-space:nowrap; }
.sw-clamped { color:var(--warn); }
.sw-empty { grid-column:1/-1; margin:0; padding:18px; text-align:center;
            color:var(--dim); font-size:13.5px; border:1px dashed var(--line);
            border-radius:8px; }

/* A shaded ball, not a flat square, so a material reads as a material: the
   checkerboard shows through anything transparent, ::before is the material
   itself (dimmed by transparency), and ::after is the gloss sheen and contact
   shadow that make it look round. */
.chip { position:relative; width:40px; height:40px; border-radius:50%;
        flex:0 0 auto; overflow:hidden;
        border:1px solid color-mix(in srgb,var(--fg) 28%,transparent);
        background-color:#8a8a8a;
        background-image:linear-gradient(45deg,#00000024 25%,transparent 25% 75%,#00000024 75%),
                         linear-gradient(45deg,#00000024 25%,transparent 25% 75%,#00000024 75%);
        background-size:12px 12px; background-position:0 0,6px 6px; }
.chip::before { content:""; position:absolute; inset:0; background:var(--chip-fill);
                opacity:var(--chip-alpha,1); }
.chip::after { content:""; position:absolute; inset:0; border-radius:50%;
               background:linear-gradient(135deg,#ffffff2e,transparent 44%,#0000001f);
               box-shadow:inset -6px -7px 10px #00000038; }
.mat-stage #canvashost X3DCanvas { height:min(56vh,600px); }
.mat-controls { margin-top:14px; }
/* Beside the stage there is no toolbar above it to clear, so it starts level. */
@media (min-width:1181px) { .mat-controls { margin-top:0; } }
/* Each column of the Material area says what it is. The three headings are the
   page's structure -- presets, preview, builder -- and they are the Mall's mono
   signage label rather than a second run of prose. */
.col-head { font-family:var(--font-mono); font-size:var(--text-xs);
            letter-spacing:var(--tracking-sign); text-transform:uppercase;
            color:var(--gold); margin:0 0 var(--space-3); }
/* What is currently loaded on the stage. It is a status line, not a heading:
   the heading above it is fixed, this changes on every selection. */
.mat-current { font-size:var(--text-md); margin:0 0 var(--space-2); color:var(--accent); }
.mat-actions { display:flex; gap:var(--space-2); flex-wrap:wrap; margin-top:var(--space-2); }
.mat-copied { font-size:13.5px; color:var(--pass); margin:var(--space-2) 0 0; min-height:1.2em; }
.mat-credits { margin-top:var(--space-4); border-top:1px solid var(--line);
               padding-top:var(--space-3); }
.mat-credits summary { font-size:13.5px; color:var(--dim); cursor:pointer; }
.mat-credits summary:hover { color:var(--fg); }
.mat-credits p { font-size:13px; color:var(--dim); margin:var(--space-3) 0 0; line-height:1.5; }
.mat-provenance { margin:-4px 0 12px; font-size:13.5px; color:var(--dim); line-height:1.45; }
.mat-provenance[hidden] { display:none; }
.mat-provenance .clamp-warn { color:var(--warn); }
.mat-controls h3 { font-size:14px; margin:14px 0 6px; color:var(--dim); }
.sliders { display:grid; grid-template-columns:repeat(auto-fit,minmax(280px,1fr)); gap:10px; }
.ctl { display:grid; grid-template-columns:118px minmax(0,1fr) auto; align-items:center;
       gap:8px; font-size:13.5px; }
/* These are the numeric readouts a creator reads off the sliders to transcribe
   into their .wrl -- the last thing that should be the smallest, dimmest text on
   the page. Was 12.5px/--dim, the only sub-13px size in the stylesheet. */
.ctl code { font-size:13.5px; color:var(--fg); min-width:118px; text-align:right; }
.ctl input[type=range] { width:100%; accent-color:var(--accent); }
.ctl input[type=color] { width:44px; height:28px; padding:0; border:1px solid var(--line);
                         border-radius:6px; background:none; cursor:pointer; }
#copy, #reset, #shot { border-radius:var(--radius-md); border:1px solid var(--line);
               background:var(--panel); color:var(--fg); cursor:pointer; font-size:14px; }
#copy, #reset { padding:8px 15px; }
#copy:hover, #reset:hover, #shot:hover { border-color:var(--accent); color:var(--accent); }
/* Icon-only, so it keeps an explicit 36px box: dropping the text label must not
   also shrink the target. The label lives in aria-label + title instead. */
#shot { display:inline-flex; align-items:center; justify-content:center;
        width:36px; height:36px; padding:0; }
#shot svg { width:19px; height:19px; fill:none; stroke:currentColor;
            stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }

/* ---- backdrop mode switch (solid / gradient / image) ------------------------ */
.seg { display:inline-flex; gap:4px; }
.seg button { padding:6px 11px; border-radius:6px; border:1px solid var(--line);
              background:var(--panel); color:var(--fg); cursor:pointer; font-size:14px; }
.seg button:hover { border-color:var(--accent); color:var(--accent); }
.backdrop-sub { display:inline-flex; align-items:center; gap:6px; }
.backdrop-sub[hidden] { display:none; }
.filebtn { padding:6px 11px; border-radius:6px; border:1px solid var(--line);
           background:var(--panel); color:var(--fg); cursor:pointer; font-size:14px; }
.filebtn:hover { border-color:var(--accent); color:var(--accent); }
.backdrop-filename { color:var(--fg); font-size:13px; max-width:160px; overflow:hidden;
                      text-overflow:ellipsis; white-space:nowrap; }

/* ---- Mall Item Checker: the checking-room workspace --------------------------
   ONE WINDOW, NO PAGE SCROLL. A creator reading "lowest Y is -1.740, must be
   -1.750" has to be able to look at the item while reading it, and the previous
   single-column page put the rule table a screen and a half below the viewer.
   So: three columns that share the viewport's height, each filling from the
   inside, with contained scroll regions where the content genuinely overflows.

   Same mechanism as .mat-page above and for the same reason -- `height`, not
   `min-height`. A flex container needs a DEFINITE height for `flex:1` to mean
   "the leftover space"; with min-height the body sizes to its content and the
   content sizes to the body, and the circularity resolves by the rule table
   growing the page to 3,000px.

   Nothing here shrinks type to make it fit. The floor is 13px, values a creator
   reads off the screen are 13.5px, controls are 14px, body is 15px, exactly as
   the rest of the stylesheet. Where something did not fit, it got a scroll
   region or a wrap, not a smaller font. */

/* THE TOOL PAGES SPEND THEIR HEIGHT ON THE VIEWER.
   /item and /material are viewport-height columns with no page scroll, so every
   pixel the shell takes is a pixel the creator cannot inspect their work in.
   Stacked, the marker, title and tagline came to 115px and the viewer lost a
   fifth of its height against the old header.
   So on these two pages only: the marker goes (the nav already says which tool
   you are in, with aria-current), and the title and tagline share one row. That
   is 115px down to about 45px. NOTHING SHRANK -- the title is one step down the
   same ladder and the tagline is unchanged at --text-sm. A fit problem is
   solved with layout, never with type size. */
.item-page .page-head, .mat-page .page-head {
  padding:var(--space-3) var(--space-5) var(--space-2);
  display:flex; align-items:baseline; flex-wrap:wrap; gap:var(--space-2) var(--space-4);
}
.item-page .page-head .sec-marker, .mat-page .page-head .sec-marker { display:none; }
.item-page .page-head h1, .mat-page .page-head h1 { font-size:var(--text-lg); }
.item-page .page-head .tag, .mat-page .page-head .tag { margin:0; flex:1 1 320px; min-width:0; }
.item-page .masthead-in, .mat-page .masthead-in { padding-block:var(--space-2); }
.item-page .brand img, .mat-page .brand img { width:34px; height:34px; }

.item-layout { display:grid; gap:14px; padding:10px 16px 16px; align-items:start;
               grid-template-columns:minmax(258px,300px) minmax(0,1fr) minmax(300px,368px); }

/* The gate is BOTH dimensions. Below either, the page returns to ordinary
   scrolling: a 600px-tall window cannot hold three panes without either hiding
   controls or shrinking text, and only one of those is allowed. */
@media (min-width:1180px) and (min-height:640px) {
  body.item-page { height:100vh; overflow:hidden; display:flex; flex-direction:column; }
  body.item-page .item-layout { flex:1; min-height:0; align-items:stretch; }
  .item-rail, .item-stage, .item-panel { height:100%; min-height:0; }
}
@media (min-width:1600px) {
  .item-layout { grid-template-columns:minmax(300px,340px) minmax(0,1fr) minmax(360px,420px); }
}
/* Narrower than three columns: the report drops under the stage and the rail
   stays beside both, so the viewer keeps its width for as long as it can. */
@media (max-width:1179px) {
  .item-layout { grid-template-columns:minmax(250px,300px) minmax(0,1fr);
                 grid-template-areas:"rail stage" "rail panel"; }
  .item-rail { grid-area:rail; } .item-stage { grid-area:stage; } .item-panel { grid-area:panel; }
  .item-page #canvashost X3DCanvas { height:min(60vh,620px); }
}
@media (max-width:860px) {
  .item-layout { grid-template-columns:1fr; grid-template-areas:"rail" "stage" "panel"; }
}

/* ---- left rail ---- */
.item-rail { display:flex; flex-direction:column; gap:10px; overflow-y:auto; }
.item-rail .dropzone { padding:14px 12px; }
.item-rail .dz-title { font-size:15px; margin-bottom:2px; }
.item-rail .dz-sub { margin-bottom:10px; }
.item-rail .dz-actions button { padding:7px 12px; }
.rail-file { margin:0; font-size:13.5px; color:var(--fg); word-break:break-all; }
.rail-block { background:var(--panel); border:1px solid var(--line); border-radius:9px;
              padding:10px 12px; }
.rail-block h2 { margin:0 0 7px; font-size:14px; color:var(--fg); font-weight:600;
                 letter-spacing:.02em; text-transform:uppercase; }
.rail-note { margin:8px 0 0; font-size:13px; color:var(--dim); line-height:1.4; }

/* The verdict is the first thing a creator looks for, so it is a block of its
   own rather than a line of coloured text. The tint is 14% over the panel, which
   leaves --fg body text at its normal contrast; the colour carries in the border
   and the status word, never in the readable text alone. */
.verdict { margin:0; padding:9px 12px; border-radius:9px; font-size:14px;
           border:1px solid var(--line); border-left-width:4px; background:var(--panel); }
.verdict.v-pass { border-left-color:var(--pass);
                  background:color-mix(in srgb,var(--pass) 12%,var(--panel)); }
.verdict.v-fail { border-left-color:var(--fail);
                  background:color-mix(in srgb,var(--fail) 12%,var(--panel)); }
.verdict.v-idle { border-left-color:var(--dim); color:var(--dim); }

/* Label/value rows. The value is the thing being read, so it gets the monospace
   and the full --fg; the label is allowed to be quieter but not smaller. */
.nums { margin:0; display:grid; gap:5px; }
.nums > div { display:flex; justify-content:space-between; align-items:baseline; gap:10px; }
.nums dt { font-size:13.5px; color:var(--dim); }
.nums dd { margin:0; font-family:ui-monospace,"Cascadia Code",monospace; font-size:13.5px;
           color:var(--fg); text-align:right; }
.nums.wide > div { border-bottom:1px solid var(--line); padding-bottom:5px; }
.nums.wide > div:last-child { border-bottom:0; }
.nums.wide dd { word-break:break-all; }

.toggles { display:flex; flex-wrap:wrap; gap:6px; }
.toggles button { padding:6px 11px; border-radius:7px; border:1px solid var(--line);
                  background:var(--bg); color:var(--fg); cursor:pointer; font-size:14px; }
.toggles button:hover { border-color:var(--accent); color:var(--accent); }

/* ---- centre stage ---- */
.item-stage { display:flex; flex-direction:column; gap:8px; min-width:0; }
.stagebar { display:flex; gap:7px; align-items:center; flex-wrap:wrap; }
.stagebar .seg { display:inline-flex; gap:4px; flex-wrap:wrap; }
.stagebar .bglabel { margin-left:4px; }
/* `.seg` sets display:inline-flex, which OUTRANKS the [hidden] default of
   display:none -- without this the Walk controls show in Inspect mode. */
.stagebar .seg[hidden] { display:none; }
.stagebar .walk-only { align-items:center; gap:8px; flex-wrap:wrap; }
/* The one instruction that makes Walk usable at all: X_ITE 4.7.0 has no
   keyboard navigation, so a visitor who does not know to DRAG cannot move. */
.stagebar .walk-hint { font-size:13px; color:var(--fg); }
.stagebar .seg button, .stagebtn {
  padding:6px 11px; border-radius:6px; border:1px solid var(--line);
  background:var(--panel); color:var(--fg); cursor:pointer; font-size:14px; }
.stagebar .seg button:hover, .stagebtn:hover { border-color:var(--accent); color:var(--accent); }
.stagebtn { margin-left:auto; }
.stagebtn:disabled { opacity:.5; cursor:default; }
.stagebtn:disabled:hover { border-color:var(--line); color:var(--fg); }
.stagebar input[type=color] { width:34px; height:30px; padding:0; border:1px solid var(--line);
                              border-radius:6px; background:none; cursor:pointer; }
.stagebar button:focus-visible, .stagebar input:focus-visible,
.toggles button:focus-visible, .tabs button:focus-visible {
  outline:3px solid var(--accent); outline-offset:2px; }

.item-page #canvashost { position:relative; flex:1; min-height:0; }
body.item-page #canvashost X3DCanvas { height:100%; min-height:0; }
@media (max-width:1179px) { body.item-page #canvashost X3DCanvas { height:min(60vh,620px); } }

/* Sits ON the render, so it cannot rely on the page's colour scheme -- it gets
   its own opaque plate and a fixed light-on-dark pair (13.9:1). */
.stage-overlay { position:absolute; left:10px; bottom:10px; margin:0;
                 padding:5px 10px; border-radius:7px; font-size:13.5px;
                 font-family:ui-monospace,"Cascadia Code",monospace;
                 background:#0d1117e6; color:#e8eaed; border:1px solid #3a4250;
                 pointer-events:none; }
.stage-overlay:empty { display:none; }

.stage-empty { flex:1; min-height:260px; display:flex; align-items:center; justify-content:center;
               text-align:center; padding:24px; background:var(--panel);
               border:2px dashed var(--line); border-radius:10px; }
.stage-empty > div { max-width:44ch; }
.stage-empty h2 { margin:0 0 10px; font-size:19px; color:var(--accent); }
.stage-empty p { margin:0 0 8px; font-size:14.5px; line-height:1.5; }
.stage-empty p:last-child { margin-bottom:0; }
.stage-empty[hidden] { display:none; }

/* ---- right report panel ---- */
.item-panel { display:flex; flex-direction:column; min-width:0;
              background:var(--panel); border:1px solid var(--line); border-radius:10px; }
.tabs { display:flex; gap:4px; padding:8px 8px 0; border-bottom:1px solid var(--line); }
.tabs button { padding:7px 13px; border-radius:7px 7px 0 0; border:1px solid transparent;
               border-bottom:0; background:none; color:var(--fg); cursor:pointer;
               font-size:14px; font-weight:600; margin-bottom:-1px; }
.tabs button:hover { color:var(--accent); }
.tabs button[aria-selected=true] { border-color:var(--line); background:var(--bg);
                                   color:var(--accent) !important; }
.tabpanes { flex:1; min-height:0; overflow-y:auto; padding:10px 12px 14px; }
.tabpanes h3 { margin:14px 0 7px; font-size:14px; color:var(--fg); }
.tabpanes h3:first-child { margin-top:2px; }
.item-panel .report { margin-top:0; }
.item-panel .report th, .item-panel .report td { padding:6px 7px; }
.item-panel pre.bounds { margin:0 0 6px; }

/* Stacked rule rows, because the report lives in a ~370px column now. The
   status word carries a colour AND the word itself -- "pass"/"FAIL"/"soft" --
   so the verdict survives a colour-blind reader and a greyscale screenshot. */
.checks { list-style:none; margin:0; padding:0; }
.checks li { display:grid; grid-template-columns:auto minmax(0,1fr); gap:9px;
             padding:9px 0; border-bottom:1px solid var(--line); }
.checks li:last-child { border-bottom:0; }
.chk-mark { font-size:13px; font-weight:700; text-transform:uppercase;
            letter-spacing:.03em; padding-top:1px; min-width:3.2em; }
.chk-body { min-width:0; }
.chk-name { margin:0; font-size:14px; }
.chk-measured { margin:2px 0 0; font-size:13.5px; color:var(--fg); word-break:break-word; }
.chk-limit { color:var(--dim); }
.checks .detail { margin:2px 0 0; }

/* `code { font-size:.92em }` near the top of this file is fine at 15px body, but
   inside the 13.5px tagline and the 13.5px filename line it lands at 12.42px --
   below the 13px floor this project treats as a hard accessibility minimum, and
   the filename is something a creator actually reads. Floor it rather than
   dropping the ratio, so nothing that already clears 13px changes size.
   (The same 12.42px case exists in /world's and /materials' taglines; left
   alone here because this lane is /item.) */
.item-page code { font-size: max(13px, .92em); }

/* The blanket `.mode-on` sets raw --accent text, which on the light scheme's
   near-white --bg measures 4.51:1 -- over the AA floor, but only just, and this
   page has more selected-state controls than any other. Mixed 18% toward --fg it
   measures 5.6:1 light / 9.6:1 dark while still reading as the accent.
   !important because the rule it overrides uses it. */
.item-page .toggles button.mode-on,
.item-page .stagebar .seg button.mode-on,
.item-page .tabs button[aria-selected="true"] {
  color: color-mix(in srgb, var(--accent) 82%, var(--fg)) !important;
  border-color: color-mix(in srgb, var(--accent) 82%, var(--fg)) !important;
}

/* Short desktop viewports: tighten SPACING so every scale-aid toggle stays above
   the rail's fold. At 1366x768 the rail ran 27px long once the avatar toggle was
   relabelled "Cybertown avatar" and wrapped the group to three rows, which put
   the Mall-envelope toggle out of sight.

   Padding and gaps only. Type size is not a lever here -- see the accessibility
   rules in CLAUDE.md. */
@media (min-width:1180px) and (max-height:820px) {
  .item-rail { gap:6px; }
  .item-rail .dropzone { padding:11px 12px; }
  .item-rail .dz-sub { margin-bottom:8px; }
  .rail-block { padding:7px 11px; }
  .rail-block h2 { margin-bottom:5px; }
  .rail-block .nums { gap:4px; }
  .toggles { gap:5px; }
  /* Budgeted for the FAILING case, which is the tall one: "3 hard rule
     failure(s). This would be rejected." wraps to two lines in a 260px rail and
     is worth ~20px. Budgeting for the passing one-line verdict instead pushed
     the Mall-envelope toggle out of sight at 1366x768 exactly when a creator is
     looking at a rejection. */
  .verdict { padding:8px 11px; }
}

/* ---- prose pages: /about and /privacy -------------------------------------
   Two ordinary scrolling pages, so they reuse .wrap and the site footer rather
   than the viewport-height column /item and /material need. The only thing
   .wrap cannot give them is a reading measure: 1560px of 15px text is a wall.
   .prose comes after .wrap in this file, so the narrower max-width wins on
   equal specificity. */
/* The measure goes on the children, not on .prose itself: a global
   `main { max-width:none !important }` above exists for the tool pages, and it
   would win here. Constraining each block keeps that rule untouched. */
.prose { padding-top:var(--space-2); }
.prose > * { max-width:var(--measure); }
.prose h2 { margin:var(--space-6) 0 var(--space-2); font-size:var(--text-lg); color:var(--cyan); }
.prose h2:first-child { margin-top:0; }
.prose p, .prose li { color:var(--dim); margin:0 0 var(--space-3); }
.prose ul { margin:0 0 var(--space-3); padding-left:1.3em; }
.prose li { margin-bottom:var(--space-2); }
.prose .lead { color:var(--fg); font-size:var(--text-md); }
/* .note sets `p { margin:0 }` for its single-paragraph use on the landing page;
   inside prose a note can carry two. */
.prose .note p + p { margin-top:var(--space-3); }
