/* ============================================================================
   KH OS — roles.css   THE COMPONENT CONTRACT
   Chad, 2026-08-14: "look at Task and how much larger the menus are in the
   sidebar and all the fonts — there's no consistency."

   WHY THIS FILE EXISTS, AND WHY THE SCALES WEREN'T ENOUGH.

   THE SCALES in DESIGN.md give the app a VOCABULARY — nine type sizes, ten
   spacing steps. That is necessary and it is not sufficient. A vocabulary does
   not say which word to use for which thing, so two modules can both be
   perfectly on-scale and still disagree. Measured on a scale-snapped build:

       child-rail scope item, Calendar : 12px / weight 400
       child-rail scope item, Tasks    : 13px / weight 600

   Same role. Both legal. Visibly different — which is exactly the complaint.

   So this file assigns ONE type spec per ROLE, and every module's markup is
   pointed at it. After this, Calendar and Tasks cannot disagree, because
   neither module gets to choose any more. A new module has nothing left to
   invent, which is the whole point.

   LOAD LAST — after styles.css, tokens.css and components.css. It is meant to
   win. Specificity is kept deliberately modest (one or two classes) so a
   genuine, deliberate exception can still out-specify it; what it stops is
   accidental drift, not intentional design.

   ⚠️ A NEW STYLESHEET MUST BE REGISTERED IN server.py. Static files are served
   from an explicit whitelist, so linking a new .css in index.html alone gets a
   404 and the sheet silently does nothing — the page looks unchanged and reads
   as "the CSS didn't work". Cost one debugging cycle on 2026-08-14.

   SCOPE OF THIS PASS: the three surfaces Chad named — the nav rail, the child
   rail, and the module header — plus the calendar height they were stealing.
   Content rows, chips and buttons are the next pass and are NOT touched here,
   so this diff stays readable.
   ============================================================================ */

:root{
  /* The roles. Read these as "a child-rail item is 13/500, everywhere." */
  /* 2026-09-04, Chad: "the wording and the text and the font and the sizes of
     boxes ... is really small. The pills are really small." Every role below
     moved up EXACTLY ONE step of the DESIGN.md ramp (12 · 13 · 15 · 17 · 20 ·
     22 — there is no 14 and no 16). Nothing here is a new size; the ramp did
     not change and no px literal was introduced. What changed is which rung
     each role sits on, which is the whole reason this file exists: one edit
     here moves the nav, both rails, every module header and every tab
     together, instead of 1,300 sites drifting apart again.

     The old values are kept in the comments so a revert is a copy-paste. */
  --role-nav-size:15px;        --role-nav-weight:500;   /* was 13 */
  --role-cap-size:13px;        --role-cap-weight:600;   /* was 11 · NAVIGATE / VIEWS */
  --role-railtitle-size:17px;  --role-railtitle-weight:600;  /* was 15 */
  --role-railitem-size:15px;   --role-railitem-weight:500;   /* was 13 */
  --role-railmeta-size:13px;   --role-railmeta-weight:500;   /* was 11 */
  --role-modtitle-size:22px;   --role-modtitle-weight:700;   /* was 20 — now equal to
                                  --role-pagetitle, which is correct: a module header
                                  title and Home's page title are the same job. */
  --role-tab-size:15px;        --role-tab-weight:600;   /* was 13 */

  /* 32 was sized for 13px text. At 15 the label filled the row edge to edge,
     so the row grows with it or the rails read as cramped rather than larger. */
  --rail-row-h:38px;           /* was 32 — one row height for BOTH rails */
  --rail-row-pad:6px 10px;
}

/* ══ 1. MODULE HEADER ══════════════════════════════════════════════════════
   LAYOUT LAW fixes the header band at --topbar-h (64px) and says to verify it
   numerically. Measured: .topbar renders at 90px in EVERY module while its own
   child .topbar-main renders at exactly 64. The law was being honoured inside
   the band and broken by its wrapper — `padding:26px 0 0` plus `margin:0 0 12px`
   on .topbar: 38px of chrome above every module in the app, uncaught because
   nobody had measured it.

   Reclaiming those 38px is what makes the calendar taller.

   The 26px is NOT a literal on .topbar — it is `padding-top:var(--page-flush-top)`
   on `.mod-doc > .topbar`, which out-specifies a bare `.topbar` rule. Do not
   try to win that with a more specific selector: --page-flush-top ALSO drives
   both rails' heights (`calc(100vh - var(--appbar-h) - var(--page-flush-top))`)
   and their negative margin-top. Overriding only the header would desynchronise
   the rails from the content and the rails would run 18px long. Retuning the
   TOKEN keeps the whole system consistent, which is the point of having one. */
:root{--page-flush-top:8px}          /* was 26px */
.topbar{margin-bottom:8px}           /* was 14px */
.topbar-main{height:var(--topbar-h);min-height:var(--topbar-h)}

/* The module name. One size in every module. */
.tb-title,.topbar h1,.topbar .page-title{
  font-size:var(--role-modtitle-size);
  font-weight:var(--role-modtitle-weight);
  letter-spacing:-.2px;line-height:1.3;margin:0;
}

/* View tabs (List / Board / Month / Week …). Same in every module. */
/* `.topbar .view-tab` (0,2,0) in styles.css out-specifies a bare .view-tab,
   so the list below is matched at that shape too — the same two-class lesson
   as .btn.btn-xs and .cal-side-col .empty-note.

   ⚠️ .view-tab WAS MISSING AND THE RULE SILENTLY DID NOTHING. Pass 1 guessed
   four class names for the view switcher — .tb-views button, .tb-view,
   .vw-tab, .seg button — and the real one is `.view-tab` inside
   `.view-tabs.vw-switch`. So the switcher Chad specifically complained about
   ("the buttons at the top ... should all be locked in place") kept rendering
   at 13.5/700 for fourteen passes, and every per-module check missed it
   because it only looked at the module's own content classes.
   Found by a full-app audit for off-ramp values, not by reading. */
.tb-views button,.tb-view,.vw-tab,.view-tab,.view-tabs button,.seg button,
.topbar .view-tab,.topbar-views .view-tab{
  font-size:var(--role-tab-size);
  font-weight:var(--role-tab-weight);
}

/* The generic "Loading…" card renders at the page's inherited 14px in four
   modules. It is a row of text you read while you wait. */
.page>.card:only-child,.loading-note,.am-list .empty-note{
  font-size:var(--role-rowtitle-size);
  font-weight:var(--role-rowtitle-weight);
}

/* The last two off-ramp values in the module header, found by the full-app
   audit rather than by any per-module check: Calendar's ＋ button at 12.5
   (it is a button, so it takes the button role) and Notes' "· Private pad"
   qualifier inside the h1 at 14 (it is meta riding beside a title). */
/* `.page-cal #newMeeting` is an ID selector (1,1,0) — the only one standing
   between this contract and the page. An ID cannot be out-specified by
   classes, so this is matched at the ID's own shape rather than escalating
   to !important, which would make the NEXT override worse than this one. */
.page-cal .cal-add,.page-cal #newMeeting{
  font-size:var(--role-btn-size);
  font-weight:var(--role-btn-weight);
}
.pn-book-in{
  font-size:var(--role-rowmeta-size);
  font-weight:var(--role-rowmeta-weight);
}

/* The generic "Loading…" card, which renders at the page's inherited 14px in
   Forms and Admin. It is transient, which is exactly why it survived fourteen
   passes: it is gone by the time you look at the module. */
.page>.card:only-child,.loading-note{
  font-size:var(--role-rowtitle-size);
  font-weight:var(--role-rowtitle-weight);
}

/* ══ 1b. BRAND MARKS — one scale, like everything else ═════════════════════
   Chad, 2026-08-17, on being offered options instead of a fix: "I have said
   I want the entire OS to have the same formatting."

   Brand marks had the same disease as the type did — four sizes, no rule:

     .kb-emoji      18px   inline in a heading (Corp Info, KB articles)
     .nav-mini      20px   nav rail  (22px at one breakpoint, for no reason)
     .mod-pick-logo 38px   Passwords / picker cards
     .hub-logo      54px   store and department worlds
     .hub-logo-txt  19px   the text fallback — not even on the type ramp

   Now three sizes on the 4pt grid, one per context, and the same everywhere:

     --mark-inline  20   riding in a line of text
     --mark-card    40   a card's leading mark
     --mark-hero    56   the mark that opens a world

   The inline mark also goes 18 -> 20 because the KH mark is a disc with "KH"
   set inside it: at 18px those letters stop resolving and it reads as a plain
   blue dot next to a chicken and a cone, which survive shrinking. It is the
   logo either way — it just has to be big enough to look like one. */
:root{
  --mark-inline:20px;
  --mark-card:40px;
  --mark-hero:56px;
}
:is(h1,h2,h3,h4,p,li,span,div)>.kb-emoji{
  height:var(--mark-inline);
  width:var(--mark-inline);
  object-fit:contain;
  vertical-align:-4px;
  border-radius:4px;
}
/* styles.css sets `.kb-body .kb-emoji{height:1.25em!important}`, which makes
   the SAME mark 21px inside a KB article and 20px everywhere else — the mark
   sized itself off whatever text it happened to sit in. !important can only
   be answered by !important, so this is the one place in the file that uses
   it, and it is used to REMOVE a variable size rather than to add one. */
.kb-body .kb-emoji,.kb-html .kb-emoji{
  height:var(--mark-inline)!important;
  width:var(--mark-inline)!important;
  max-width:none!important;
  object-fit:contain;
}
.nav-mini,.avatar.nav-mini,.rail .nav-item img,.rail .nav-mini{
  width:var(--mark-inline);
  height:var(--mark-inline);
  flex:0 0 var(--mark-inline);
}
.mod-pick-logo,.fchip-logo.fchip-logo-lg{
  width:var(--mark-card);
  height:var(--mark-card);
  flex:0 0 var(--mark-card);
}
.hub-logo{
  width:var(--mark-hero);
  height:var(--mark-hero);
  flex:0 0 var(--mark-hero);
}
/* The text fallback when a board has no image was 19px — off the ramp. */
.hub-logo-txt{
  font-size:var(--role-modtitle-size);
  font-weight:var(--role-modtitle-weight);
}

/* ══ 2. NAV RAIL — the navy one, global ════════════════════════════════════
   Measured before: nav items 13.33px/400 (an inherited value, not a px literal
   anywhere — which is why a scale sweep over px literals never saw it) while
   the DEPARTMENT and LOCATION items below them rendered 13px/700. Same role,
   different type, in the same rail. */
.rail .nav-item,
.rail .nav-dept,
.rail .nav-loc,
.rail nav.nav button:not(.nav-edit){
  font-size:var(--role-nav-size);
  font-weight:var(--role-nav-weight);
  line-height:1.35;
  min-height:var(--rail-row-h);
  padding:var(--rail-row-pad);
}

/* NAVIGATE / DEPARTMENTS / LOCATIONS captions, and the EDIT affordance. */
.rail .nav-cap,.rail .nav-cap-row,.rail .nav-edit,.rail .rail-cap{
  font-size:var(--role-cap-size);
  font-weight:var(--role-cap-weight);
  letter-spacing:.08em;text-transform:uppercase;
}

/* ══ 3. CHILD RAIL — the module's scope list ═══════════════════════════════
   THIS is the surface Chad pointed at. Every module's child rail is a
   different component (.mtg-rail, the calendar's source list, the KB tree),
   which is precisely why they drifted. They are unified here by ROLE, not by
   being rewritten into one component — that would be a far larger change for
   the same visual result. */

/* 3a. The rail's own title — "August 2026", a folder name. */
:is(.mod-rail,.mtg-rail) .rail-title,:is(.mod-rail,.mtg-rail) .crail-title,.cal-label,.mod-rail h2,.mod-rail h3{
  font-size:var(--role-railtitle-size);
  font-weight:var(--role-railtitle-weight);
  letter-spacing:-.1px;line-height:1.3;
}
/* .cal-label was 16/800 Poppins — display weight on a rail label is the
   "shouty" read. It joins the rail-title role. */
.cal-label{font-family:inherit}

/* 3b. Section captions — VIEWS, STORES, TAGS, FILTER. Already 11/600 in Tasks;
   Calendar had these at four different sizes. */
:is(.mod-rail,.mtg-rail) .section-label,
:is(.mod-rail,.mtg-rail) .msec-head,
:is(.mod-rail,.mtg-rail) .side-sec-h,
:is(.mod-rail,.mtg-rail) .pn-fol-head,
.cal-colors>summary{
  font-size:var(--role-cap-size);
  font-weight:var(--role-cap-weight);
  letter-spacing:.08em;text-transform:uppercase;
  color:var(--muted);
}

/* 3c. THE ITEM. The one that measured 12/400 in Calendar and 13/600 in Tasks. */
:is(.mod-rail,.mtg-rail) .fchip,
:is(.mod-rail,.mtg-rail) .pn-fol,
:is(.mod-rail,.mtg-rail) .mtg-vw,
:is(.mod-rail,.mtg-rail) .lrow,
:is(.mod-rail,.mtg-rail) .scope-row,
.cal-src-row{
  font-size:var(--role-railitem-size);
  font-weight:var(--role-railitem-weight);
  line-height:1.35;
  min-height:var(--rail-row-h);
  padding:var(--rail-row-pad);
  border-radius:8px;
}

/* 3d. The item's leading icon/swatch rides at the item's size, never its own. */
:is(.mod-rail,.mtg-rail) .vw-ic,:is(.mod-rail,.mtg-rail) .fchip-ic,.cal-src-row .cs-swatch{
  font-size:var(--role-railitem-size);
  line-height:1;
}

/* 3e. Counts and helper text sitting beside an item. */
:is(.mod-rail,.mtg-rail) .fchip-n,:is(.mod-rail,.mtg-rail) .pn-n,:is(.mod-rail,.mtg-rail) .rail-note,.cal-colors .cc-n{
  font-size:var(--role-railmeta-size);
  font-weight:var(--role-railmeta-weight);
}

/* The drag grip measured 13/400 next to a 14/400 head — decorative, so it
   simply follows its row rather than holding a size of its own. */
:is(.mod-rail,.mtg-rail) .msec-grip,:is(.mod-rail,.mtg-rail) .cs-grip{font-size:inherit;line-height:1}

/* 3f. THE RAIL ROOT IS NOT ALWAYS .mod-rail — and that is the real bug.
   Meetings renders `aside.mtg-rail` with NO .mod-rail class, so every rule
   above silently skipped it and its rail sat at 13/400 items and 11/700
   captions. A contract that depends on a module remembering to add a class
   is not a contract; it is a convention, and conventions are what drifted.

   :is() lets one rule cover every rail root, so a NEW module that invents its
   own rail class is the only way to escape — and that is a one-line fix here
   rather than a rediscovery. Specificity is unchanged: every argument is a
   single class, and :is() takes the highest of them. */
:is(.mod-rail,.mtg-rail) .mtg-side-l,
:is(.mod-rail,.mtg-rail) .mtg-side-row{
  font-size:var(--role-railitem-size);
  font-weight:var(--role-railitem-weight);
  line-height:1.35;
}
:is(.mod-rail,.mtg-rail) .mtg-side-row{
  min-height:var(--rail-row-h);padding:var(--rail-row-pad);border-radius:8px;
}
:is(.mod-rail,.mtg-rail) .mtg-side-h{
  font-size:var(--role-cap-size);
  font-weight:var(--role-cap-weight);
  letter-spacing:.08em;text-transform:uppercase;color:var(--muted);
}
/* The rail's own empty state — "No meetings match these filters" — was 12.5,
   off the ramp entirely. */
:is(.mod-rail,.mtg-rail) .empty-note{
  font-size:var(--role-railmeta-size);
  font-weight:var(--role-railmeta-weight);
  line-height:1.45;
}

/* ── AN ACTIVE ROW CHANGES COLOUR, NEVER TYPE ──────────────────────────────
   Notes' notebook list rendered the SELECTED book at 13/800 and every other
   book at 13/500. Selecting a row therefore re-flowed it — the text got
   heavier and wider under the cursor. That is a third way for a rail to look
   inconsistent, and it is invisible in a static screenshot because it depends
   on which row happens to be selected.

   The rule: state changes background and colour. It never changes size or
   weight. Everything below is the same treatment, selected or not. */
:is(.mod-rail,.mtg-rail) .pn-book-name,
:is(.mod-rail,.mtg-rail) .pn-fol.active .pn-book-name,
:is(.mod-rail,.mtg-rail) .pn-fol-add{
  font-size:var(--role-railitem-size);
  font-weight:var(--role-railitem-weight);
}
/* .pn-count is the notebook tally — 10.5/800, off the ramp and heavier than
   the name it counts. (The earlier rule guessed `.pn-n`; the class is
   `.pn-count`, so it never matched — a reminder to read the DOM, not guess.) */
:is(.mod-rail,.mtg-rail) .pn-count{
  font-size:var(--role-count-size);
  font-weight:var(--role-count-weight);
}
/* The hint line under the notebooks, and the ＋ inside it. */
:is(.mod-rail,.mtg-rail) .pn-fol-hint,
:is(.mod-rail,.mtg-rail) .pn-fol-hint b{
  font-size:var(--role-railmeta-size);
  font-weight:var(--role-railmeta-weight);
}

/* 3g. CALENDAR'S RAIL IS A DIFFERENT COMPONENT ENTIRELY.
   It is `aside.cal-side-col` inside `.cal-flex` — it never carried .mod-rail,
   which is exactly why none of the rules above reached it and why it drifted
   to ELEVEN type treatments against Tasks' three. Measured before this block:

     August / 2026   17.5/600 and 17.5/500      caret 10/400    ✕ 15/400
     ⚙ Filters       11/600                     store rows 12.5/400
     🎨 12.5/600     colour note 11/400         🗑 12/400  label 12/600
     "Sign out/in…"  11.5/400

   Mapped onto the SAME roles below. Note this is a mapping, not a rewrite:
   the markup is untouched, so nothing about the calendar's behaviour moves. */
.cal-side-col .cal-rail-month,
.cal-side-col .crm-m,.cal-side-col .crm-y{
  font-size:var(--role-railtitle-size);
  font-weight:var(--role-railtitle-weight);
  letter-spacing:-.1px;line-height:1.3;
}
/* The year sits back by COLOUR, not by size — two sizes in one label is what
   made this read as two different things. DESIGN.md's Calendar section already
   says the month is what you read; this keeps that true in the rail too. */
.cal-side-col .crm-y{color:var(--muted)}
/* `font-size:inherit` here was wrong: the rail ROOT is 14px, so ✕ inherited 14
   and stayed a tenth treatment. Decorative controls take the item size
   explicitly — "inherit" is only safe when the parent is itself on a role. */
.cal-side-col .crm-caret,.cal-side-col .x-close{
  font-size:var(--role-railitem-size);
  font-weight:var(--role-railitem-weight);
  line-height:1}

/* Scope items: the store colour rows ARE this rail's equivalent of Tasks'
   .fchip list, and the tool buttons sit on the same row rhythm. */
.cal-side-col .cc-name,
.cal-side-col .cal-src-tools .fchip{
  font-size:var(--role-railitem-size);
  font-weight:var(--role-railitem-weight);
  line-height:1.35;
}
.cal-side-col .cc-row{min-height:var(--rail-row-h);padding:var(--rail-row-pad);border-radius:8px}
/* The two disclosure icons sat at 12.5/600 and 12/400 — they inherit weight
   from their <summary>, so the weight has to be stated or they stay two
   treatments even once the sizes agree. */
.cal-side-col .cc-ico,.cal-side-col .ct-ico{
  font-size:var(--role-railitem-size);
  font-weight:var(--role-railitem-weight);
  line-height:1}

/* Section heads — "Calendar colours", "Recently deleted". Same caption role as
   Tasks' VIEWS / STORES / TAGS. */
.cal-side-col .cc-lbl,.cal-side-col .ct-lbl{
  font-size:var(--role-cap-size);
  font-weight:var(--role-cap-weight);
  letter-spacing:.08em;text-transform:uppercase;color:var(--muted);
}

/* Helper text and counts. The doubled class is deliberate: a longer selector
   in components.css was still winning on font-size (the weight from this rule
   applied while the size did not — the tell that something more specific, not
   something later, was in front). Doubling .cal-side-col buys specificity
   without reaching for !important, which would make the next override worse. */
.cal-side-col.cal-side-col .cc-note,
.cal-side-col.cal-side-col .empty-note,
.cal-side-col.cal-side-col .cal-src-note,
.cal-side-col.cal-side-col .cc-n{
  font-size:var(--role-railmeta-size);
  font-weight:var(--role-railmeta-weight);
  line-height:1.45;
}

/* ══ 4. CALENDAR HEIGHT ════════════════════════════════════════════════════
   Chad: "there's a lot of empty space above the menus and you could build the
   calendar up taller so there's more usable space and more information."

   Measured before: the first day sat 204px down a 950px viewport — 21% of the
   screen was chrome. 38px of that is the .topbar wrapper, fixed in section 1.
   The weekday header was 38px tall for one line of 11px uppercase text; at the
   caption role with a 4pt rhythm it needs 24. Together that returns ~52px,
   which is half a week row. */
.cal-grid-head,.cal-whead{
  font-size:var(--role-cap-size);
  font-weight:var(--role-cap-weight);
  letter-spacing:.08em;text-transform:uppercase;
  height:24px;min-height:24px;padding:0 0 4px;
  align-items:center;
}
/* The sticky month name is --t-title1 per DESIGN.md's Calendar section. Its
   BAND is trimmed to a 4pt rhythm; the NAME is not shrunk — that was the
   explicitly wrong read on 2026-08-14 and must not be repeated. */
.cal-msep{font-size:var(--t-title1);padding:8px 2px 4px}


/* ══ 5. CONTENT — what is INSIDE the modules ═══════════════════════════════
   Pass 1 unified the chrome. The content drifted the same way and for the
   same reason. Measured before this block:

     row / event TITLE   Tasks 13.5/600      Calendar 11.5/600
     section head        Tasks 11/800        Calendar (weekday) 11/600
     count badge         Tasks 11.5/700
     chip                Tasks 10.5/700
     avatar initials     Tasks 8.5/700   ← smaller than any size on the ramp
     Tasks content total 9 treatments · Calendar 6

   The roles below are the CONTENT equivalents of the chrome roles. There are
   more of them than in the chrome because content genuinely has more jobs —
   but every one is on the ramp, and every module answers to the same one. */
:root{
  --role-rowtitle-size:15px;   --role-rowtitle-weight:500;  /* was 13 */
  --role-rowmeta-size:13px;    --role-rowmeta-weight:500;   /* was 11 */
  --role-chip-size:13px;       --role-chip-weight:600;      /* was 11 */
  --role-count-size:13px;      --role-count-weight:600;     /* was 11 */

  /* Calendar's grid is denser than a list, so its event text gets its own
     role rather than borrowing --role-rowtitle. That is a deliberate,
     documented exception — the kind the contract allows. What it is NOT is
     each module inventing a size, which is what 11.5 vs 13.5 was. */
  --role-caldate-size:15px;    --role-caldate-weight:500;   /* was 13 */
  /* The event pill is the ONE role that moves a half-step rather than a full
     one: 12 → 13, not 12 → 15. A day cell is 175px wide and holds four pills;
     at 15px it holds three, so a full step would have bought legibility with
     hidden events — the trade Chad has rejected before ("cards first, then
     drill down" only works if the card shows the day). 13 with the taller cell
     below keeps all four AND ends the 12px squint. */
  --role-event-size:13px;      --role-event-weight:600;     /* was 12 */
}

/* 5a. The primary text of a row — a task, a meeting, a contact, a ticket.
   .ct-name b is Contacts' version: it was 14/700, the heaviest and largest
   body text anywhere in the app, against 13/500 for the identical job in
   Tasks. A <b> carries the emphasis semantically; it does not need to also
   be two steps up the ramp. */
.l-title,.tt-title,.mtg-title,.ct-title,.row-title,.ct-name b{
  font-size:var(--role-rowtitle-size);
  font-weight:var(--role-rowtitle-weight);
  line-height:1.4;
}

/* 5b. Secondary facts riding alongside it — store, assignee, timestamp, and
   in Contacts the role line and the phone/email links (11.5/500 and 12.5/400,
   two more sizes for one job). */
.tc-name,.l-meta,.row-meta,.l-when,.ct-sub,.ct-link{
  font-size:var(--role-rowmeta-size);
  font-weight:var(--role-rowmeta-weight);
}

/* Counts appear in content as well as in rails — Contacts' group headers use
   the same .fchip-n at 11/800. One count role everywhere. */
.fchip-n,.ct-group-h .fchip-n{
  font-size:var(--role-count-size);
  font-weight:var(--role-count-weight);
}

/* 5c. Chips — status, due, tag, priority. One size app-wide. The overdue
   chip was 10.5/700: below the ramp AND bolder than the row it sat on, which
   is why a single late task shouted louder than the task's own name. */
.chip,.l-due,.tt-chip,.st-pill,.tt-due,.att-more,.au-opt{
  font-size:var(--role-chip-size);
  font-weight:var(--role-chip-weight);
}
/* The variant chips carry their own two-class rules (.chip.due, .chip.overdue,
   .chip.tag) which out-specify a bare .chip, so the weight has to be restated
   at the same specificity or they stay at 700 while the base chip moves.

   `.chip.chip` rather than an enumerated list: naming each variant meant
   .chip.wait-late survived at 11/700 in Support because it was not on the
   list, and the next variant anyone adds would survive too. Doubling the
   class matches the variants' specificity generically, and roles.css loads
   last so it wins the tie. Enumerating variants is a list that goes stale;
   matching their SHAPE does not. */
.chip.chip{
  font-size:var(--role-chip-size);
  font-weight:var(--role-chip-weight);
}
/* The status pill — "To do" / "Assigned" / "Waiting on…" — was 11.5/700, its
   own size AND the heaviest text in the row. It is a chip like any other. */
.status-pill{
  font-size:var(--role-chip-size);
  font-weight:var(--role-chip-weight);
}

/* 5d. Group headings inside content — OVERDUE / TODAY / THIS WEEK. Same
   caption role as the rail's VIEWS / STORES, because they do the same job.
   800 weight on an 11px label is the "shouty" read Chad keeps flagging. */
.sgroup-head .section-label,.sec-head,.group-head{
  font-size:var(--role-cap-size);
  font-weight:var(--role-cap-weight);
  letter-spacing:.08em;text-transform:uppercase;
}
.sg-count,.sec-count,.count-badge{
  font-size:var(--role-count-size);
  font-weight:var(--role-count-weight);
}
/* Both of these state their own font-weight in styles.css (.l-sel-tick is 700,
   .sg-caret inherits 400), so setting size alone left two more treatments
   behind. Decorative marks follow the chip role in full. */
.sg-caret,.l-sel-tick,.l-check-tick{
  font-size:var(--role-chip-size);
  font-weight:var(--role-chip-weight);
  line-height:1;
}

/* 5e. Avatar initials were 8.5px — smaller than anything on the ramp, which
   is why they read as smudges rather than initials. */
.avatar,.avatar-stack .avatar{
  font-size:var(--role-count-size);
  font-weight:var(--role-count-weight);
}

/* 5e2. Empty states. .empty-note is the app's one "nothing here" line and it
   was rendering at 12.5 in content and 11.5/12/12.5 inline in four places.
   In content it is something you READ, so it takes the row size; inside a
   rail the rail-scoped rule above keeps it at meta. All four inline
   font-sizes were removed from app.js — their padding was kept, since
   padding is layout and this contract is about type. */
.empty-note{
  font-size:var(--role-rowtitle-size);
  font-weight:var(--role-rowmeta-weight);
}

/* 5f. The ghost "+ Add task" row reads as a row, so it takes the row size. */
.lrow-add,.add-row{
  font-size:var(--role-rowtitle-size);
  font-weight:var(--role-rowtitle-weight);
}

/* 5g. Calendar grid. The date numeral was 12/700 — bolder than the events it
   sits above, so the eye landed on the number instead of the day's content.
   DESIGN.md's Calendar section already says timed events are a dot, not a
   colour block; this keeps the same restraint in the type.

   A bare .cal-date / .cal-pill rule does NOT reach the month grid. Both
   selectors are redeclared several times in styles.css and the ones that
   actually win are two- and three-class: `.cal-card-month .cal-date` (12/700),
   `.cal-cell .cal-pill` (11.5/600), `.cal-pill.ep-line .ep-t` (11/600) and
   `.ep-time` (10/600). Matched here at equal specificity so load order decides,
   rather than escalating — escalating is what produced this pile-up. */
.cal-date,
.cal-card-month .cal-date,
.cal-cell .cal-date{
  font-size:var(--role-caldate-size);
  font-weight:var(--role-caldate-weight);
}
.cal-pill,
.cal-cell .cal-pill,
.cal-grid .cal-pill.span-start,.cal-grid .cal-pill.span-mid,.cal-grid .cal-pill.span-end,
.cal-pill.ep-line .ep-t{
  font-size:var(--role-event-size);
  font-weight:var(--role-event-weight);
}
.cal-pill b{font-weight:700}
.cal-time,.cal-more,.cal-pill.ep-line .ep-time{
  font-size:var(--role-rowmeta-size);
  font-weight:var(--role-rowmeta-weight);
}


/* ══ 6. BUTTONS, CARDS AND THE PAGE TITLE ══════════════════════════════════
   Home is the landing page and it carried TWELVE type treatments — more than
   any module — including 10.5/800, 12.5/500, 12.5/600, 13.5/700, 14.5/700,
   15/800 and 19.5/500. Six of those seven are off the ramp entirely.

   Home also has NO .topbar: it is a `.page`, not a `.mod-doc`, so the module
   header rules never applied to it. That is a structural gap in LAYOUT LAW,
   not a type gap, and it is NOT fixed here — restructuring Home's shell is a
   separate change. What is fixed is that its title now lands on the ramp. */
:root{
  --role-btn-size:15px;        --role-btn-weight:600;  /* was 13 — a button now
                                  reads at body size, which is what "the buttons are
                                  small" meant. --role-body is already --t-body and
                                  does not move; it is the anchor everything else is
                                  measured against. */
  --role-body-size:15px;       --role-body-weight:400;
  --role-cardtitle-size:17px;  --role-cardtitle-weight:700;
  --role-pagetitle-size:22px;  --role-pagetitle-weight:700;
}

/* 6a. Buttons. One size for every button in the app. .btn was 13.5/700. */
.btn,.btn-primary,.btn-ghost{
  font-size:var(--role-btn-size);
  font-weight:var(--role-btn-weight);
}

/* 6b. Card titles — a store card, a "Your work" panel head. DESIGN.md assigns
   --t-headline (17) to card titles; .sc-name was 15/800 and .card h3 14.5/700,
   two sizes for one job and neither on the ramp.

   A TILE IS A CARD. Home's hub tiles (.ht-name) and Finances' module picker
   (.mod-pick-nm, 14/700) do the same job as a store card — a big clickable
   thing with a name and a description — so they take the card title role
   rather than the button role. Before this they were 13/600 and 14/700. */
.sc-name,.card>h3,.card-title,.panel-title,
.hub-tile .ht-name,.mod-pick-nm{
  font-size:var(--role-cardtitle-size);
  font-weight:var(--role-cardtitle-weight);
  letter-spacing:-.2px;
}
.sc-sub,.card-sub,.ht-desc,.mod-pick-sub{
  font-size:var(--role-rowmeta-size);
  font-weight:var(--role-rowmeta-weight);
}
/* Bold inside an empty state was 700 against the note's own 500. */
.empty-note b{font-weight:var(--role-cap-weight)}

/* 6b-2. BODY PROSE. A paragraph of explanatory text inside a card — My Files'
   "…@kaleohospitality…" note — was 13.5/700: off the ramp and bolder than a
   row title. DESIGN.md's --t-body (15) is exactly this role, and it is the one
   place in the app where the 15 on the ramp actually gets used.

   Scoped to .card p rather than all <p> on purpose: a broad `p` rule would
   reach list rows and brief lines that are already on other roles, and this
   pass is meant to be reviewable.

   Extended to `.page > p` as well (Workload's legend paragraph was 14/400):
   a DIRECT child of the page is standalone prose. Still not a blanket `p`
   rule, which would reach paragraphs nested inside rows and cards that
   already answer to other roles.

   :not(.section-label) because THIS RULE CAUSED A REGRESSION. `p.section-label`
   on the store worlds is a caption, but `.page>p` (0,1,1) out-specifies
   `.section-label` (0,1,0), so the caption rendered as 15/400 body text.
   Found by measuring a store world, not by reading the file — a reminder
   that a contract rule can itself be the drift. */
.card p,.page>p:not(.section-label){
  font-size:var(--role-body-size);
  font-weight:var(--role-body-weight);
  line-height:1.5;
}
.card p b,.card p strong{font-weight:var(--role-cap-weight)}

/* 6c. The page title. Home's greeting is a PAGE title, which DESIGN.md puts at
   --t-title2 (22). It was 26/800. The module header title stays at 20 — these
   are different roles and the doc names both. */
.greet-h1{
  font-size:var(--role-pagetitle-size);
  font-weight:var(--role-pagetitle-weight);
  letter-spacing:-.4px;
}
.greet-eyebrow,.section-label,.bh-eyebrow,.help-dot,
.brief-col>h4,.bucket-label{
  font-size:var(--role-cap-size);
  font-weight:var(--role-cap-weight);
  letter-spacing:.08em;
}
/* A ticket line in the brief is a row, not a caption — it was 12.5/600, its
   own size, sitting directly under a caption at 11. */
.brief-row>span,.brief-row .br-title{
  font-size:var(--role-rowtitle-size);
  font-weight:var(--role-rowtitle-weight);
}
/* The brief's headline sentence — 19.5 is not a size; 20 is. */
.bh-line{font-size:var(--t-title3);font-weight:500}

/* CONDITIONAL UI HIDES DRIFT. Home's "Needs a decision" panel only renders
   when there ARE decisions, so every earlier audit of Home — including the
   one that reported it 12 -> 7 — never saw it. Its head was 10.5/800 (below
   the ramp AND breaking the 800-under-20 rule) and its rows 12.5/600.
   Anything gated on data needs measuring WITH that data present. */
.bh-decide-h{
  font-size:var(--role-cap-size);
  font-weight:var(--role-cap-weight);
  letter-spacing:.08em;text-transform:uppercase;
}
.bh-dt{
  font-size:var(--role-rowtitle-size);
  font-weight:var(--role-rowtitle-weight);
}

/* 6d. Stat numerals on the brief and the store cards (12/800 and 12.5/600). */
.bh-stat,.bh-stat b,.sc-stat,.sc-stat b{
  font-size:var(--role-count-size);
  font-weight:var(--role-count-weight);
}
/* Small print inside the brief and the store health pills. */
.brief-row em,.bh-pill em,.mini-meta{
  font-size:var(--role-rowmeta-size);
  font-weight:var(--role-rowmeta-weight);
}
/* A mini task row on Home is a row like any other. */
.mini-title{
  font-size:var(--role-rowtitle-size);
  font-weight:var(--role-rowtitle-weight);
}

/* 6e. THE THIRD HEADER PATTERN. Roadmap, Finances and Passwords do not use
   .topbar OR Home's greeting block — they render `.page-head` with an <h1>
   at 26/700 and a `.sub` at 13/400. That is 37 call sites in app.js, so this
   one rule is the widest-reaching in the file.

   Counting them up: .topbar (module header), .greet-row (Home) and .page-head
   are THREE different page-heading structures for one job. Unifying their TYPE
   is what this pass can do; unifying their STRUCTURE is a LAYOUT LAW change
   and is deliberately not attempted here. */
.page-head h1{
  font-size:var(--role-pagetitle-size);
  font-weight:var(--role-pagetitle-weight);
  letter-spacing:-.4px;
}
.page-head .sub{
  font-size:var(--role-rowtitle-size);
  font-weight:var(--role-rowtitle-weight);
  color:var(--muted);
}

/* 6f. Section headings inside a page. Ten of these carried an inline
   font-size (15/16/17/18/19px — five sizes for one job) which no stylesheet
   could reach; those were removed from app.js and the margins kept. */
.page section>h2,.page-head+section h2,.card h2{
  font-size:var(--role-cardtitle-size);
  font-weight:var(--role-cardtitle-weight);
  letter-spacing:-.2px;
}

/* 6g. Form controls. A select rendering its options at 12.5/400 is the same
   drift as anything else — inputs are things you READ. */
input,select,textarea,option,.inp{
  font-size:var(--role-rowtitle-size);
  font-weight:var(--role-rowtitle-weight);
  font-family:inherit;
}

/* 6h. .fchip outside a rail is a BUTTON (＋ New item), not a scope row. The
   rail-scoped rule above is two classes and keeps winning inside a rail, so
   the same class reads correctly in both places without a new class name. */
.fchip{
  font-size:var(--role-btn-size);
  font-weight:var(--role-btn-weight);
}

/* 6i. The Roadmap board. Column heads are captions, counts are counts, card
   text is a row and its note is meta — the same four roles as everywhere. */
.gb-col-name{
  font-size:var(--role-cap-size);
  font-weight:var(--role-cap-weight);
  letter-spacing:.08em;text-transform:uppercase;
}
.gb-col-n{
  font-size:var(--role-count-size);
  font-weight:var(--role-count-weight);
}
.gb-card-t,.gb-card-t>span{
  font-size:var(--role-rowtitle-size);
  font-weight:var(--role-rowtitle-weight);
}
.gb-card-d,.gb-empty,.gb-pen{
  font-size:var(--role-rowmeta-size);
  font-weight:var(--role-rowmeta-weight);
}


/* ══ 7. THE KB, DOCUMENTS AND CHECK-OFFS ═══════════════════════════════════

   7a. KB tree. Its nodes are scope rows like any rail's, and its caret was
   9px — the smallest text in the OS by 1.5px and two steps below the ramp. */
.kb-node-label{
  font-size:var(--role-railitem-size);
  font-weight:var(--role-railitem-weight);
}
.kb-caret{
  font-size:var(--role-chip-size);
  font-weight:var(--role-chip-weight);
  line-height:1;
}
.kb-back{
  font-size:var(--role-btn-size);
  font-weight:var(--role-btn-weight);
}

/* 7b. Documents rows. .doc-tag was 10.5/700 — the same off-ramp, over-bold
   treatment the task chips had, in a different module. */
.doc-t,.doc-name{
  font-size:var(--role-rowtitle-size);
  font-weight:var(--role-rowtitle-weight);
}
.doc-s{
  font-size:var(--role-rowmeta-size);
  font-weight:var(--role-rowmeta-weight);
}
.doc-tag{
  font-size:var(--role-chip-size);
  font-weight:var(--role-chip-weight);
}
.doc-tagbtn{
  font-size:var(--role-chip-size);
  font-weight:var(--role-chip-weight);
  line-height:1;
}
/* The file-type glyph is an icon, not text; 19px is its size as a picture. */
.doc-ic{font-size:var(--t-title3);line-height:1}

/* 7c. CHECK-OFFS — the module with NO CLASSES AT ALL.
   Eleven type treatments, six of them inline styles on bare <div>s with no
   class to hook. A stylesheet genuinely cannot reach markup like that, so
   this is the one place in these passes where the fix HAD to be app.js: ten
   class hooks were added (.co-stat-n, .co-stat-l, .co-table, .co-rowsub,
   .co-note, .co-when, .co-summary) and the type was lifted out of the inline
   styles into the rules below. Dynamic colours stayed inline, because those
   are data — a status tone is computed per row and belongs in the markup.

   This is the pattern the rest of the Tools group will need. Writing a
   surface as inline-styled classless divs opts it out of the design system
   entirely, and no amount of CSS can opt it back in. */
.co-stat-n{
  font-size:var(--t-title1);font-weight:800;
  font-family:Poppins,Figtree,sans-serif;   /* display sizes keep the brand face */
  line-height:1.2;
}
.co-stat-l{
  font-size:var(--role-cap-size);
  font-weight:var(--role-cap-weight);
  text-transform:uppercase;letter-spacing:.08em;
  color:var(--muted);margin-top:4px;
}
.co-table{
  font-size:var(--role-rowtitle-size);
  font-weight:var(--role-rowtitle-weight);
}
.co-rowsub,.co-when,.co-summary{
  font-size:var(--role-rowmeta-size);
  font-weight:var(--role-rowmeta-weight);
}
.co-note{
  font-size:var(--role-rowtitle-size);
  font-weight:var(--role-rowmeta-weight);
}
/* Section headings inside check-offs ("Freddy's — Zenput") were classless
   <div style="font-weight:700"> at the page's inherited 14px. They are card
   heads, so they take the card-title role. */
.co-sec-h{
  font-size:var(--role-cardtitle-size);
  font-weight:var(--role-cardtitle-weight);
  letter-spacing:-.2px;
}

/* ══ 8. WORKLOAD AND ADMIN ═════════════════════════════════════════════════

   8a. Workload. Six treatments; the count numeral was 20/800 next to a
   13.5/700 name, so the number shouted over the person it described. */
.wl-name{
  font-size:var(--role-rowtitle-size);
  font-weight:var(--role-rowtitle-weight);
}
.wl-count{
  font-size:var(--t-title3);              /* --t-title3: this IS the headline figure */
  font-weight:700;
}
.wl-caret{
  font-size:var(--role-chip-size);
  font-weight:var(--role-chip-weight);
  line-height:1;
}
.wl-boards .note,.wl-note{
  font-size:var(--role-rowmeta-size);
  font-weight:var(--role-rowmeta-weight);
}

/* 8b. ADMIN — 22 type treatments, the most of any surface in the OS.
   It is the newest big console and it was built entirely without a contract,
   which is what that number measures. Two values are worth naming because
   they are not merely off the ramp, they are not real:

     .adm-sech b   11px / weight 900   — 900 does not exist on either stack
     .ovrtag        8.5px / weight 800 — the SECOND 8.5px in the app; the
                                         other was the task avatar initials

   A browser silently rounds 900 down to the heaviest face it has, so the
   declaration reads as intentional and renders as something else. */
.adm-sub{
  font-size:var(--role-rowtitle-size);
  font-weight:var(--role-rowtitle-weight);
}
.adm-mrs-h,.ws-card>b{
  font-size:var(--role-cardtitle-size);
  font-weight:var(--role-cardtitle-weight);
  letter-spacing:-.2px;
}
.jsm-tab{
  font-size:var(--role-tab-size);
  font-weight:var(--role-tab-weight);
}
.adm-add{
  font-size:var(--role-btn-size);
  font-weight:var(--role-btn-weight);
}
.adm-nm,.adm-mn,.adm-prov b,.adm-mrs>div{
  font-size:var(--role-rowtitle-size);
  font-weight:var(--role-rowtitle-weight);
}
.adm-rl,.adm-pem,.adm-md,.adm-prov span,.ws-card-sub{
  font-size:var(--role-rowmeta-size);
  font-weight:var(--role-rowmeta-weight);
}
/* The People / Groups list heads inside the admin cards. */
.adm-plist-head>b,.adm-card>h3{
  font-size:var(--role-cap-size);
  font-weight:var(--role-cap-weight);
  letter-spacing:.08em;text-transform:uppercase;
}
/* Badges, tags and the avatar monogram all answer to the chip role. */
.adm-you,.lvl-badge,.ovrtag,.adm-fin-chip,.adm-av{
  font-size:var(--role-chip-size);
  font-weight:var(--role-chip-weight);
}
/* Field labels and section heads are captions. */
.adm-field label,.adm-sech,.adm-sech b{
  font-size:var(--role-cap-size);
  font-weight:var(--role-cap-weight);
  letter-spacing:.08em;
}
/* The "KH" monogram in the admin bar is a mark, sized as a graphic. */
/* ⚠️ THIS RULE BROKE THE CONTRACT IT BELONGS TO. I set it to 17/800 in pass
   6, and THE SCALES says weight 800 is banned below 20px — the rule I had
   quoted two passes earlier. Caught by the full-app audit, not by review.
   A mark is a graphic, but it is still type until it is an <svg>. */
.adm-mark{
  font-size:var(--role-cardtitle-size);
  font-weight:var(--role-cardtitle-weight);
  line-height:1;
}


/* ══ 9. ONE HEADER FOR EVERY MODULE — EXCEPT HOME ══════════════════════════
   Chad, 2026-08-14: "Home is OK to be different than the rest of them, but
   the rest of them should have the same look and feel."

   The OS draws page headers three different ways:

     .topbar       Tasks, Calendar, Meetings, Contacts, Notes
     .greet-row    Home only
     .page-head    Roadmap, Finances, Passwords, Check-offs, Forms,
                   Scorecards, Workload — 37 call sites in app.js

   Measured side by side at 1600x950, .topbar vs .page-head:

                    .topbar        .page-head
     top              70              92        22px lower
     height           72              64
     padding        8px 0 0           0
     margin-bottom     8              14
     position       sticky          STATIC      ← scrolls away
     title size       20              22

   The last two are the ones that matter. LAYOUT LAW says the header is
   sticky precisely so "scrolling content can never carry menus away" — and
   half the app never honoured it. And the 22px offset is not the header's
   own doing: `.page` carries `padding:30px 34px 40px` while `.page-flush`
   carries 8. That is the ".page vs .page-flush drift" DESIGN.md lists under
   "gotchas that cost a day", still live and still costing 22px.

   HOME IS DELIBERATELY EXEMPT. .greet-row and .greet-h1 keep their own
   treatment (22/700) on Chad's explicit instruction — it is a landing page,
   not a module, and it earns a different opening. Everything else matches.

   This is still CSS, not surgery: the three structures remain three
   structures. What is unified is every dimension you can see. Collapsing
   them into one shared renderer is the follow-up, and it is a markup change
   with real regression risk — worth doing on its own branch, not smuggled
   into a type pass. */

/* 9a. Kill the frame drift, so a module's header starts at the same y
       whether or not it uses the flush frame. */
.page:not(.page-flush){padding-top:var(--page-flush-top)}

/* 9b. Give .page-head the geometry and the behaviour of .topbar. */
.page-head{
  min-height:calc(var(--topbar-h) + 8px);   /* 8px pad + the 64px band = 72 */
  padding:8px 0 0;
  margin:0 0 8px;
  position:sticky;
  top:0;
  z-index:20;
  background:var(--bg);
  align-items:center;
}

/* 9b-2. ⚠️ THE SUBTITLE GOES, AND THIS IS THE ONE JUDGEMENT CALL HERE.

   With the subtitle stacked under the title, .page-head's h1 landed 15px
   higher than .topbar's — the title could not sit on the same y as every
   other module while a second line shared its band. LAYOUT LAW already
   settled this for .topbar, in Chad's own framing: "A second line (subtitle)
   would make height vary per module, so the subtitle is hidden in the bar."
   The .page-head modules simply never had the rule applied to them.

   So this hides lines like "Cleaning checklists and temperature logs —
   done, due, or missed" on Check-offs, Roadmap, Finances, Workload and
   Passwords. That is real copy disappearing, not just pixels moving, which
   is why it is called out here rather than buried.

   TO REVERT: delete this one rule. The titles then sit 15px low on those
   modules again, and the band grows by the subtitle's height. */
.page-head .sub{display:none}

/* 9c. ONE module title size. .page-head h1 was 22 against .topbar's 20 —
       my own doing in pass 4, when the two families were still allowed to
       differ. They are not any more. Home is the only 22 left. */
.page-head h1{
  font-size:var(--role-modtitle-size);
  font-weight:var(--role-modtitle-weight);
  letter-spacing:-.2px;
}
/* Home keeps its own voice — this is the exemption, stated once, in one
   place, so it reads as a decision rather than a survivor. */
.greet-h1{
  font-size:var(--role-pagetitle-size);
  font-weight:var(--role-pagetitle-weight);
}

/* 9d. AND THERE IS A FOURTH. Support renders `.jsm-brand` — not .topbar, not
   .page-head, not Home's greeting. Its h2 was 22/700. Brought to the module
   title role so Support opens like every other module.

   Four page-heading structures is the real headline of these passes. Each
   one was reasonable when it was written; together they are the reason the
   OS "has no consistency". CSS can make them look identical, which is what
   is happening here — it cannot stop a fifth from being written next week.
   Only one shared renderer can do that. */
.jsm-brand h2{
  font-size:var(--role-modtitle-size);
  font-weight:var(--role-modtitle-weight);
  letter-spacing:-.2px;
}
.jsm-brand p{
  font-size:var(--role-body-size);
  font-weight:var(--role-body-weight);
}


/* ══ 10. SUPPORT, AND THE SHARED VIEW PICKER ═══════════════════════════════
   Support measured 19 treatments — second only to Admin's 22.

   .view-* is the view picker from views.js, SHARED by Meetings, Equipment
   and Tasks, so it drifting drifts three modules at once. It carried four
   type treatments across one button: icon 13/400, label 13.3333/400, caret
   10/400, tick 13/800, plus a 9.5/800 menu caption — the smallest text in
   the OS after the KB caret. A button is one thing; it reads as one size. */
.view-btn,.view-btn .v-icon,.view-btn .vb-label,.view-btn .v-caret,
.view-item,.v-check{
  font-size:var(--role-btn-size);
  font-weight:var(--role-btn-weight);
  line-height:1.2;
}
.vm-cap{
  font-size:var(--role-cap-size);
  font-weight:var(--role-cap-weight);
  letter-spacing:.08em;text-transform:uppercase;
}

/* Ticket rows. .tix-sum was 13.5/600 and .lz 10.5/800 — the same two
   mistakes (half-pixel row title, tiny over-bold status chip) that Tasks and
   Documents each made independently. Third module, same pair. */
.tix-sum{
  font-size:var(--role-rowtitle-size);
  font-weight:var(--role-rowtitle-weight);
}
.tix-meta{
  font-size:var(--role-rowmeta-size);
  font-weight:var(--role-rowmeta-weight);
}
.tix-key,.lz,.chip.wait-age,.wr-lead{
  font-size:var(--role-chip-size);
  font-weight:var(--role-chip-weight);
}
.wr-n,.wr-n b{
  font-size:var(--role-count-size);
  font-weight:var(--role-count-weight);
}
.jsm-howto{
  font-size:var(--role-btn-size);
  font-weight:var(--role-btn-weight);
}
.filter-label{
  font-size:var(--role-cap-size);
  font-weight:var(--role-cap-weight);
  letter-spacing:.08em;text-transform:uppercase;
}
/* The ticket-type glyph is a picture, sized as one. */
.rt-icon{font-size:var(--t-headline);line-height:1}


/* ══ 10b. MODALS — the Create surface, app-wide ════════════════════════════
   Per LAYOUT LAW pop-ups exist for CREATE and nothing else, which makes the
   modal one of the most-seen surfaces in the OS — and it had never been
   measured. Its heading was 18/700 and its lede 14/400: both off the ramp,
   and 18 is the fourth distinct "heading" size after 17, 20 and 22.

   A modal is a card that floats. It takes the card roles. */
.modal>h2,.modal>h3,.modal-title{
  font-size:var(--role-cardtitle-size);
  font-weight:var(--role-cardtitle-weight);
  letter-spacing:-.2px;
}
.modal>p,.modal-sub{
  font-size:var(--role-body-size);
  font-weight:var(--role-body-weight);
  line-height:1.5;
}

/* ══ 10c. THE FORMS BUILDER — and a FIFTH header ═══════════════════════════
   The builder is a full-screen editor from formsbuilder.js with its own
   `.fb-bar`. That is the fifth way this app draws the top of a screen, after
   .topbar, .greet-row, .page-head and .jsm-brand.

   It is treated as an EDITOR rather than a module — like the Documents
   editor, it legitimately carries a two-line bar (form name over workspace ·
   field count) because you need to know what you are editing. So it keeps
   its shape and only moves onto the ramp. That is a documented exception,
   which is what the contract is for; an undocumented one is drift.

   11 treatments, 8 of them off the ramp: 16/600, 11.5/400, 12.5/700, 10/800,
   11.5/600, 13/700, 12/700. */
.fb-bar-title{
  font-size:var(--role-cardtitle-size);
  font-weight:var(--role-cardtitle-weight);
  letter-spacing:-.2px;
}
.fb-bar-sub{
  font-size:var(--role-rowmeta-size);
  font-weight:var(--role-rowmeta-weight);
}
.fb-switch button,.fb-ins-tabs button{
  font-size:var(--role-tab-size);
  font-weight:var(--role-tab-weight);
}
/* The inspector's Field / Form tabs are tabs like the Build / Preview pair
   beside them — they were 13/700 while those were 12.5/700. */
.fb-palette-note{
  font-size:var(--role-rowmeta-size);
  font-weight:var(--role-rowmeta-weight);
}
.fb-palette-h{
  font-size:var(--role-cap-size);
  font-weight:var(--role-cap-weight);
  letter-spacing:.08em;text-transform:uppercase;
}
.fb-tile-l{
  font-size:var(--role-chip-size);
  font-weight:var(--role-chip-weight);
}
.fb-onsubmit-t{
  font-size:var(--role-rowtitle-size);
  font-weight:var(--role-rowtitle-weight);
}
.fb-onsubmit-d,.fb-onsubmit-d b{
  font-size:var(--role-rowmeta-size);
  font-weight:var(--role-rowmeta-weight);
}

/* ══ 10d. STORE AND DEPARTMENT WORLDS — a SIXTH header ═════════════════════
   Every store (CSC Houma, FFC Thibodaux…) and every department (Exec, Ops,
   Marketing) renders `.hub-head`: a .hub-kicker eyebrow over an h1 over a
   .hub-sub. That is the sixth way this app opens a screen, and it is the one
   Chad's team sees most — it is the landing page of a store's whole world.

   Nine treatments, six off the ramp: 10/800 kicker, 28/800 title, 13/400
   sub, 19/800 stat, 18/500 prose, 12.5/400 empty state.

   Brought to the same shape as .page-head, per Chad 2026-08-14: "the rest of
   them should have the same look and feel." Home stays exempt; a store world
   is a module, not the front door. */
.hub-head h1{
  font-size:var(--role-modtitle-size);
  font-weight:var(--role-modtitle-weight);
  letter-spacing:-.2px;
}
.hub-kicker,.hbm-head{
  font-size:var(--role-cap-size);
  font-weight:var(--role-cap-weight);
  letter-spacing:.08em;text-transform:uppercase;
}
/* Same call as .page-head's subtitle: a second line in the title block puts
   the title on a different y from every other module. Delete this one rule
   to bring the store's city back. */
.hub-sub{display:none}
.hub-stat,.hub-stat b{
  font-size:var(--t-title3);font-weight:700;      /* headline figure, as in Workload */
}
.hb-prose p{
  font-size:var(--role-body-size);
  font-weight:var(--role-body-weight);
  line-height:1.5;
}
.hbm-empty{
  font-size:var(--role-rowmeta-size);
  font-weight:var(--role-rowmeta-weight);
}

/* ══ 10e. TABLES — one set of roles for every table in the OS ══════════════
   Found on Cash & Deposits, but tables are everywhere: P&L, Check-offs,
   Meetings' Table view, the admin console. They had no roles at all, so each
   one invented its own: th 11.5/700, totals 14.5/800, section head 13/700,
   its <small> 10.8333/700 (an em-derived value, which is why no px sweep
   would ever have found it).

   A table header is a caption. A cell is a row. A totals row is a row that
   is emphasised by WEIGHT, not by growing two sizes larger than the data it
   totals — that was 14.5/800 against 13px cells. */
/* `[class] th` rather than `thead th`, and rather than a list. Every table
   in the app scopes its own rules by class — .cr-table th, .admin-table th,
   .cd-table th, .bon-table th, .mtg-table th, .fa-tbl th, .amx th … twelve of
   them and counting — and a class beats a bare element selector, so
   `thead th` lost everywhere. `[class] th` has the SAME specificity as
   `.cr-table th` (0,1,1) and roles.css loads last, so it wins the tie
   generically. Enumerating the twelve would be a list that goes stale the
   moment someone adds a thirteenth; this matches their shape instead.
   Same lesson as `.chip.chip` in pass 8. */
[class] th{
  font-size:var(--role-cap-size);
  font-weight:var(--role-cap-weight);
  letter-spacing:.08em;text-transform:uppercase;
  color:var(--muted);
}
[class] td{
  font-size:var(--role-rowtitle-size);
  font-weight:var(--role-rowtitle-weight);
}
/* Totals must out-specify [class] td, so they carry the class level too. */
[class] tfoot td,tr.cr-tot td,tr.total td{
  font-size:var(--role-rowtitle-size);
  font-weight:var(--role-cap-weight);      /* emphasis by weight, not size */
}
/* Section heads inside a table page, and their inline qualifier. */
.cash-h2{
  font-size:var(--role-cardtitle-size);
  font-weight:var(--role-cardtitle-weight);
  letter-spacing:-.2px;
}
.cash-h2 small{
  font-size:var(--role-cap-size);
  font-weight:var(--role-cap-weight);
}
.cr-note,.cr-note b{
  font-size:var(--role-rowmeta-size);
  font-weight:var(--role-rowmeta-weight);
}
/* A figure quoted inline in a sentence ("0 slips, 0 unmatched"). 15 is on
   the ramp; 700 was not in the weight set at that size. */
.cr-summary b{
  font-size:var(--role-body-size);
  font-weight:var(--role-cap-weight);
}

/* ══ 10f. ITEM DETAIL — task, ticket, event, person ════════════════════════
   The pane you open when you click a row. 15 treatments, and TWO of them at
   weight 650 — the value DESIGN.md names as a bug and says to delete on
   sight, because 650 exists on neither font stack. It had been sitting in
   the most-opened pane in the app: .id-reach-who at 12.5/650 and .rel-n at
   11/650.

   ONE DETAIL TITLE. .id-title was 22/800 while the admin person pane's h2
   was 17/700 and a meeting's was its own thing again. A detail pane is a
   detail pane whatever it contains, so all three take the module-title role.
   (.adm-ph h2 is moved out of the card-title rule above to join them —
   pass 6 put it there when detail panes had no role of their own.) */
.id-title,.adm-ph h2,.meet-page .mm-head h2{
  font-size:var(--role-modtitle-size);
  font-weight:var(--role-modtitle-weight);
  letter-spacing:-.2px;
}
.id-label,.rel-head{
  font-size:var(--role-cap-size);
  font-weight:var(--role-cap-weight);
  letter-spacing:.08em;text-transform:uppercase;
}
/* The description is prose — it was 13.5/400, the only 13.5 left in the app. */
.id-text{
  font-size:var(--role-body-size);
  font-weight:var(--role-body-weight);
  line-height:1.5;
}
/* The progress stages read as tabs, because that is what they are. */
.id-stage{
  font-size:var(--role-tab-size);
  font-weight:var(--role-tab-weight);
}
/* An update: who said it, when, and what. */
.act-bubble>b{
  font-size:var(--role-rowtitle-size);
  font-weight:var(--role-cap-weight);
}
.act-bubble>div,.id-reach-who,.rel-t{
  font-size:var(--role-rowtitle-size);
  font-weight:var(--role-rowtitle-weight);
}
.id-empty,.rel-s{
  font-size:var(--role-rowmeta-size);
  font-weight:var(--role-rowmeta-weight);
}
.lc-act,.rel-n{
  font-size:var(--role-chip-size);
  font-weight:var(--role-chip-weight);
}

/* ══ 10g. MEETING DETAIL AND THE MEETINGS RAIL BUCKETS ═════════════════════
   12 treatments. The meeting rail's date buckets (Last week / Earlier this
   month / Older) had their own three: caret 10/700, label 11.5/700, row date
   11/400 — a fourth variety of rail row on top of the three already fixed. */
.mtg-blabel,.notes-priv{
  font-size:var(--role-cap-size);
  font-weight:var(--role-cap-weight);
  letter-spacing:.08em;
}
.mtg-bcaret{
  font-size:var(--role-chip-size);
  font-weight:var(--role-chip-weight);
  line-height:1;
}
.mtg-row-d,.mtg-storelab{
  font-size:var(--role-rowmeta-size);
  font-weight:var(--role-rowmeta-weight);
}
.mm-back{
  font-size:var(--role-btn-size);
  font-weight:var(--role-btn-weight);
}
/* .btn-xs is a deliberate small-button variant, but 11.5 is not a size.
   It takes the chip role — the smallest legal step — rather than a size of
   its own invention. */
/* `.btn.btn-xs` in styles.css is two classes; a bare .btn-xs loses to it. */
.btn.btn-xs{
  font-size:var(--role-chip-size);
  font-weight:var(--role-chip-weight);
}
/* The notes textarea is prose you write, so it is prose you read. */
/* .notes-area is set FOUR times in styles.css — bare, .field textarea,
   .mtg-doc .notes-area (15px) and .meet-page .notes-area (14px) — so the same
   textarea renders at a different size depending on which screen opened it.
   Matched at the two-class level so one rule covers every entry point. */
.meet-page .notes-area,.mtg-doc .notes-area,.field .notes-area{
  font-size:var(--role-body-size);
  font-weight:var(--role-body-weight);
  line-height:1.5;
}

/* ══ 10h. EQUIPMENT ════════════════════════════════════════════════════════
   10 treatments — and WEIGHT 650 TWICE MORE (.eq-value-name 13/650,
   .eq-flag 11.5/650). That makes four instances of a weight that exists on
   neither font stack, in a codebase whose own DESIGN.md says to delete it on
   sight. Two were in the task detail pane, two are here. It is not a typo
   someone made once; it is a value that got copied.

   Also another em-derived size: .eq-card-h b at 13.3333px, the same shape as
   the nav rail's before pass 1. No sweep over px literals can see these,
   which is the argument for measuring the rendered page rather than the
   source. */
.eq-value-name,.eq-card-h b,.eq-manual-n{
  font-size:var(--role-rowtitle-size);
  font-weight:var(--role-rowtitle-weight);
}
.eq-value-n{
  font-size:var(--role-cardtitle-size);
  font-weight:var(--role-cardtitle-weight);
}
.eq-ser,.eq-est-line,.eq-manual-m{
  font-size:var(--role-rowmeta-size);
  font-weight:var(--role-rowmeta-weight);
}
.eq-flag{
  font-size:var(--role-chip-size);
  font-weight:var(--role-chip-weight);
}
.eq-manual-i{font-size:var(--role-body-size);line-height:1}

/* ══ 10i. CALENDAR WEEK / DAY — the time grid ══════════════════════════════
   DESIGN.md lists the .tg-* time grid under "what replacing it costs: real
   rebuild, working, and hard to get right". Nothing structural is touched
   here — only type, which the same entry calls cheap to restyle.

   THE DAY NUMBER CHANGES SIZE WHEN IT IS TODAY: 19/700 normally, 15/700 in
   .tg-day.today. That is the same bug as Notes' selected notebook in pass 3 —
   state altering TYPE rather than colour — and it is why the week header
   looked subtly uneven on any week containing today. Both states now take one
   size; "today" is carried by colour, as everywhere else.

   The hour axis was 9.5/700 and an event's time 9.5/600 — the joint-smallest
   text in the OS alongside the Chart Guide's old 9.5 menu caption. */
.tg-dhead b,.tg-day.today .tg-dhead b{
  font-size:var(--role-modtitle-size);
  font-weight:var(--role-modtitle-weight);
}
.tg-hr{
  font-size:var(--role-cap-size);
  font-weight:var(--role-cap-weight);
  letter-spacing:.04em;
}
.tg-ev b{
  font-size:var(--role-chip-size);
  font-weight:var(--role-chip-weight);
}
.tg-ev span{
  font-size:var(--role-rowmeta-size);
  font-weight:var(--role-rowmeta-weight);
}

/* ══ 10j. THE KB ARTICLE — the only long-form PROSE in the OS ══════════════
   Everything else in this file is interface. A KB article is a document: the
   body inside `.kb-body` is rendered content, so its h1/h2/p/strong come from
   the content itself rather than from a component, and it needs a reading
   hierarchy rather than a UI one.

   12 treatments, and WEIGHT 900 for the second time (`th strong` at 11/900,
   after .adm-sech b in Admin). 900 exists on neither font stack — the browser
   rounds it down, so the source claims one thing and the screen shows
   another. Two independent authors reached for it, which is what happens
   when nothing says what the weights are.

   Body text is --t-body (15) with a 1.6 measure — the one place in the OS
   where the ramp's 15 is doing the job it was designed for. */
.kb-doc-head h2{
  font-size:var(--role-modtitle-size);
  font-weight:var(--role-modtitle-weight);
  letter-spacing:-.2px;
}
.kb-badge{
  font-size:var(--role-chip-size);
  font-weight:var(--role-chip-weight);
}
.kb-upd{
  font-size:var(--role-rowmeta-size);
  font-weight:var(--role-rowmeta-weight);
}
.kb-dot{
  font-size:var(--role-railitem-size);
  font-weight:var(--role-railitem-weight);
}
/* The article's own heading hierarchy — two levels, both on the ramp. */
.kb-body h1{
  font-size:var(--role-pagetitle-size);
  font-weight:var(--role-pagetitle-weight);
  letter-spacing:-.4px;line-height:1.25;
}
.kb-body h2,.kb-body h3{
  font-size:var(--role-cardtitle-size);
  font-weight:var(--role-cardtitle-weight);
  letter-spacing:-.2px;
}
.kb-body p,.kb-body li,.kb-body a{
  font-size:var(--role-body-size);
  font-weight:var(--role-body-weight);
  line-height:1.6;
}
.kb-body strong,.kb-body b,.kb-body th strong{
  font-weight:var(--role-cap-weight);      /* kills the 900 */
}

/* ══ 10k. DOCUMENTS EDITOR — a SEVENTH top-of-screen structure ═════════════
   `.gdoc-bar`, after .topbar, .greet-row, .page-head, .jsm-brand, .fb-bar and
   .hub-head. Like the forms builder it is an EDITOR, so it legitimately shows
   what you are editing in its bar; it moves onto the ramp and keeps its shape.

   Its outline rail is an eighth rail variety: caption 10.5/800, empty note
   11.5/400. Same two roles as every other rail.

   ⚠️ ONLY THE CHROME IS MEASURED. preview-local.sh copies document RECORDS
   but leaves the Google Doc bodies in Drive, so the canvas renders empty
   here. The editor's document type — the .tt-page / ProseMirror styles that
   tokens.css section 4 describes — is UNVERIFIED and needs measuring against
   a real document. */
.gdoc-title{
  font-size:var(--role-cardtitle-size);
  font-weight:var(--role-cardtitle-weight);
  letter-spacing:-.2px;
}
.gdoc-live{
  font-size:var(--role-chip-size);
  font-weight:var(--role-chip-weight);
}
.nd-out-head span{
  font-size:var(--role-cap-size);
  font-weight:var(--role-cap-weight);
  letter-spacing:.08em;text-transform:uppercase;
}
.nd-out-head button{
  font-size:var(--role-chip-size);
  font-weight:var(--role-chip-weight);
}
.nd-out-empty,.nd-out-list a{
  font-size:var(--role-rowmeta-size);
  font-weight:var(--role-rowmeta-weight);
}

/* ══ 10l. THE MEETINGS TABLE ROWS ══════════════════════════════════════════
   .mt-st at 11.5/650 — the FIFTH instance of weight 650, in a module twice
   reported clean by per-module checks. It sits in the Table view, which the
   rail-and-content sweeps never opened. */
.mt-time,.mt-store,.mt-att{
  font-size:var(--role-rowmeta-size);
  font-weight:var(--role-rowmeta-weight);
}
.mt-st{
  font-size:var(--role-chip-size);
  font-weight:var(--role-chip-weight);
}

/* ══ 10m. THE MEETINGS HUB — found only on PRODUCTION data ═════════════════
   Chad, 2026-08-17: after the roles work went live I audited the real app in
   his browser. Nine of eleven modules were clean. Meetings was not, and the
   reason matters more than the fix:

   THE LOCAL PREVIEW HAD ZERO UPCOMING MEETINGS. The whole .ag-* agenda and
   the rail's meeting buckets simply do not render without data, so fourteen
   passes of local measurement never saw them. This is the same trap as
   Scorecards measuring "clean" while empty — flagged in an earlier commit,
   and then walked straight into. An empty surface is an UNMEASURED surface.

   Six violations, including a SIXTH weight-650 (.ag-time 12.5/650) after I
   had reported 650 eliminated from the app. It was eliminated from every
   screen I could see. */
.ag-dlabel,.mtg-side-clear{
  font-size:var(--role-cap-size);
  font-weight:var(--role-cap-weight);
  letter-spacing:.08em;
}
.mtg-row-t{
  font-size:var(--role-rowtitle-size);
  font-weight:var(--role-rowtitle-weight);
}
/* ⚠️ .ag-title DID NOT carry the row role — I assumed it did, and the audit
   could not contradict me because of how the audit works.
   components.css sets `.ag-title{font-size:var(--t-footnote);font-weight:600}` and no
   roles rule ever named it. The glyph <span> inside it was inheriting that
   13.5, so pointing the span at `inherit` inherited the wrong value.

   WHY THE AUDIT MISSED IT: the sweep only measures LEAF elements (a node with
   children is skipped, because its computed size describes its text-less box
   rather than any visible text). .ag-title has a child span, so it was never
   measured directly — only its child was, which then read as "a span with a
   bad size" instead of "a title with a bad size". A container's own type is
   invisible to a leaf-only scan. Worth remembering before trusting a clean
   audit: it proves the LEAVES are on the ramp, not every element. */
.ag-title{
  font-size:var(--role-rowtitle-size);
  font-weight:var(--role-rowtitle-weight);
}
.ag-title span,.ag-title>*{
  font-size:inherit;
  font-weight:inherit;
}
.ag-time,.ag-who{
  font-size:var(--role-rowmeta-size);
  font-weight:var(--role-rowmeta-weight);
}

/* ══ 10n. THE LAST THREE, found by a STRICTER live audit ═══════════════════
   After .ag-title, I rewrote the sweep to measure any element that OWNS
   visible text rather than only leaf nodes. That immediately surfaced three
   more that the leaf-only scan had been blind to:

     .l-store-btn  14/400    the store button on a task row (Tasks)
     .bh-slack     11.5/700  "Customize" / "Post to #todo" (Home brief)
     .ht-badge     11.5/700  the count badge on a hub tile

   All three own their text directly and had no child, which means the leaf
   scan SHOULD have caught them — it did not, because it also required the
   element to have no element children, and each of these wraps a glyph.
   Two separate conditions, one blind spot each. */
.l-store-btn,.l-store{
  font-size:var(--role-rowmeta-size);
  font-weight:var(--role-rowmeta-weight);
}
.bh-slack,.ht-badge,.bh-pill,.hbm-tag{
  font-size:var(--role-chip-size);
  font-weight:var(--role-chip-weight);
}
/* .bh-pill (Home's store health chips, 12.5/600) and .hbm-tag (the "Overdue"
   tag on a hub's must-happen row, 10.5/700) are the last two the strict audit
   found. Both are chips; neither was on any earlier list because both sit on
   screens that only render with real data — Home's health row needs stores
   with open work, and the must-happen row needs something overdue. */

/* ══ 11. THE CHART GUIDE ═══════════════════════════════════════════════════
   Eight treatments across its own .cg-* family: 18/700, 15.5/700, 12.5/400,
   10.5/800, 10/700 — five of eight off the ramp.

   Worth getting right rather than leaving as a curiosity: Chad wants this
   moved out of the Tools group and into Reports as part of the report
   builder's toolbox. Whatever it looks like becomes part of Reports, so it
   should already be speaking the contract when it gets there.

   Its two heading levels are kept as two — a section head above a grid of
   chart cards is a genuine hierarchy, not drift. They just move onto the
   ramp: 18 -> 20 (section) and 15.5 -> 17 (card). */
.chart-glossary .cg-section-head h2{
  font-size:var(--role-modtitle-size);
  font-weight:var(--role-modtitle-weight);
  letter-spacing:-.2px;
}
.chart-glossary .cg-title{
  font-size:var(--role-cardtitle-size);
  font-weight:var(--role-cardtitle-weight);
  letter-spacing:-.2px;
}
.chart-glossary .cg-q{
  font-size:var(--role-rowtitle-size);
  font-weight:var(--role-rowtitle-weight);
}
.chart-glossary .cg-tag,.chart-glossary .cg-chip,.chart-glossary .cg-ic-what,.chart-glossary .cg-ic-when,.chart-glossary .cg-ic-why{
  font-size:var(--role-chip-size);
  font-weight:var(--role-chip-weight);
}
/* `.cg-field b` in styles.css is two classes plus an element, so the
   class-only rule above loses to it. Matched at the same shape rather than
   escalating — escalation is what built the pile-up in the first place. */
.chart-glossary .cg-field b{
  font-size:var(--role-chip-size);
  font-weight:var(--role-chip-weight);
}
.chart-glossary .cg-foot,.chart-glossary .cg-field{
  font-size:var(--role-rowmeta-size);
  font-weight:var(--role-rowmeta-weight);
}

/* ── Table reports (2026-08-18) ──────────────────────────────────────────────
   Sizes come from the role tokens, never from literals — a table is rows of
   text and a header, which are the row-title and caption roles by another
   name. THE SCALES: no new sizes were invented here. */
.rep-tbl-meta{
  font-size:var(--role-rowmeta-size);font-weight:var(--role-rowmeta-weight);
  color:var(--ink-soft);margin:0 0 8px;
}
/* Wide tables scroll INSIDE this box. Without it a twelve-column Equipment
   report makes the whole page scroll sideways, which DESIGN.md forbids. */
.rep-tbl-wrap{overflow-x:auto;border:1px solid var(--line-soft);border-radius:12px;background:var(--card)}
.rep-tbl{border-collapse:collapse;width:100%;min-width:max-content}
.rep-tbl th,.rep-tbl td{
  text-align:left;padding:8px 12px;white-space:nowrap;
  border-bottom:1px solid var(--line-soft);
}
.rep-tbl th{
  font-size:var(--role-cap-size);font-weight:var(--role-cap-weight);
  color:var(--ink-soft);text-transform:uppercase;letter-spacing:.04em;
  position:sticky;top:0;background:var(--card);z-index:1;
}
.rep-tbl td{font-size:var(--role-rowtitle-size);font-weight:400}
.rep-tbl tbody tr:last-child td{border-bottom:none}
.rep-tbl .rt-r{text-align:right;font-variant-numeric:tabular-nums}

/* Builder controls */
.tcol-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:4px 12px}
.tcol{display:flex;align-items:center;gap:6px;
  font-size:var(--role-railitem-size);font-weight:var(--role-railitem-weight)}
.tfil-row{display:flex;gap:8px;align-items:center;margin-bottom:6px;flex-wrap:wrap}
.tfil-row .tf-col,.tfil-row .tf-op{min-width:130px}
.tfil-row .tf-val{flex:1;min-width:120px}
.modal-wide{max-width:680px}

/* Type picker */
.rtype-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:10px;margin:6px 0 4px}
.rtype-card{
  display:flex;flex-direction:column;align-items:flex-start;gap:4px;text-align:left;
  padding:14px;border:1px solid var(--line-soft);border-radius:12px;
  background:var(--card);cursor:pointer;
}
.rtype-card:hover{border-color:var(--accent);background:var(--bg)}
.rtype-card b{font-size:var(--role-cardtitle-size);font-weight:var(--role-cardtitle-weight)}
.rtype-glyph{font-size:var(--mark-inline);line-height:1}
.rtype-desc{font-size:var(--role-rowmeta-size);font-weight:var(--role-rowmeta-weight);color:var(--ink-soft)}
