/* ============================================================================
   ui_shell.css — the house chrome for every AXR configurator.  ONE MASTER.

   `pipeline/ui_shell.css` is the only editable copy. Every `<Piece>/web/` and every
   `Deploy_<Piece>` site carries a BYTE-IDENTICAL copy, asserted by
   `pipeline/checks/ui_shell_check.mjs` (the same posture pattern_check takes with
   layout_patterns.js: a stale copy is a gate that proves nothing about what shipped).
   Sync with `python3 pipeline/sync_ui_shell.py`.

   ------------------------------------------------------------------ the chrome ---
   Design pass 2026-08-24, consolidated 2026-08-26. The brief was the owner's: "the
   controls and the UI are overwhelming, displaying the model." The rule now: ONE quiet
   card at the bottom, one group of controls open at a time, and the dims line always
   readable because "does it fit" is the first question a customer has.

   ALL VISIBLE CHROME LIVES UNDER `header` OR `#panel`. shots_batch's `__mk.hide()`,
   parity.mjs and every every Deploy bundle verify empty the frame by clearing exactly those two
   selectors, so a floating control that survived it would turn up in every sheet this
   pipeline takes. `#uirestore` is the one sibling, display:none except under
   `body.uihidden`. `#qtoast` is the second and it hides under `body.uihidden` too.

   NOTHING here may be class `hide` or attribute `hidden` if it WRAPS a control the
   share layer drives: config_share's setVal/drive both skip `closest('.hide,[hidden]')`
   as their "not on offer" test. Collapsed groups therefore use `.grp` / `.grp.on` and
   plain display:none — off-screen is not the same as unavailable, and a shared link
   carrying a value from a closed tab must still restore.

   ---------------------------------------------------------------- the theme -------
   NO PALETTE LIVES HERE. Every colour is a custom property the page defines in its own
   `:root` block, because the palette is the one part of the chrome that is genuinely
   per-piece — Storm and Fracture ship two (the light one is set from the ROOM, in
   applyRoom), Chitinous, MoM and JoL ship one each. The METRICS are parameterised the
   same way, defaulted here to the four Rock & Soar pages' shared values, so a page that
   differs (Journey of Leaves runs a slightly larger type scale) says so in eleven
   custom properties rather than in eleven duplicated rules.

   Required from the page:  --bg --ink --dim --line --gold --panel --scrim --sub --sub2
                            --card --edge --chip --seg --segon --shadow --goldsoft
   Optional overrides:      every `--ui-*` below.
   ========================================================================== */

*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
html,body{height:100%;background:var(--bg);color:var(--ink);
  font:var(--ui-font,400 15px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif);
  overscroll-behavior:none}
#stage{position:fixed;inset:0}
canvas{display:block;touch-action:none}
.vh{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);
  clip-path:inset(50%);white-space:nowrap}

/* ---------------------------------------------------------------------- header --
   Brand and piece name, and a spec line that stays in the DOM for screen readers and
   for `__share.dims()` whether or not the piece shows it. */
header{position:fixed;top:0;left:0;right:0;padding:var(--ui-header-pad,15px 20px 26px);
  pointer-events:none;z-index:3;
  background:linear-gradient(to bottom,rgba(var(--scrim),var(--ui-scrim-a,.62)),
                             rgba(var(--scrim),0) var(--ui-scrim-stop,94%))}
.brand{font-size:var(--ui-brand-fs,9.5px);letter-spacing:var(--ui-brand-ls,.28em);
  text-transform:uppercase;color:var(--gold)}
h1{font:var(--ui-h1,400 23px/1.08 Georgia,"Times New Roman",serif);
  margin:var(--ui-h1-margin,4px 0 2px);letter-spacing:var(--ui-h1-ls,.01em)}
.sub{font-size:11.5px;color:var(--dim)}

/* ------------------------------------------------------------------ the card ---
   #panel is a transparent, click-through frame; the card inside it is the only opaque
   thing and is as small as the open group needs, so shrinking it hands the picture
   back to the piece automatically — the framing solve measures #panel.offsetHeight. */
#panel{position:fixed;left:0;right:0;bottom:0;z-index:4;display:flex;justify-content:center;
  padding:0 14px calc(14px + env(safe-area-inset-bottom));pointer-events:none;background:none}
#card{pointer-events:auto;width:100%;max-width:920px;border-radius:18px;
  background:var(--card);border:1px solid var(--edge);box-shadow:var(--shadow);
  backdrop-filter:blur(22px) saturate(var(--ui-card-sat,1.3));
  -webkit-backdrop-filter:blur(22px) saturate(var(--ui-card-sat,1.3));
  overflow:hidden}

#dockbar{display:flex;align-items:center;gap:var(--ui-dock-gap,14px);
  padding:var(--ui-dock-pad,9px 12px 9px 15px)}
.dims{flex:1 1 auto;min-width:0;text-align:left}
.dimline{display:flex;align-items:baseline;gap:6px;flex-wrap:wrap}
.dimline b{font:400 17.5px/1.15 Georgia,serif;color:var(--ink);letter-spacing:.01em}
.dimline span{font-size:10px;color:var(--dim);letter-spacing:.07em}
.dimline i{width:1px;height:13px;background:var(--edge);display:inline-block;margin:0 3px}
.dimsub{font-size:10px;color:var(--sub2);margin-top:2px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.unit{margin-left:5px;padding:2px 8px;border-radius:999px;border:1px solid var(--edge);
  background:transparent;color:var(--dim);font:inherit;font-size:9.5px;cursor:pointer;
  letter-spacing:.06em;text-transform:uppercase}
.unit:hover{color:var(--ink);border-color:var(--gold)}

#tabs{display:flex;gap:2px;background:var(--seg);border-radius:999px;padding:3px;
  flex:var(--ui-tabs-flex,0 0 auto);min-width:var(--ui-tabs-min,auto)}
.tab{border:0;background:transparent;color:var(--dim);font:inherit;
  font-size:var(--ui-tab-fs,12px);padding:var(--ui-tab-pad,6px 14px);border-radius:999px;
  cursor:pointer;white-space:nowrap;flex:var(--ui-tab-flex,0 1 auto);
  transition:background .18s,color .18s}
.tab:hover{color:var(--ink)}
.tab[aria-selected="true"]{background:var(--segon);color:var(--ink)}
:root.light .tab[aria-selected="true"]{box-shadow:0 1px 3px rgba(40,44,44,.14)}

#dockChevron{flex:0 0 auto;width:28px;height:28px;display:grid;place-items:center;
  border:0;border-radius:50%;background:transparent;color:var(--dim);cursor:pointer;
  transition:background .18s,color .18s}
#dockChevron:hover{background:var(--seg);color:var(--ink)}
#dockChevron svg{transition:transform .24s}
#card:not(.open) #dockChevron svg{transform:rotate(180deg)}

/* ----------------------------------------------------------------- the drawer --
   One group at a time. Capped and scrollable so no arrangement of controls can ever
   take the frame back. */
#drawer{display:none;border-top:1px solid var(--edge);padding:var(--ui-drawer-pad,11px 15px 14px);
  max-height:var(--ui-drawer-max,min(40vh,318px));overflow-y:auto;overscroll-behavior:contain}
#card.open #drawer{display:block}
#drawer::-webkit-scrollbar{width:6px}
#drawer::-webkit-scrollbar-thumb{background:var(--edge);border-radius:3px}
.grp{display:none}
.grp.on{display:block}

.fld{display:grid;grid-template-columns:var(--ui-lbl-col,76px) 1fr;align-items:center;
  gap:12px;padding:4px 0}
.label{font-size:9px;letter-spacing:.2em;text-transform:uppercase;color:var(--sub)}
.row{display:flex;gap:var(--ui-row-gap,6px);flex-wrap:wrap;align-items:center}
.ctl{display:flex;gap:12px;align-items:center;flex-wrap:wrap}
.active-name{font-size:12.5px;color:var(--ink)}

/* Colour is a swatch, not a labelled chip: the dots and the name of the one that is
   chosen. The full colourway list is one glance wide instead of two rows deep. */
.sw{width:27px;height:27px;padding:0;border:0;border-radius:50%;background:transparent;
  cursor:pointer;transition:transform .16s}
.sw i{display:block;width:100%;height:100%;border-radius:50%;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.22),inset 2px 3px 5px rgba(255,255,255,.35),
    inset -2px -3px 6px rgba(0,0,0,.45)}
.sw:hover{transform:scale(1.09)}
.sw[aria-pressed="true"]{outline:1.5px solid var(--gold);outline-offset:2.5px}
#colours,#stories{gap:10px}

.chip{display:flex;align-items:center;gap:8px;padding:var(--ui-chip-pad,6px 13px 6px 7px);
  border-radius:999px;border:1px solid var(--edge);background:var(--chip);color:var(--ink);
  cursor:pointer;font-size:12.5px}
.chip:hover{border-color:var(--ui-chip-hover,var(--edge))}
/* `.dot` is the glass/metal swatch inside a labelled chip; `i` is the season swatch,
   a flat conic gradient that must NOT carry the glass highlight. Two components. */
.chip .dot{width:18px;height:18px;border-radius:50%;box-shadow:inset 0 0 0 1px rgba(255,255,255,.22),
  inset 2px 3px 5px rgba(255,255,255,.35),inset -2px -3px 6px rgba(0,0,0,.45)}
.chip i{width:19px;height:19px;border-radius:50%;flex:none;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.22)}
.chip[aria-pressed="true"],.chip[aria-pressed=true]{border-color:var(--gold);
  background:var(--ui-chip-on,var(--chip))}
.mini{padding:5px 11px;border-radius:999px;border:1px solid var(--edge);background:var(--chip);
  color:var(--dim);cursor:pointer;font:inherit;font-size:12px;transition:.16s}
.mini:hover{color:var(--ink);border-color:var(--dim)}
.mini[aria-pressed="true"],.mini[aria-pressed=true]{border-color:var(--gold);color:var(--ink);
  background:var(--goldsoft)}

.sliders{padding-top:4px}
@media (min-width:760px){ .sliders{display:grid;grid-template-columns:1fr 1fr;gap:0 26px} }
.slider{display:grid;grid-template-columns:var(--ui-lbl-col,76px) 1fr var(--ui-out-col,46px);
  align-items:center;gap:12px;padding:4px 0;font-size:11.5px;color:var(--dim)}
.slider span:first-child{letter-spacing:.16em;text-transform:uppercase;font-size:9px;color:var(--sub)}
.slider input{width:100%;min-width:0;accent-color:var(--gold);background:transparent}
.slider output{text-align:right;font-variant-numeric:tabular-nums;color:var(--ink);font-size:11.5px}
.slider.off{opacity:.32;pointer-events:none}
.slider.hide{display:none}

/* Contractual honesty: the clearance line and the "As built" note stay, shown with the
   controls that can make them bind rather than under everything at all times. */
.notes{margin-top:9px;padding-top:9px;border-top:1px solid var(--edge);
  display:flex;flex-direction:column;gap:3px}
.note{font-size:10.5px;line-height:1.55;color:var(--sub2);
  font-variant-numeric:var(--ui-note-num,normal)}
.note b{color:var(--gold);font-weight:600}

#studio{display:none;flex-direction:column;gap:2px;margin-top:9px;padding-top:9px;
  border-top:1px solid var(--edge)}
#studio.open{display:flex}
/* NOTE: `#studio .slider` is deliberately NOT here. Storm widens the look-dev label
   column to 96px, and an id+class selector outranks the portrait `.slider` rule from
   OUTSIDE the media query — emitting it for everyone would silently re-columns four
   other pages' studio folds on a phone. It stays a Storm-local override. */

/* the share layer injects these into #panel / #studio; the page re-homes them into the
   Save group after installShare, so their own spacing is neutralised here */
#g-save #axr-lead{border-top:0;margin-top:0;padding-top:0;max-width:none}
#g-save #axr-lead .axr-row{max-width:560px}
#g-save #axr-embedrow{margin-top:10px}
#g-save #axr-embed{max-width:none}

/* ---- loading ---- */
#load{position:fixed;inset:0;display:grid;place-items:center;background:var(--bg);z-index:9;
  font-size:var(--ui-load-fs,10.5px);letter-spacing:var(--ui-load-ls,.3em);
  text-transform:var(--ui-load-tt,uppercase);color:var(--dim);
  transition:opacity var(--ui-load-tr,.5s)}
#load.gone,#load.done{opacity:0;pointer-events:none}
#bar{width:var(--ui-bar-w,170px);height:var(--ui-bar-h,2px);background:var(--ui-bar-bg,var(--line));
  margin-top:var(--ui-bar-mt,13px);overflow:hidden}
#bar i{display:block;height:100%;width:0;background:var(--gold);transition:width .25s}

/* Stage controls. Deliberately INSIDE <header> — see the note at the top of this
   file. The restore button is the one sibling, shown only under .uihidden. */
#stagebtns{position:absolute;right:var(--ui-stage-r,18px);top:var(--ui-stage-t,15px);
  z-index:5;display:flex;gap:6px;pointer-events:auto}
/* the whole filter, not a saturate() amount: `saturate(1)` is a no-op in arithmetic but
   not in the compositor, and a page that wants plain blur must be able to say so */
#stagebtns .mini,#uirestore{background:var(--ui-stage-bg,var(--card));
  backdrop-filter:var(--ui-stage-blur,blur(14px) saturate(1.2));
  -webkit-backdrop-filter:var(--ui-stage-blur,blur(14px) saturate(1.2));
  color:var(--ui-stage-fg,var(--dim))}
#uirestore{position:fixed;right:var(--ui-stage-r,18px);top:var(--ui-stage-t,15px);
  z-index:5;display:none;pointer-events:auto}
body.uihidden header,body.uihidden #panel{display:none}
body.uihidden #uirestore{display:block}

/* ---- toast ----
   The one piece of chrome allowed outside header/#panel, because it reports on the
   RENDERER and has to survive a hidden UI being the reason nothing is on screen. It
   still hides under .uihidden so the look-dev shots stay empty. */
#qtoast{position:fixed;left:50%;bottom:18px;transform:translateX(-50%);z-index:60;
  background:rgba(20,18,15,.92);color:#e8e2d6;border:1px solid rgba(255,255,255,.14);
  border-radius:999px;padding:8px 14px;font-size:13px;display:flex;gap:10px;align-items:center;
  box-shadow:0 6px 24px rgba(0,0,0,.4);pointer-events:auto}
#qtoast a{color:#e8b96a}
#qtoast button{background:none;border:0;color:inherit;font-size:16px;cursor:pointer;line-height:1}
body.uihidden #qtoast{display:none}

/* -------------------------------------------------------------------- portrait --
   Same card, full width, tabs on their own line. The drawer is allowed a little more
   of a phone because there is no other place for it to go, and it still leaves the
   model the top half. */
@media (max-width:620px){
  header{padding:var(--ui-header-pad-s,12px 14px 20px)}
  h1{font-size:var(--ui-h1-fs-s,20px);max-width:var(--ui-h1-max-s,none)}
  .brand{font-size:9px;letter-spacing:var(--ui-brand-ls-s,.24em)}
  .sub{display:none} .brandx{display:none}
  #stagebtns{right:var(--ui-stage-r-s,12px);top:12px;gap:5px}
  #stagebtns .mini,#uirestore{font-size:11px;padding:5px 9px}
  #uirestore{right:var(--ui-stage-r-s,12px);top:12px}
  #panel{padding:0 8px calc(8px + env(safe-area-inset-bottom))}
  #card{border-radius:16px}
  #tabs::-webkit-scrollbar{display:none}
  .tab{padding:var(--ui-tab-pad-s,6px 10px);font-size:var(--ui-tab-fs-s,11.5px)}
  #drawer{padding:var(--ui-drawer-pad-s,11px 13px 14px);
    max-height:var(--ui-drawer-max-s,min(46vh,340px))}
  .fld{grid-template-columns:1fr;gap:4px;padding:4px 0}
  .label{font-size:8.5px}
  /* stacked chip rows are a whole element's worth of picture. One line that scrolls
     instead — the half-visible next chip is the affordance, and nothing leaves the
     offer. */
  .grp .row{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;
    padding-bottom:1px;-webkit-overflow-scrolling:touch}
  .grp .row::-webkit-scrollbar{display:none}
  .grp .row > *{flex:0 0 auto}
  .ctl{flex-wrap:nowrap;gap:10px}
  #colours,#stories{overflow:visible}
  .active-name{flex:0 0 auto;font-size:12px}
  .slider{grid-template-columns:var(--ui-lbl-col-s,66px) 1fr var(--ui-out-col-s,42px);gap:9px}
}

/* The dims block only exists on the pages that carry one; the grid that places it is
   opt-in via `body.has-dims` so a page without it keeps the flex dockbar it had. */
@media (max-width:620px){
  body.has-dims #dockbar{display:grid;grid-template-columns:1fr auto;
    grid-template-areas:"dims chev" "tabs tabs";gap:8px 6px;
    padding:var(--ui-dock-pad-s,9px 10px 9px 13px);align-items:center}
  body.has-dims .dims{grid-area:dims}
  body.has-dims #dockChevron{grid-area:chev}
  body.has-dims #tabs{grid-area:tabs;overflow-x:auto;scrollbar-width:none;
    justify-content:space-between}
  body:not(.has-dims) #dockbar{padding:var(--ui-dock-pad-s,9px 10px 9px 13px);
    gap:var(--ui-dock-gap-s,var(--ui-dock-gap,14px))}
  body:not(.has-dims) #tabs{overflow-x:auto;scrollbar-width:none}
}
