/* ============================================================================
   JOY CORE — the sheet. Written from scratch, 5 Sep 2026.

   This replaces Virya's stylesheet outright. It is not a re-skin of it: the old
   file was a Microsoft-era ERP console carried across from another client, and
   it brought that client's display face, that client's greys, a second "PART 2 —
   legacy class names, re-cut" half for pages Joy Core does not have, and an Odoo
   vocabulary. Patching kept producing a screen that looked borrowed, because it
   was. Every rule below is here because a Joy Core screen needs it, and the
   classes are unchanged so no markup moved.

   THE COLOUR IS MEASURED, NOT PROPOSED. Every hex in the palette block was read
   out of the live theme CSS on stateofjoy.in (Shopify Horizon 3.2.1) on 3 Sep
   2026. Where Joy's own design system has an answer, that answer wins.

   THREE FACES, THREE JOBS.
     Anton        the wordmark and the big figures. Joy's own display face.
                  Never a label, never below ~20px — it has one weight and no
                  lowercase rhythm to speak of.
     Montserrat   tracked caps: column headers, tab labels, chips, eyebrows.
                  Short strings that are glanced at, and on brand.
     Open Sans    every sentence, every table cell, every number a person has to
                  read carefully. This is the one addition to Joy's published
                  system and it earns its place: Montserrat is a geometric sans
                  with tight apertures, built for headlines, and this console is
                  12–15px text read for hours, which is the one thing it is bad
                  at. Everything SEEN is Joy's; everything READ is humanist.

   INK IS DARKER THAN THE STOREFRONT, TWICE OVER. Joy sets body text at 81%
   black on its tinted section grounds. A console is read all day on a near-white
   ground, so it takes the darker end of Joy's own range — and then darker again,
   because the greys inherited from the old sheet were called out as unreadable
   twice. Contrast on the ground is stated beside each value; nothing used for
   prose sits below 7:1.
   ========================================================================== */

/* ---------------------------------------------------------------- tokens -- */
:root{
  /* =======================================================================
     THE LOOK SYSTEM — three complete visual worlds, one component library.

     Asa, 5 Sep 2026: "enable all, with a theme switch, I love them all…
     we will have 20 pages of dashboard once we connect Tally, Unicommerce".
     That is exactly the case for tokens rather than three stylesheets: a board
     written once inherits all three looks and any look added later, because no
     component below this block names a colour, a face or a radius directly.

     THE CONTRACT. Every rule in this file and in the app sheets reads only
     these names. If a component needs a value that is not here, the value is
     wrong or the token is missing — it is never a literal.

       RUN    a factory size-breakdown sheet. Oat paper, condensed display,
              monospaced figures, ledger red reserved for a shortfall.
       BOX    product-led. Bone ground, heavy grotesque, the photograph carries
              the row. Best for anything per-SKU.
       METER  an instrument panel. Near-white, condensed technical type, colour
              almost absent — one blue, only for a state to act on.

     Light ground in all three, by instruction. There is deliberately no dark
     variant: a console read on a shop floor under strip lighting is a
     light-ground product, and a half-considered dark mode is worse than none.
     ======================================================================= */

  /* ---- RUN (the default) ------------------------------------------------ */
  --paper:#F5F3ED;   /* oat, not cream — the accent is red, and cream + red is
                        the one combination that reads as a wedding invitation */
  --w:#FFFFFF;
  --mist:#E7E4D9;
  --line:#CFCBBB;
  --line-2:#E2DFD2;

  --ink:#16150F;                     /* 15.9:1 on paper */
  --ink-2:#33312A;                   /* 11.2:1 */
  --ink-3:#565348;                   /*  7.3:1 */
  --ink-4:#75705F;                   /*  4.7:1 — the quietest text still read */

  --disp:"Oswald","Arial Narrow",sans-serif;
  --label:"Oswald","Arial Narrow",sans-serif;
  --body:"Source Sans 3",-apple-system,"Segoe UI",system-ui,sans-serif;
  --mono:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  /* Figures are monospaced in Run: a column of them is meant to be scanned. */
  --figures:var(--mono);
  --disp-w:600; --disp-tr:.005em;
  --track:.17em; --track-sm:.07em;

  --accent:#B3251E;                  /* ledger red — a shortfall, never a mood */
  --accent-ink:#8E1D17;
  --r:0; --r-lg:0;                   /* a ruled sheet has no rounded corners  */
  --bar-h:58px;
  --shadow:none;
  --rule:1px solid var(--ink);       /* the heavy rule that separates sections */

  /* ---- shared across all three ------------------------------------------ */
  --gap:18px;
  --ease:cubic-bezier(.22,.61,.36,1);
  --ok:#1F6B3F; --warn:#8A6A08; --danger:#B3251E; --info:#2F5AA8;

  /* Chart series. Validated: every ADJACENT pair clears the colour-vision
     check — worst is ΔE 14.1 (protan), 20.4 (normal vision), all five inside
     the lightness band, above the chroma floor, and over 3:1 on the ground.
     Fixed order, never cycled by value, so a filter that changes the series
     count cannot repaint the survivors. Shared by all three looks on purpose:
     a chart colour that changed with the theme would make the same series two
     different colours in two screenshots of the same week. */
  --s1:#3E63C7; --s2:#E35B45; --s3:#0094A6; --s4:#A8720B; --s5:#8A3A63;

  /* Names the app sheets still use. Aliases, not a second palette. */
  --card:var(--w); --edge:var(--line); --edge-2:var(--line-2);
  --line-soft:var(--line-2); --clay:var(--mist); --clay-soft:var(--line-2);
  --font-display:var(--disp); --font-label:var(--label); --font-text:var(--body);
  --serif:var(--body); --stack:var(--body); --green-deep:#0E3B24;
  --r-sm:var(--r); --r-pill:999px; --sweep-conic:none;
  --t1:.12s var(--ease); --t2:.2s var(--ease); --t3:.32s var(--ease);
  --red:var(--accent); --red-ink:var(--accent-ink);
  --coral:#E35B45; --coral-ink:#B23A26; --green-ink:#1F6B3F;
  /* --green was dropped in the token rewrite and admin.css still asks for it on
     the status dot and the provider LED. An undefined var() resolves to nothing,
     so both rendered invisible — a status light that is never lit. It is the
     look's own OK colour, not Joy's storefront green, so it tracks the theme. */
  --green:var(--ok);
  --yellow-ink:#8A6A08; --navy-ink:#2F5AA8; --pink-ink:#8A3A63; --butter:#FCE584;
}

/* ---- BOX — product-led. The look for anything per-SKU. ------------------- */
[data-look="box"]{
  --paper:#EDEAE3; --w:#FFFDF8; --mist:#E1DCCF;
  --line:#D3CEBF; --line-2:#E4E0D3;
  --ink:#141414; --ink-2:#333330; --ink-3:#57544C; --ink-4:#77736A;

  --disp:"Archivo Black","Archivo",system-ui,sans-serif;
  --label:"Archivo",system-ui,sans-serif;
  --body:"Archivo",-apple-system,"Segoe UI",system-ui,sans-serif;
  --mono:ui-monospace,SFMono-Regular,Menlo,monospace;
  /* Figures are set in the body face here, not mono: Box is read as a shelf of
     products, and a monospaced price beside a photograph looks like a receipt. */
  --figures:var(--body);
  --disp-w:400; --disp-tr:-.02em;
  --track:.2em; --track-sm:.06em;

  --accent:#B02B1F; --accent-ink:#8C2118;
  --r:2px; --r-lg:3px;
  --bar-h:60px;
  --shadow:0 1px 2px rgba(20,20,20,.05);
  --rule:1px solid var(--line);
}

/* ---- METER — the instrument panel. --------------------------------------- */
[data-look="meter"]{
  --paper:#FCFCFB; --w:#FFFFFF; --mist:#F0F1F3;
  --line:#DDE0E4; --line-2:#EDEEF0;
  --ink:#1A1D21; --ink-2:#31363D; --ink-3:#525963; --ink-4:#6E7680;

  --disp:"IBM Plex Sans Condensed","IBM Plex Sans",system-ui,sans-serif;
  --label:"IBM Plex Sans Condensed","IBM Plex Sans",system-ui,sans-serif;
  --body:"IBM Plex Sans",-apple-system,"Segoe UI",system-ui,sans-serif;
  --mono:"IBM Plex Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  --figures:var(--mono);
  --disp-w:700; --disp-tr:-.01em;
  --track:.16em; --track-sm:.05em;

  /* Blue is a STATE here, not a brand colour: it marks a value somebody has to
     act on, and it is the only colour on the screen that is not a chart. */
  --accent:#2F5AA8; --accent-ink:#254A8C;
  --r:2px; --r-lg:3px;
  --bar-h:54px;
  --shadow:0 1px 2px rgba(26,29,33,.05);
  --rule:1px solid var(--line);
}

/* ============================================================================
   METER 2 — the Joy colour theme.

   Asa, 6 Sep 2026: "instead of the blues, we use the pastel yellow pastel
   orange that is the Joy colour scheme."

   Meter's structure exactly — IBM Plex, 2px radii, hairline rules — re-grounded
   in warm light. Only the colour changes; every measurement is inherited, which
   is the point of a second Meter rather than a fourth look.

   PASTEL IS A GROUND, NOT AN ACCENT, and that distinction is the whole design.
   A pastel yellow carries white text at about 2:1 — illegible, and it fails
   WCAG by a mile. So the pastels do what pastels are good at: paper, fills,
   table headers, the wash behind a section. The accent that text actually sits
   on is the same hue taken down to #B45309, which clears white at 5.02:1 and
   passes AA. Both are orange; only one of them can be typed on.

   THE CHART SERIES IS THE SAME FIVE HUES, REORDERED — warm first, blue demoted
   from lead to fourth. Recolouring a categorical palette to be "on brand" is how
   charts stop being readable: the five were chosen to survive colour blindness,
   and any warm-only set collapses under protanopia. Validated against this
   ground with scripts/validate_palette.js — all five checks pass, worst adjacent
   CVD ΔE 14.1 (protan), normal-vision floor 20.1.
   ========================================================================== */
[data-look="joy"]{
  /* Warm paper, and a butter mist for fills and table heads. */
  --paper:#FFFCF4; --w:#FFFFFF; --mist:#FDF3DF;
  --line:#EADFC6; --line-2:#F5EDDC;
  /* Warm-shifted inks rather than Meter's cool greys — a neutral grey on a warm
     ground reads as dirty, and a grey that was picked reads as chosen. */
  --ink:#23201A; --ink-2:#3D382E; --ink-3:#6B6353; --ink-4:#8C846F;

  --disp:"IBM Plex Sans Condensed","IBM Plex Sans",system-ui,sans-serif;
  --label:"IBM Plex Sans Condensed","IBM Plex Sans",system-ui,sans-serif;
  --body:"IBM Plex Sans",-apple-system,"Segoe UI",system-ui,sans-serif;
  --mono:"IBM Plex Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  --figures:var(--mono);
  --disp-w:700; --disp-tr:-.01em;
  --track:.16em; --track-sm:.05em;

  /* 5.02:1 against white — the pastel above it is 2:1 and would be unreadable. */
  --accent:#B45309; --accent-ink:#8F4107;
  --r:2px; --r-lg:3px;
  --bar-h:54px;
  --shadow:0 1px 2px rgba(35,32,26,.06);
  --rule:1px solid var(--line);

  /* Status stays a separate vocabulary from the brand: good/warn/bad must never
     be mistaken for "the accent", or a warning reads as decoration. Warmed to
     sit on this ground, contrast preserved. */
  --ok:#1F6B3F; --warn:#8A6A08; --danger:#B3251E; --info:#0F6E8A;
  --green:var(--ok);

  /* Warm-led, blue demoted. Same five hues, so every chart stays legible. */
  --s1:#E35B45; --s2:#0094A6; --s3:#A8720B; --s4:#3E63C7; --s5:#8A3A63;
}


/* ------------------------------------------------------------------ base -- */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0; background:var(--paper); color:var(--ink-2);
  font:400 15px/1.55 var(--body);
  -webkit-font-smoothing:antialiased; -moz-osx-font-smoothing:grayscale;
  /* Figures line up in columns everywhere in this product. */
  font-variant-numeric:tabular-nums;
}
img{max-width:100%}
/* Prose links are underlined; structural ones are not. A card, a tile or a nav
   item is a target you click, not a reference you follow, and underlining all of
   them was the single loudest thing wrong with the home page. */
a{color:var(--accent-ink); text-decoration-thickness:1px; text-underline-offset:2px}
a:hover{color:var(--ink)}
a.card,a.card--link,a.nav,a.bar__mark,a.src,a.tile,.cards a,.figures a{text-decoration:none}
a.card,a.card--link,.cards a{color:inherit}
:focus-visible{outline:2px solid var(--accent); outline-offset:2px; border-radius:2px}
::selection{background:var(--butter); color:var(--ink)}
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms!important; transition-duration:.01ms!important}
}

/* ------------------------------------------------------------------ type -- */
h1,h2,h3,h4{margin:0; color:var(--ink); font-weight:400}
/* The display face is set through its own weight and tracking tokens, never a
   literal. Anton has one weight; Oswald needs 600 to hold a headline and
   Archivo Black is already 400-and-heavy — a hardcoded 400 rendered Oswald as
   a thin condensed line and made the whole Run look flimsy. */
h1,.h1{font:var(--disp-w) clamp(30px,3.4vw,44px)/1.03 var(--disp);
  letter-spacing:var(--disp-tr); color:var(--ink)}
h2,.h2{font:var(--disp-w) clamp(21px,2.2vw,27px)/1.1 var(--disp); letter-spacing:var(--disp-tr)}
/* h3 is a section label, not a headline — Anton at 15px is a smudge. */
h3,.h3{font:600 12px/1.25 var(--label); letter-spacing:var(--track-sm); color:var(--ink)}
p{margin:0 0 12px; color:var(--ink-2); max-width:72ch}
b,strong{font-weight:700; color:var(--ink)}
code,.mono{font:400 12.5px/1.45 var(--mono); background:var(--mist);
  padding:2px 5px; border-radius:2px; color:var(--ink)}
.lead{font-size:16px; line-height:1.6; color:var(--ink-2); max-width:70ch; margin:12px 0 0}
.sub{font-size:clamp(16px,1.6vw,20px); font-weight:400; line-height:1.55;
  color:var(--ink-2); max-width:48ch; margin:22px 0 0}
.sub b,.lead b{color:var(--ink); font-weight:600}
.lbl{font:600 10px/1 var(--label); letter-spacing:var(--track);
  text-transform:uppercase; color:var(--ink-3); margin-bottom:14px}
.l--fine{font-size:12px; line-height:1.45; color:var(--ink-3); margin-top:3px; max-width:30ch}
.num{font-variant-numeric:tabular-nums}
.tone{color:var(--ink-3)}

/* =========================================================================
   THE COMMAND BAR
   Created once, at load, and never re-rendered — see CLAUDE.md. These rules
   assume that: there is no enter/exit animation on the bar itself, because it
   never enters or exits.

   WRITTEN AGAINST THE REAL MARKUP THIS TIME. The previous pass styled a bar
   that does not exist: it treated `.bar__logo` as the anchor and `.bar__mark`
   as a background-image span, when in the actual document `.bar__mark` IS the
   anchor and it contains an <img class="bar__logo"> plus a <span class="bar__name">.
   It also styled `.nav a`, but `.nav` is the class on each link, not on a
   container. Every one of those selectors matched nothing, which is why the
   navigation was the first thing that looked wrong.
   ========================================================================= */
.shell{min-height:100dvh; display:flex; flex-direction:column}
.bar{
  position:sticky; top:0; z-index:60; min-height:var(--bar-h);
  display:flex; align-items:center; gap:26px; padding:0 22px;
  /* The ground, not a literal. This was hardcoded to Run's off-white, so on the
     Joy and Meter looks the bar sat as a cool grey strip above a warm page. */
  background:color-mix(in srgb, var(--paper) 94%, transparent);
  backdrop-filter:saturate(140%) blur(10px);
  border-bottom:1px solid var(--line);
}
/* The anchor. The logo is a 202x180 PNG, so it is given a height and auto
   width — constraining the width instead squashes it to a smear. */
.bar__mark{display:flex; align-items:center; gap:10px; text-decoration:none; flex:0 0 auto}
/* The source is 202x180 — a wordmark with its own padding, not an icon. At 24px
   it came out 27px wide and read as a smudge. */
.bar__logo{height:30px; width:auto; display:block}
/* "Core" sits beside the Joy wordmark. It is set wider and lighter so it reads
   as the product name after the brand, not as a second wordmark competing with
   the first — and optically aligned, because Anton's cap height sits high. */
.bar__name{font:var(--disp-w) 19px/1 var(--disp); letter-spacing:.07em; color:var(--ink);
  white-space:nowrap; position:relative; top:1px}
.bar__nav{display:flex; align-items:center; gap:2px; min-width:0}
.bar__who{margin-left:auto; position:relative; display:flex; align-items:center; gap:8px;
  flex:0 0 auto; padding:6px 8px; border-radius:var(--r); cursor:pointer;
  font-size:13px; color:var(--ink-2)}
.bar__who:hover{background:var(--mist)}
.bar__burger{display:none; background:none; border:1px solid var(--line); border-radius:var(--r);
  padding:7px 11px; cursor:pointer; color:var(--ink);
  font:600 11px/1 var(--label); letter-spacing:var(--track-sm)}

/* `.nav` is the link itself. */
.nav{
  display:inline-flex; align-items:center; gap:7px; padding:9px 14px;
  font:600 12.5px/1 var(--label); letter-spacing:.04em;
  color:var(--ink-3); text-decoration:none; border-radius:var(--r);
  white-space:nowrap; transition:color .15s var(--ease), background .15s var(--ease);
}
.nav:hover{color:var(--ink); background:var(--mist)}
.nav.on,.nav[aria-current="page"]{color:var(--ink); background:var(--w);
  box-shadow:inset 0 0 0 1px var(--line)}
.nav[hidden]{display:none}

/* ---- the account menu ----------------------------------------------------
   Opened by hover OR focus. Hover alone is unreachable from a keyboard, and
   this menu holds the only way out of the product. */
.av{width:26px; height:26px; border-radius:50%; flex:0 0 26px; display:grid; place-items:center;
  background:var(--ink); color:var(--w); font:600 10px/1 var(--label); letter-spacing:.04em;
  text-transform:uppercase; overflow:hidden}
.who__chev{width:11px; height:11px; opacity:.5; flex:0 0 11px}
.who__menu{
  position:absolute; right:0; top:calc(100% + 6px); z-index:70; min-width:220px;
  background:var(--w); border:1px solid var(--line); border-radius:var(--r-lg);
  box-shadow:var(--shadow); padding:6px;
  opacity:0; visibility:hidden; transform:translateY(-3px);
  transition:opacity .13s var(--ease), transform .13s var(--ease), visibility .13s;
}
.bar__who:hover .who__menu,.bar__who:focus-within .who__menu{
  opacity:1; visibility:visible; transform:none}
.who__me{display:block; padding:9px 11px 10px; font-size:12px; color:var(--ink-3);
  border-bottom:1px solid var(--line-2); margin-bottom:4px}
.who__me b{display:block; font-size:13.5px; color:var(--ink); margin-bottom:2px}
.who__out{display:flex; align-items:center; gap:9px; padding:9px 11px; border-radius:var(--r);
  font-size:13.5px; color:var(--danger); text-decoration:none}
/* Derived from the token, not a literal. #FBEDEA was Run's red at 8% baked in,
   so on Joy and Meter the sign-out row lit up pink against a warm or cool page
   in a hue neither theme contains. */
.who__out:hover{background:color-mix(in srgb, var(--danger) 9%, var(--w)); color:var(--danger)}
.who__menu a,.who__menu button{display:flex; align-items:center; gap:9px; width:100%;
  text-align:left; padding:9px 11px; border:0; background:none; border-radius:var(--r);
  font:400 13.5px/1.3 var(--body); color:var(--ink-2); cursor:pointer; text-decoration:none}
.who__menu a:hover,.who__menu button:hover{background:var(--mist); color:var(--ink)}
.bar__out{margin-left:4px; background:none; border:0; padding:7px 9px; cursor:pointer;
  color:var(--ink-3); border-radius:var(--r); display:inline-flex}
.bar__out:hover{color:var(--danger); background:var(--mist)}
.veil{position:fixed; inset:0; z-index:65}

/* ---------------------------------------------- the page head / menu ------ */
.wrap{width:100%; max-width:1320px; margin:0 auto; padding:0 20px}
.page{padding-bottom:80px}
.v-pane[hidden]{display:none!important}
/* The head slot is SHARED between apps — see CLAUDE.md. It is emptied and
   re-declared by whichever app is resuming, never restored as remembered
   markup, because remembered markup comes back with dead handlers. */
.v-head-slot{border-bottom:1px solid var(--line); background:var(--paper);
  position:sticky; top:var(--bar-h); z-index:50}
/* ---- the head slot's own menus -------------------------------------------
   WRITTEN AGAINST WHAT joy-shell.js ACTUALLY EMITS. The previous pass invented
   both of these: it styled `.titles` as a container for an h1 and `.third` as a
   right-aligned column of caption text. Neither is what the shell renders —
   both are <nav> elements full of BARE anchors:

     <nav class="titles"> <a class="on" data-sect="shopify">Shopify</a> …
     <nav class="third">  <a class="on" data-third="range">The range</a> …

   With no rule matching `.titles a`, every menu item rendered as run-together
   body text: "The rangeTradePersonalisationCustomersFulfilment". That is the
   second time a from-scratch sheet has been written against markup I assumed
   rather than read, and it is the whole reason to check the emitter first. */
/* The head had NO bottom padding, so on any app whose menu is a single row —
   Admin is the obvious one — the first card sat directly under the last tab
   with nothing between them, and the menu read as part of the content rather
   than as the thing that switches it. The hairline is what makes the row a rail
   instead of a floating list of words. */
.head{display:flex; flex-direction:column; align-items:stretch; gap:0;
  padding:16px 0 0; margin-bottom:20px; border-bottom:1px solid var(--line)}
/* A board menu already draws its own travelling rule on the active tab, so the
   container rule sits directly under it and the two read as one rail. */
.head:has(.third){margin-bottom:16px}
.crumb{font:600 10px/1 var(--label); letter-spacing:var(--track);
  text-transform:uppercase; color:var(--ink-3); margin-bottom:10px}

/* Level one: the SOURCE. Shopify today; Tally and Unicommerce when they land. */
.titles{display:flex; align-items:center; gap:4px; flex-wrap:wrap; margin-bottom:2px}
.titles a{display:inline-flex; align-items:center; gap:7px; text-decoration:none;
  padding:7px 12px 8px; border-radius:var(--r); color:var(--ink-3);
  font:var(--disp-w) 17px/1.1 var(--disp); letter-spacing:var(--disp-tr);
  transition:color .13s var(--ease), background .13s var(--ease)}
.titles a:hover{color:var(--ink); background:var(--mist)}
.titles a.on{color:var(--ink)}
.titles .cnt{font:600 10px/1 var(--label); padding:3px 6px; border-radius:var(--r);
  background:var(--mist); color:var(--ink-3); font-variant-numeric:tabular-nums}

/* Level two: the BOARD. A travelling rule, not a pill — these change often and
   a row of filled pills competes with the data underneath. */
.third{display:flex; align-items:flex-end; gap:2px; flex-wrap:wrap;
  overflow-x:auto; scrollbar-width:none; margin-top:6px}
.third::-webkit-scrollbar{display:none}
.third a{display:inline-flex; align-items:center; text-decoration:none; white-space:nowrap;
  padding:9px 13px 10px; color:var(--ink-3);
  font:600 11.5px/1 var(--label); letter-spacing:var(--track-sm);
  border-bottom:2px solid transparent; transition:color .13s var(--ease)}
.third a:hover{color:var(--ink)}
.third a.on{color:var(--ink); border-bottom-color:var(--accent)}
.titles a:focus-visible,.third a:focus-visible{outline:2px solid var(--accent);
  outline-offset:-2px}
.topbar{display:flex; align-items:center; gap:12px; flex-wrap:wrap; padding:12px 0}

/* ---- section tabs: a rule that travels, not a pill that balloons ---------- */
.tabs{display:flex; gap:2px; align-items:flex-end; overflow-x:auto; scrollbar-width:none;
  padding-top:14px}
.tabs::-webkit-scrollbar{display:none}
.tab{
  position:relative; background:none; border:0; cursor:pointer; white-space:nowrap;
  padding:10px 13px 11px; color:var(--ink-3);
  font:600 11.5px/1 var(--label); letter-spacing:var(--track-sm);
  border-bottom:2px solid transparent; transition:color .15s var(--ease);
}
.tab:hover{color:var(--ink)}
.tab.on{color:var(--ink); border-bottom-color:var(--accent)}
.rotor{display:flex; gap:2px; align-items:center}

/* -------------------------------------------------------------- sections -- */
.sect,.section{padding:26px 0 0}
.split{display:grid; gap:var(--gap); grid-template-columns:1.4fr 1fr}
.grid,.g{display:grid; gap:var(--gap)}

/* ---- the landing hero ----------------------------------------------------
   The headline is the one place Anton is allowed to be large. It gets a MEASURE
   in characters rather than a container width: at 1140px the old headline set
   three words per line and left two thirds of the row empty, which is what read
   as "the home page is off". Balanced so the last line is never one orphan word. */
.hero{padding:54px 0 6px}
.hero-t{font:var(--disp-w) clamp(38px,5.4vw,62px)/1 var(--disp); letter-spacing:var(--disp-tr);
  max-width:17ch; text-wrap:balance; color:var(--ink)}
.hero .sub{max-width:54ch; font-size:clamp(16px,1.5vw,19px); line-height:1.6;
  margin-top:22px}
h1,h2,h3{text-wrap:balance}
.aura{display:none}   /* the old sheet's decorative glow. Joy's storefront has none. */

/* ------------------------------------------------------------------ card -- */
/* auto-FIT, not auto-fill. auto-fill lays down as many tracks as the row can
   hold and leaves the empty ones standing, which is why two fleet cards sat in
   the left quarter of a 1140px row with two dead columns beside them. auto-fit
   collapses the empty tracks so the cards fill the row they are in. The 460px
   ceiling stops two cards stretching into billboards. */
.cards{display:grid; gap:14px;
  grid-template-columns:repeat(auto-fit,minmax(280px,1fr)); max-width:1000px}
.card{background:var(--w); border:1px solid var(--line); border-radius:var(--r-lg);
  padding:16px 18px 18px}
/* Border, fill and shadow each say "separate object", so they are spent by
   role rather than stamped on every block. Only a card you can click lifts. */
.card--link{cursor:pointer; transition:box-shadow .18s var(--ease), transform .18s var(--ease)}
.card--link:hover{box-shadow:var(--shadow); transform:translateY(-1px)}
.card__body{color:var(--ink-2); font-size:13.5px; line-height:1.6; margin-top:8px}
.card__role{font:600 9.5px/1 var(--label); letter-spacing:var(--track);
  text-transform:uppercase; color:var(--ink-3); display:block; margin-bottom:9px}
/* Inside a card, h3 is the title, not a section label — so it takes the body
   face at a size that outranks the role chip above it. Elsewhere h3 stays a
   tracked Montserrat label; the difference is the container, which is the
   honest place for it. */
.card h3,.card .h3{font:600 16px/1.3 var(--body); letter-spacing:0; color:var(--ink)}
.cards a.card{display:block}

/* --------------------------------------------------------------- figures -- */
.figures{display:grid; gap:1px; background:var(--line); border:1px solid var(--line);
  border-radius:var(--r-lg); overflow:hidden;
  grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); margin:18px 0 0}
.figures>div{background:var(--w); padding:14px 16px 16px}
/* One rule for both names. `.figures` is the shell's and `.j-figs` is the block
   system's; they render the same markup and styling only one of them is how a
   board ends up reading "188Styles live" on a single line — which then forces
   the grid track wide enough to collapse the whole row to one column. */
/* A headline figure is set in the FIGURES face, which is the display face in
   Box and the monospace in Run and Meter — a column of numbers meant to be
   compared reads better monospaced, and Box is not that kind of screen. */
.figures .n,.j-figs .n,.kpi__n{display:block; font:600 30px/1 var(--figures);
  color:var(--sc-band-num,var(--ink)); letter-spacing:.01em}
.figures .l,.j-figs .l,.kpi__l{display:block; margin-top:8px; font:600 9.5px/1.25 var(--label);
  letter-spacing:var(--track); text-transform:uppercase; color:var(--ink-3)}

/* ----------------------------------------------------------------- state -- */
/* State is never colour alone: every one of these carries a word, and the dot
   is a second channel for anyone who cannot separate the hues. */
.st{display:inline-flex; align-items:center; gap:6px; font:600 11px/1 var(--label);
  letter-spacing:var(--track-sm); color:var(--ink-3)}
.st i,.dot{width:7px; height:7px; border-radius:50%; background:currentColor; display:block}
.st--ok{color:var(--ok)} .st--bad{color:var(--danger)} .st--warn{color:var(--warn)}
.chips{display:flex; flex-wrap:wrap; gap:6px}
.chip,.pill{display:inline-flex; align-items:center; gap:5px; padding:4px 9px;
  border:1px solid var(--line); border-radius:2px; background:var(--w);
  font:600 10px/1.35 var(--label); letter-spacing:var(--track-sm);
  text-transform:uppercase; color:var(--ink-3)}
.chip--approved,.pill--ok{color:var(--ok); border-color:#C6DED2; background:#EAF3EE}
.chip--flag,.pill--bad{color:var(--danger); border-color:#F0D3CE; background:#FBEDEA}
.pill--warn{color:var(--warn); border-color:#E7D6A6; background:#FCF7E7}
.chip--draft{color:var(--ink-4); background:var(--mist)}
.pill--ai{color:var(--info); border-color:#CBD5EC; background:#EEF1F8}

/* -------------------------------------------------------------- controls -- */
.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:8px;
  padding:10px 16px; min-height:38px; border-radius:var(--r);
  border:1px solid var(--ink); background:var(--ink); color:var(--w);
  font:600 12px/1 var(--label); letter-spacing:var(--track-sm);
  cursor:pointer; transition:background .15s var(--ease), border-color .15s var(--ease);
}
.btn:hover{background:#000; border-color:#000}
.btn:disabled{opacity:.45; cursor:not-allowed}
.btn--ghost{background:var(--w); color:var(--ink); border-color:var(--line)}
.btn--ghost:hover{background:var(--mist); border-color:var(--ink-4)}
.btn--go{background:var(--accent); border-color:var(--accent); color:var(--w)}
.btn--go:hover{background:var(--accent-ink); border-color:var(--accent-ink)}
.btn--bad{background:var(--danger); border-color:var(--danger); color:var(--w)}
.btn--sm{min-height:30px; padding:6px 11px; font-size:11px}
input,select,textarea{
  font:400 14px/1.4 var(--body); color:var(--ink); background:var(--w);
  border:1px solid var(--line); border-radius:var(--r); padding:9px 11px; width:100%;
}
input:focus,select:focus,textarea:focus{border-color:var(--ink-4); outline:none;
  box-shadow:0 0 0 3px rgba(227,91,69,.12)}
label{display:block; font:600 10px/1 var(--label); letter-spacing:var(--track);
  text-transform:uppercase; color:var(--ink-3); margin-bottom:6px}

/* ---------------------------------------------------------------- tables -- */
/* Wide content scrolls inside its own container. The page body never scrolls
   sideways — that is the rule the old sheet broke on every board. */
.table-wrap,.scroll-x{overflow-x:auto; -webkit-overflow-scrolling:touch}
table{border-collapse:collapse; width:100%; font-size:13.5px}
th,td{text-align:left; padding:9px 12px; border-bottom:1px solid var(--line-2);
  vertical-align:top}
thead th{position:sticky; top:0; background:var(--mist); color:var(--ink-3);
  font:600 9.5px/1.3 var(--label); letter-spacing:var(--track); text-transform:uppercase;
  border-bottom:1px solid var(--line); white-space:nowrap}
tbody tr:hover{background:#FDFCF7}
td.num,th.num{text-align:right; font-variant-numeric:tabular-nums; white-space:nowrap}
td.key{font-family:var(--mono); font-size:12.5px; color:var(--ink)}
.null{color:var(--ink-4)}

/* ---------------------------------------------------------------- sheets -- */
.sheet{background:var(--w); border:1px solid var(--line); border-radius:var(--r-lg);
  padding:20px 22px}
.sheet__acts{display:flex; gap:8px; justify-content:flex-end; margin-top:18px;
  padding-top:16px; border-top:1px solid var(--line-2)}
.modal{background:var(--w); border-radius:var(--r-lg); box-shadow:var(--shadow);
  width:min(520px,100%); padding:22px 24px; max-height:86dvh; overflow:auto}

/* --------------------------------------------------------- working light -- */
.working{display:inline-flex; align-items:center; gap:9px; color:var(--ink-3);
  font:600 11px/1 var(--label); letter-spacing:var(--track-sm)}
.working::before{content:""; width:9px; height:9px; border-radius:50%;
  background:var(--accent); animation:pulse 1.1s ease-in-out infinite}
.working__t{color:var(--ink-3)}
@keyframes pulse{0%,100%{opacity:.28}50%{opacity:1}}
@keyframes fade{from{opacity:.5; transform:translateY(3px)}to{opacity:1; transform:none}}

/* ---- the sign-in card ---------------------------------------------------- */
.signin{min-height:100dvh; display:grid; place-items:center; padding:24px}
.signin__card{width:min(400px,100%); background:var(--w); border:1px solid var(--line);
  border-radius:var(--r-lg); padding:32px 30px 28px; box-shadow:var(--shadow)}

/* ============================================================================
   THE BLOCK SYSTEM — the shared language of the boards (Signals' Shopify
   screens, the mirror explorer). It introduces no colours of its own: a band
   takes --sc-band from brand.css, so a section can be tinted per client without
   any rule here knowing which client it is.
   ========================================================================== */
.j-band{background:var(--sc-band,var(--mist)); color:var(--sc-band-fg,var(--ink-2));
  border-top:1px solid var(--sc-band-rule,var(--line)); padding:22px 0 30px; margin-top:26px}
.j-block{background:var(--w); border:1px solid var(--line); border-radius:var(--r-lg);
  padding:16px 18px 18px; margin-top:14px}
.j-block>h3{margin-bottom:4px}
.j-figs{display:grid; gap:1px; background:var(--line); border:1px solid var(--line);
  border-radius:var(--r-lg); overflow:hidden;
  grid-template-columns:repeat(auto-fit,minmax(150px,1fr))}
.j-figs>div{background:var(--w); padding:14px 16px 16px}
/* A note is prose, so it takes prose ink — not a lighter grey because it is
   short. Length is not the same thing as importance. */
.j-note{font-size:13.5px; line-height:1.6; color:var(--ink-2); max-width:74ch; margin:8px 0 0}
.j-note b{color:var(--ink)}
.j-ok{color:var(--ok); font-weight:600}
.j-flag{color:var(--danger); font-weight:600}
.j-flag--quiet{color:var(--warn); font-weight:600}
.j-tbl{width:100%}
.j-tw{overflow-x:auto}

/* ---- charts -------------------------------------------------------------- */
.j-svg{width:100%; height:auto; display:block; margin:8px 0 2px}
.dn__a{transition:opacity .15s var(--ease)}
.dn:hover .dn__a{opacity:.72}
.dn__a:hover{opacity:1!important}

/* ---- responsive ---------------------------------------------------------- */
/* The one word in the home headline that carries the accent. One word, once —
   a highlight applied to a phrase stops being a highlight. */
.g{color:var(--accent)}

@media (max-width:820px){
  :root{--gap:14px}
  /* ONE MENU, NOT THREE. The bar carried a burger, a theme switch and an
     account block side by side, so a 375px bar held three separate controls and
     the page title had nowhere to go. Closed, the bar is now the mark and the
     word Menu. Open, everything folds out beneath it in one list: pages, then
     theme, then who you are and the way out.

     The panel is IN FLOW rather than absolutely positioned. Absolute needed the
     bar to be a containing block, which is what made somebody write
     `position:relative` here and silently kill the sticky; and it floated over
     the first card so the top of the page was unreachable while the menu was
     open. In flow, the bar simply grows and pushes the page down. */
  .bar__burger{display:block; order:3; margin-left:auto}
  .bar__nav{display:none}
  .bar__who{display:none}
  .bar.is-open{flex-wrap:wrap; padding-bottom:10px}
  .bar.is-open .bar__nav{display:flex; order:4; width:100%; flex-direction:column;
    align-items:stretch; gap:0; margin-top:8px; padding-top:6px;
    border-top:1px solid var(--line)}
  .bar.is-open .bar__nav a{padding:11px 4px}
  /* NO DROPDOWN INSIDE A DROPDOWN. The account block carries a hover menu, which
     is right on a desktop bar and wrong once the block is already inside the
     open burger: the reader has opened a menu and is asked to open another one
     to reach Sign out — and hover does not exist on a touch screen anyway, so
     the only way in was a stray focus.
     Inside the burger it is flattened: name and Sign out sit in the list. */
  .bar.is-open .bar__who{display:flex; order:6; width:100%; margin:8px 0 0;
    flex-wrap:wrap; align-items:center; justify-content:flex-start;
    padding:10px 4px 0; border-top:1px solid var(--line); cursor:default}
  .bar.is-open .bar__who:hover{background:none}
  .bar.is-open .who__chev{display:none}
  .bar.is-open .who__menu{position:static; opacity:1; visibility:visible;
    transform:none; box-shadow:none; border:0; background:none; padding:0;
    min-width:0; width:100%; margin-top:4px}
  .bar.is-open .who__me{display:none}          /* the name is already beside it */
  .bar.is-open .who__out{padding:10px 4px}
  .bar.is-open .bar__out{margin-left:auto}
  /* STAYS STICKY. This said `position:relative`, which was added so the burger's
     absolutely-positioned dropdown had something to resolve against — and it
     silently overrode `position:sticky` from the base rule, so on every phone
     the bar scrolled off the top and the reader lost the navigation entirely.
     `sticky` is a positioned element too, so the dropdown resolves against it
     just as well and the bar stays where it belongs. */
  .bar{flex-wrap:wrap; position:sticky; top:0}

  .third{margin-left:0; text-align:left; width:100%}
  .wrap{padding:0 15px}
}

/* ---- print --------------------------------------------------------------- */
@media print{
  .bar,.v-head-slot,.tabs,.btn{display:none!important}
  body{background:var(--w)}
  .j-block,.card{break-inside:avoid; box-shadow:none}
}

/* ---- rules the rewrite dropped ------------------------------------------
   Found by diffing every class the markup emits against every class any sheet
   defines. Each of these is live in signals.js, admin.js or login.html and was
   rendering unstyled. */
.bar__out{margin-left:6px; background:none; border:0; padding:7px 10px; cursor:pointer;
  font:600 11px/1 var(--label); letter-spacing:var(--track-sm); color:var(--ink-3);
  border-radius:var(--r)}
.bar__out:hover{color:var(--danger); background:var(--mist)}
.chip--role{color:var(--info); border-color:#CBD5EC; background:#EEF1F8}
/* A board states when it was read. Never omitted, never in the smallest ink —
   a figure whose age is unreadable is a figure nobody can act on. */
.j-asof{font:600 10px/1 var(--label); letter-spacing:var(--track); text-transform:uppercase;
  color:var(--ink-3); margin-bottom:10px; display:block}
.j-title{font:var(--disp-w) clamp(20px,2vw,25px)/1.1 var(--disp); color:var(--sc-band-fg,var(--ink));
  letter-spacing:.005em; margin:0 0 4px}
.mx-main{min-width:0; display:flex; flex-direction:column; gap:12px}
/* The pipeline strip in Admin. */
.pipe{border:1px solid var(--line); border-radius:var(--r-lg); background:var(--w);
  overflow:hidden; margin-top:12px}
.pipe-head{display:flex; align-items:center; gap:10px; padding:11px 14px;
  background:var(--mist); border-bottom:1px solid var(--line);
  font:600 10px/1 var(--label); letter-spacing:var(--track); text-transform:uppercase;
  color:var(--ink-3)}
.pipe-body{padding:13px 14px}
/* The sign-in card's inner parts.
   THE LOCKUP RULES ARE GONE FROM HERE. This block described an OLDER sign-in
   design in which `.signin__logo` was the wrapper and `.signin__mark` was a 30px
   background-image icon. login.html now uses the same two names the other way
   round — mark is the wrapper, logo is Joy's drawn <img> — so
   `.signin__mark{width:30px;flex:0 0 30px}` was silently clamping the whole
   lockup to 30px and squashing the 202x180 mark into it. On a phone that put the
   logo half off the left edge with CORE printed through it.
   Nothing else in the product uses either name; the page that does owns them. */
.signin__box{width:min(400px,100%)}
.signin__sub{font-size:14px; line-height:1.55; color:var(--ink-3); margin:0 0 24px}
/* A skeleton row, on only while something is loading. */
.sk-on{background:linear-gradient(90deg,var(--mist) 25%,#EFEEE8 50%,var(--mist) 75%)
  0 0/200% 100%; animation:sk 1.2s linear infinite; color:transparent!important;
  border-radius:2px}
@keyframes sk{to{background-position:-200% 0}}
@media (prefers-reduced-motion:reduce){.sk-on{animation:none}}
.small{font-size:12.5px; color:var(--ink-3)}
/* `.or` is a divider with a word in it, used on the sign-in card. */
.or{display:flex; align-items:center; gap:13px; margin:20px 0;
  font-size:12.5px; color:var(--ink-3)}
.or::before,.or::after{content:""; flex:1; height:1px; background:var(--line)}

/* ---- the look switch ------------------------------------------------------
   Three named worlds, not a light/dark toggle, so they are spelled out rather
   than hidden behind an icon — "Run", "Box" and "Meter" mean something here and
   a sun/moon would mean nothing. Sits before the account block, at the quiet
   end of the bar: it is changed once and then left alone. */
.looks{display:inline-flex; border:1px solid var(--line); border-radius:var(--r);
  overflow:hidden; background:var(--w); flex:0 0 auto; margin-left:auto}
.looks + .bar__who{margin-left:14px}
.looks button{appearance:none; border:0; background:none; cursor:pointer;
  padding:7px 11px; font:600 10.5px/1 var(--label); letter-spacing:var(--track-sm);
  color:var(--ink-3); white-space:nowrap; transition:color .12s var(--ease),
  background .12s var(--ease)}
.looks button + button{border-left:1px solid var(--line-2)}
.looks button:hover{color:var(--ink); background:var(--mist)}
.looks button[aria-pressed="true"]{background:var(--ink); color:var(--paper)}
.looks button:focus-visible{outline:2px solid var(--accent); outline-offset:-2px}
/* The theme switch moves INTO the burger on a phone. Left in the bar it took a
   whole second row — 92px of a 812px screen permanently spent on something
   chosen once and then never touched again. */
@media (max-width:820px){
  .looks{display:none}
  .bar.is-open .looks{display:flex; order:5; width:100%; margin:10px 0 0}
}

/* ============================================================================
   DIALOGS — appAlert / appConfirm / appPrompt / appForm, and the working chip.

   THESE HAD NO STYLING AT ALL. q.js has always emitted .v-veil / .v-sheet /
   .v-btn / .v-lbl — the class names from the Virya sheet this stylesheet
   replaced — and the rebuild took the names with it. So every dialog in the
   platform ("Sync the Workspace directory?", "Give a role", every error) was
   rendering as raw unstyled HTML at the top of the document: no overlay, no
   panel, no buttons. Thirteen call sites across five apps.

   Renaming the classes in q.js would have been the tidier fix and the wrong
   one — it is a shared primitive loaded by every app, and the names are also
   in markup this stylesheet cannot see. The names stay; the styling comes back,
   on look tokens, so dialogs follow Run, Box and Meter like everything else.
   ========================================================================== */
.v-veil{position:fixed; inset:0; z-index:200; padding:22px;
  background:rgba(0,0,0,.42); display:grid; place-items:center}
.v-sheet{width:min(460px,100%); max-height:86dvh; overflow:auto; padding:22px;
  background:var(--w); border:1px solid var(--line); border-radius:var(--r-lg);
  box-shadow:0 18px 48px rgba(0,0,0,.18)}
.v-sheet h3{margin:0 0 10px; font:var(--disp-w) 19px/1.2 var(--disp);
  letter-spacing:var(--disp-tr); color:var(--ink)}
/* A confirm is a QUESTION AND ITS CONSEQUENCE. The sentence under the title is
   what somebody is agreeing to, so it gets reading measure and real contrast —
   not the muted caption treatment a label would take. */
.v-sheet p{margin:0 0 18px; max-width:56ch; font:400 13.5px/1.6 var(--body);
  color:var(--ink-2)}
.v-sheet .v-lbl{display:block; margin:0 0 6px; font:600 10px/1 var(--label);
  letter-spacing:var(--track); text-transform:uppercase; color:var(--ink-3)}
.v-sheet input,.v-sheet select,.v-sheet textarea{margin-bottom:14px}
/* Actions right-aligned, confirm last: the destination of the eye is the button
   that does the thing, and Cancel must never be the one under the cursor. */
.v-sheet__acts{display:flex; justify-content:flex-end; gap:10px; margin-top:20px;
  padding-top:16px; border-top:1px solid var(--line)}
.v-btn{display:inline-flex; align-items:center; justify-content:center; gap:8px;
  padding:10px 16px; min-height:38px; border-radius:var(--r); cursor:pointer;
  border:1px solid var(--ink); background:var(--ink); color:var(--w);
  font:600 12px/1 var(--label); letter-spacing:var(--track-sm);
  transition:background .15s var(--ease), border-color .15s var(--ease)}
.v-btn:hover{background:#000; border-color:#000}
.v-btn--go{background:var(--accent); border-color:var(--accent); color:var(--w)}
.v-btn--go:hover{background:var(--accent-ink); border-color:var(--accent-ink)}
.v-btn--quiet{background:var(--w); color:var(--ink-2); border-color:var(--line)}
.v-btn--quiet:hover{background:var(--mist); color:var(--ink); border-color:var(--ink-4)}
.v-btn--danger{background:var(--danger); border-color:var(--danger); color:var(--w)}
.v-btn:focus-visible{outline:2px solid var(--accent); outline-offset:2px}

/* The "working" chip, bottom-left and clear of everything. */
.v-working{position:fixed; left:18px; bottom:18px; z-index:190;
  display:flex; align-items:center; gap:10px; padding:9px 14px;
  background:var(--w); border:1px solid var(--line); border-radius:var(--r-lg);
  box-shadow:var(--shadow)}
.v-working__txt{font:400 12.5px/1.3 var(--body); color:var(--ink-2)}

/* ============================================================================
   SIDEWAYS SCROLLING, EVERYWHERE IT HAPPENS.

   Asa, 6 Sep 2026: "if needed for tables, allow left right finger swipe to
   scroll… don't show scrollbar".

   Every horizontal scroller in the product is listed here rather than each
   stylesheet solving it again — there are seven of them across four apps and
   they were behaving four different ways.

   overscroll-behavior-x:contain is the one that matters on a phone and is the
   easiest to miss: without it, swiping a table that has reached its end hands
   the gesture to the BROWSER, which reads it as back/forward navigation. A
   reader flicking through a wide table would leave the page.
   ========================================================================== */
.sg-tw, .fw-tw, .wr, .table-wrap, .ex-grid, .rb-wrap, .scroll-x{
  overflow-x:auto;
  overflow-y:hidden;
  -webkit-overflow-scrolling:touch;      /* momentum on iOS */
  overscroll-behavior-x:contain;         /* the swipe stays in the table */
  scrollbar-width:none;                  /* Firefox */
  -ms-overflow-style:none;               /* old Edge */
}
.sg-tw::-webkit-scrollbar, .fw-tw::-webkit-scrollbar, .wr::-webkit-scrollbar,
.table-wrap::-webkit-scrollbar, .ex-grid::-webkit-scrollbar,
.rb-wrap::-webkit-scrollbar, .scroll-x::-webkit-scrollbar{ display:none; width:0; height:0 }

/* A hidden scrollbar hides the AFFORDANCE too — nothing says the table moves.
   So the scroller carries a soft fade on its right edge, painted only while
   there is more to see. `background-attachment:local` is what makes that
   automatic: the fade is anchored to the content, so it scrolls away as the
   reader reaches the end and needs no JavaScript to know where that is. */
@media (max-width:820px){
  .sg-tw, .fw-tw, .wr, .table-wrap, .ex-grid, .rb-wrap{
    background:
      linear-gradient(to right, var(--w) 30%, transparent) left / 22px 100% no-repeat local,
      linear-gradient(to left,  var(--w) 30%, transparent) right / 22px 100% no-repeat local,
      radial-gradient(farthest-side at 0 50%, rgba(0,0,0,.10), transparent) left / 12px 100% no-repeat scroll,
      radial-gradient(farthest-side at 100% 50%, rgba(0,0,0,.10), transparent) right / 12px 100% no-repeat scroll;
  }
}

/* ---- phone -----------------------------------------------------------------
   Containers, their children, and the space between them. The desktop grids
   collapse to one column; what needed care is that several of them were sized
   with minmax() floors WIDER than a phone, which forces a horizontal scroll on
   the PAGE rather than inside the component. */
@media (max-width:600px){
  :root{ --gap:12px }
  .wrap{ padding:0 14px }

  /* One column — and minmax(0,1fr), not 1fr. THIS IS THE WHOLE FIX and it is
     the least obvious rule in this file: a grid item's default min-width is
     `auto`, which means "at least as wide as my content". A board holding a
     nine-column table therefore forced its own track to 495px inside a 347px
     parent, and the page scrolled sideways while the table sat there not
     scrolling at all. minmax(0,…) lets the track be narrower than its content,
     which hands the overflow back to the table where it belongs. */
  .split, .split2, .cards{ grid-template-columns:minmax(0, 1fr) }
  .tiles{ grid-template-columns:repeat(2, minmax(0, 1fr)) }
  /* An ODD last tile spans the row rather than leaving a filled empty cell
     beside it. Five tiles in two columns drew a blank tinted box next to the
     last one, which reads as a card that failed to load rather than as the end
     of the list. :last-child:nth-child(odd) is true only when the count is odd,
     so nothing changes for an even set. */
  .tiles > :last-child:nth-child(odd){ grid-column:1 / -1 }
  /* The same reason, for anything laid out with flex rather than grid. */
  .split > *, .split2 > *, .cards > *, .tiles > *{ min-width:0 }

  /* Space BETWEEN containers, which is what reads as structure on a small
     screen — on desktop the columns do that job and the vertical rhythm can be
     tighter. */
  .board, .card, .j-block{ margin-bottom:14px; padding:16px 14px }
  .board:last-child, .card:last-child{ margin-bottom:0 }

  /* Nothing may push the page sideways: the tables scroll, the page does not. */
  .board > *, .card > *{ min-width:0 }
  h1, h2, .board__t{ overflow-wrap:anywhere }

  /* A number and its label stop competing for one line. */
  .board__hd{ flex-wrap:wrap; gap:4px 10px }
}

/* ============================================================================
   THE MENUS SCROLL, THEY DO NOT STACK.

   Asa, 6 Sep 2026: "sub menus should be scrollable… 01 Growing to 06 Bought
   should not be stacked… left right swipe scroll but no scrollbar".

   Both menu rows had `flex-wrap:wrap`, and `.third` ALSO had `overflow-x:auto`
   — wrap wins, so the overflow rule never fired and six stages stacked onto
   three lines. A menu whose height depends on the window is a menu that moves
   the content under it every time the phone rotates.

   nowrap is the fix; the scroll and the hidden bar were already written and
   simply unreachable.
   ========================================================================== */
.titles, .third, .tabs, .usr-tabs, .looks{
  flex-wrap:nowrap;
  overflow-x:auto;
  overscroll-behavior-x:contain;         /* the swipe never becomes a back gesture */
  -webkit-overflow-scrolling:touch;
  scrollbar-width:none;
  -ms-overflow-style:none;
  /* Land on a whole item, never halfway through a label. */
  scroll-snap-type:x proximity;
}
.titles::-webkit-scrollbar, .third::-webkit-scrollbar, .tabs::-webkit-scrollbar,
.usr-tabs::-webkit-scrollbar, .looks::-webkit-scrollbar{ display:none; width:0; height:0 }
.titles > *, .third > *, .tabs > *, .usr-tabs > *, .looks > *{
  scroll-snap-align:start; flex:0 0 auto; white-space:nowrap;
}

/* A LEGEND MUST NOT PUSH ITS CARD OPEN. `.lg__v` carried min-width:64px and
   `.lg__l` was nowrap, so a long series name plus a wide figure made the list
   wider than the board holding it and the text ran past the edge. The label
   truncates (it already had the ellipsis, it just had no width to truncate
   within) and the figure gives up its floor on a narrow screen. */
.lg, .lg li{ min-width:0; max-width:100% }
.lg__l{ min-width:0; flex:1 1 auto }
@media (max-width:600px){
  .lg__v{ min-width:0 }
  .lg--row{ gap:6px 14px }
}
