/* spec: specs/features/root-login-membership.md
   task: rlm-025-test-playwright-a11y-visual
     specs/tasks/root-login-membership/025-test-playwright-a11y-visual.md
   feature: specs/features/root-login-membership.md (ac-16 — no horizontal layout break)
   contracts: (none)
   design: specs/design.md#accessibility, specs/design/core/
   architecture: specs/architecture.md#design-system-relationship
   generated-by: spec-implementer
   ============================================================
   Vetyr Design System — PLATFORM DIALECT
   ------------------------------------------------------------
   The platform analogue of kontroll's module-dialect sheet
   (kontroll.css): the ONE slot where the platform layer is
   allowed to author scoped CSS, linked LAST in platform/base.html
   AFTER the vendored core cascade (core.css -> patterns.css ->
   components.css). It redefines NO core token or class and holds
   NO hardcoded colour, size, font, spacing, or radius literal.

   ac-16 — no horizontal layout break on the root request surface,
   under nb and en. The box-model normalization that keeps the
   full-width email input from rendering its padding+border OUTSIDE
   the 100% box (a horizontal scrollbar, scrollWidth > clientWidth,
   in the longer nb locale) is now declared ONCE on the core control
   rule (`.input, .select, .textarea { box-sizing: border-box }` in
   specs/design/core/components.css, inherited through the vendored
   core cascade). The identity form controls carry those DS classes,
   so they inherit it — this dialect sheet no longer needs the earlier
   per-surface `main { box-sizing }` stopgap and stays intentionally
   empty of declarations.
   ============================================================ */

/* spec: specs/features/management-surface-framework.md
   task: msf-005-shell-base (shell layout)
     specs/tasks/management-surface-framework/005-shell-base.md
   feature: specs/features/management-surface-framework.md (ac-4, ac-5, ac-12)
   design: specs/design.md#the-management-shell, specs/design/core/
   architecture: specs/architecture.md#design-system-relationship
   generated-by: spec-implementer
   ============================================================
   MANAGEMENT-SHELL LAYOUT SCAFFOLD (platform dialect)
   ------------------------------------------------------------
   The surface COMPOSITION classes platform/manage/base.html +
   _nav.html emit but the vendored core does NOT own: the skip
   link (.skip), the nav|main two-column grid (.shell), the main
   scroll region (.adm-main), the content padding (.adm-body), and
   the two native <details> banner disclosures (.org-switcher /
   .user-menu with their .org-switch-menu / .user-menu-list pop-out
   lists) plus the optional .banner-context slot. Per the DS
   precedent a shell COMPOSITION is a DIALECT, not core — kontroll
   authors its console grid in kontroll.css; the management shell
   authors its grid here. This sheet redefines NO core token/class
   and composes only vendored `var(--…)` tokens. The only raw values
   are the DS's sanctioned STRUCTURAL constants (a fixed nav-rail
   WIDTH, like a breakpoint; the 1px hairline; a z-index layering
   ordinal; viewport units) — never a themeable colour / font / size
   / spacing / radius literal.
   Scope note: platform.css is ALSO linked by the identity base
   (li-051), so the full-height app-shell rule is scoped by
   `:has(.shell)` — present only on the management shell — leaving
   the identity request surfaces (no `.shell`) in normal page flow.
   The responsive «☰ Meny» narrow-collapse is msf-011 (not added
   here — this is only the base ≥lg layout + disclosure base styles).
   ============================================================ */

/* Skip link — the FIRST focusable element on the page (ac-4): visually hidden until it
   takes focus, then a solid Papir chip pinned top-left above the banner. Reuses the DS's
   own visually-hidden clip idiom (components.css `.combo-lbl-sr`), so it needs no
   off-screen magic offset. */
.skip {
  position: absolute;
  left: var(--space-2);
  top: var(--space-2);
  z-index: 200;                 /* above the banner while focused (layering ordinal) */
  width: 1px;                   /* clipped to nothing until focus (the DS clip idiom) */
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  padding: var(--space-2) var(--space-3);
  background: var(--papir);
  color: var(--navy);
  font-weight: var(--font-weight-bold);
  font-size: var(--text-sm);
  border-radius: var(--radius);
  text-decoration: none;
  white-space: nowrap;
}
.skip:focus {
  width: auto;
  height: auto;
  overflow: visible;
  clip-path: none;
}

/* Full-height app shell — the page body never scrolls; the banner stays put and the
   content scrolls WITHIN `.adm-body` (mirrors the kontroll console app-shell). Scoped to
   `:has(.shell)` so ONLY the management shell adopts it; the identity surfaces that also
   link this sheet keep their normal page flow. */
body:has(.shell) {
  height: 100svh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

/* The nav | main two-column grid — the persistent nav landmark beside the main landmark
   (ac-4). The 248px nav rail is a fixed structural width (like a breakpoint); the content
   track is fluid. */
.shell {
  flex: 1 1 auto;
  min-height: 0;                /* shrink to the bounded shell height so children scroll */
  display: grid;
  grid-template-columns: 248px 1fr;
}

/* MAIN landmark — the content region. It clips its own overflow and delegates the scroll
   to `.adm-body`, so the content-header stays put while the body scrolls beneath it. */
.adm-main {
  min-width: 0;                 /* let the fluid track shrink rather than force overflow */
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--void);
}

/* Content padding + the actual scroll region under the fixed content-header. */
.adm-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: var(--space-6);
}

/* ── Banner <details> disclosures (.org-switcher · .user-menu · .banner-context) ──
   PROMOTED to the SHARED design core (specs/design/core/components.css, beside the
   `.k-*` banner chrome) so the kontroll console — which links components.css but NOT
   this platform dialect — composes the SAME org-switcher + tier user-menu disclosures
   from one source. Authored here no longer; the chrome is one-source in the core. */

/* spec: specs/features/login-issuance.md
   task: li-079-auth-shell-optical-seat
     specs/tasks/login-issuance/079-auth-shell-optical-seat.md
     — the BLOCK AXIS: the optical seat. This task rewrote this stanza and the
       block-axis declarations below; nothing else about the shell moved.
   prior-task: li-076-auth-shell-bounded-centred-branded
     specs/tasks/login-issuance/076-auth-shell-bounded-centred-branded.md
     — authored this shell: the bound, the gutter floor, the inline centring, the
       mark's alignment and the panel rhythm. All of that is unchanged here and
       li-076 does NOT re-run: it was correct against the criterion it was built
       against, and the criterion moved (owner ruling, `1a09cd5`).
   feature: specs/features/login-issuance.md
     contributes-to: ac-50 (the bound), ac-51 (the gutter floor, the inline
                     centring, and (a) the OPTICAL SEAT on the block axis),
                     ac-53 (all THREE values are core TOKENS; this is dialect
                     composition adding no core component class)
   contracts: (none — task declares contracts: [])
   design: specs/design.md § "The sign-in surface — a bounded column on the Navy field",
           specs/design/core/core.css (--measure-form / --gutter-viewport /
           --optical-centre — the seat's derivation and its expiry conditions live
           with the token, not here)
   architecture: specs/architecture.md#design-system-relationship
   generated-by: spec-implementer
   generated-at: 2026-07-29T18:10:00Z
   ============================================================
   AUTH-SHELL LAYOUT (platform dialect)
   ------------------------------------------------------------
   The auth surfaces (the org-scoped and root magic-link request, the claim,
   step-up, the org picker, the no-access page — every surface extending
   platform/base.html) are ONE bounded column, seated on the full Navy field.
   They are NOT console pages. Before this rule they rendered an unbounded panel
   in a `<main>` with no measure, no gutter and no placement: ~1900px of email
   field at 1920px, and a panel flush to both screen edges at 390px.

   THREE VALUES, THREE DIFFERENT JOBS, ALL OWED. `--measure-form` CAPS the column
   at wide viewports; `--gutter-viewport` FLOORS its distance to the screen edge
   at narrow ones; `--optical-centre` PLACES it on the block axis. Capping alone
   leaves the phone flush to the edge; the gutter alone leaves the desktop field
   ~1900px wide; both together still leave the column sitting wherever the block
   axis happens to drop it. None of the three is ever re-typed here (ac-53
   clause 1), and the ANTI-ALIAS TRAP IS DIFFERENT FOR THE THIRD, because it is a
   different kind of value: the two LENGTHS must not be aliased to a `--space-*`
   step, and the unitless FRACTION must not be aliased to a `--leading-*` step —
   the only other unitless family in core. A leading answers "how tall is a line";
   the seat answers "where does a block sit". They are neighbours in spelling and
   in nothing else. All three are core tokens precisely so a cross-surface
   guarantee is not wired to a local rhythm that could be retuned underneath it.

   THE BLOCK AXIS IS A SHARE OF THE FREE SPACE, NEVER A FRACTION OF THE VIEWPORT
   (ac-51 (a)). Of the FREE block space — the viewport's block size MINUS the
   group's RENDERED block size — `--optical-centre` sits above the group and the
   remaining `1 - --optical-centre` below. Spelled instead as a fixed fraction OF
   THE VIEWPORT it would put the group's top edge ABOVE the viewport's top — not
   merely scrolled-for but unreachable — for any group taller than 76.4% of the
   viewport, which is INSIDE the short branch where ac-51 (b) still forbids a
   scrollbar. Against the free space the offset vanishes as the group grows, so
   the seat's correct limit IS the geometric midpoint and the short and tall
   branches meet continuously.

   THE BLOCK PADDING WAS REMOVED HERE, AND WHAT WENT WITH IT IS RECORDED RATHER
   THAN DROPPED SILENTLY. This box carried `padding-block: var(--gutter-viewport)`
   for the TALL branch — so "a column taller than the viewport never opens flush
   against the top edge either". Padding on THIS box participates in the split, and
   a symmetric inset seats the group `(1 - 2 * --optical-centre) = 0.236` of it LOW:
   20px of gutter lands the group 4.7px low against a 2px tolerance. It is a
   composition error, not measurement noise, and the tolerance must not be widened
   to swallow it. It was invisible until now only because the OLD split was 1 : 1,
   where the two paddings cancel exactly; a 1 : phi split does not cancel.
     THE RULED PROPERTY WINS: the seat is ac-51 (a), and the tall-branch inset is a
   property the feature raises and EXPLICITLY DECLINES TO RULE ON ("whether the TALL
   branch owes a minimum block inset … Open finding"). So the inset is GONE and an
   overlong picker now opens flush against the top edge on the tall branch. That is
   an owner decision owed, not a decision taken here.
     IT WAS NOT BOUGHT BACK, and the near-misses that look like they would keep it
   are named so the next reader does not re-introduce one: a compensating offset
   that cancels the padding on one branch is a hand-computed constant wearing CSS
   and would be spelled as exactly the bare fraction ac-53 forbids; block padding or
   block margins on `.auth-shell` re-enter the same 0.236 error (margins) or hide
   the inset INSIDE the measured group box, where the gate passes while the visible
   ink still sits 4.7px off the seat (padding); and a `min-block-size` on the top
   spacer binds whenever the free space is under ~52px, breaking the seat on part of
   its own branch. A means that genuinely does not participate in the split would be
   welcome — none was found, and the search is recorded so it is not silently redone.

   NO BREAKPOINT, AND THAT IS THE POINT. Every clause of ac-51 this sheet answers is
   unconditional: the padding floors the gutter at EVERY width, the max-inline-size
   and the inline auto margins cap and centre at EVERY width, and a share of the free
   space is width-independent by construction. ac-51's narrow / block-placement cell
   is EMPTY — an open finding the owner did not rule, not a prohibition — so there is
   nothing to switch off below 420px, and a media query here would be a breakpoint
   literal with no design meaning (drift, ac-14/ac-25).

   SCOPE. The management shell links this same sheet, so — exactly as its own
   full-height rule is scoped `body:has(.shell)` — the auth shell's page-level
   rule is scoped `body:has(.auth-shell)`. A management surface carries no
   `.auth-shell` and is untouched; a class-token match is exact, so `.auth-shell`
   is never matched by `.shell`.

   NO NEW CORE COMPONENT CLASS (ac-53 clause 3). Nothing is added to the vendored
   core: the panel is `.card`, the entry `.field` / `.input`, the actions `.btn` /
   `.submit-row`, status `.st-shape`, the mark `.k-brand` / `.logo`. The one new
   name is the shell container itself; the one rule that reaches a core class
   (`.k-brand`) is SCOPED to it and re-declares no bare core rule. The seat adds no
   name at all: it is carried by two generated spacers on the shell's own page box.

   The only raw values are the DS's sanctioned STRUCTURAL constants: `100svh` (a
   viewport unit) and `100%` (fill the containing block), plus two UNITLESS numbers
   that are neither lengths nor design values — the `0` flex-basis (the spacers
   contribute no size of their own) and the `1` in `calc(1 - var(--optical-centre))`
   (the whole of the free space, from which the seat's complement is derived rather
   than re-typed). Every other value is a vendored `var(--…)` token.
   ============================================================ */

/* The page box the column sits on. Full viewport block size, so a short surface has
   free space to be SEATED IN; the gutter is page padding, so it floors the distance
   from every content box to the screen edge rather than from one hand-picked panel.
   The Navy field itself is a page BACKGROUND fill (core.css `body`) and is exempt
   from the bound — full-bleed by design. The column on it is what is bounded.
   `box-sizing: border-box` is CORRECT BUT NO LONGER LOAD-BEARING, and the difference
   is worth spelling out because it used to be the other way round. While this box
   carried `padding-block`, content-box would have measured the 100svh AGAINST THE
   CONTENT BOX and added the two block paddings on top, so every surface whose content
   fits would still have scrolled by exactly that padding. The block padding is gone
   (see the stanza above), and `padding-inline` does not enter the block-size
   calculation at all — so content-box and border-box now resolve this box's block size
   identically. The declaration stays because it is still the right thing to say about
   this box, and because a future block-axis inset would need it; nothing in the sheet
   currently depends on it. The core declares border-box on the form controls only, not
   universally, so this box says it for itself. */
body:has(.auth-shell) {
  box-sizing: border-box;
  min-block-size: 100svh;
  display: flex;
  flex-direction: column;
  padding-inline: var(--gutter-viewport);  /* ac-51's floor, on the axis it names */
}

/* THE SEAT (ac-51 (a)) — the free block space, split 1 : phi by two generated spacers.
   `flex-grow` distributes SHARES OF THE POSITIVE FREE SPACE, which is exactly the
   quantity the criterion is stated against, and exactly the degradation property the
   block auto margins it replaces had: on the tall branch there is no positive free
   space, both spacers resolve to zero, and the group opens at the top and scrolls
   normally — ac-51's two branches, still with NO branch in the CSS, and still meeting
   continuously at the boundary where the free space reaches zero and the seat becomes
   the geometric midpoint.
   The two factors sum to 1, so the whole of the free space is seated: the group's top
   edge lands at `--optical-centre` of it. The lower share is DERIVED from the token
   (`calc(1 - …)`) rather than written as a second fraction — the complement re-typed
   as its own decimal is the same drift ac-53 forbids for the seat, one subtraction
   removed, and it would silently stop tracking the token if the token ever moved.
   The spacers are generated content with EMPTY content, so they add no markup, no
   class and no node to the accessible tree — the seat is composition, not structure. */
body:has(.auth-shell)::before,
body:has(.auth-shell)::after {
  content: "";
  flex-basis: 0;   /* pure free-space shares — the spacers carry no size of their own */
}
body:has(.auth-shell)::before { flex-grow: var(--optical-centre); }
body:has(.auth-shell)::after  { flex-grow: calc(1 - var(--optical-centre)); }

/* The content group — the brand mark + the panel, the box ac-51 measures.
   `margin-inline: auto` is the INLINE half of what `margin: auto` used to do here, and
   only that half: on the cross axis the auto margins split the remaining width equally,
   which is ac-51's inline centring. The block half is deliberately NOT auto any more —
   auto margins split free space EQUALLY and cannot be weighted, which is precisely the
   geometric centring the owner's ruling replaced; leaving them auto would also put two
   mechanisms in competition for one quantity, since main-axis auto margins consume
   whatever the spacers above did not. `inline-size: 100%` lets the group take the full
   gutter-inset width up to the cap, so the bound binds at wide viewports and the gutter
   binds at narrow ones. Nothing here insets the group's own contents: block padding on
   THIS box would sit inside the rendered block size ac-51 measures, where it would pass
   the gate while pushing the visible ink off the seat. */
.auth-shell {
  inline-size: 100%;
  max-inline-size: var(--measure-form);
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

/* The mark is content-sized and centred over the column (rather than stretched to the
   full measure and hugging its leading edge). Scoped composition of a core class —
   it re-declares no bare `.k-brand` rule and changes nothing in the two shells that
   already render the mark. */
.auth-shell > .k-brand {
  align-self: center;
}

/* The panel column keeps the shell's own rhythm between the heading and the panel.
   The heading is a CONTENT BOX inside the bound — it is not a console band, which is
   why the auth surfaces render it directly instead of in a full-bleed `.page-head`. */
.auth-shell > main {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}
