/* ============================================================================
 * rotate-gate.css — the landscape-PHONE gate (owner ruling 2026-08-30).
 *
 * "Don't allow sideways on iPhone." Landscape phone ceases to be a supported
 * presentation of the portal, which SUPERSEDES the iOS audit's F-IOS-4
 * deferral (the record header overrunning in landscape no longer wants a
 * landscape design — it wants no landscape).
 *
 * WHY THIS IS ITS OWN SHEET, AND NOT A SECTION OF shell.css. The ruling says
 * "the portal", and the portal is not only the pages that extend base.html:
 * NINE templates in this app own their own <head>, and EIGHT of them carry the
 * gate. Six are the AUTH FAMILY — `/login` above all, the highest-traffic phone
 * surface there is and the first screen an installed app lands on — plus
 * `/offline` and `/setup`. (The ninth, `/onboarding`, is the one registered
 * exclusion; the reason is at the register in
 * tests/styleguide/test_rotate_gate.py.)
 *
 * Counted from disk rather than from memory, because the split is the argument:
 * the six auth templates link NEITHER components.css NOR shell.css, `/setup`
 * links components.css but not shell.css, and only `/offline` links both. So a
 * rule in shell.css is unreachable from SEVEN of the eight, and one in
 * components.css from SIX. The gate first shipped in shell.css and left
 * `/login` ungated, which is the defect this sheet fixes.
 *
 *   ONE PRIMITIVE, NINE CONSUMERS (base.html + the eight), ZERO DUPLICATION.
 *
 * The gate is declared HERE and nowhere else (pinned tree-wide by
 * tests/styleguide/test_rotate_gate.py), its markup is the ONE partial
 * `templates/partials/rotate_gate.html`, and every document that can render on
 * a phone links this sheet and includes that partial. The covered set is
 * DERIVED from disk by the same test, not hand-listed here.
 *
 * IT IS SELF-SUFFICIENT GIVEN tokens.css. That is the requirement the auth
 * family imposes and it is why the pane's anatomy is re-declared below,
 * SCOPED to `.tp-rotate-gate`. Read the scoped block's own banner before
 * calling that a second implementation — it is one definition of ONE surface,
 * loaded identically by every consumer, pinned value-for-value against
 * the state family it mirrors.
 *
 * NO JAVASCRIPT. The div is always in the DOM and its visibility is the media
 * query alone (D-085 bans inline JS; a JS gate would also fail with JS off and
 * flash the broken layout before it ran).
 *
 * THE SCOPING IS THE WHOLE DESIGN — four conditions, each load-bearing:
 *
 *   (orientation: landscape)  the presentation being refused.
 *   (max-height: 500px)       PHONES ONLY. An iPhone 17 Pro in landscape is
 *                             338-402pt tall, a 17 Pro Max 440pt and an SE
 *                             ~375pt; the SHORTEST iPad — mini, landscape — is
 *                             744pt. 500 sits in the empty middle of that gap,
 *                             so no iPad is ever caught and no phone is ever
 *                             missed. iPads keep their designed landscape
 *                             presentation untouched: at 940+ that is the
 *                             DESKTOP shell, measured correct on device
 *                             2026-08-30. That also settles the kiosk question
 *                             — Launchpad is a TABLET surface (D-164), so the
 *                             height guard excludes it by construction rather
 *                             than by a URL exception.
 *   (hover: none)             \ together, a TOUCH primary input. A short
 *   (pointer: coarse)         / DESKTOP window — a half-height browser at
 *                             1440x480 — is landscape and under 500px tall,
 *                             and gating it would be a portal-wide regression
 *                             on a device the ruling says nothing about.
 *
 * IT DELIBERATELY DOES NOT TEST display-mode: the ruling is about the phone
 * held sideways, not about how the page was launched, so the gate applies in
 * an installed standalone window AND in a Safari tab. The manifest's
 * `orientation: "portrait"` (app/core/pwa.py) is the other half of the ruling
 * — it stops the rotation where the OS honours it, and this stops the
 * presentation where it does not (iOS web clips ignore it).
 *
 * WHAT IT IS NOT. It is not focus management: the page behind stays in the
 * accessibility tree and stays tabbable, and `aria-hidden="true"` on the pane
 * keeps the gate itself silent in both states. That is deliberate — the gate
 * answers a VISUAL overrun, a screen-reader user is not blocked by one, and
 * `inert` on the shell would need the JavaScript this design does without.
 *
 * INERT UNTIL THE QUERY MATCHES. Exactly ONE rule sits outside the media
 * block, and it is `display: none`. Everything else — the layer, the pane, its
 * type — is inside, so linking this sheet onto a pixel-matched standalone
 * surface adds no rule that can paint in portrait, on desktop, or on a tablet.
 *
 * MEASURED IN REAL WEBKIT (2026-08-30, `matchMedia` + `getComputedStyle` per
 * condition, EIGHT profiles): gate on iPhone 17 Pro landscape 874x402, 17 Pro
 * Max landscape 956x440 and SE landscape 667x375, covering the whole viewport;
 * NO gate on portrait phone 402x874, iPad mini landscape 1133x744, iPad Pro 11
 * landscape 1194x834, a 1440x480 desktop window or a 1440x900 desktop. On both
 * iPads the height guard was the ONLY condition that failed, and on the short
 * desktop window the hover/pointer pair was — so deleting either guard gates a
 * working, designed presentation. The table is in
 * tests/styleguide/test_rotate_gate.py's docstring.
 *
 * THE SECOND HALF OF THAT RUN IS WHAT SETTLES THE MIRROR, and it is the reason
 * to keep the numbers here. The probe drove the REAL rendered `/auth/login`
 * (tokens + brand + login + this sheet — no components.css, no shell.css) and
 * the REAL rendered `/offline` (tokens + components + shell + this sheet), and
 * the two agree on EVERY measured value: display grid, position fixed, z-index
 * 600, the pane's rect equal to the viewport, resolved family Inter (not the
 * UA serif — that is what the `font-family` declaration below buys), title
 * 15px/700 Inter, mark 40x40 at r11 with a 16px glyph on rgb(238,241,245)
 * (`--tp-hairline-2`), block height 116px inside a 354px content box, not
 * scrolling. One gate, one rendering, with or without the component sheet.
 *
 * ⚠ NO AUTOMATED HARNESS EXECUTES THIS QUERY, and a previous version of this
 * comment claimed one did. It said "A LANDSCAPE-PHONE HARNESS NOW SEES THIS.
 * tests/test_safe_area_sweep.py's landscape profile and the iOS-simulator
 * verify lane drive a real touch phone sideways, so from here on they
 * photograph the gate rather than the page." The first half is FALSE and was
 * written without checking: every `browser.new_context(...)` in that file
 * passes `viewport=` ALONE (lines 1104, 1181, 1378, 1468, 1599 — the landscape
 * cell — and 1759), and `has_touch` / `is_mobile` appear nowhere in the test
 * tree at all. A Playwright context without them reports `hover: hover` and
 * `pointer: fine`, so two of the four conditions can never match and that
 * sweep still photographs the PAGE. The iOS-simulator lane (`/verify-ui-by-
 * emulator`) does drive genuine WebKit on a real touch profile and WOULD see
 * the gate — but it is an on-demand macOS-only lane, not something the suite
 * runs. So: the pins on this sheet are SOURCE-LEVEL, the 2026-08-30 WebKit
 * measurement above is the executed evidence, and wiring a touch landscape
 * profile into the sweep is unclaimed work rather than a thing already done.
 * ========================================================================= */

/* THE DEFAULT STATE, and the only rule outside the query. */
.tp-rotate-gate {
  display: none;
}

@media (orientation: landscape) and (max-height: 500px) and (hover: none) and (pointer: coarse) {
  .tp-rotate-gate {
    position: fixed;
    inset: 0;
    z-index: 600; /* --tp-z-rotate-gate. A PLAIN INTEGER, like every other
                   * z-index in the portal: tokens.css names and documents the
                   * ladder, the sheet declares it, and the ladder sweeps parse
                   * digits — a var() here would drop this surface out of that
                   * audit silently. */
    display: grid;
    place-items: center;
    /* Very short landscape phones (the 338pt class) with large text can still
     * overflow the message block, and a gate the reader cannot finish reading
     * is worse than the layout it replaced. */
    overflow-y: auto;
    background: var(--tp-bg);
    /* THE TYPE CONTEXT IS THE GATE'S OWN. On a page that extends base.html the
     * body already carries the UI face from components.css; on the five
     * standalone surfaces that link neither components.css nor shell.css it
     * does NOT (auth.css and login.css set the face on their own page box,
     * which the gate is a sibling of), so a gate that inherited would render
     * the message in the UA's serif on exactly the screens this sheet exists
     * to reach. */
    font-family: var(--tp-font-ui);
    color: var(--tp-ink);
    line-height: 1.5;
    /* The insets, per the one-operator-per-axis rule: additive on the vertical
     * axis, max() on the horizontal. Landscape is the orientation where the
     * horizontal one matters — the Island moves to a SIDE and reports ~59px
     * there — and this pane is the full viewport, so it owns both edges. */
    padding-top: calc(var(--tp-space-24) + var(--tp-safe-top));
    padding-bottom: calc(var(--tp-space-24) + var(--tp-safe-bottom));
    padding-left: max(var(--tp-space-24), var(--tp-safe-left));
    padding-right: max(var(--tp-space-24), var(--tp-safe-right));
  }

  /* ---- The pane, SCOPED — one definition of one surface --------------------
   *
   * The MARKUP is not re-implemented: the partial renders `state_chrome()`,
   * the shared state block, so the gate composes the same anatomy every edge
   * state in the app composes. What is declared below is that anatomy's
   * PRESENTATION, scoped to `.tp-rotate-gate`, and it is here for a reason
   * that is structural rather than stylistic:
   *
   *   `.tp-state` / `.tp-tile` live in components.css, which SIX of the eight
   *   gated documents — the whole auth family — deliberately do not link:
   *   components.css opens with an element-level reset (`body`, `a`, `h1`-`h6`,
   *   `button, input, select, textarea`), and pulling that onto pixel-matched
   *   standalone surfaces is a design call, not an implementation detail.
   *   `.tp-gp` lives in shell.css, which only `/offline` links.
   *
   * So the choice was never "reuse or duplicate" — it was "reuse on the two
   * documents that already load the families and leave six ungated", or "one
   * scoped definition that renders IDENTICALLY on all of them". These rules
   * are (0,2,0) and win over both
   * families wherever those families ARE loaded, so the gate looks the same on
   * `/claims` as on `/login` regardless of link order. That is the opposite of
   * the per-page divergence the 2026-07-24 owner directive bans: there is one
   * gate, defined once, and no page overrides it.
   *
   * ⚠ THE VALUES ARE PINNED, NOT COPIED-AND-FORGOTTEN.
   * tests/styleguide/test_rotate_gate.py reads `.tp-state`, `.tp-state-icon`,
   * `.tp-state-title`, `.tp-state-body` and `.tp-tile` OUT OF components.css
   * and asserts each mirrored declaration below still matches, with the two
   * deliberate divergences held in a register that carries its reason. Change
   * the state family and this sheet fails until it moves with it.
   *
   * THE 40 RUNG, NOT THE 64. The gate does NOT wrap the block in `.tp-gp`, so
   * it takes the state block's own card-scale metrics rather than the generic
   * page's. Two reasons, and the second is the hard one: `.tp-gp` is shell.css
   * (unreachable from seven of the eight gated documents — only `/offline`
   * links that sheet), and its page-scale title is 19px, which
   * is off §9.5's eight-rung ladder — a NEW sheet may not introduce an
   * off-ladder size (tests/styleguide/test_cohesion_token_ladder.py registers
   * them per sheet and the register may only shrink). The 40-rung block is
   * also the better fit for the viewport this surface is the only one to
   * render in: a landscape phone is 338-440pt tall before insets. */

  .tp-rotate-gate .tp-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    /* THE ONE DIVERGENCE (registered in the test): components.css pads the
     * block 44/32. This surface supplies its own inset-aware padding on the
     * layer above, and 88px of vertical padding inside a viewport that is
     * 440pt tall at its tallest is the difference between a message that fits
     * and one that scrolls. */
    padding: 0;
    margin: 0 auto;
  }

  /* The mark, mirroring `.tp-tile`'s BASE rule — which is the 40 rung and the
   * neutral tone already ("declared on the base so a tile that somehow reaches
   * the browser without a rung class lands ON the ladder rather than at
   * zero"), so this is one source, not two. `margin-bottom` is
   * `.tp-state-icon`'s own and only declaration. */
  .tp-rotate-gate .tp-state-icon {
    display: inline-grid;
    place-items: center;
    flex: 0 0 auto;
    width: 40px;
    height: 40px;
    border-radius: 11px;
    font-size: 16px;
    background: var(--tp-hairline-2);
    color: var(--tp-ink-2);
    margin-bottom: var(--tp-space-12);
  }

  /* `.tp-tile i` — whatever class list reaches the <i>, the glyph is the
   * rung's font-size (components.css's second lock against Font Awesome's own
   * sizing classes riding in on the icon name). */
  .tp-rotate-gate .tp-state-icon i {
    font-size: inherit;
  }

  .tp-rotate-gate .tp-state-title {
    font-size: var(--tp-type-card-title-size);
    font-weight: var(--tp-type-card-title-weight);
    color: var(--tp-type-card-title-ink);
    line-height: 1.3;
  }

  .tp-rotate-gate .tp-state-body {
    font-size: var(--tp-type-body-dense-size);
    font-weight: var(--tp-type-body-dense-weight);
    color: var(--tp-type-body-dense-ink);
    margin-top: 5px;
    line-height: 1.5;
    /* The state family's copy measure, unchanged. It was briefly 260 here — a
     * number chosen so the one-sentence body fits on ONE line on a landscape
     * phone rather than two — and that is exactly the invented value this repo
     * refuses: no board publishes 260, and the mirror is worth more when the
     * only thing it diverges on is the one thing it has to. Measured at 220:
     * the block is 116px inside a 354px content box, so nothing scrolls. */
    max-width: 220px;
  }
}
