/**
 * The invitee's join view, rendered by `index.html`. SELF-CONTAINED ON PURPOSE: every custom
 * property carries its own fallback. That was load-bearing while `guest.html` rendered this
 * screen too and named its colours differently — relying on the host rendered Join as white
 * text on nothing — and it is kept because the next host will be the same shape.
 * The plate is `docs/design/guest-surface.md` §2; the components are §3.
 */

  /* THE ACTIVITY PLATE (card `32` D8) — three background LAYERS blended with
     `background-blend-mode: color`, NEVER `mix-blend-mode`, which has no backdrop here and
     goes flat #EA79A1. On the page's own column, not a fixed child. §2.1-§2.4. */
  :root {
    /* THE FALLBACK IS INERT ON TODAY'S ONE HOST (`index.html` loads `app.css`, which declares
       `--tan`) and kept for the same reason the header gives: it was load-bearing on
       `guest.html`, and the next host will be that shape. Pinning this to a literal instead was
       TRIED AND WITHDRAWN the same day — §2.2. */
    --activity-plate-base: var(--tan, #F3EADE);
    /* -3.7% AND 92.6% ARE MEASURED — two frames agree to a tenth of a percent (§2.3). */
    --activity-gradient: linear-gradient(to bottom, #EA79A1 -3.7%, rgba(234,121,161,0) 92.6%);
    /* The asset's own viewBox height, NEVER per-screen; one number for both schemes. §2.4. */
    --activity-plate-h: 676px;
    --activity-wash-h: 676px;
    /* The band `9237:96215` leaves UNDER the edge: 852 - 676, read from the other end. */
    --activity-band-h: 176px;
    /* HOW FAR THE PAINTED BOX HAS BEEN PULLED UP PAST ITS OWN TOP. One name, because FOUR
       declarations have to agree or the plate tears. NOT `var(--safe-top)` read directly in
       those four: right by accident here, wrong the day this page declares it. §2.5. */
    --activity-bleed: 0px;
  }
  #app.activity-plate {
    /* THE TAN IS A LAYER AND NOT THE `background-color`, which paints the whole background
       box — these columns are taller than the plate, and it put the step 155px low. */
    background-color: var(--bg, #FAF3EA);
    background-image:
      var(--activity-gradient),
      var(--watermark, url("/img/pi-watermark.svg")),
      linear-gradient(var(--activity-plate-base, #F3EADE), var(--activity-plate-base, #F3EADE));
    background-repeat: no-repeat, no-repeat, no-repeat;
    /* Offsetting the mark by the same `--activity-bleed` the box grew by moves ONE edge.
       THE DEFAULTS ARE FALLBACKS HERE AND NOT DECLARATIONS ON `:root`, AND THAT IS THE WHOLE
       REASON THIS READS ODDLY. A custom property is substituted where it is DECLARED, so
       `--activity-plate-y: var(--activity-bleed)` on `:root` resolves the bleed AT `:root` — where
       it is 0 — and inherits down as a flat `0px`, past the `--activity-bleed: var(--safe-top)`
       this very file sets on `#app.activity-plate` nine lines below. Written that way the mark
       stopped moving with the box on every notched device: `browser-plate-bleed` measured y=0
       under a 59px strip, in both engines. As a fallback it is resolved HERE, on the element that
       paints, so the two sides keep the values the longhands spelled as literals before PI-10394's
       watermark round and only the desktop Pi Web join — which sets both explicitly, on itself —
       sees anything different. It is the same trap `--activity-bleed`'s own note warns about. */
    background-position: top center, center var(--activity-plate-y, var(--activity-bleed)), top center;
    background-size:
      100% calc(var(--activity-wash-h) + var(--activity-bleed)),
      var(--activity-plate-w, 100%) var(--activity-plate-h),
      100% calc(var(--activity-wash-h) + var(--activity-bleed));
    background-blend-mode: color, normal, normal;
  }
  /* WEI, 2026-08-23: "The background should extend all the way to the top... It should not
     leave a gap." Negative margin, padding given back. `#app` only — §2.5. */
  #app.activity-plate {
    --activity-bleed: var(--safe-top, 0px);
    margin-top: calc(-1 * var(--activity-bleed));
    padding-top: calc(20px + var(--activity-bleed));
  }
  /* 534 IS 539 LESS PI-9953's 5: the description moved out from under the tile and up under
     the title, so the last thing on the wash is the tile and the edge is 24 below IT. The
     drop is the whole layout change — was tile + 16 + line + 21, is 8 + line + 24 — and so
     is the same on all three arms, whatever the description wraps to.
     PI-10640 — THAT LAST CLAUSE STOPPED BEING TRUE, SO THE EDGE IS MEASURED NOW. The constants
     below were a length from `#app`'s top, which is the tile's bottom only while the description
     above it takes a known number of lines. "Pi creates a daily question…" takes three at 360–460,
     FOUR at 320–340 and two on a wide card, so a fixed edge is wrong somewhere in the phone range
     whatever number it holds: the first cut of this ticket, +22, left the tile 37.5px OVER the
     edge at 320. `readPreviewWash()` in `app.jsx` publishes `--preview-wash-h` = the tile's
     bottom + `--preview-wash-tail` (the frame's 24 under it), and every arm below reads that.
     THE CONSTANTS STAY, AS THE FALLBACK: the first frame paints before the script measures, and the
     render shim has no layout. They carry the three-line case, `--preview-line-step`, because that
     is the frame's (`18199:244686` grows `Top` 523 -> 545 for it) and most phones'. */
  .activity-plate:has(.preview-head) {
    --preview-line-step: 22px;
    --preview-wash-tail: 24px;
    --activity-wash-h: var(--preview-wash-h, calc(534px + var(--preview-line-step)));
  }

  /* When WE draw the row: `534 = 38 + 40 + 432 + 24`, 432 being head -> tile bottom, and the
     term that moved is the head control's DIAMETER, so it is a token. Mutually exclusive. §2.6. */
  .activity-plate:has(.preview-head):has(.head-pinned:not(.head-hosted)) {
    --activity-wash-h: var(--preview-wash-h, calc(494px + var(--head-disc, 40px) + var(--preview-line-step)));
  }

  /* When PI draws it: 534 is a length from `#app`'s top and the block under it is not, so
     the wash is the free variable. `472 = 16 + 432 + 24` plus the row's own height. §2.6. */
  .activity-plate:has(.preview-head):has(.head-hosted) {
    --activity-wash-h: var(--preview-wash-h, calc(472px + var(--pi-chrome-top, 0px) + var(--preview-line-step)));
  }

  /* On a phone taller than the frame: THE EDGE IS 24px ABOVE THE BLOCK THAT FOLLOWS IT, and
     676 is only what that relationship comes to on an 852 board. `max()` so the wash can only
     GROW; the `min()` is the keyboard case, where the bar CLIMBS and the wash does not. */
  /* NOT `clamp()`: it is `max(A, min(B, C))`, which lets the 676px FLOOR beat the CAP. */
  #app.activity-plate:not(:has(.preview-head)) {
    --activity-wash-h: min(
      max(
        676px,
        calc(100svh - var(--safe-top, 0px) - var(--kb, 0px) - var(--activity-band-h))
      ),
      calc(100svh - var(--safe-top, 0px) - var(--kb, 0px) - var(--cta-h, 0px))
    );
    /* NO `--safe-top` TERM: the strip is now inside this box rather than above it. §2.7. */
    min-height: calc(100svh - var(--kb, 0px) - env(safe-area-inset-bottom, 0px));
  }

  /* PI-10174 — ON A PAGE FRAME THE BAND IS MEASURED FROM THE COLUMN'S FOOT, NOT THE VIEWPORT'S.
     `14491:139309` opens `Bottom area` at y=912 of 1024, and a wash written against the window
     cannot say that. WHICH rule pinned it to 676 before, and at which width, is in
     `docs/design/guest-surface.md` §2.6.
     `100%` AND NOT `100svh`: 100% of the background positioning area is the COLUMN, which the
     `min-height` above has already floored at the window — so the seam is at the window's foot
     while the page fits and at the PAGE's own foot when it does not, which is what `Bottom area`
     means on a frame, and it is one expression instead of a floor and a cap.
     `- var(--activity-bleed)` cancels the term `background-size` adds back for the negative
     margin above; without it the band is short by `--safe-top` on a notched device and by NOTHING
     in any headless browser.
     NOT IN app.css's `min-width: 768px` BLOCK, where the 112 depth lives: Pi Web decides
     `fullscreen` off the WINDOW (`[appId].tsx`) and a media query here measures the IFRAME, so a
     1024-wide window with Pi's sidebar open hands this page a ~760 frame — fullscreen, and below
     the query. The depth is a width question; the origin is not. */
  html[data-presentation="fullscreen"] #app.activity-plate:has(.dp-join) {
    --activity-wash-h: calc(100% - var(--activity-band-h) - var(--activity-bleed));
  }

  /* `gap: 0`, and every step below is one of the frame's own numbers on its own child.
     `flex: 1` FOR ALL FOUR STATES, where three of them had it; inert in the container. §3. */
  .dp-join { display: flex; flex-direction: column; gap: 0; flex: 1; }
  /* 616 is the desktop age gate's column. `width: 100%` and NOT left `auto`: auto cross-axis
     margins with no definite width hand sizing to shrink-to-fit — measured at 573px. */
  .dp-join { width: 100%; max-width: 616px; margin-left: auto; margin-right: auto; }
  .dp-center { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 8px; }

  /* One head for all four variants. Numbers off a 1:1 render, NOT off layer names. §3.1. */
  .dp-head { display: flex; flex-direction: column; align-items: center; }
  /* THE MARK'S OWN ROW, so the two sign-in pills have somewhere to sit beside it.
     `9237:96216` draws them 40 tall (y16..55) centred on the mark's 20 of ink (y26..45) and
     STILL puts the tile where the frames without them put it — 186 on both `14433:119586`
     (pills) and `14430:118599` (none), measured 1:1. So the pills are taken OUT OF FLOW: a
     flex row sized to its tallest child would push the whole head down 10px on the one frame
     that has them, and `.dp-head-tile`'s 107 is measured from the mark. With no pills, and on
     Pi web where the mark itself is hidden, this row is exactly what the mark was. */
  .dp-head-row { position: relative; display: flex; align-items: center; width: 100%; }
  /* CENTRED ON THE COLUMN, which is where the frame puts them: x95..296 of a 393 frame against
     `#app`'s 24px gutter — centre 195.5 to the column's 196.5. Right-aligning instead would run
     them to x369, under the 58x66 corner Pi floats its ✕ in (`browser-join-chrome-corners`);
     centred they stop at 296, 39 clear of it, at every frame width. Gap 15 = the frame's
     204 − 189. `browser-join-signin` measures both rects against that corner. */
  .dp-head-auth {
    position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
    display: flex; gap: 15px;
  }
  /* Phone default — the desktop block below swaps them. */
  .dp-head-auth .dp-auth-long { display: none; }
  /* pi-design makes a MEDIUM secondary TRANSPARENT (`components.css`, compound
     {variant:secondary, size:medium}); the frame draws it filled `#FAF3EA` over the pink
     ground. Overridden here and not in the package, which is generated from plaza. Same
     specificity as the rule it beats, and this sheet is linked after it. */
  /* `:not(:hover):not(:active)`: at (0,3,0) the bare selector ties pi-design's `:hover` and
     `:active` and this file loads after it, so a flat `background` took the press feedback with
     it. Kept identical to Stars Aligned's copy (`stars-aligned.css`) — the two pairs are
     deliberately the same pixels. */
  .dp-head-auth .pi-btn[data-variant="secondary"]:not(:hover):not(:active) {
    background: var(--color-background);
  }
  /* PI'S MARK, TOP-LEFT AND THEMED (Wei, 2026-08-22). `margin-left` buys the case where
     the corner is not the page's; `max(0px, …)` is why it is inert elsewhere. MASKED, and
     `mask-size: 100% 100%` NOT `contain`, which fits by height and shrinks it. §3.1. */
  .dp-wordmark {
    display: block; width: 24px; height: 20px;
    align-self: flex-start; margin: 0;
    margin-left: max(0px, calc(var(--pi-chrome-lead, 0px) - var(--gutter, 0px)));
    background: var(--color-text-accent, #131212);
    -webkit-mask-image: url("/img/pi-wordmark.svg");
            mask-image: url("/img/pi-wordmark.svg");
    -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;
    -webkit-mask-position: center;
            mask-position: center;
    -webkit-mask-size: 100% 100%;
            mask-size: 100% 100%;
  }
  /* …AND NOT AT ALL ON PI WEB, WHICH DRAWS ITS OWN — reported as "dual logo".
     `9237:96216` carries exactly one, and it is in `TopNavigation`'s `Leading`
     slot at 16,16 40x40. On Pi Web that slot is the HOST's: `mini-app-chrome`'s
     `showsWordmark` puts Pi's mark there on any screen with nothing to go back
     to, which is every `/join/*` route. So the frame's one mark is already drawn
     and this is a second.

     THE MARGIN ABOVE IS WHY IT READ AS TWO SIDE BY SIDE RATHER THAN A BLUR: it
     already shifts this mark clear of `--pi-chrome-lead`, so the two never
     overlapped — they sat adjacent, at 18..52 and 47..82 of the card. That hedge
     was written against a host that reserved the corner; it is the wrong answer
     for a host that FILLS it, and it stays for the hosts that only reserve.

     NOT GATED ON `inPi`, WHICH WOULD REOPEN A CLOSED BUG. `join-view.js` records
     that an `inPi` gate once hid this mark from `6589:31891` and Wei reported it
     on 2026-08-12: the mark belongs on both hosts. That ruling predates the web
     host's chrome by a fortnight and is untouched here — `data-host="pi-web"` is
     written only by `applyHost` for `host === "pi-web"`, a string neither native
     repo contains, so iOS, Android and the plain browser keep the mark exactly as
     Wei asked. What changes is only WHO draws it on the one host that draws one. */
  html[data-host="pi-web"] .dp-wordmark { display: none; }
  /* AND ON WEB THE PAIR NEEDS ITS OWN LINE, because the rule above is what takes it away. The pills
     are centred on `.dp-head-row` (`top: 50%`), and that row's only IN-FLOW child is the mark — so
     hiding the mark collapses the row to nothing and 50% resolves to the document's top edge. The
     pair then draws ~25px above Pi's own mark and ✕, clipping the frame on a short viewport. It
     reads as deliberate because the mark is gone and nothing else in the row moved.

     `position: static` ON THE ROW, NOT `fixed` ON THE PAIR — which is the shape `app.css`'s desktop
     rule already uses, and its comment says why `fixed` is not available here: `screen-fwd` is
     declared on `#app`'s children with `fill: both` and fills at an IDENTITY MATRIX, which still
     establishes a containing block, so `.dp-join` captures `fixed` as well as `absolute`
     (`offsetParent` measured as `.dp-join`, `transform` as `matrix(1,0,0,1,0,0)`, never `none`).
     A `fixed` pair would therefore land differently on a cold load than after an in-app navigation.
     Taking the ROW out of flow instead leaves the pair absolute against the column — the same
     coordinate space it already had — and just stops the collapsed row deciding where 50% is.

     <768 ONLY. At >=768 `app.css`'s own rule already sets `top: 20px` against a static row and was
     measured that way in round 5; an unscoped rule here would leave that geometry half-governed by
     this one. Below it, 15 measured on f10.

     WEB ONLY besides, on the same `data-host` test the rule above uses. iOS and Android draw their
     own mark in that corner, so their row does not collapse, `top: 50%` is already right there, and
     both are shipped and verified — nothing about them changes. */
  @media (max-width: 767.98px) {
    html[data-host="pi-web"] .dp-head-row { position: static; }
    html[data-host="pi-web"] .dp-head-auth {
      top: calc(env(safe-area-inset-top, 0px) + 15px);
      transform: translateX(-50%);
    }
  }
  /* 107 — the frame's mark ink ends at 45 and the tile starts at 152. ALL FOUR STATES. */
  .dp-head-tile {
    width: 192px; height: 192px; border-radius: 24px;
    background: var(--activity-swatch, #EA79A1);
    display: grid; place-items: center;
    margin-top: 107px;
  }
  /* BOTH NUMBERS: the drawing is 144:131, and any other ratio LETTERBOXES an SVG. §3.1. */
  .dp-head-icon { display: block; width: 120.21px; height: 109.36px; }
  /* R-9780-b: `--color-text-accent` AND NOT `--color-text-primary-base`, which agree in light
     and diverge in dark. It is also what the rest of this head draws in. §3.2. */
  .dp-head-name {
    margin-top: 11px;
    font-size: 18px; color: var(--color-text-accent, #131212);
    letter-spacing: var(--track-tight, -0.02em); line-height: 25px;
  }
  /* heading_s: 24, and the ink is `#base/text-accent` — it was a BUTTON FILL. F04-12. */
  /* 1.1 AND NOT `--lh-heading`, whose 1.42 is Pi's NATIVE line box. THE 32 IS SCOPED: hoisting
     it onto the bare class jumped the guest page's four other headings a type tier. §3.3. */
  .dp-title {
    font-family: var(--font-heading, ui-serif, Georgia, "Iowan Old Style", serif);
    font-size: 24px; font-weight: 400; color: var(--color-text-accent, #131212);
    letter-spacing: var(--track-heading, -0.015em);
    line-height: 1.1; margin: 0;
  }
  /* The join view's own headline, and a DIRECT child — three different `.dp-title`s exist. */
  .dp-join > .dp-title { font-size: 32px; margin-top: 24px; text-align: center; }
  /* 19: the frame's step less the line box's slack; the 16 measured from a block now gone. */
  .dp-group { margin-top: 19px; }
  /* -2%, not -1%: `7607:44893` is -0.28px on 14. */
  .dp-sub {
    font-size: 14px; color: var(--muted, #6B6258); letter-spacing: var(--track-tight, -0.02em);
    line-height: var(--lh-copy, 1.4); margin: 0;
  }
  /* `6103:88966`: five 40x40 seats at a 30 pitch, then 8, then the count. */
  .dp-spots-row { display: flex; align-items: center; gap: 8px; margin-top: 4px; }
  .dp-spots { display: flex; }
  .dp-spot {
    width: 40px; height: 40px; border-radius: 50%;
    /* app.css declares `--paper: var(--bg)`; the fallback only serves the guest page. */
    /* `#button/disabled-fill` #BEB09D, what the frames draw an empty seat in. */
    background: var(--seat-free, #BEB09D); border: 2px solid var(--paper, #FAF3EA);
    margin-left: -10px; display: grid; place-items: center;
    /* Charcoal-50, as the thread's own seats now take it (D17). `7607:44887` tracks it at -2%.
       `--miniapp-color-seat-ink` is pi-design's, and `index.html` links the barrel, so the
       fallback is now vestigial rather than the guest page's only source. */
    font-size: 18px; font-weight: 500; letter-spacing: var(--track-tight, -0.02em);
    color: var(--miniapp-color-seat-ink, #131212);
  }
  .dp-spot:first-child { margin-left: 0; }
  /* The overflow chip inherits `.dp-spot` whole; only "+12" never wrapping is left to say. */
  .dp-spot-more { white-space: nowrap; }
  /* 12/1.4 at -1% — `7607:44892` and `Label/XS`. */
  .dp-spots-count {
    font-size: 12px; color: var(--muted, #6B6258); letter-spacing: var(--track-body, -0.01em);
    line-height: var(--lh-copy, 1.4); margin: 0; white-space: nowrap;
  }
  .dp-join-line { margin-top: 2px; }
  /* Pushed, not `position: fixed`, which does not work here: `#app`'s children carry an
     animation with `fill: both`, and the leftover transform makes this a containing block. */
  .dp-foot { display: flex; flex-direction: column; }
  /* EVERY STATE'S FOOT: the join state was excepted on a reason that does not hold. */
  .dp-join .dp-foot { margin-top: auto; padding-top: 14px; }
  .dp-join .dp-whatspi { margin-top: 0; }
  /* PI-9491's board makes all four states one component, so there is no separate blocker
     head or foot. NOT measured off the new blocker frames at 1:1 — a known gap. */
  /* The frame's own rhythm inside the message block, not `.dp-center`'s uniform 8. */
  .dp-blocker-body { gap: 0; margin-top: 24px; }
  .dp-blocker-body > .dp-title { font-size: 32px; }
  .dp-blocker-body > .dp-spots-row { margin-top: 24px; }
  /* `Headings/XS` in the DISPLAY face and `#base/text-accent` — two surfaces, two inks. */
  .dp-blocker-body > .dp-sub {
    margin-top: 40px;
    font-family: var(--font-heading, ui-serif, Georgia, "Iowan Old Style", serif);
    font-size: 18px; font-weight: 400; line-height: 1.1;
    letter-spacing: var(--track-body, -0.01em);
    color: var(--color-text-accent, #131212);
  }
  /* The census line (`10192:93179`) is the JOIN view's on the blocker's body, so both rules
     above are undone for it. Adjacent-sibling, so only the state that has one pays. */
  .dp-blocker-body > .dp-join-line {
    margin-top: 8px;
    font-family: inherit;
    font-size: 14px; font-weight: 400; line-height: var(--lh-copy, 1.4);
    letter-spacing: var(--track-tight, -0.02em);
    color: var(--muted, #6B6258);
  }
  .dp-blocker-body > .dp-join-line + .dp-sub { margin-top: 24px; }
  /* Round 68: the blocker's button is in the SAME bottom band as the join view's now. */
  .dp-blocker-foot > .dp-whatspi { margin-top: 0; padding-top: 26px; }
  /* 56 tall. `min-height`, not `height`, so a label that wraps grows the pill. F04-12. */
  /* THE LABEL IS `#message/text-contrast`, NOT `--on-accent` (Wei, 2026-08-22: "dark mode
     button text should be dark"). Dark goes 3.32:1 -> 4.45:1. §3.5. */
  .dp-btn {
    width: 100%; min-height: 56px;
    display: flex; align-items: center; justify-content: center;
    background: var(--ink-deep, #1A4631);
    color: var(--miniapp-color-message-text-contrast, #FCFAF7);
    border: none; padding: 8px 15px;
    border-radius: 100px; font-size: 16px; font-weight: 500; font-family: inherit;
    letter-spacing: var(--track-body, -0.01em); line-height: var(--lh-single, 1.3);
    cursor: pointer; margin-top: 4px; text-align: center; text-decoration: none;
  }
  /* 353 IS THE COMPONENT'S OWN MAX and belongs to a 616px column. THE 768 WRAPPER IS THE
     POINT — this comment used to say the opposite, and unwrapped the cap engaged at every
     viewport >= 402 and pulled the CTA off the content gutter every other node tracks. */
  /* AND THE DESKTOP FOOT IS ONE ROW, NOT A STACK. `11048:51166` (BottomCTA) draws its
     Content (`I11048:51166;357:1630`) 495x56 centred in 1440: the tertiary 118 wide at
     x=0, the primary 345 at x=150 — so "What's Pi?" reads LEFT of the CTA and the gap is
     32. `row-reverse` and not two `order`s, so the DOM keeps the primary first and the tab
     order stays action-then-aside. Both states, because `blockerHtml` and `joinHtml` share
     this foot. */
  /* DESKTOP DRAWS THIS PAIR BIGGER, and the numbers come off `14447:121225` rather than off a
     DS size name. Measured: the row is 299x48, Log in 128x48 at x=0, Sign up 159x48 at x=140 —
     so 48 tall and a 12 gap (140 - 128). NEITHER `data-size` fits: `--pi-control-md` is 40 and
     `--pi-control-lg` is 56, and `large` would also take `width: 100%` and flex each pill to
     the 353 cap. So the size stays `medium` (which already gives `width: fit-content`) and the
     three values the frame actually changes are stated here.

     PHONE IS UNTOUCHED — `9237:96216` draws the same pair at the medium default, 40 tall with
     the 16px label, which is what ships today. This block starts at 768. */
  @media (min-width: 768px) {
    .dp-head-auth { gap: 12px; }
    .dp-head-auth .pi-btn {
      height: 48px;
      /* Action/M. `medium` is Action/S 16px; the frame's label is 18. */
      font-size: 1.125rem;
      /* 128 IS A MIN-WIDTH, not a measured label. "Log in" is narrow enough that
         its box is decided by the floor, and the frame's 128 is exactly what
         `data-size="large"` carries (`components.css`) — medium's floor is 96,
         which left Log in 32px short of the frame while Sign up, being
         content-wide, landed within 2px. Stated here rather than taken by
         switching size, for the reason above. */
      min-width: 128px;
    }
    /* ALL THREE STATES, because pi-design keeps the 10px radius on the primary's
       hover and active specifically:
         `.pi-btn[data-variant="primary"][data-size="medium"]:hover, :active`
       at (0,4,0), which outranks a bare `.dp-head-auth .pi-btn` at (0,2,0). Left
       to that, the pill squared off the moment the pointer landed on Log in.
       Spelling the two states out here reaches (0,5,0), so this does not rest on
       stylesheet order the way the `background` override below does. */
    .dp-head-auth .pi-btn,
    .dp-head-auth .pi-btn[data-variant="primary"][data-size="medium"]:hover,
    .dp-head-auth .pi-btn[data-variant="primary"][data-size="medium"]:active {
      border-radius: var(--pi-radius-pill);
    }
    /* THE DESKTOP FRAME'S LABEL. `14447:121225` says "Sign up for free" where the
       phone frame (`9237:96216`) says "Sign up" — the two genuinely disagree, so
       the markup carries both and the width decides, which CSS alone cannot do to
       a text node. */
    .dp-head-auth .dp-auth-short { display: none; }
    .dp-head-auth .dp-auth-long { display: inline; }
    .dp-join .dp-foot {
      flex-direction: row-reverse; align-items: center; justify-content: center; gap: 32px;
    }
    /* `flex: none` because `width: 100%` on a row item flexes against the whole band, and
       the auto margins were the COLUMN's centring — in a row they eat the 32px gap. */
    .dp-join .dp-btn { flex: none; max-width: 353px; margin: 0; }
    /* The frame's tertiary is a 118-wide Button (`I11048:51166;1417:3467`); ours is a bare
       link, so its box is stated rather than paid for in padding — at its ~84px of ink the
       pair centres 17px off the frame's. The 26px step above it was the column's. */
    .dp-join .dp-whatspi { flex: none; min-width: 118px; margin-top: 0; padding-top: 0; }
  }
  .dp-btn:disabled { opacity: 0.45; cursor: default; }
  /* The Join button had no pressed state, and it is the only button on the screen. */
  .dp-btn { transition: background-color .2s ease; }
  .dp-btn:active, .dp-btn.pressed {
    background: var(--ink-deep-tap, #21322A); transition-duration: 0s;
  }
  .dp-btn:disabled:active, .dp-btn:disabled.pressed { background: var(--ink-deep, #1A4631); }
  @media (prefers-reduced-motion: reduce) { .dp-btn { transition: none; } }
  /* 18, not 14: 18/-2% sets the frame's 84 of ink where 16 and 17 land 10 and 7 short. */
  .dp-whatspi {
    margin-top: auto; padding-top: 26px; text-align: center; font-size: 18px;
    letter-spacing: var(--track-tight, -0.02em); line-height: var(--lh-single, 1.3);
    color: var(--accent, #038247); text-decoration: none;
  }

  /* Pi's loading dots (PI-9134) ARE `.pi-wait` / `.pi-dots` in pi-design (`css/wait.css`),
     lifted from here with every value unchanged; `index.html` links the barrel before this
     file. The label rides the DS's own `.pi-sr-only` — it is still IN the document rather
     than an `aria-label`, because `PLACEHOLDERS` in `app.js` matches its text. §3.6. */
