/* APSIS — COSMIC WARP entry portal. Neon-on-void. Strict CSP: no inline style, all dynamic values via JS setProperty/classList. */

:root {
  --void:   #050310;
  --void-2: #0b0722;
  --ink:    #eaf0ff;
  --muted:  #9aa6d6;
  --dim:    #66709c;
  --cyan:   #37e8ff;
  --magenta:#ff2fa0;
  --violet: #8b6bff;
  --gold:   #ffd27a;

  --edge: rgba(140,160,255,.18);
  --edge-hot: rgba(55,232,255,.55);

  --ui: "Inter", "SF Pro Display", "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;

  --warp-in: 620ms;
  --ease-out: cubic-bezier(.16,.9,.3,1);
}

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
  overflow-x: hidden;
}

body {
  min-height: 100dvh;
  background: var(--void);
  color: var(--ink);
  font-family: var(--ui);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
  overflow-y: hidden;
  position: relative;
}

/* ---------- skip link ---------- */
.skip {
  position: fixed;
  left: 12px;
  top: -60px;
  z-index: 60;
  padding: 10px 16px;
  border-radius: 10px;
  background: var(--cyan);
  color: #04121a;
  font-weight: 700;
  font-size: 14px;
  text-decoration: none;
  transition: top .18s var(--ease-out);
}
.skip:focus-visible { top: 12px; outline: 2px solid var(--ink); outline-offset: 3px; }

/* ---------- cosmic void backdrop (GPU-free first paint) ---------- */
.void {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  background:
    radial-gradient(120% 80% at 50% 118%, #12082e 0%, rgba(11,7,34,0) 55%),
    radial-gradient(90% 70% at 50% -20%, #0a0a2c 0%, rgba(5,3,16,0) 60%),
    var(--void);
  pointer-events: none;
}
.neb {
  position: absolute;
  border-radius: 50%;
  filter: blur(70px);
  opacity: .5;
  mix-blend-mode: screen;
  will-change: transform;
}
.neb--cyan   { width: 46vmax; height: 46vmax; left: -12vmax; top: 8vmax;  background: radial-gradient(circle, rgba(55,232,255,.5), transparent 65%); animation: drift1 34s ease-in-out infinite; }
.neb--mag    { width: 40vmax; height: 40vmax; right: -14vmax; top: -10vmax; background: radial-gradient(circle, rgba(255,47,160,.42), transparent 65%); animation: drift2 41s ease-in-out infinite; }
.neb--violet { width: 52vmax; height: 52vmax; left: 30vmax; bottom: -26vmax; background: radial-gradient(circle, rgba(139,107,255,.4), transparent 66%); animation: drift3 47s ease-in-out infinite; }

@keyframes drift1 { 0%,100%{ transform: translate3d(0,0,0) scale(1);} 50%{ transform: translate3d(4vmax,3vmax,0) scale(1.08);} }
@keyframes drift2 { 0%,100%{ transform: translate3d(0,0,0) scale(1);} 50%{ transform: translate3d(-5vmax,4vmax,0) scale(1.1);} }
@keyframes drift3 { 0%,100%{ transform: translate3d(0,0,0) scale(1);} 50%{ transform: translate3d(-3vmax,-4vmax,0) scale(1.06);} }

.grain {
  position: absolute;
  inset: -50%;
  opacity: .05;
  background-image: radial-gradient(rgba(255,255,255,.9) .5px, transparent .5px);
  background-size: 3px 3px;
}

/* ---------- WebGL star-tunnel ---------- */
.warp {
  position: fixed;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1.1s ease;
}
html.gpu-live .warp { opacity: 1; }

/* ---------- FX overlay: the 8 gesture-tech demos ---------- */
.fxlayer {
  position: fixed;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  overflow: hidden;
}
.fxc {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  opacity: 0;
  mix-blend-mode: screen;
  transition: opacity .6s ease;
  will-change: opacity;
}
.fxc.on { opacity: .78; }

/* HUD chip naming the active gesture-tech */
.hud {
  position: fixed;
  z-index: 30;
  left: 50%;
  top: max(14px, env(safe-area-inset-top));
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 15px;
  border-radius: 999px;
  background: rgba(9, 10, 30, .72);
  border: 1px solid var(--edge-hot);
  box-shadow: 0 0 24px -6px rgba(55, 232, 255, .5);
  backdrop-filter: blur(9px);
  font-size: 12px;
  max-width: min(92vw, 460px);
  animation: hudin .3s var(--ease-out);
}
.hud[hidden] { display: none; }
.hud-g {
  font-weight: 800;
  letter-spacing: .18em;
  color: var(--cyan);
  white-space: nowrap;
}
.hud-t {
  color: var(--muted);
  letter-spacing: .02em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
@keyframes hudin { from { opacity: 0; transform: translateX(-50%) translateY(-6px); } }

/* gesture panel launcher */
.gbtn {
  position: fixed;
  z-index: 31;
  right: max(14px, env(safe-area-inset-right));
  bottom: max(14px, env(safe-area-inset-bottom));
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 14px;
  border-radius: 999px;
  border: 1px solid var(--edge);
  background: rgba(9, 10, 30, .7);
  color: var(--muted);
  font-family: var(--ui);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .08em;
  cursor: pointer;
  backdrop-filter: blur(9px);
  transition: color .2s ease, border-color .2s ease, box-shadow .2s ease;
}
.gbtn:hover { color: var(--ink); border-color: var(--edge-hot); box-shadow: 0 0 20px -8px rgba(55, 232, 255, .6); }
.gbtn-ico { width: 16px; height: 16px; color: var(--cyan); flex: none; }

/* gesture panel */
.gpanel {
  position: fixed;
  z-index: 32;
  right: max(14px, env(safe-area-inset-right));
  bottom: calc(max(14px, env(safe-area-inset-bottom)) + 48px);
  width: min(340px, calc(100vw - 28px));
  /* LAW 1 — THIS is the site where the static fallback is load-bearing, and it is `svh` INSTEAD of
     `dvh`, not beneath it. Unlike the app shell, this page really scrolls (`body{overflow-y:auto}` in
     the landscape block below), and `.gpanel` is fixed + bottom-anchored, so it grows UPWARD: a `dvh`
     cap would drag its top edge down/up on every toolbar retraction while the reader scrolls — a
     scroll-linked reflow of a layout box. `svh` sizes for the toolbar-out worst case, so it always
     FITS and never moves; the panel scrolls internally either way. (`70vh` resolved to `70lvh`, i.e.
     too generous — that was the original defect.)
     The `min(70%, 520px)` line above it is the live no-`dvh`/`svh` fallback: `.gpanel` is
     `position:fixed` with `top:auto`, so its containing block is the initial containing block and the
     percentage is a viewport percentage in every engine. It is not dead cascade — `svh` and `dvh`
     share one support baseline (2022), so only a non-viewport-unit line can ever apply. */
  max-height: min(70%, 520px);
  max-height: min(70svh, 520px);
  overflow-y: auto;
  padding: 14px;
  border-radius: 16px;
  border: 1px solid var(--edge);
  background: linear-gradient(180deg, rgba(18, 22, 52, .94), rgba(8, 9, 26, .96));
  box-shadow: 0 24px 60px -24px rgba(0, 0, 0, .9);
  backdrop-filter: blur(12px);
  animation: panelin .22s var(--ease-out);
}
.gpanel[hidden] { display: none; }
@keyframes panelin { from { opacity: 0; transform: translateY(8px); } }
.gpanel-head {
  margin: 0 0 12px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--cyan);
}
.gpanel-grid {
  display: grid;
  gap: 7px;
}
.gitem {
  display: grid;
  grid-template-columns: 26px 1fr;
  grid-template-rows: auto auto;
  align-items: center;
  column-gap: 11px;
  padding: 9px 11px;
  border-radius: 11px;
  border: 1px solid transparent;
  background: rgba(255, 255, 255, .03);
  color: var(--ink);
  font-family: var(--ui);
  text-align: left;
  cursor: pointer;
  transition: background .18s ease, border-color .18s ease, transform .12s ease;
}
.gitem svg { grid-row: 1 / 3; width: 22px; height: 22px; color: var(--cyan); }
.gitem:hover, .gitem:focus-visible {
  background: rgba(55, 232, 255, .08);
  border-color: var(--edge-hot);
  transform: translateX(2px);
  outline: none;
}
.gitem-g { font-size: 13px; font-weight: 700; letter-spacing: .02em; }
.gitem-t { font-size: 11px; letter-spacing: .01em; color: var(--muted); }

:where(.gbtn, .gitem):focus-visible { outline: 2px solid var(--cyan); outline-offset: 3px; }

/* event-horizon collapse at the moment of the jump — DARK by mandate: a bright white-out here
   is a photosensitivity hazard; the jump falls INTO the void, it never flashes */
.blowout {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: radial-gradient(circle at 50% 50%, #0b0d12 0%, #060a14 45%, rgba(6,10,20,0.86) 70%, rgba(6,10,20,0) 100%);
  opacity: 0;
  pointer-events: none;
  transition: opacity 520ms ease-in;
}
html.blowout-on .blowout { opacity: 1; transition-duration: 460ms; }

/* ---------- the gate ---------- */
.gate {
  position: relative;
  z-index: 10;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: clamp(18px, 2.6vh, 30px);
  max-width: 660px;
  margin: 0 auto;
  padding: clamp(28px, 6vw, 84px);
  transition: transform var(--warp-in) var(--ease-out), opacity var(--warp-in) ease;
}
html.crossing .gate { transform: scale(1.6) translateZ(0); opacity: 0; filter: blur(6px); }

/* brand row */
.brand {
  display: flex;
  align-items: center;
  gap: 12px;
}
.mark { width: 38px; height: 38px; color: var(--cyan); flex: none; filter: drop-shadow(0 0 8px rgba(55,232,255,.5)); }
.mark-apo { animation: apo 6s linear infinite; transform-origin: 20px 20px; }
@keyframes apo { to { transform: rotate(360deg); } }
.wordmark {
  font-weight: 800;
  font-size: 20px;
  letter-spacing: .42em;
  padding-left: .1em;
  color: var(--ink);
}
.reg {
  margin-left: auto;
  font-size: 11px;
  letter-spacing: .26em;
  text-transform: uppercase;
  color: var(--dim);
}

/* eyebrow */
.eyebrow {
  margin: clamp(10px,2vh,26px) 0 0;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .34em;
  text-transform: uppercase;
  color: var(--cyan);
}

/* display headline — wide cinematic */
.display {
  margin: 0;
  font-weight: 800;
  font-size: clamp(46px, 12vw, 108px);
  line-height: .92;
  letter-spacing: -.01em;
  text-transform: uppercase;
  color: var(--ink);
  text-shadow: 0 0 40px rgba(90,120,255,.25);
  overflow-wrap: anywhere;
}
.display-accent {
  background: linear-gradient(96deg, var(--cyan) 0%, #b6a3ff 46%, var(--magenta) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  text-shadow: none;
}

.sub {
  margin: 0;
  max-width: 46ch;
  font-size: clamp(15px, 1.7vw, 18px);
  line-height: 1.6;
  color: var(--muted);
}

/* ---------- key form ---------- */
.key { margin: clamp(6px,1.6vh,16px) 0 0; }
.key-label {
  display: block;
  margin-bottom: 10px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--muted);
}
.slab {
  display: flex;
  align-items: stretch;
  gap: 8px;
  padding: 7px;
  border-radius: 16px;
  background: linear-gradient(180deg, rgba(20,26,60,.72), rgba(9,10,30,.72));
  border: 1px solid var(--edge);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.05), 0 18px 50px -24px rgba(0,0,0,.9);
  backdrop-filter: blur(8px);
  transition: border-color .2s ease, box-shadow .25s ease;
}
.slab:focus-within {
  border-color: var(--edge-hot);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.06), 0 0 0 3px rgba(55,232,255,.18), 0 18px 50px -20px rgba(0,0,0,.9);
}
.key-input {
  flex: 1 1 auto;
  min-width: 0;
  height: 54px;
  padding: 0 16px;
  border: 0;
  background: transparent;
  color: var(--ink);
  font-family: var(--ui);
  font-size: 18px;
  letter-spacing: .16em;
  outline: none;
}
.key-input::placeholder { color: #4a5384; letter-spacing: .4em; }
.key-input:focus-visible { outline: none; }

.submit {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  height: 54px;
  padding: 0 22px;
  border: 0;
  border-radius: 11px;
  cursor: pointer;
  font-family: var(--ui);
  font-weight: 700;
  font-size: 15px;
  letter-spacing: .06em;
  color: #041018;
  background: linear-gradient(96deg, var(--cyan), #7ad7ff);
  box-shadow: 0 0 22px rgba(55,232,255,.4), inset 0 1px 0 rgba(255,255,255,.5);
  transition: transform .16s var(--ease-out), box-shadow .2s ease, filter .2s ease, opacity .2s ease;
}
.submit-ico { width: 20px; height: 20px; }
.submit:hover:not(:disabled) { transform: translateY(-1px); box-shadow: 0 0 30px rgba(55,232,255,.6), inset 0 1px 0 rgba(255,255,255,.6); }
.submit:active:not(:disabled) { transform: translateY(0) scale(.98); }
.submit:disabled { opacity: .4; cursor: not-allowed; filter: grayscale(.3); box-shadow: none; }

:where(.submit, .key-input, .skip):focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 3px;
}

.hint {
  margin: 12px 2px 0;
  font-size: 12.5px;
  letter-spacing: .04em;
  color: var(--dim);
}

/* verdict / aria-live */
.verdict {
  margin: 4px 2px 0;
  min-height: 1.4em;
  font-size: 14px;
  letter-spacing: .02em;
  color: var(--cyan);
  transition: color .2s ease;
}
.verdict.is-deny { color: #ff6b8f; }

/* deny shake */
html.deny .slab { animation: shake .42s cubic-bezier(.36,.07,.19,.97) both; border-color: rgba(255,90,130,.6); }
html.deny .void { animation: denyflash .5s ease; }
@keyframes shake {
  10%,90% { transform: translateX(-2px); }
  20%,80% { transform: translateX(4px); }
  30%,50%,70% { transform: translateX(-8px); }
  40%,60% { transform: translateX(8px); }
}
@keyframes denyflash { 0%,100%{ filter: none; } 30%{ filter: hue-rotate(-30deg) brightness(1.15); } }

/* footer */
.rest {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: clamp(14px,3vh,34px) 0 0;
  font-size: 12px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--dim);
}
.rest-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--cyan);
  box-shadow: 0 0 8px var(--cyan);
  animation: pulse 2.6s ease-in-out infinite;
}
@keyframes pulse { 0%,100%{ opacity: .4; } 50%{ opacity: 1; } }

/* ---------- responsive ---------- */
@media (max-width: 560px) {
  .slab { flex-wrap: wrap; }
  .submit { width: 100%; justify-content: center; }
  .reg { display: none; }
  .display { font-size: clamp(38px, 12.5vw, 64px); }
  .gbtn span { display: none; }        /* icon-only pill on narrow screens */
  .gbtn { padding: 10px; }
}

/* short + landscape: let the gate scroll, shrink the cinematic type so nothing clips */
@media (max-height: 560px) and (orientation: landscape) {
  body { overflow-y: auto; }
  .gate {
    justify-content: flex-start;
    min-height: auto;
    gap: clamp(10px, 2vh, 18px);
    padding: clamp(18px, 5vh, 34px) clamp(24px, 6vw, 60px) 72px;
    max-width: 760px;
  }
  .display { font-size: clamp(30px, 8vh, 56px); }
  .sub { font-size: 14px; max-width: 52ch; }
  .eyebrow { margin-top: 8px; }
  .key-input, .submit { height: 48px; }
  /* LAW 1 — `svh`, not `dvh`, for the reason spelled out at the base rule: `body{overflow-y:auto}`
     two lines up means this page genuinely scrolls, and a fixed bottom-anchored panel sized in `dvh`
     moves its top edge every time the toolbar retracts. `svh` is the worst-case-smallest, so it fits
     and never jumps. `calc(100% - 74px)` is the live pre-2022 fallback (fixed element, `top:auto`,
     so the percentage resolves against the initial containing block). */
  .gpanel { max-height: calc(100% - 74px); max-height: calc(100svh - 74px); }
}

/* very short viewports (small landscape phones) */
@media (max-height: 420px) {
  .brand { margin-bottom: 2px; }
  .rest { margin-top: 12px; }
  .display { line-height: .95; }
}

/* ---------- reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  .neb, .mark-apo, .rest-dot, .grain { animation: none !important; }
  .warp { transition: none; }
  .gate { transition: opacity 160ms ease; }
  html.crossing .gate { transform: none; filter: none; }
  .blowout { transition: opacity 160ms ease; }
  .hud, .gpanel { animation: none; }
  .fxc { transition: opacity 200ms ease; }
  * { scroll-behavior: auto; }
}

/* exit veil: the LAST painted frame before leaving must match the app's dark canvas (#0b0d12) so the
   browser never shows a bright frame between documents — photosensitivity mandate, applies to every exit */
.exit-veil{ position:fixed; inset:0; z-index:2147483000; background:#0b0d12; opacity:0; pointer-events:none; transition:opacity .3s ease; }
.exit-veil.is-on{ opacity:1; }
@media (prefers-reduced-motion: reduce){ .exit-veil{ transition:none; } }


/* =========================================================================================================
   BIND PORT - THE CHIP'S LANE, AND THE KEYBOARD'S OWN VIEWPORT STATE
   PORT-NOTES STEP 7 - R319/§312 SESSION-BIND, piece portals-p4p5p6p7. ASL v2 Laws 1, 3, 4, 5 and 11;
   brief items 6, 26, 27, 28 and 33.

   Everything above this line is the host's: the scene, the type, the copy, the colours, the key form's
   markup. This block is the one thing bind.com puts on the door that the host never had -
   wall/shared/motion.css, which fixes the shared sensor chip to the block-end corner of the VIEWPORT at
   calc(14px + env(safe-area-inset-bottom)). A fixed element does not scroll, so a document that scrolls
   underneath it can never end above it, and the profile ASL Law 11 names - Keyboard-open, 390 x 430 - is
   exactly where a door's key row lands in the corner the chip is fixed in.

   THE LANE is PORT-NOTES STEP 7's own string, and it is a reservation, not a restyle: the chip is never
   moved, shrunk, hidden or dropped to make the room. 52px is the chip's tallest measured box (44px at 390
   and above, 52px at 320 and 344 where its armed line wraps to two); 14px is the inset the chip already
   sits at, and 14px more is the gutter between the two; the safe-area inset is added again because the
   chip's own position carries it. tests/portal-gate.mjs re-measures the real chip at both widths on every
   run, so a chip that ever grows reddens the arm instead of quietly eating a row.

   THE SHELL IS THE SCROLL REGION (ASL Law 4). The document is clipped and <body> scrolls, so the scrolling
   box's block-end edge IS the lane's block-start edge by construction - no scroll offset exists that can
   park the key row under the chip. The scene layers stay fixed to the whole viewport and are unchanged:
   only the door's own content column ends higher.
   ========================================================================================================= */
:root {
  --b-lane: calc(52px + 28px + env(safe-area-inset-bottom, 0px));
  /* THIS DOOR HAS A SECOND FIXED CONTROL, and it needs a band of its own for the same reason. .gbtn is
     the gesture launcher; the host fixed it to the block-end corner too, so moving it above the chip put
     it INSIDE the scroll region instead of outside it. MEASURED at 844 x 390, where this door scrolls:
     the launcher sat at y 186..216 across an Engage button at y 157..195 - 54 x 9 CSS px of the primary
     action, taken by a control that only opens a demo panel. A fixed element over a scrolling region
     collides at SOME offset, always; the only fix is that the region ends above it. 44px is the
     launcher's box at pointer:coarse and 14px is the same gutter the chip's lane uses.
     THE BAND STAYS IN THE BLOCK AXIS, and round 2 measured the alternative rather than assuming it.
     Spending these 58 px as an inline-end gutter on the key row - the device .rest already uses two
     rules below - was tried and refused, because the launcher is only 44 px wide where the host makes
     it icon-only. MEASURED on the shipped door, the inline gutter the key row would need to clear it
     is 0 px at 1440 (the centred column never reaches the launcher there), 44 px at 390 and at 320,
     and 90.2 px at 844x390 - which is the exact profile the collision above was measured at. A flat
     58 px gutter is 32.2 px short there, so it would hand the Engage button back to the launcher; a
     gutter wide enough for the widest launcher (154.8 x 44 with its label) would take that width off
     the key row at every size, including the ones where nothing collides. */
  --b-dock: calc(44px + 14px);
}

html { overflow: hidden; }
body {
  /* the host's own `min-height: 100dvh` is the viewport, and a min-height outranks a height: it has to be
     released here or the shell keeps the whole screen and the lane is a promise the layout never keeps. */
  min-block-size: 0;
  block-size: calc(100dvh - var(--b-lane) - var(--b-dock));
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--edge) transparent;
}

/* the host sized the gate to the VIEWPORT; the shell is the viewport now. */

/* SAFE CENTRING (ASL Law 8: reading order survives reflow). MEASURED on this door before this rule: at
   1440x900 the shell is 762 px, the content 1069, and a centred column put the brand row above the
   scrollport's own start, where no scroll can reach it.
   WHAT THE HOST REALLY DOES, re-measured 2026-08-31 on reference/meridian-landing/p4 served plain,
   because round 1 recorded this wrongly as "clipped 22 px at each end": the host's <body> is
   `overflow: hidden` and its .gate is `justify-content: center`, but the gate's own box GROWS to its
   content (1069 px against a 900 px viewport), so the centring has nothing to centre and the overflow
   all falls off the END. Clipped at the block START: 0.0 px, at every profile. Clipped at the block
   END: 85.2 px at 1440x900, 124.1 at 1194x834, 33.4 at 844x390, 0 at 390x844, 194.2 at 390x430 and
   62.7 at 320x568 - the footer, and at the shortest profiles the verdict line above it. The host loses
   its last row and cannot scroll to it; the port can, so the alignment must drop to `start` exactly
   when centring would put content out of reach. That is what the `safe` keyword is for, and it changes
   nothing at any size where the door fits. */
.gate { min-block-size: 100%; justify-content: safe center; }
/* the launcher is fixed at the INLINE end, and the footer rule is the only content that ever reaches the
   block end of the scroll. Measured at 320 and 390: they can meet. A block-end padding would have bought
   the clearance by pushing every centred word up a further 42 px; an inline-end gutter on the one row that
   can reach it costs no vertical space at all. */
.rest { padding-inline-end: 52px; }
/* MEASURED 2026-08-31 at 390 x 430 with the notch insets pushed in: .gbtn sat at y 360..396, across a chip
   at y 338..382 - a tap on the lower third of the launcher opened the sensor prompt. Both of the door's
   own block-anchored controls move above the lane; neither changes size, colour or behaviour. */
.gbtn   { bottom: var(--b-lane); }
.gpanel { bottom: calc(var(--b-lane) + 48px); }

/* ASL Law 11's second sentence: the primary action reachable WITHOUT dismissing the keyboard. */
/* The scroll-margin box is what "scroll an element into view" uses, so it is how a focused field brings
   the rest of its row with it. It is only needed where the row WRAPS and the primary action sits below
   the field - below 560px on this door, which is exactly where the host makes the action full-width.
   RE-MEASURED 2026-08-31, AFTER THE LOAD-TIME FOCUS WENT (index.html carries the reason). This margin
   no longer shapes any scroll the DOOR performs: the door lands at 0 everywhere and the only scroll
   left is the one the visitor's own tap or Tab asks for. Round 1 recorded this rule against numbers
   that came from the earlier geometry and they are corrected here rather than repeated: the honest
   figures, taken by focusing the field on the shipped door, are
     320x568  without the rule the focus scrolls 0 px and the Engage button is below the shell;
              with it, 142 px and the field and the button are both wholly on. THE RULE EARNS ITS
              PLACE HERE.
     390x430  248 px without, 314 px with; the field and the button are wholly on either way.
     1440x900 307 px either way - the split and a single large value are indistinguishable above
              560px, which is the whole reason the large value is scoped below it.
   Round 1 also claimed "at 1440x900 the single large value scrolled the door 164 px away from its own
   masthead on load; split, it scrolls 44". Both halves were wrong at HEAD: with --b-dock in the shell
   the load scroll was 307 of a 307 maximum, not 44 - 44 was the figure from before --b-dock existed
   and it was never re-measured - and there is no load scroll of any size now. */
.key-input { scroll-margin-block: 16px 28px; }
@media (max-width: 560px) { .key-input { scroll-margin-block-end: 148px; } }


/* =========================================================================================================
   BIND PORT - THE ASL v2 SECTION 5 MATRIX, ROW BY ROW   (R319/S312 SESSION-BIND, piece portals-p4p5p6p7)
   Brief items 26, 27, 28, 34, 35, 36. Nothing here is taste: every rule below was written against a named
   offender that tests/matrix.mjs printed on a real render of THIS door, and the run that named it is in
   evidence/portals-p4p5p6p7-r1/. The host never ran the matrix on these seven pages; bind.com does.
   ========================================================================================================= */

/* SAFE AREA (section 5 assertion 6). The harness's rule is geometric: no fixed element's CONTENT box may
   lie inside an inset band, and its own selection record names the escape - an element that pads with
   env(safe-area-inset-*) declares that its content clears the band. These layers are the scene: aria-hidden,
   pointer-events:none, painted, never read and never tapped. Padding is free for a box whose paint is a
   background or whose children are positioned against its padding box (that box does not move). It is NOT
   free for a <canvas>: the bitmap is scaled into the content box, so on a device that reports a notch the
   decorative field is rescaled by up to (47+34)/844 = 9.6 per cent in the block axis and by nothing at all
   on every device that reports no inset. That rescale was measured and accepted; the alternative was to
   leave the layers full-bleed and hand back thirty-five red rows on a rule the estate treats as a build
   failure. Nothing readable and nothing tappable is inside either band - the first flow text and every
   control are recorded per row in matrix-results.json. */
.void, .warp, .fxlayer, .blowout {
  padding: env(safe-area-inset-top, 0px) env(safe-area-inset-right, 0px)
           env(safe-area-inset-bottom, 0px) env(safe-area-inset-left, 0px);
}

/* THE SKIP LINK, AND THE KEYBOARD-OPEN PROFILE. The harness refuses a FIXED element parked entirely outside
   the viewport, because at 390x430 a fixed element the layout has forgotten is exactly what strands a
   control. A skip link parked off-screen until focus is the standard pattern and it is kept - as an
   ABSOLUTE element, which is what p5's own host already does, so it belongs to the document that scrolls
   rather than to the viewport the chip is fixed to. Its offsets, its colours and its copy do not change. */
.skip { position: absolute; }

/* TOUCH TARGETS (section 5 assertion 5, estate L6). Measured at pointer:coarse: the skip link 136.5x39 and,
   below 560px where the host makes the launcher icon-only, #gbtn 38x38 - and 154.8x36 in landscape. Raised
   with min-block-size and min-inline-size so nothing is restyled and no type moves. */
@media (pointer: coarse) {
  .skip { display: inline-flex; align-items: center; min-block-size: 44px; }
  .gbtn { min-block-size: 44px; min-inline-size: 44px; justify-content: center; }
}

/* VISIBLE FOCUS ON THE KEY FIELD (section 5 assertion 4, estate L6, brief item 21 - the floor that
   outranks spectacle, always). THIS IS THE GAP'S OWN CONSEQUENCE, not a second gap, and it is recorded
   as one. Removing the load-time focus (index.html) is what let the harness's Tab traversal START at
   the top of the document, and the first thing it walked into was this:
     input#key  focused with no visible indicator (outline none 3px, box-shadow unchanged, border
                unchanged)  - tests/matrix.mjs, the keyboard cut, on all six coarse profiles.
   It was always true. It was invisible to the harness only because the field began the traversal
   already focused, so Tab never arrived at it. The cause is a specificity accident in the host's own
   stylesheet: `.key-input:focus-visible { outline: none; }` at line 396 scores (0,2,0), and the rule
   twenty-six lines below it - `:where(.submit, .key-input, .skip):focus-visible { outline: 2px solid
   var(--cyan); outline-offset: 3px; }` - scores (0,1,0) because :where() contributes nothing. The door
   ASKS for this ring on this field and never gets it; the submit and the skip link, named in the same
   selector list, do. So nothing here is invented: the same two declarations, at a weight that lands,
   in the door's own colour. The slab's :focus-within glow stays exactly as it is - it is a 3 px ring
   at 18 per cent alpha on the PARENT, which is a nice touch and is not a focus indicator. */
.key-input:focus-visible { outline: 2px solid var(--cyan); outline-offset: 3px; }

/* AXE, CRITICAL, button-name. Below 560px the host hides the launcher's label with display:none, and a
   button whose only child is an aria-hidden svg has no accessible name at all. The label is kept in the
   accessibility tree and taken out of the picture instead. It is absolutely positioned, so it is not a flex
   item and contributes no gap. */
@media (max-width: 560px) {
  .gbtn span {
    display: block;
    position: absolute;
    inline-size: 1px; block-size: 1px;
    margin: -1px; padding: 0; border: 0;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
}

/* FORCED COLOURS (section 5 cross-cut). With the OS palette in force the host's gradients and hairlines are
   discarded and three controls are left with no edge at all. ButtonBorder is the system's own. */
@media (forced-colors: active) {
  .skip, .gbtn, .gitem, .key-input, .submit { border: 1px solid ButtonBorder; }
  .skip { text-decoration: underline; }
}

/* ZOOM 200% (section 5 cross-cut). At mobileS the layout viewport is 160 CSS px and APSIS, set at .42em of
   letter-spacing beside a 38px mark, ran from 78 to 180. The brand row is allowed to wrap; at every width
   the matrix runs unzoomed it never does. */
.brand { flex-wrap: wrap; }

/* THE HINGE (section 5, foldable spanned: nothing critical inside it). The gate column is centred, so on a
   two-segment surface it lay across the fold with the key input on both sides of it. It moves into the
   first segment. This is a page-shell query - a viewport segment, which ASL Law 2 names - not a width. */
@media (horizontal-viewport-segments: 2) {
  .gate { max-inline-size: 44%; margin-inline: 4% auto; }
}
