/* ═══════════════════════════════════════════════════════════════════════════
   PORTAL — V10 "EVORRAH SIGNATURE" SKIN
   Added 2026-08-20. Owner: "redo the client portal with this branding and V10",
   pointing at brand/showcase.html#type and V10 of
   docs/CLIENT_PORTAL_REDESIGN_10_DIRECTIONS.html.

   WHAT THIS IS
   A skin, not a rewrite. It changes how the portal LOOKS and nothing about what
   it does: no markup moved, no module edited, no workflow touched. It loads
   AFTER app/styles/portal.css and restyles what is already there.

   WHY A SEPARATE FILE RATHER THAN EDITING portal.css
   portal.css is 5,000+ lines and is being edited by several concurrent sessions
   right now (the Crimson Pro / palette migration is mid-flight in this very
   tree). A separate layer is revertible by deleting one <link>, cannot collide
   with their hunks, and keeps the diff readable.

   ⚠ SPECIFICITY: portal-rooms.js INJECTS its stylesheet into <head> at boot, so
   it lands AFTER this file and wins ties. Every rule here that targets a
   .rooms / .td- / .g360 selector is therefore prefixed with `body` to carry one
   extra element of specificity. Do not "tidy" those prefixes away.

   ── THE TWO SOURCES THIS IMPLEMENTS ────────────────────────────────────────

   A · BRAND LAW (brand/brand.json, brand/showcase.html §07 type, §08 signature)
       Three faces, one job each:
         Crimson Pro       — display. Headlines, the house voice. 300–600, −0.01em.
         Libre Baskerville — titles. Card and section titles at 400.
         Montserrat        — ALL UI: labels, body, buttons. 200–600.
         (mono is machine values only and appears nowhere a client reads.)
       The five signatures:
         1 the plate frame — 0.5px hairline, inset 14px, on hero surfaces
         2 the turn        — one roman→bloodstone-italic turn per headline
         3 the tracked label — 0.62rem / 0.24em / uppercase / sageDeep
         4 square corners  — radius 0, 2px inputs, 50% circles. Nothing else.
         5 ivory ground    — Old Lace #FBF7EF; Platinum #F4F7F3 is the only tint
       Forbidden, and enforced below: gradients · drop shadows as decoration ·
       pure #FFF surfaces · #000 · any fourth typeface.
       Rules are sageDeep, not grey: 0.15 quiet, 0.30 for an edge.
       Colour ratio: ivory 70 · onyx 18 · sageDeep 8 · bloodstone 3 · gold <1.
       Motion: one curve, cubic-bezier(.22,.61,.36,1); reveals, never bounces,
       never travels more than 12px; reduced motion collapses to opacity 120ms.

   B · V10 (docs/CLIENT_PORTAL_REDESIGN_10_DIRECTIONS.html)
       · ruled bands instead of cards — a top hairline and air do a card's job
       · the display face carries the event, the UI face carries the work
       · tabular numerals everywhere a number can change
       · gold appears exactly twice: approval, and delivery
       · the journey rail — stage marks on the room nav, so the client always
         knows where the event is in its life (stamped by portal-v10-journey.js;
         this file only styles the marks it writes)
   ═══════════════════════════════════════════════════════════════════════════ */

:root{
  /* The three faces. Named here so a future face change is one line, not 600. */
  --v10-display:'Crimson Pro',Georgia,'Times New Roman',serif;
  --v10-title:'Libre Baskerville',Georgia,serif;
  --v10-ui:'Montserrat',-apple-system,'Helvetica Neue',Arial,sans-serif;

  /* Rules are the quiet layer at two strengths — chapter 8.8, AINS row 24-a. They were sageDeep
     at .15 and .30, and portal-v15.css's body[data-active-tab] block already repoints all three
     onto the Onyx steps. THIS LAYER IS THE ONE UNDERNEATH THAT: row 24-b's finding is that the
     whole Portal's chapter-08 paint hangs on one attribute in one tag, and a :root that still
     holds the retired palette is a trapdoor back to it rather than a floor. The values now AGREE
     with the rebind instead of contradicting it, so losing the attribute costs the room's layout
     and not its palette. Non-text: a hairline carries no meaning, so §1.4.11 does not apply —
     measured against Linen, onyx-12 reads 1.28 where sage .15 read 1.27, the same edge. */
  --v10-rule:var(--ch8-onyx-12);
  --v10-rule-edge:var(--ch8-onyx-20);
  /* The same hairline on an onyx ground: Linen at 26%, which is what the rebind writes. */
  --v10-rule-ink:color-mix(in srgb, var(--ch8-linen) 26%, transparent);

  --v10-ease:cubic-bezier(.22,.61,.36,1);
  --v10-quick:200ms; --v10-base:300ms; --v10-considered:500ms;

  /* Structural rhythm only — component interiors may use any 2px multiple. */
  --v10-band:32px;      /* air above a ruled band's content */
  --v10-band-gap:48px;  /* between bands */
}

/* ─────────────────────────────────────────────────────────────────────────
   1 · THE DISPLAY VOICE
   Crimson Pro was already being downloaded by client-portal.html and used by
   NOTHING: measured 2026-08-20, portal.css carried 471 Montserrat and 146 Libre
   Baskerville declarations and zero Crimson Pro. The house voice was absent from
   the one surface a paying client spends its time in. This is that fix.

   Libre Baskerville KEEPS the card and section titles — that is its job in the
   showcase, and taking it would flatten two levels into one.
   ───────────────────────────────────────────────────────────────────────── */
body .nsc-title,
body #tab-today .td-reg,
body .rhb-title, body .dsh-title, body .ds-hero-title, body .ds-hero-h1,
body .glance-event-name,
body h1.portal-h1, body .portal-hero-title,
body .tab-panel > h1,
body .bb-hero-title, body .sec-hero h1,
/* the login headline carries the turn ("your portal." in bloodstone italic),
   and a turn only belongs inside a display heading — so it speaks in the
   display voice too (added 2026-08-21, PBA render sweep) */
body .login-title,

/* ⚠ THE FOUR PAGE TITLES THIS LIST MISSED — added 2026-08-21 (portal formatting
   pass). This is not a new idea; it is the migration above finishing.

   WHAT WAS MEASURED. Rendered at 1440px, the h2 that titles each room:

     room        class              face                 weight
     concept     .ds-hero-h1        Crimson Pro          300   ✓ in the list
     designs     .ds-hero-h1        Crimson Pro          300   ✓ in the list
     briefing    .bsp-hero-title    Libre Baskerville    400   ✗ missing
     rsvp        .rsvp-hero-title   Libre Baskerville    400   ✗ missing
     suite       .rhb-heading       Libre Baskerville    400   ✗ missing
     billing     .billing-title     Libre Baskerville    400   ✗ missing
     my-events   .billing-title     Libre Baskerville    400   ✗ missing

   Same tag, same 43.2px, ONE structural role — the page's own title — rendered
   in two different faces at two different weights depending on which room the
   client happened to open. This file's own header says which is right: "Crimson
   Pro — display. Headlines, the house voice… Libre Baskerville KEEPS the card
   and section titles." A room's title is a headline.

   ⚠ THIS WAS A MIS-AIMED LIST, NOT A DELIBERATE TWO-FACE DESIGN, and that is
   measurable rather than a matter of opinion. Of the thirteen selectors above,
   EIGHT match nothing at all in the portal — .rhb-title, .dsh-title,
   .ds-hero-title, .glance-event-name, .portal-h1, .portal-hero-title,
   .bb-hero-title, .sec-hero. The suite hero's real class is .rhb-HEADING; the
   list reaches for .rhb-TITLE and misses by one word. The list was written
   against selectors that do not exist while the four that do went untouched.

   They are left in place rather than deleted: they cost nothing at runtime, and
   a name that is dead in the portal today may be live in markup a later session
   adds. The point of writing them out here is that the next person can see which
   half of this list is load-bearing without re-deriving it.

   ⚠ AND THIS IS THE STANDING HAZARD OF AN ENUMERATED SELECTOR LIST — the same
   failure this repo already recorded in app/styles/tokens.css about
   prefers-reduced-motion: "correct on the day it is written and silently wrong
   after the next component lands." A new room with a new hero class will drift
   again, and only a rendered check catches it. That check now exists:
   `node scripts/portal-format-shots.mjs` prints HEADING SCALE IN FORCE, and a
   second face appearing at page-title size is visible in one line of its
   output. */
body .bsp-hero-title,
body .rsvp-hero-title,
body .rhb-heading,
body .billing-title{
  font-family:var(--v10-display)!important;
  font-weight:300;
  letter-spacing:-.01em;
  line-height:1.08;
}

/* ⚠ ONE GAP UNDER A PAGE TITLE, NOT FIVE. Added 2026-08-21 (portal formatting
   pass). Every room's title is the same structural element doing the same job —
   naming the page — and the air beneath it was a different size in each room:

     room                 class              margin-bottom
     concept / designs    .ds-hero-h1        18px
     suite                .rhb-heading       16px
     billing / my-events  .billing-title     12px
     briefing             .bsp-hero-title    10px
     rsvp                 .rsvp-hero-title    8px

   Nothing distinguishes these rooms in a way that a 10px difference in leading
   air could express; the five values are simply five sessions' worth of
   independent decisions. --sp-2 (16px, app/styles/tokens.css) is the value on
   the shared scale nearest the cluster and is the one .rhb-heading already
   used, so this moves four classes and leaves the fifth where it was. */
body .ds-hero-h1,
body .bsp-hero-title,
body .rsvp-hero-title,
body .rhb-heading,
body .billing-title{
  margin-bottom:var(--sp-2);
}

/* ⚠ ONE PAGE-TITLE SIZE ON A PHONE, NOT FOUR. Added 2026-08-26 (1000/1000 UX/UI
   pass). The face and the air under these titles were unified above on
   2026-08-21 — but only .rsvp-hero-title had a mobile step (portal.css:1207,
   1.75rem at ≤700px). Measured live at 390px: Guests and Experience titled at
   28px while Design and Files titled at 38.4px — the SAME structural element,
   ten pixels apart depending on the room. This gives the other four classes the
   step .rsvp-hero-title already has, at the same breakpoint, so all five rooms
   agree at every width (43.2px desktop / 28px phone, both measured). */
@media(max-width:700px){
  body .ds-hero-h1,
  body .bsp-hero-title,
  body .rhb-heading,
  body .billing-title{
    font-size:1.75rem;
  }
}

/* The turn — signature 2. One roman-to-bloodstone-italic turn per headline.
   Applied only to an <em> inside a display heading, which is where the house
   writes it; it is never decoration on arbitrary emphasis. */
body .nsc-title em,
body .tab-panel > h1 em,
body .portal-hero-title em{
  font-style:italic;
  color:var(--bloodstone,#B02A45);
  font-weight:300;
}

/* Numbers that can change are tabular, always. A count that shifts its own
   column width as guests reply reads as instability. */
body .hr-num, body .gs-cell-val, body .gs-cal-day, body .gs-cal-countdown,
body .rsvp-chip-n, body .td-stats b, body .bb-rev-counter,
body .headcount-lead strong, body .hc-num{
  font-variant-numeric:tabular-nums lining-nums;
  font-feature-settings:"tnum" 1,"lnum" 1;
}

/* ─────────────────────────────────────────────────────────────────────────
   2 · THE TRACKED LABEL — signature 3
   0.62rem / 0.24em / uppercase / sageDeep, above every block. The portal
   already sets small uppercase labels in muted INK; sageDeep is what the house
   actually does (measured: 381 borders and every marketing label), and it is
   the single change that most makes these screens read as Evorrah.
   ───────────────────────────────────────────────────────────────────────── */
body .gs-glance-hdr, body .gs-cell-lbl, body .gs-cal-month,
body .nsc-eyebrow, body .hr-label, body .td-reg-label,
body .section-eyebrow, body .bb-section-label, body .pd-label{
  font-family:var(--v10-ui);
  font-size:var(--text-micro);
  letter-spacing:.24em;
  text-transform:uppercase;
  font-weight:600;
  color:var(--sage,#265450);
}

/* ─────────────────────────────────────────────────────────────────────────
   3 · NO SHADOWS — brand.json tokens.forbidden: "drop shadows as decoration".
   Measured 2026-08-20: 76 box-shadow declarations in portal.css. The house
   draws depth with a hairline. Kept deliberately: the two OVERLAY surfaces,
   where the shadow is not decoration but the thing that says "this floats above
   the page and the page is still there underneath" — a modal and a drawer.
   ───────────────────────────────────────────────────────────────────────── */
body .tab-panel *:not(.modal):not(.drawer):not([role="dialog"]),
body .gs-glance, body .gs-cal, body .gs-cal-box, body .gs-cell,
body .hr-card, body .nsc-inner, body .bb-slot, body .bb-thumb,
body .rsvp-card, body .item-card, body .ndl-card, body .pd-card{
  box-shadow:none!important;
}
/* An overlay may still carry its lift. */
body .modal, body [role="dialog"], body .bb-lightbox, body .drawer,
body #editGuestModal, body .g360{ box-shadow:0 24px 60px var(--border-s,rgba(30,11,14,.22))!important; }

/* ─────────────────────────────────────────────────────────────────────────
   4 · SQUARE CORNERS — signature 4. Three radii exist: 0, 2px on inputs,
   50% on circles. Anything else reads as software rather than stationery.
   ───────────────────────────────────────────────────────────────────────── */
/* ⚠ !important IS LOAD-BEARING HERE and is not laziness. Measured in the live
   portal: the surviving radii (3px, 5px, 8px, 10px on .pri, .ros-open,
   .tables-room-default and several bare divs) are INLINE styles, which beat any
   selector however specific. Without !important the rule is simply ignored and
   the law is not enforced. The circle rule below carries it for the same reason
   and must stay AFTER this block so 50% still wins. */
body .tab-panel div, body .tab-panel section, body .tab-panel article,
body .tab-panel button, body .tab-panel a, body .tab-panel span,
body .gs-glance, body .gs-cal, body .gs-cal-box, body .gs-cell, body .hr-card{
  border-radius:0!important;
}
body .tab-panel input, body .tab-panel select, body .tab-panel textarea{ border-radius:2px!important; }
body .tab-panel .avatar, body .gs-dot, body .gs-swatch,
body .tab-panel [class*="circle"], body .tab-panel [class*="avatar"],
body .hr-ring, body .rsvp-dot, body .pal-dot{ border-radius:50%!important; }
/* An SVG ring or donut must keep its own geometry. */
body .tab-panel svg, body .tab-panel svg *{ border-radius:0!important; }

/* Form controls do NOT inherit a font family from the page — they fall back to
   the user agent's, which is why the guest table's buttons and inputs were
   computing as Arial while everything around them was Montserrat. The brand is
   explicit that Montserrat is "all UI, labels, body copy, buttons". */
body .tab-panel button, body .tab-panel input, body .tab-panel select,
body .tab-panel textarea, body .rooms button,
/* ⚠ THE HEADER IS NOT A TAB PANEL. Scoping this to .tab-panel left the header's
   own controls on the user agent's font: #inboxBell renders the unread count in
   Arial, measured in the rendered portal on 2026-08-21. The chrome a client sees
   before they see anything else was the one place still off-face. */
body .portal-hdr button, body .portal-hdr input, body .portal-hdr select,
body .hdr-bell, body .hdr-account, body .hdr-switch{
  font-family:var(--v10-ui);
}

/* The same UA fallback applies to COLOUR and to the tick itself. A bare
   <select> (run-of-show incident widget) rendered its text in UA black — the
   named-forbidden colour — and every checkbox ticked in the OS's blue, the
   most browser-default thing a control can do. Measured in the rendered
   portal 2026-08-21 (108 elements at rgb(0,0,0), accent-color:auto).
   Low specificity on purpose: any classed rule still wins. */
body input, body select, body textarea{
  color:var(--onyx,#1E0B0E);
  accent-color:var(--bloodstone,#B02A45);
}

/* ─────────────────────────────────────────────────────────────────────────
   5 · CARDS BECOME RULED BANDS — the V10 idea.
   A card's job is to say "these things belong together". A top hairline and
   32px of air say it just as clearly and stop the page reading as a dashboard
   of tiles. The panel keeps Platinum ONLY where it is genuinely a resting
   surface; everywhere else it sits directly on the Old Lace ground.
   ───────────────────────────────────────────────────────────────────────── */
body .gs-glance, body .gs-cal, body .hr-card, body .nsc-inner{
  background:transparent;
  border:0;
  border-top:1px solid var(--v10-rule);
  padding:var(--v10-band) 0 0;
  margin:0 0 var(--v10-band-gap);
}

/* ⚠ THE TWO FULL-BLEED BANDS NEED THEIR HORIZONTAL GUTTER BACK — AND THEIR
   CENTRING. Fixed 2026-08-21 (portal formatting pass). This is a correction to
   the rule directly above, not a new idea, and it is written out because the
   rule above is RIGHT for two of its four targets and wrong for the other two.

   WHAT THE BAND RULE ASSUMES. "A top hairline and 32px of air do a card's job"
   presumes the band sits INSIDE something that already supplies a side gutter.
   That holds for .gs-glance and .gs-cal, which live in .tab-split-aside —
   measured at 1440px, they sit at x=1041 and x=1063, comfortably inboard.

   WHERE IT BREAKS. .nsc-inner (the "your next step" bar on the home screen) and
   .hr-card (host readiness) are FULL-BLEED: their parents compute
   padding-left:0px. So `padding:var(--v10-band) 0 0` left them with no side
   gutter at all and their text landed ON the window edge. Measured before this
   fix, at FOUR widths and not just the narrow one:

     viewport   .nsc-eyebrow  .nsc-title  .nsc-btn  .nsc-inner  .hr-card
     1920px     left=0        left=0      left=0    left=0      left=0
     1440px     left=0        left=0      left=0    left=0      left=0
     1280px     left=0        left=0      left=0    left=0      left=0
     1025px     left=0        left=0      left=0    left=0      left=0

   A screenshot at 1440px shows the consequence plainly: the "Y" of "YOUR NEXT
   STEP · STEP 4 OF 5" is clipped, "Your suite is approved." reads "our suite is
   approved.", and the left edge of the primary SEE MY SUITE button is cut off.
   That is the portal's home screen and its principal call to action.

   ⚠ THE MARGIN IS THE SECOND HALF, AND IT IS THE ONE THAT IS EASY TO MISS.
   `margin:0 0 var(--v10-band-gap)` above also overrode .nsc-inner's
   `margin:0 auto` (app/styles/portal.css:395). app/styles/portal.css:5338
   states as fact that ".nsc-inner is max-width:1080px;margin:0 auto and
   re-centres itself inside whatever width is left" and builds the whole
   aside-reservation design on it — that sentence has been FALSE in the running
   product since this skin landed. Measured at 1920px: free space 1500px,
   max-width 1080px, so it should sit at x=210; it sat at x=0.

   WHY 32px. It is --v10-band, the structural unit already declared at the top of
   this file, and it is also exactly what .nsc-inner carried before the skin
   (portal.css:395, `padding:18px 32px 20px`). No new number is introduced.

   SAFE BY box-sizing. `*{box-sizing:border-box}` (portal.css:1) is global and
   both elements compute border-box, so this padding eats into the content box
   and cannot widen either band or reintroduce overflow.

   ⚠ SCOPED TO THE TWO FULL-BLEED SELECTORS ON PURPOSE. Adding the gutter to the
   shared rule above would also indent .gs-glance and .gs-cal inside the aside,
   which are correct today — that would be a fresh visual regression in the name
   of fixing this one. */
body .nsc-inner, body .hr-card{
  padding-left:var(--portal-gutter);
  padding-right:var(--portal-gutter);
}

/* ⚠ AND THE BAR IS LEFT-ALIGNED ON THE PAGE GUTTER, NOT AUTO-CENTRED. This
   corrects the first version of THIS fix, which restored `margin:0 auto`
   because app/styles/portal.css:5338 documents it as deliberate. Re-measuring
   showed that comment's PREMISE is false.

   It claims .nsc-inner "re-centres itself inside whatever width is left — so
   the card stays visually centred on the free space exactly like the tab
   content above it." The tab content is not centred. Measured, left edge in px:

     viewport   .hdr-logo   room hero title   .nsc-title (auto-centred)
     1920px     64          64                242
     1600px     64          64                 82
     1440px     64          64                 32
     1280px     64          64                 32
     1100px     64          64                 32

   The house left edge is a rock-solid 64px at every width, in the header and in
   every room. The auto-centred bar was the only thing on the page that moved —
   178px right of the house edge at 1920 and 32px left of it at 1440. It aligned
   with nothing, at any width, which is why it never looked settled.

   So: keep max-width:1080px (a sensible measure cap for a line of prose) but
   anchor it at the gutter instead of centring it. The bar now shares one left
   edge with the logo above it and the room title below it, at every width.
   --portal-gutter is --sp-6 = 64px (app/styles/tokens.css), the value 51
   declarations in portal.css were already using by hand. */
body .nsc-inner{
  margin-left:0;
  margin-right:0;
}
/* On a phone portal.css:409 already steps .nsc-inner down to 18px of side
   padding. Match it here so the two rules cannot disagree, and drop .hr-card
   to the same figure — a 64px gutter on a 390px screen would leave 262px of
   usable width. */
@media(max-width:600px){
  body .nsc-inner, body .hr-card{ padding-left:18px; padding-right:18px; }
}

/* The cells inside the glance sidebar were nested boxes — a box inside a box
   inside a sidebar. They become rows divided by one quiet rule. */
body .gs-cell, body .gs-cal-box{
  background:transparent;
  border:0;
  border-bottom:1px solid var(--v10-rule);
  padding:12px 0;
}
body .gs-cell:last-child, body .gs-cal-box:last-child{ border-bottom:0; }
/* ⚠ .gs-grid PAINTS THE GAPS, it is not a container tint. It carries ink at 13%
   and its cells sit on top with their own opaque background, so the 1px grid
   gap reads as a rule. Making the cells transparent (above) therefore exposed
   the whole slab — a grey block behind the event details, which is what the
   first pass shipped. The cells now draw their own rule, so the grid does not
   need to paint anything. */
body .gs-grid{ background:transparent; }

/* Suite pieces keep an edge, because a piece of artwork genuinely is an object
   on the page — but it is the house's drawn edge, not a shadowed tile. */
body .bb-slot{
  background:transparent;
  border:1px solid var(--v10-rule-edge);
  border-radius:0;
}

/* ─────────────────────────────────────────────────────────────────────────
   6 · THE PLATE FRAME — signature 1.
   A 0.5px hairline inset 14px: the edge of an engraver's plate. It goes on hero
   surfaces only. Used more widely it stops being a signature and becomes a
   border. `pointer-events:none` because it must never eat a click.
   ───────────────────────────────────────────────────────────────────────── */
/* ⚠ DESKTOP ONLY, above the `hand` breakpoint (brand.json tokens.breakpoint).
   The frame is an inset hairline and assumes the hero has padding to be inset
   FROM. Below 640px these heroes run their content to the viewport edge, so the
   frame drew a box the text then overflowed — seen on the 375px pass. A plate
   frame that its own content crosses is worse than no frame. */
@media (min-width:641px){
  body #nextStepCard, body .ds-hero, body .rhb-hero, body .bb-hero{
    position:relative;
  }
  body #nextStepCard::after, body .ds-hero::after, body .rhb-hero::after, body .bb-hero::after{
    content:"";
    position:absolute;
    inset:14px;
    border:.5px solid var(--v10-rule-edge);
    pointer-events:none;
  }
}

/* ─────────────────────────────────────────────────────────────────────────
   7 · THE JOURNEY RAIL — V10's own contribution.
   The five-room rail stays exactly where it is and keeps its behaviour; it
   gains a stage mark, so the rail answers "where is my event in its life?"
   as well as "where am I?". The marks are stamped by portal-v10-journey.js as
   data-stage on each room button; this file only draws them.
     done → gold   (ceremony: the two moments gold is licensed for)
     now  → bloodstone (action: this is where the event actually is)
     todo → a hollow sage ring
   ───────────────────────────────────────────────────────────────────────── */
/* ⚠ THE RAIL IS HORIZONTAL AT EVERY WIDTH — it is a top tab row, not the
   vertical rail the V10 prototype drew. Measured in the live portal, not
   assumed: a first pass put the mark at left:9px and it read as a stray dot
   floating beside the word. Under the label is the only placement that works
   for a tab, so it is the placement at all widths. */
/* ⚠ ADDS NO HEIGHT, deliberately. A first pass set padding-bottom:16px here and
   grew the header by 1px — caught by test/portal-header-reserve.test.mjs, which
   asserts the rendered header equals the --hdr-reserve the page reserves for it
   (148 against 147). The rail button already carries 12px of bottom padding
   (portal-rooms.js), so the mark sits INSIDE it and the geometry is untouched. */
body .rooms button[data-stage]{ position:relative; }
body .rooms button[data-stage]::before{
  content:"";
  position:absolute;
  left:50%; bottom:3px;
  width:5px; height:5px;
  margin-left:-2.5px;
  border-radius:50%;
  border:1px solid var(--sage,#265450);
  background:transparent;
  transition:background var(--v10-base) var(--v10-ease),
             border-color var(--v10-base) var(--v10-ease),
             box-shadow var(--v10-base) var(--v10-ease);
}
body .rooms button[data-stage="done"]::before{
  background:var(--gold,#BC9862);
  border-color:var(--gold,#BC9862);
}
body .rooms button[data-stage="now"]::before{
  background:var(--bloodstone,#B02A45);
  border-color:var(--bloodstone,#B02A45);
  box-shadow:0 0 0 3px rgba(157,6,23,.12);   /* the halo is the SAME red as the dot: --ch8-crimson at 12% */
}

/* ─────────────────────────────────────────────────────────────────────────
   8 · COLOUR ROLES HELD TO THEIR JOB
   bloodstone = ACTION ONLY (≈3% of a surface). gold = CEREMONY ONLY (<1%):
   approval and delivery, nothing else. Anything that had drifted into using
   the action colour as decoration is returned to sageDeep here.
   ───────────────────────────────────────────────────────────────────────── */
/* ⚠ THREE OF THESE FOUR PAIRS WERE FAILING AA WHEN THIS WAS MEASURED (2026-09-03, AINS row 24-a),
   AND THE REBIND IS WHY. Each pill sets a translucent wash AND reads its ink from a retired token
   name; portal-v15.css repoints those names onto chapter 08, so the INK moved and the WASH did
   not. Sage ink at 7.59:1 on Linen became onyx-62 at 4.94, and then the pill's own tint darkened
   the ground under it — the compound-dimming class rows 34/35 record, arriving through a token
   rebind instead of an opacity. Measured on Linen and on Alabaster, because a chip is placed by
   its container and a source census cannot say which:

     approved  amber on gold .14        4.37 / 3.88   ->  moss on moss .06        5.21 / 4.60
     pending   onyx-62 on sage .085     4.35 / 3.83   ->  onyx-78 on onyx .12     6.66 / 5.88
     review    crimson on blood .075    6.76 / 5.96   ->  crimson on crimson .075 6.58 / 5.79

   Only the review pair was passing, and it trades 0.18 to stop being a red the rebind has already
   moved off — the wash was still the retired bloodstone under an ink that is now Crimson.

   ⚠ THE INK IS NAMED DIRECTLY, NOT LEFT TO THE REBIND. These rules read var(--gold-ink) and
   var(--sage); a rebind can repoint those, and did, but it cannot know that this particular ink
   sits on a tint. Naming the step here is what makes the pair statable.

   ⚠ AND `approved` CHANGES COLOUR FAMILY, DELIBERATELY. It was gold, and chapter 08's Amber means
   RUNNING LATE — the state/attribution distinction this row already applied to portal-rooms.js
   and to .ros-var.ok. Moss is the book's own "done", so the pill now says what it means. */
body .bb-status-pill.approved, body .status-approved, body .t-done{
  color:var(--ch8-moss);
  background:rgba(59,107,36,.06);                    /* --ch8-moss at 6% */
}
body .bb-status-pill.in_progress, body .status-pending, body .t-us{
  color:var(--ch8-onyx-78);
  background:rgba(20,20,20,.12);                     /* --ch8-onyx at 12% */
}
body .bb-status-pill.in_review, body .t-you{
  color:var(--ch8-crimson);
  background:rgba(157,6,23,.075);                    /* --ch8-crimson at 7.5% */
}

/* ─────────────────────────────────────────────────────────────────────────
   9 · ONE FOCUS RING — brand.json tokens.focus.
   2px bloodstone, 3px offset, square. The audit counted 42 `outline:none` and
   11 distinct ring styles across the house; this is the one.
   ───────────────────────────────────────────────────────────────────────── */
body .tab-panel a:focus-visible, body .tab-panel button:focus-visible,
body .tab-panel input:focus-visible, body .tab-panel select:focus-visible,
body .tab-panel textarea:focus-visible, body .rooms button:focus-visible{
  outline:2px solid var(--bloodstone,#B02A45);
  outline-offset:3px;
  border-radius:0;
}

/* ─────────────────────────────────────────────────────────────────────────
   10 · NO FOURTH FACE — "Never another face."
   portal.css still carried Playfair Display (3), Lora (3) and Cinzel (3) —
   nine declarations of three faces the house does not own. They are returned
   to the display voice here rather than left to be discovered by a client.
   ───────────────────────────────────────────────────────────────────────── */
body [style*="Playfair"], body [style*="Cinzel"], body [style*="Lora"],
body [style*="font-family:Georgia"], body [style*="font-family: Georgia"],
body .keepsake-title, body .tng-title{
  font-family:var(--v10-display)!important;
}
/* portal-run-of-show.js injects `#rosCard h3{font-family:Georgia,serif}` — the
   only place a bare Georgia reaches the live portal UI. Beaten on specificity
   (1,0,2 against its 1,0,1) rather than with !important, and set to the TITLE
   face because "The run of show" is a card title, which is Libre Baskerville's
   stated job.
   ⚠ THE OTHER TWO Georgia declarations are LEFT ALONE ON PURPOSE:
   portal-exports.js and crm-document-generators.js build printed documents that
   open in their own window WITHOUT the portal's font links, so Georgia is a
   deliberate safe serif there. Sweeping them would render those exports in
   Times, which is worse than the thing being fixed. */
body #rosCard h3{ font-family:var(--v10-title); }

/* ─────────────────────────────────────────────────────────────────────────
   11 · MOTION — one curve, and it reveals.
   Nothing springs, nothing travels more than 12px. Under reduced motion every
   transition collapses to opacity at 120ms, which is the house rule rather
   than a blanket `none` (a state change still has to be perceivable).
   ───────────────────────────────────────────────────────────────────────── */
body .tab-panel .bb-slot, body .tab-panel .gs-cell, body .rooms button,
body .nsc-btn, body .bb-file-btn{
  transition:background var(--v10-quick) var(--v10-ease),
             color var(--v10-quick) var(--v10-ease),
             border-color var(--v10-quick) var(--v10-ease),
             transform var(--v10-base) var(--v10-ease);
}
@media (prefers-reduced-motion:reduce){
  body .tab-panel *, body .rooms button{
    transition:opacity 120ms linear!important;
    animation-duration:120ms!important;
  }
}

/* ─────────────────────────────────────────────────────────────────────────
   13 · THE ORGANISATION BAR — the one place B2B looked like another product.
   Measured 2026-08-21 on the Meridian org home: #portal-org-bar painted a
   1280×198px band of sageDeep as a GROUND. It is on-palette, so the brand audit
   passes it, and it is still wrong twice over:
     · ROLE — brand.json gives sageDeep to "labels, secondary copy, rules, quiet
       information". It is the structural colour, not a page ground; ivory is
       "the page ground, on every surface".
     · RATIO — tokens.colorRatio budgets sageDeep at 8% and ivory at 70%. One
       band inverted that on the first screen a B2B client sees.
   And it is exactly the failure the brief names: "Do not accidentally create
   two unrelated visual products." The organisation bar now uses the house
   ground with a ruled edge, like every other band in the portal, and keeps its
   identity through type and the sage label — not through a slab of colour.
   ───────────────────────────────────────────────────────────────────────── */
/* ⚠ !important IS REQUIRED HERE. The bar carries an INLINE style —
   `background:var(--navy,#265450);color:#FBF7EF` — written by
   portal-org-home.js, and an inline declaration beats any selector. Worth
   noting for whoever owns that module: `--navy` is not a token this house
   defines, so the bar was already falling through to its sageDeep fallback. */
/* ⚠ `--ink` IS DEFINED BY NOTHING, so this fallback is not a fallback — it is the paint. Checked
   across all six stylesheets client-portal.html links and the CSS portal-rooms.js injects: no
   declaration of it anywhere, which is exactly the shape of the `--navy` finding two comments
   above. Chapter 08's Onyx, 16.43:1 on Linen against the retired ink's 16.88 — stated rather than
   buried, because chapter 08's Onyx is deliberately lighter than the retired near-black. */
body #portal-org-bar{
  background:var(--ivory,#FBF7EF)!important;
  color:var(--ink,#141414)!important;
  border-bottom:1px solid var(--v10-rule);
  border-top:1px solid var(--v10-rule);
}
/* ⚠ !important ON THE DESCENDANTS TOO, AND THIS ONE WAS LEARNED THE HARD WAY
   (2026-08-25). The block above correctly notes that "an inline declaration
   beats any selector" and arms the BAR with !important — then this rule, one
   line later, omits it. portal-org-home.js also wrote `color:#FBF7EF` inline on
   the bar's toggle LINK, so the bar turned ink and the link stayed ivory: 1:1
   contrast, an invisible control on the first screen a B2B client sees. The
   inline colour is now gone from that module as well (the real fix); this
   !important is the belt, so the next inline colour written there cannot
   reopen the same hole. */
body #portal-org-bar a, body #portal-org-bar button,
body #portal-org-bar span, body #portal-org-bar div{ color:var(--ink,#141414)!important; }
/* ⚠ THE PATTERN, NOT THE INSTANCE. The organisation surfaces paint themselves
   with an inline `background:var(--navy,#265450)` — and `--navy` is not a token
   this house defines, so every one of them was already falling through to its
   sageDeep fallback and using the structural colour as a ground. Targeting the
   inline declaration catches the bar, the events list and anything the module
   adds later, instead of chasing each block as it is discovered. */
body [style*="var(--navy"]{
  background:var(--ivory,#FBF7EF)!important;
  /* `--ink` is defined by nothing — see the bar rule above; this literal is the paint. */
  color:var(--ink,#141414)!important;
  border-bottom:1px solid var(--v10-rule);
}
body [style*="var(--navy"] a, body [style*="var(--navy"] span,
body [style*="var(--navy"] div, body [style*="var(--navy"] button{ color:inherit; }
/* Its eyebrow keeps sageDeep — that IS the structural colour's job. */
body #portal-org-bar .oi-label, body #portal-org-bar [class*="label"],
body #portal-org-bar [class*="eyebrow"]{ color:var(--sage,#265450); }

/* ─────────────────────────────────────────────────────────────────────────
   12 · GROUNDS — ivory is the page, Platinum is the only tint, and there is
   no third. Any surface that had reached for pure white is returned.
   ───────────────────────────────────────────────────────────────────────── */
body .tab-panel [style*="#fff"], body .tab-panel [style*="#FFF"],
body .tab-panel [style*="rgb(251,247,239)"]{
  background-color:var(--ivory,#FBF7EF)!important;
}
/* ⚠ NO BLANKET PLATINUM. A first pass tinted .gs-glance-body and it filled a
   sub-block of the sidebar with a grey slab that read as a stray box — Platinum
   is "the page a card sits on", not a wrapper to reach for. It is left to the
   surfaces that already choose it.

   The date block in the glance calendar filled with BLOODSTONE, which the
   palette reserves for action and nothing else (tokens.colorRatio: 3%, "if
   bloodstone exceeds ~3% of a surface it has stopped meaning action"). A date
   is not an action. It takes the ink instead, which is also what makes the one
   red thing on the screen the thing that actually needs the client. */
body .gs-cal-day, body .gs-cal-month{
  background:var(--onyx,#1E0B0E);
  color:var(--ivory,#FBF7EF);
}

/* ─────────────────────────────────────────────────────────────────────────
   13 · ONE PAGE GUTTER — the portal gets a left edge (2026-08-25)

   Owner, on a screenshot of the Experience room: "design wise this is bad."
   The specific complaint is that "MORE FOR YOUR GUESTS", "ASK ABOUT YOUR EVENT"
   and "WHERE YOUR GUESTS ARE" print with their first letter touching the window,
   while the block directly above them is inset 64px.

   ⚠ THIS IS NOT A BROKEN PANEL. IT IS A MISSING ONE. Measured with
   scripts/portal-format-shots.mjs at 1440px, every one of the ten rooms computes

       .tab-panel.active  →  padding-left: 0px   padding-right: 420px

   The 420 is the At a Glance reserve and is correct. The 0 is the defect: the
   page declares no left gutter at ANY width, so each band has been improvising
   one. What the bands actually compute, read off the live document:

     0px   #conceptHero · #creativeDirectionBanner · #dsHero · #itemsSection
           #briefSubmittedPanel · #dayOfPrintPanel · #guestToolsPanel
           #fileStates · #itemsSectionFinal · #docsSectionSuite
     18px  #rosCard · #photoSpaceCard
     22px  #revRemainingBanner
     24px  .td-wrap · .designs-ask · #rm-host-experience
     28px  #designSuiteGrid
     48px  #eventCountdownHero · #finalSuiteGrid
     64px  #rsvpHero · #guestsEmptyState · #reviewHeroBlock · .billing-hero

   Seven different left margins, and ten bands with none at all. The same defect
   exists at 390px with a different set of numbers (14/16/18/20/22/24/48/64).

   ⚠ AND ALMOST NONE OF THESE ARE CARDS, WHICH IS WHY THE PADDING IS THE PAGE'S
   JOB AND NOT THEIRS. Reading background-color back out of the live document,
   24 of the 30 top-level bands compute rgb(251,247,239) — the ivory page ground
   itself. Under this skin they are ruled bands, not cards (see §B of this file's
   header: "ruled bands instead of cards"), so their horizontal padding was never
   an interior; it was a hand-rolled page gutter. Six are genuinely distinct
   surfaces and keep an interior: #prodDetailsPanel (sage 3% tint),
   #rosCard / #photoSpaceCard / #rmPinned (1px rule), #revRemainingBanner (3px
   left accent).

   THIS IS THE 2026-08-21 FIX FINISHING. That pass added the gutter to exactly
   two selectors — .nsc-inner and .hr-card — and its own note says why it was
   scoped: adding it to the shared band rule "would also indent .gs-glance and
   .gs-cal inside the aside". True, and still true: this rule targets the PANEL
   and its direct children, so nothing inside .tab-split-aside is reached.

   WHY THREE VALUES. 64px is --portal-gutter (tokens.css:204, = --sp-6), already
   the portal's de-facto page inset and what four of the bands had chosen for
   themselves. 64px on a 390px phone would leave 262px of text column, so the
   phone takes 20px — the most common value already in force there — and the
   tablet 32px (--sp-4). No new number is introduced at any width.

   SAFE BY box-sizing. *{box-sizing:border-box} (portal.css:1) is global, so this
   padding eats into the content box and cannot widen a panel or reintroduce the
   sideways scroll the harness reports as zero.

   RE-MEASURE, DO NOT TRUST THIS COMMENT: node scripts/portal-format-shots.mjs
   ───────────────────────────────────────────────────────────────────────── */
:root{ --v10-gutter:20px; }
@media(min-width:768px){  :root{ --v10-gutter:var(--sp-4,32px); } }
@media(min-width:1025px){ :root{ --v10-gutter:var(--portal-gutter,64px); } }

body .tab-panel.active{ padding-left:var(--v10-gutter); }
/* Below the aside breakpoint there is no 420px reserve, so the right edge needs
   the gutter too. At >=1025px portal.css's padding-right:420px stands. */
@media(max-width:1024px){ body .tab-panel.active{ padding-right:var(--v10-gutter); } }

/* The bands stop supplying their own. Direct children only — nothing nested,
   and nothing inside the aside. */
body .tab-panel.active > div,
body .tab-panel.active > section,
body .tab-panel.active > article{
  padding-left:0;
  padding-right:0;
}

/* The six real surfaces keep an interior, on one value rather than four. */
body .tab-panel.active > #prodDetailsPanel,
body .tab-panel.active > #rosCard,
body .tab-panel.active > #photoSpaceCard,
body .tab-panel.active > #revRemainingBanner{
  padding-left:22px;
  padding-right:22px;
}

/* ⚠ THE SECOND HALF: THREE BANDS CARRY THE GUTTER AS A MARGIN, AND THREE
   OUTRANK THE RULE ABOVE ON SPECIFICITY. Both were found by re-measuring rather
   than by reading, and neither is visible in the CSS text.

   MARGINS. .suite-sample-label (64px), #printSpecs (48px) and #upgradeSection
   (48px) never had padding — their inset was margin-left, doing the same
   stand-in job. With the page gutter now in force they measured 128, 112 and
   112, i.e. exactly gutter + their own. Zeroing the horizontal margin puts them
   on the same line as everything else; the vertical margin is rhythm and stays.

   SPECIFICITY. #designSuiteGrid (28px), #finalSuiteGrid (48px) and .designs-ask
   (24px) survived `body .tab-panel.active > div` because their own declarations
   are id-strength or later in the cascade. They are re-stated here at
   id strength rather than reached for with !important.

   #rmPinned is deliberately NOT touched: it is the pinned "while you work"
   aside, right-aligned by design at x=770, and it is an <aside>, so the child
   rule above never matched it in the first place. */
body .tab-panel.active > .suite-sample-label,
body .tab-panel.active > #printSpecs,
body .tab-panel.active > #upgradeSection{
  margin-left:0;
  margin-right:0;
}
body .tab-panel.active > #designSuiteGrid,
body .tab-panel.active > #finalSuiteGrid,
body .tab-panel.active > .designs-ask{
  padding-left:0;
  padding-right:0;
}

/* ⚠ AND TWO MORE ON THE BRIEFING PANEL, FOUND THE SAME WAY AND MISSED FOR TWO
   DIFFERENT REASONS (2026-09-08, owner: "formatting here too"). Measured at
   1280px on the Vision room, text left edge, against a page gutter of 64:

     .pkg-lbl / .pkg-val   64   ✓        #briefForm            64   ✓
     .brief-prog          128   ✗ 64+64  .brief-reassure      112   ✗ 64+48

   Three left edges on one screen: the package row and the form on the house
   line, the progress rail one gutter further in, and the tinted reassurance
   strip between the two. Exactly the "gutter + their own" arithmetic the note
   above describes, and the same two numbers — 128 and 112 — it already records
   for three other survivors.

   WHY EACH ESCAPED. `.brief-prog` is a <div> and IS matched by the child rule
   above, but portal.css:4491 declares `padding:0 64px!important` inside the
   BRIEFING PROGRESS BAR redesign block, and !important outranks any selector
   here. That block also carries eight more `.brief-prog` padding arms across
   the breakpoints (600/900/768/640/480/380 and a 681–1024 arm it shares with
   .brief-prefilled), so zeroing it once here is one rule against eleven edits
   in that file, and cannot leave a width behind. `.brief-reassure` is a <p>,
   and the child rule above names div/section/article only — it was never
   reached at any specificity.

   !important IS USED ON THE FIRST ARM ONLY, and only because its opponent is
   !important too; the note above prefers id strength and that is still right
   where it can win. The second arm needs nothing: (0,2,1) over (0,1,0).

   HORIZONTAL ONLY. `.brief-prog` keeps its 44px height and `.brief-reassure`
   its 10px of vertical air — that is rhythm, and it is not what doubled. */
body .tab-panel.active > .brief-prog{
  padding-left:0!important;
  padding-right:0!important;
}
body .tab-panel.active > .brief-reassure,
/* ⚠ AND ITS SIBLING ONE LINE BELOW IT, MISSED IN THE SAME PASS (2026-09-08,
   second look). `.autosave-hint-top` is the live "● Saved 14:32" proof that
   replaces the promise in `.brief-reassure` the moment the first autosave
   fires. It is a <p> with its own `padding:2px 48px 10px`, so it escaped for
   exactly the reason `.brief-reassure` did — the div/section/article child
   rule above never named it — and it carries the SAME three breakpoint arms
   (900/600) in portal.css:5423-5425, all of them (0,1,0) and none !important.
   Measured on the Vision room against a 32px gutter: box at 32, text at 64,
   while .pkg-lbl, .brief-prog-lbl and .brief-reassure all sat at 32. It is
   display:none until the first save, which is why a screenshot of an untouched
   form does not show it — the client sees it only once they start typing,
   which is the worst moment to move a line. */
body .tab-panel.active > .autosave-hint-top{
  padding-left:0;
  padding-right:0;
}

/* ⚠ AND TWO THAT NEEDED A SOURCE FIX, NOT A CSS ONE — .designs-ask and
   #rm-host-experience kept a 24px interior against every rule above, including
   an id-strength one. Neither is a specificity problem: both carry the padding
   as an INLINE style, which outranks any stylesheet short of !important.
     app/client-portal.html:3252        style="…padding:22px 24px 0…"
     app/scripts/portal-rooms.js:1437   wrap.style.cssText = 'padding:24px;…'
   Fixed at those two lines instead (22px 0 0 / 24px 0), so the horizontal inset
   is supplied once by the gutter and the vertical rhythm is untouched. Nothing
   is restated here — if a rule appears in this file for either selector again,
   the inline style has come back. */

/* ⚠ THE GLANCE HEADER RULE — 2px of onyx under a 0.58em label (2026-08-25).
   Owner, on a zoomed screenshot of this rail: "design wise this is bad." The
   heaviest line on the whole card sat under its smallest piece of type, and it
   contradicts this file's own header: "Rules are sageDeep, not grey: 0.15 quiet,
   0.30 for an edge." A 2px near-black rule is neither, and it is the only one of
   its weight in the portal. It reads as a divider bar rather than a card header,
   which is most of why the rail looked broken when its body scrolled under it.
   The edge strength (0.30) is the right one here — it IS an edge. */
body .gs-glance-hdr{
  border-bottom:1px solid var(--v10-rule-edge);
}

/* ═══════════════════════════════════════════════════════════════════════════
   9 · COHESION PASS — 2026-08-25 (evening). Owner, on live portal screenshots:
   the portal "feels like many features added over time without being properly
   integrated … conflicting layouts, inconsistent formatting, unnecessary visual
   density, unclear hierarchy." Every rule below consolidates something that
   existed twice, or aligns one stray component with a system that already
   exists in this file. Nothing is removed — measured first with
   scripts/portal-format-shots.mjs, then fixed, then re-measured.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 9a · ONE TELLING OF THE NEXT STEP PER SCREEN ─────────────────────────
   The Next Step hero band (#nextStepCard) rendered identically on all TEN
   rooms — ~230px of the same sentence above every screen — while the
   At a Glance rail repeats it in compact form ("Next: …" + GO) and the
   Guests/Files pinned column repeats it a third time. Three tellings of one
   fact on one screen is most of what reads as "stacked formats".

   The hero now shows ONLY on Today (the room whose job it is), and only
   hides where the rail is actually present to carry the message:
   · body[data-active-tab]  — stamped by portal-rooms.js syncFromTab(); absent
     before the rooms engine boots, so the pre-boot paint is untouched.
   · body.portal-aside-visible — set only when the At a Glance rail rendered.
   · ≥1025px — below that the rail is display:none (portal.css:5554), the
     hero is the ONLY next-step surface, and it stays on every room.
   display uses !important deliberately: renderNextStepCard() sets an inline
   display:block, and this is a scoped, documented override of that inline
   style, same pattern as §4's radius law. */
@media (min-width:1025px){
  body.portal-aside-visible[data-active-tab]:not([data-active-tab="today"]) #nextStepCard{
    display:none !important;
  }
}

/* ── 9b · THE RAIL'S FRAME ENDS WHERE ITS CONTENT ENDS ────────────────────
   .tab-split-aside was position:fixed with BOTH top and bottom pinned, so its
   0.5px frame always spanned the full window height — after the 2026-08-25
   trim its content is ~470px, leaving up to ~300px of empty bordered ivory
   below Suite Progress that read as a broken, half-loaded panel. Height now
   follows content; max-height keeps the same footer clearance the bottom pin
   enforced (81px = the fixed opaque footer + breathing room — see
   portal.css's warning at .tab-split-aside), and the body still scrolls inside
   when long.
   ⚠ THIS BLOCK IS WHY portal.css's OWN `bottom:calc(81px + …)` NEVER RUNS ON THIS
   ELEMENT. `body .tab-split-aside` outranks `.tab-split-aside` and sets bottom:auto,
   so the footer clearance moved here and portal.css's number is the losing half of
   a pair. A guard that checked only the other file would have passed while the
   panel was sliced — so test/portal-footer-geometry.test.mjs asserts BOTH, and
   derives the figure from the footer's own markup.
   ⚠ 62px → 81px on 2026-09-06: the legal row took §8.7's type floor and then the
   44px house touch floor, and the footer went 42px → 61px underneath both files. */
@media (min-width:1025px){
  body .tab-split-aside{
    bottom:auto;
    max-height:calc(100vh - var(--hdr-h,var(--hdr-reserve,140px)) - var(--beta-h,0px) - 20px - 81px - env(safe-area-inset-bottom, 0px));
    display:flex;
    flex-direction:column;
  }
  body .gs-glance{ height:auto; max-height:100%; }
}

/* ── 9c · PAGE HEADERS: ONE ALIGNMENT ─────────────────────────────────────
   Billing and My Events were the only two rooms with a CENTRED page title
   (portal.css:4342 .billing-hero) — every other room is left-aligned on the
   64px house edge (F-6, 2026-08-21). Two alignments for one structural role
   is a conflict, not a variation; the Today letter keeps its centring because
   it is correspondence, not a page header. */
body .billing-hero{ text-align:left; padding-top:48px; padding-bottom:32px; }
body .billing-sub{ margin:0; }

/* ── 9d · ONE TRACKED-LABEL VOICE ─────────────────────────────────────────
   §2 of this file states the label law (.62rem/.24em/uppercase/sage) and its
   list missed four label classes that grew up in module-injected stylesheets:
   the Today lanes' .td-lbl (.55rem/.22em/ink-grey), the pinned column's
   .rm-pin-k, and the Files countdown's .ech-eyebrow (.36em tracking — the
   widest tracking in the portal, on one element). The lane COLOUR coding
   (you/us/auto) survives: those rules are .td-lane.you .td-lbl — higher
   specificity than this. .rm-wall-k is deliberately absent: it sits on the
   dark artwork ground and keeps its light ink. */
body .td-lbl, body .rm-pin-k, body .ech-eyebrow{
  font-family:var(--v10-ui);
  font-size:var(--text-micro);
  letter-spacing:.24em;
  text-transform:uppercase;
  font-weight:600;
  color:var(--sage,#265450);
}

/* ── 9e · ONE CHIP FAMILY ─────────────────────────────────────────────────
   Status chips came in three unrelated shapes: .nsc-ball (.6rem/.14em, 4x12px,
   tint or fill), the rail's occasion tag .gs-glance-hdr-tag (.52em, solid
   dark-green fill — the only solid dark chip in the portal), and the event
   set-up ESSENTIALS tag. One family now: the quiet sage tint for facts and
   house-side states, the bloodstone fill (unchanged, .nsc-ball-you) reserved
   for "waiting on you". */
body .gs-glance-hdr-tag{
  font-size:var(--text-micro);
  letter-spacing:.14em;
  font-weight:700;
  /* The same rebind arithmetic as the status pills, and the same answer: this tag is a FACT, so
     it is quiet layer, not colour. onyx-62 on the sage tint measured 4.10 on Linen and 3.63 on
     Alabaster — under AA on both — where onyx-78 on the Onyx-12 tint reads 6.66 and 5.88. */
  color:var(--ch8-onyx-78);
  background:rgba(20,20,20,.12);
  padding:4px 10px;
}

/* ── 9f · CONTROL FLOOR AT EVERY WIDTH ────────────────────────────────────
   The 44px floor existed only inside the ≤640px media arm (portal.css:5507),
   so the SAME control rendered 31–42px on desktop and 44px on a phone —
   measured per room: Suite 31/39/46/107, Files 34/38/42, Billing 42/48,
   My Events 38. One floor for standard actions at every width; .ros-open is
   deliberately absent — the run-of-show card's 36px operational density is a
   recorded decision (PORTAL_FORMATTING_LEDGER §21), and the choice-card
   heights (107/118/150) are content, not controls. */
body .mts-open, body .ds-share-btn, body .pdp-save, body #ewRetryBtn,
body .upgrade-card-btn, body .ndl-fmt-btn, body .billing-action-btn,
body .upgrade-btn, body .yp-btn, body .td-btn,
body .yp-v-in, body select.yp-v-in, body input.yp-v-in{
  min-height:44px;
}
/* .pdp-save's bespoke 13px vertical padding rendered it 46px beside 44px
   siblings — one off-system value on the screen. 11px + the floor above = 44
   exactly, same as .nsc-btn. (.upgrade-btn's 48px is NOT flattened: it is the
   --btn-pad-lg token, a deliberate large-CTA tier, not drift.) */
body .pdp-save{ padding-top:12px; padding-bottom:12px; }

/* ── 9g · THE FILES COUNTDOWN, AT ROOM SCALE ──────────────────────────────
   #eventCountdownHero opened the Files room with numerals up to 4.8rem and a
   bloodstone wash — a second hero, LOUDER than the room's own title, above the
   files it exists to deliver, duplicating the countdown the rail already
   shows. It stays (celebration is a feature), at a scale that no longer
   competes with the page header (43.2px), and on the ivory ground like every
   other band — the wash was the only pink surface in the portal. */
body .event-countdown-hero{ padding-top:32px; padding-bottom:28px; }
body .event-countdown-hero::before{ background:transparent; }
body .ech-num{ font-size:clamp(2rem, 4vw, 3.2rem); }
body .ech-title{ font-size:clamp(1.3rem, 2.6vw, 1.9rem); margin-bottom:20px; }

/* ── 9h · THE MOODBOARD WALL IS A FRAMED PANEL ────────────────────────────
   .rm-wall renders the client's real moodboard HTML in an iframe under a
   bottom-anchored gradient. Above the gradient the artwork sits directly on
   the ivory page ground with no boundary, so its own rotated captions and
   sketches read as loose, broken page content overlapping the Today room
   (owner's screenshot, 2026-08-25). A hairline contains it: the art is
   unmistakably a framed panel, exactly like the plate-frame heroes. */
body .rm-wall{ border:1px solid var(--v10-rule-edge); }

/* ── 9i · THE LETTER'S NAME IN THE DISPLAY VOICE ──────────────────────────
   Register A's name line (.td-letter .nm) is an <h1> whose injected style sets
   face and size but not weight, so it inherited bold — the only 700-weight
   display type in the portal (measured 37.8px/700 against the system's
   43.2px/300). The letter keeps its centred correspondence form; only the
   weight joins the family. */
body .td-letter .nm{ font-weight:400; }

/* ── 9j · THE RAIL AT RAIL SCALE (owner follow-up, 2026-08-25 evening) ─────
   Two measured defects made At a Glance read as broken:

   1 · §5's ruled-band rule names `.gs-cal` — but the only .gs-cal in the
   portal is the little calendar INSIDE the 320px rail, so a page-scale band
   treatment landed on a rail-scale component: 32px top padding and a 48px
   band-gap under a 119px block. That 48px is the dead space under the date
   the owner pointed at, and it pushed the rail's last section past its own
   edge. Measured before: event section 209px, rail content 617px against
   519px visible — Your Guests clipped mid-content behind an invisible
   scrollbar. Scoped restore, not a change to §5: bands stay bands on the
   page; inside the rail the calendar is a component.

   2 · The rail inherited the page's 18px base type, so every em-padding in a
   320px column was sized for a 1440px page. 16px is the rail's own scale;
   everything inside is em-based and follows. Content 617px → ~480px:
   the whole card fits its frame again in the common states. */
body .tab-split-aside .gs-cal{
  padding:0;
  margin:0 0 .4em;
  border:0;
  background:transparent;
}
body .tab-split-aside .gs-glance{ font-size:16px; }

/* ── 9k · THE HEADER PAIR (owner follow-up: "client and event dropdowns") ──
   A second event on the account makes #hdrEventSwitcher appear beside the
   client chip. The two are the same kind of control and already share the
   chip form; what made them read as clutter is that the CLIENT chip had no
   width cap — "ÉLise Fontaine-Beaumont" ran the chip to 354px, EVENT ran to
   307px, and together they crushed the support line to "answered wi…" at
   1440. The event name already capped at 180px; the client name now caps the
   same way, the two chips share one interior metric, and the pair sits
   tighter than it sits to the bell — so it reads as one group, two choices:
   which event · whose account. Both dropdowns keep working exactly as they
   do; nothing is hidden. */
body .hdr-client-name{
  max-width:200px;
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
}
body .hdr-event-chip, body .hdr-client-chip{ gap:8px; padding:8px 14px; }
body .hdr-meta{ gap:8px; }
body .hdr-meta .hdr-bell{ margin-right:6px; }

/* ── 9l · THE RAIL RETIRED, THE COLUMN MADE WHOLE (owner, 2026-08-25 evening:
   "shouldn't we remove the at a glance …?") ─────────────────────────────────
   populatePortalAsides() (client-portal.html) no longer sets
   body.portal-aside-visible, which is the one switch everything keyed on:
   the 360px reserve (portal.css:5564), the two full-width bars' reserves
   (:5599, :5608) and §9a's hide of the Next Step band all stop applying, so
   the band returns on every room as the single next-step surface and the
   content column gets its width back. One thing the class's absence does NOT
   supply is a right page gutter — the un-classed panel computes
   padding-right:0 at ≥1025px (it was only ever indented FOR the rail), so
   the gutter is stated here, symmetric with the left one from the 2026-08-25
   morning pass. */
@media (min-width:1025px){
  body:not(.portal-aside-visible) .tab-panel.active{ padding-right:var(--v10-gutter); }
}

/* ── 9m · EVENT + CLIENT AS ONE CONTROL (same owner message: "consolidate
   dropdowns to event and client being together") ───────────────────────────
   The two chips are the same kind of control and now read as one segmented
   unit: attached edges, the client chip's left border as the divider. The
   negative margin cancels .hdr-meta's 10px flex gap between exactly these
   two. When the account has one event, #hdrEventSwitcher is display:none and
   both rules are inert — the client chip stands alone, unchanged. Both
   dropdown menus keep their own behaviour and anchors. */
body #hdrEventSwitcher{ margin-right:-10px; }
body #hdrEventSwitcher .hdr-event-chip{ border-right:0; }

/* ── 9n · THE EVENT SWITCHER ON A PHONE (register ME-082, 2026-08-26) ───────
   MEASURED, not guessed: at a 375px viewport the switcher's right edge sat at
   **393px** — 18px off the screen — and the chip stood **34px** tall, under the
   house's 44px touch floor. The page did not report a horizontal overflow
   because the header clips it, which is exactly why this survived: the control
   was partly unreachable and nothing said so.

   Three narrow changes, none of which touch the desktop rendering:
   1. The "EVENT" eyebrow is dropped. It labels a chip that is already showing
      an event name, and it is the single widest piece of dead width here.
   2. The name gets a max-width in `ch`, so it truncates with its existing
      ellipsis instead of pushing the caret off-screen.
   3. The chip meets the touch floor, and the menu anchors to the RIGHT edge so
      a long event name cannot push the dropdown out of view either.
   Specificity matches the `body .hdr-event-chip` rules above deliberately —
   a bare `.hdr-event-chip` here would lose to them and silently do nothing. */
@media (max-width:600px){
  body .hdr-event-chip{ padding:8px 12px; min-height:44px; box-sizing:border-box; }
  body .hdr-event-chip .hdr-event-label{ display:none; }
  body .hdr-event-chip .hdr-event-name{ max-width:14ch; }
  body #hdrEventSwitcher .hdr-event-dropdown{ left:auto; right:0; min-width:min(260px, calc(100vw - 32px)); }
  /* The options ARE the switch, so they carry the floor too. MEASURED with the
     rule removed: an option with a sub-line is 62–81px and fine, but one WITHOUT
     a sub-line — an event with no type and no date — is **43px**. One pixel under,
     on the only control this row is about.
     ⚠ min-height ALONE, no flex. The first version added `display:flex;
     align-items:center` and that was a real regression: the option's sub-line is a
     child <div>, so a row flex box put "wedding · 2026" BESIDE the event name
     instead of under it, and the two-line option collapsed 81px → 63px. Block
     layout already stacks them correctly; only the floor was missing. */
  body #hdrEventSwitcher .hdr-event-opt{ min-height:44px; }
}

/* ── 9o · THE HEADER PAIR STACKS, AND ITS TYPE COMES DOWN (2026-09-05) ──────
   §9k above capped both names because the two chips "crushed the support line
   to 'answered wi…' at 1440". That cap held until ledger 19-h gave the event
   chip a SECOND string: the occasion-and-plan eyebrow, written by
   scripts/portal-event-header.js. It is not a short word — "MILESTONE
   BIRTHDAY · ESSENTIALS" renders 313px at .72rem/.2em — and side by side with
   the name it put the pair back over budget.

   MEASURED at 1440px, signed in as the walkthrough client (Adaeze Okafor /
   "Margaret Okafor's 70th Birthday" — real-length data, per §23 of the
   formatting spec), before this block:

     .hdr-meta                954px   of the 1280px the row has to give
     .hdr-support             216px   shown, of 484px needed  → "hello@evor…"
     #hdrEventName            180px   shown, of 240px needed  → "…70t…"

   TWO THINGS, ONE CAUSE. The eyebrow and the name are a label and its value;
   they do not have to sit on one line. Stacked, the chip is as wide as the
   WIDER of the two rather than as wide as their sum, and the type can come
   down to eyebrow scale because it is no longer competing for a baseline with
   the name beside it.

   ⚠ SIZES IN PX, NOT IN --text-* ON PURPOSE. tokens.css sets
   html{font-size:112.5%} for this portal (18px root, owner 2026-08-03 "make
   the portal match the site"), so --text-xs lands at 12.96px and --text-base
   at 16.2px — body scale, not header-chrome scale. There is no token below
   --text-xs to name, and moving the root is an owner decision the v15 skin
   explicitly declines to make (portal-v15.css, note 2). So the two header
   eyebrows state their own size and say why here.

   AFTER, same viewport and same client:

     .hdr-meta                582px
     .hdr-support             432px   shown, 432px needed  → whole line reads
     #hdrEventName            not clipped; #hdrClientName not clipped
     .hdr-top height          74.4px  (was 74.4px)

   THE HEIGHT IS THE CONSTRAINT, and it is why .hdr-top's vertical padding
   comes down 18px → 14px in the same breath: a two-line chip is 46px where a
   one-line chip was 38px, and --hdr-reserve (135px on desktop since 2026-09-05, portal.css) is asserted
   against the header's settled height by test/portal-header-reserve.test.mjs.
   Trading 8px of row padding for the 8px the second line costs keeps the
   reserve exactly right and the CLS work behind it untouched.

   ⚠ min-width:601px. Below it §9n already settles this chip its own way — the
   eyebrow is dropped entirely and the chip takes the 44px touch floor — so a
   stacked rule there would either fight that or do nothing. The phone header
   is deliberately out of scope. */
@media (min-width:601px){
  body .hdr-top{ padding:14px 64px; }
  body .hdr-event-chip, body .hdr-client-chip{
    flex-direction:column; align-items:flex-start; gap:0;
    padding:6px 30px 6px 14px;   /* right side holds the caret, now absolute */
    position:relative;
  }
  body .hdr-event-label, body .hdr-client-label{
    font-size:9.5px; letter-spacing:.14em; line-height:1.5; white-space:nowrap;
    /* ONE INK FOR THE PAIR. .hdr-client-label sits in portal.css's big
       "…{color:var(--onyx)}" list and rendered #141414 while .hdr-event-label
       rendered #464542 beside it — invisible while the two labels were on
       separate lines of a wide row, plainly wrong now they are stacked eyebrows
       two chips apart. The quieter ink is the eyebrow's, and it measures ~7.9:1
       on Linen, so this is a design call and not a contrast concession. */
    color:var(--ch8-onyx-78);
  }
  body .hdr-event-name, body .hdr-client-name{
    font-size:12.5px; line-height:1.45; display:block;
    max-width:210px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  }
  body .hdr-client-name{ max-width:170px; }
  /* Out of the column and centred on the whole chip: as a third flex child it
     would sit UNDER the name and make the chip three lines tall. */
  body .hdr-event-chip .hdr-account-caret,
  body .hdr-client-chip .hdr-account-caret{
    position:absolute; right:11px; top:50%; transform:translateY(-50%);
    margin:0; font-size:10px; line-height:1;
  }
  /* The support line comes down with the pair — same register, and the tighter
     tracking is what lets the whole sentence read at 1280 rather than at 1600.
     ⚠ 12px EXACTLY, AND IT IS A FLOOR, NOT A TASTE. This line's <strong> is the
     address in Crimson (portal-v15.css), and Crimson is a functional colour —
     scripts/portal-brand-conformance.mjs reports functional ink under
     SMALL_TEXT_PX (12) on a pale ground as a finding. 11.5px measured fine and
     would have been a red on the next conformance run. */
  body .hdr-support{ font-size:12px; letter-spacing:.04em; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   10 · THE ONE LEFT EDGE — WHERE THE TEXT STARTS  (2026-08-25)
   ───────────────────────────────────────────────────────────────────────────
   Owner, on screenshots of two different rooms: "i dont love that the left
   formatting throughout the client portal changes everytime. can we make one
   law about the left formatting? id prefer everything cohesive and equal, and
   left formatting always the same."

   THE LAW, in one sentence: in every room, at every width, the first character
   of text begins at the page gutter — and a band gets there in exactly one of
   two ways, never both and never neither.

     (a) A BARE BAND — a stretch of page with no surface of its own — has no
         interior at all and inherits the panel's gutter (§7 above).
     (b) A FULL-BLEED SURFACE — a hero, a white section, a ruled band that
         paints something — cancels the panel gutter with a negative margin and
         supplies the SAME value as its own interior.

   Both land the text on one line. What is forbidden is doing both, which is
   what almost every defect here turned out to be: the panel's gutter plus a
   private copy of the same number, i.e. text at exactly 2 × gutter.

   ⚠ THE PATTERN WAS ALREADY IN THE FILE, IN #nextStepCard, AND THAT IS THE ONE
   THAT WAS RIGHT. Measured at 1440 before this block:
       #nextStepCard   box    0..1440   .nsc-inner padding-left 64  → text  64  ✓
       #conceptHero    box   64..1376   .ds-hero-body    "     64  → text 128  ✗
   The Next Step hero is not a child of .tab-panel, so the gutter never reached
   it and its own 64px interior IS the gutter. .ds-hero is a child, so it got
   the gutter twice. Same component language, opposite result, and the second
   is what the owner screenshotted. So case (b) is not a new invention — it is
   the existing correct hero generalised to the sections that missed it.

   ⚠ DO NOT "FIX" THIS BY DELETING THE INNER PADDING. That was this block's
   first attempt and it was wrong, caught by rendering it rather than by
   reasoning: §6 draws THE PLATE FRAME — a hairline at inset:14px — on
   .ds-hero, .rhb-hero, .bb-hero and #nextStepCard, and that frame assumes the
   hero has an interior to be inset from. With the interior at 0 the heading
   ran straight through its own frame, the exact failure §6's own comment
   already warns about ("A plate frame that its own content crosses is worse
   than no frame") and had already fixed once at 375px. Pixel-checked: a solid
   rule at x=78 with "Your moodboards," starting at x=64 and crossing it.
   The interior is load-bearing. Cancel the OUTER gutter, never the inner one.

   ⚠ LEFT ONLY — the right margin is deliberately untouched. At ≥1025px the
   panel reserves its right side for the At a Glance rail (padding-right:420px,
   portal.css). A symmetric negative margin would slide these white sections
   under a position:fixed rail. The owner's law is about the left edge; the
   right side already has a reason to be where it is.

   MEASURED RESULT — node scripts/portal-format-shots.mjs, ── BAND LEFT EDGES:
   before, 9 bands off the gutter at 1440 (six at exactly +64, .bsp-inner
   drifting 146→274→354 as the window widened); after, every band on the
   gutter at 1440 / 1280 / 1024 / 768 / 390, and horizontal overflow still 0
   of 50 room×viewport combinations.

   RE-MEASURE, DO NOT TRUST THIS COMMENT:
     node scripts/portal-format-shots.mjs
     node --test test/portal-left-edge.test.mjs
   ═══════════════════════════════════════════════════════════════════════════ */

/* (b) — the full-bleed surfaces. The section stops the page adding a gutter
   outside it, and its interior supplies the same one inside. Both halves are
   needed and both are stated in terms of --v10-gutter.

   ⚠ THE INTERIOR MUST BE THE TOKEN, NOT 64. This is what the first version of
   this block got wrong, and only a multi-breakpoint measurement showed it: the
   interiors are written `padding:52px 64px` in portal.css, a fixed 64 at every
   width, while --v10-gutter steps 64 → 32 → 20 down the breakpoints. So
   cancelling the outer gutter fixed 1440 and 1280 and left #conceptHero and
   #creativeDirectionBanner at +32 on a small laptop and a tablet — a defect
   that only exists between 641px and 1024px, which is exactly the band a
   desktop-only check never visits. Stated once, in the token, it holds at every
   width by construction.

   !important on the interiors is required, not preferred: .item-card (:4019),
   .items-sec-hdr (:4013) and .docs-ledger's siblings all declare their padding
   !important in portal.css. */
body .tab-panel.active > .ds-hero,
body .tab-panel.active > .creative-direction-banner,
body .tab-panel.active > .items-section,
body .tab-panel.active > .docs-section,
body .tab-panel.active > #briefSubmittedPanel{
  /* !important because .items-section declares `margin:0!important`
     (portal.css:4012); without it the section keeps the page gutter AND its own
     interior, which is the exact 2 × gutter this block exists to remove. */
  margin-left:calc(-1 * var(--v10-gutter)) !important;
}
body .tab-panel.active .ds-hero-body,
body .tab-panel.active .cdl-card,
body .tab-panel.active .cdl-section-hdr,
body .tab-panel.active .items-sec-hdr,
body .tab-panel.active .item-card,
body .tab-panel.active .docs-ledger,
body .tab-panel.active .docs-section-hdr{
  padding-left:var(--v10-gutter) !important;
}

/* ⚠ AND THE TWO THAT CENTRE THEMSELVES — the "changes everytime" half.
   .bsp-inner is max-width:860px + margin:0 auto, and #homeTimelineCard and its
   three siblings carry padding-left:max(64px, calc((100% - 732px)/2)). Both are
   functions of the viewport, so their left edge MOVED while every other band
   held still: measured 146px at 1024, 274px at 1280, 354px at 1440. They keep
   their max-width — a measure cap is the right way to hold a line of prose to a
   readable length — but they are anchored at the gutter instead of centred,
   exactly as .nsc-inner was in §6 and .billing-container was in portal.css.
   !important is required, not preferred: portal.css:4394 and :4398 declare both
   with !important themselves, so an ordinary declaration loses at any
   specificity.

   ⚠ .cdl-card IS HERE BECAUSE ITS ANCHORING LIVED IN THE MARKUP, NOT THE CSS.
   portal.css:3327 still says `margin:0 auto`; what actually held it left was an
   inline `style="margin-left:0;margin-right:auto"` on the div in
   client-portal.html:3053. So the letter looked correct in the running portal
   while the STYLESHEET said centre — and anyone deleting that inline attribute,
   or rendering this card from a different template, would get the drifting
   version back with nothing to catch it. Found by test/portal-left-edge.test.mjs,
   which mounts the real nesting without the inline style and measured the card
   at gutter+250 / +36 / +0 across the three widths. The rule now lives where
   the law is. */
body .tab-panel.active .bsp-inner,
body .tab-panel.active .cdl-card{
  margin-left:0 !important;
  margin-right:auto !important;
}
body .tab-panel.active .bsp-hero,
body .tab-panel.active .bsp-status-bar,
body .tab-panel.active .bsp-readonly-bar,
body .tab-panel.active #homeTimelineCard{
  padding-left:var(--v10-gutter) !important;
}

/* ── 9p · ON A PHONE THE HEADER IS TWO ROWS (2026-09-10, owner decision EVD-166, check 02-03) ──────
   MEASURED on production's code signed in as the walkthrough client: at 320–430px the event switcher
   and the Account menu (My Events · Billing · Help · Sign Out) sat PAST the right edge in Chrome,
   WebKit and Firefox, and the Account menu again at 480–540px and 601–700px. Nothing scrolled
   sideways — .portal-hdr clips — which is why every overflow gate stayed green; and
   test/event-switcher-mobile.test.mjs measures the switcher ALONE in a fixture, never beside the
   logo, the Stop messages control (§9A.1, on every width) and the bell.
   Two rows rather than smaller controls: on top the logo · Stop messages · bell; below, the event
   (it takes the room) · the Account menu. .hdr-meta becomes display:contents so its children place on
   the header's own grid. Previewed at 320/360/390/430/480/540/600/700px: every control on screen, no
   overlaps, both dropdowns open inside the screen; the header grows ~22px, so the phone bands of
   --hdr-reserve (portal.css) are re-measured by test/portal-header-reserve.test.mjs.
   701px and wider: untouched. */
@media (max-width:700px){
  body .portal-hdr .hdr-top{grid-template-columns:minmax(0,1fr) auto auto;grid-template-areas:"logo stop bell" "event account account";column-gap:8px;row-gap:10px}
  body .portal-hdr .hdr-logo{grid-area:logo;justify-self:start}
  body .portal-hdr .hdr-meta{display:contents}
  body .portal-hdr #hdrRef{display:none}
  body .portal-hdr #portalCommsStop{grid-area:stop}
  body .portal-hdr #inboxBell{grid-area:bell}
  body .portal-hdr #hdrEventSwitcher{grid-area:event;min-width:0;justify-self:stretch}
  body .portal-hdr #hdrEventSwitcher .hdr-event-chip{min-width:0;border-right:1px solid var(--v10-rule-edge, var(--border))}
  body .portal-hdr #hdrEventSwitcher .hdr-event-name{max-width:none;min-width:0}
  body .portal-hdr #hdrAccountSwitcher{grid-area:account;justify-self:end;min-width:0}
  body .portal-hdr .hdr-client-chip{min-width:0}
  body .portal-hdr .hdr-account-dropdown{left:auto;right:0}
  body .portal-hdr #hdrEventSwitcher .hdr-event-dropdown{left:0;right:auto;min-width:min(260px, calc(100vw - 28px))}
}
