/* ============================================================================
   AUTH.CSS — the signed-out surface's whole stylesheet.

   Extracted from shared/styles.css on 2026-08-27 (Phase 2a of the UI
   program): every rule that can apply on /login, /signup, /forgot, /reset,
   /welcome and the workspace-unavailable page, found by matching the full
   sheet against those pages' real DOM in both themes at both widths, plus
   the classes shared/auth.js toggles at runtime. The prose that used to
   surround these rules lives in shared/styles.css's history — this file
   stays terse on purpose: it ships raw to every first-time visitor, and it
   used to cost them 388 KB to render a login form.

   THE TOKEN BLOCKS AT THE TOP ARE THE PRODUCT'S ONE COPY. app/src/main.tsx
   imports this file FIRST (before the app system sheet) once Phase 2b lands,
   so the palette, radii, type scale and motion tokens exist exactly once and
   the login page and the app can never disagree about them.

   EDITING: a rule needed by both worlds belongs here; a rule only the app
   uses belongs in the app system sheet. When the auth pages gain markup,
   bring its rules here — nothing signed-out reads any other stylesheet.
   ============================================================================ */

  /* optional, NOT swap (David, 2026-09-01: "I don't want any movement. should
     just load and show up"). swap paints every string in the fallback face and
     then re-paints it in Inter, and the two have different metrics — so every
     text box on the page changes size at once, a little after it appears.
     Measured on a throttled production load it was the single widest source of
     motion: the demo gateway's fields moved sixteen times, and on /login the
     card's own foot changed width and re-centred everything above it.

     `optional` is the only font-display that promises this cannot happen: the
     font is used if it is ready almost immediately, and otherwise the fallback
     is kept FOR THE WHOLE PAGE LOAD rather than swapped in later. It is only
     tolerable because the file is preloaded and same-origin (see the <link> in
     each page's head), so it wins that race on anything but a cold, slow first
     visit — and the cost when it loses is one page in a fallback face, against
     a guarantee that nothing on the page ever jumps. */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 800;
  font-display: optional;
  src: url(/assets/fonts/inter-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 800;
  font-display: optional;
  src: url(/assets/fonts/inter-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
:root {
  color-scheme: light dark;

  --dd-font-sans: "Inter", -apple-system, "Segoe UI", sans-serif;

  --dd-topbar-h: 48px;

  --dd-fs-3xs: .625rem;
  --dd-fs-2xs: .6875rem;
  --dd-fs-xs:  .75rem;
  --dd-fs-sm-: .8125rem;
  --dd-fs-sm:  .875rem;
  --dd-fs-md:  1rem;
  --dd-fs-lg:  1.125rem;

  --dd-gray-50:  light-dark(#f5f5f3, #2c2d2f);
  --dd-gray-100: light-dark(#efefec, #353739);
  --dd-gray-200: light-dark(#e3e3df, #424547);
  --dd-gray-300: light-dark(#cfcfca, #535558);
  --dd-gray-500: light-dark(#93938e, #919396);

  --dd-ink:       light-dark(#111112, #f7fafd);
  --dd-ink-light: light-dark(#3a3a3d, #d5d7db);
  --dd-ink-quiet: light-dark(#5e5f66, #abaeb1);

  --dd-canvas:  light-dark(#fdfdfc, #181919);
  --dd-panel:   light-dark(#ffffff, #222324);
  --dd-rail:    light-dark(#f7f7f5, #0b0c0c);
  --dd-border:  light-dark(rgb(17 17 18 / .08), #3c3e40);
  --dd-hairline:light-dark(rgb(17 17 18 / .06), #303233);
  --dd-inverse: light-dark(#151516, #f7fafd);
  --dd-on-inverse: light-dark(#fdfdfc, #181919);

  /* INK IS THE DEFAULT ACCENT (2026-09-23, the Tab pass): black on white,
     white on black. Blue stays the status blue and one of the choices. */
  --dd-accent: light-dark(#151516, #f5f5f4);
  --dd-on-accent: light-dark(#ffffff, #111112);

  --dd-green:  light-dark(#1a7742, #63d18c);
  --dd-red:    light-dark(#b53333, #fe8181);
  --dd-amber:  light-dark(#8a6410, #f0c057);
  --dd-blue:   light-dark(#2563eb, #8aafff);

  --dd-teal:   light-dark(#0f6d70, #63cfd0);
  --dd-orange: light-dark(#a8500f, #f5a26a);
  --dd-pink:   light-dark(#b03565, #f79bbd);
  --dd-violet: light-dark(#6741c8, #b3a0f5);

  --dd-blue-wash: light-dark(#edf2fd, #242e45);

  /* THE APP'S SCALE, and it has to be said out loud because this file got it
     wrong for three days. These four are 5/8/12 in app/src/styles/system.css
     as of the 2026-08-29 Attio pass, which retuned them UP from 3/5/8 — and
     this sheet, which declares its own copy so the signed-out pages can paint
     before the SPA bundle exists, kept the old three. Every front door was
     drawn a full step tighter than the product behind it: the sign-in card,
     its fields, its buttons, the demo gateway's persona tiles, the welcome
     wizard. Nobody had to change a rule to fix it, because every rule here
     already asks for the token; the token was simply the wrong number.
     Same failure mode as this file's colour block (see the dark half's note),
     and darkPalette.test.ts now holds BOTH copies to the app's values. */
  --dd-radius-s: 6px;
  --dd-radius-m: 10px;
  --dd-radius-l: 16px;
  --dd-radius-xl: 24px; /* the Tab card: auth, gateway, marketing */
  --dd-radius-full: 999px;

  /* THE DEPTH SYSTEM, the app's values from app/src/styles/system.css (move
     them together; DESIGN.md "Depth"). One recipe at every level, a 1px
     alpha ring (the surface's edge, so nothing draws a border), a contact
     shadow, a soft mid shadow, a wide low blue-slate haze, and a top inner
     highlight, the 3D edge. Buttons are flat, like the homepage's (David
     asked for no button shadows there, and every door has to match it), so
     --dd-shadow-btn resolves to nothing; --dd-shadow-chip is the one small
     lift, worn here by a picked tile and by the preview's active rail row. */
  --dd-highlight: light-dark(rgb(255 255 255 / .9), rgb(255 255 255 / .06));
  --dd-highlight-strong: light-dark(rgb(255 255 255 / .9), rgb(255 255 255 / .09));
  --dd-shadow-card:
    0 0 0 1px light-dark(rgb(17 17 18 / .05), rgb(255 255 255 / .07)),
    0 1px 2px light-dark(rgb(40 45 70 / .05), rgb(0 0 0 / .4)),
    0 6px 16px -6px light-dark(rgb(45 55 90 / .08), rgb(0 0 0 / .45)),
    0 24px 60px -20px light-dark(rgb(45 55 90 / .16), rgb(0 0 0 / .6)),
    inset 0 1px 0 var(--dd-highlight);
  --dd-shadow-pop:
    0 0 0 1px light-dark(rgb(17 17 18 / .06), rgb(255 255 255 / .09)),
    0 2px 4px -1px light-dark(rgb(40 45 70 / .06), rgb(0 0 0 / .4)),
    0 10px 20px -8px light-dark(rgb(45 55 90 / .12), rgb(0 0 0 / .45)),
    0 24px 48px -16px light-dark(rgb(45 55 90 / .20), rgb(0 0 0 / .6)),
    inset 0 1px 0 var(--dd-highlight-strong);
  --dd-shadow-dialog:
    0 0 0 1px light-dark(rgb(17 17 18 / .06), rgb(255 255 255 / .09)),
    0 2px 6px light-dark(rgb(40 45 70 / .06), rgb(0 0 0 / .4)),
    0 14px 28px -10px light-dark(rgb(45 55 90 / .14), rgb(0 0 0 / .5)),
    0 36px 88px -16px light-dark(rgb(45 55 90 / .26), rgb(0 0 0 / .7)),
    inset 0 1px 0 var(--dd-highlight-strong);
  --dd-shadow-chip:
    0 0 0 1px light-dark(rgb(17 17 18 / .06), rgb(255 255 255 / .07)),
    0 1px 2px light-dark(rgb(40 45 70 / .07), rgb(0 0 0 / .4)),
    0 2px 6px -2px light-dark(rgb(45 55 90 / .08), rgb(0 0 0 / .35)),
    inset 0 1px 0 var(--dd-highlight);
  --dd-shadow-btn: 0 0 0 0 transparent;
  /* The app's quiet control fills (round 3), for the preview miniatures:
     the filled search field and a neutral chip. */
  --dd-fill: light-dark(rgb(17 17 18 / .04), rgb(255 255 255 / .06));
  --dd-fill-hover: light-dark(rgb(17 17 18 / .065), rgb(255 255 255 / .09));
  /* THE GLOW, Tab's two layers verbatim. --dd-glow is the page wash rising
     from below the fold; --dd-dome is the blurred ring (paint it on an
     absolutely placed ellipse, aspect 1.5, width min(1600px, 180vw),
     bottom -58%, filter blur(5px)). Signed-out and marketing surfaces only. */
  --dd-glow: radial-gradient(circle at 50% 115%, light-dark(rgb(149 172 235 / .34), rgb(110 132 210 / .22)), transparent 43%);
  --dd-dome: radial-gradient(closest-side, light-dark(rgb(255 255 255 / .92), rgb(11 12 12 / .9)) 30%, light-dark(rgb(175 193 242 / .30), rgb(120 140 215 / .16)) 61%, light-dark(rgb(130 156 230 / .36), rgb(110 132 210 / .26)) 78%, transparent 100%);
  --dd-periwinkle-wash: light-dark(#f1f3fd, #272a31);
  --dd-periwinkle: light-dark(#6f86e0, #9fb1f0);
  --dd-ink-eyebrow: light-dark(#66718a, #9aa4bb);

  --dd-ease: cubic-bezier(.3, .7, .3, 1);

  --dd-dur: 160ms;
  --dd-dur-hover: 0ms;
  --dd-dur-slow: 220ms;

  --dd-dur-pop: 90ms;

  --dd-ease-pane: cubic-bezier(.4, .01, .2, 1);

  --dd-dur-fold: 180ms;

  --dd-dur-draw: 140ms;

  --dd-tick: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.6 6.3 4.8 8.5 9.4 3.7' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");

  --dd-alert: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='9.25' fill='none' stroke='%23000' stroke-width='1.8'/%3E%3Cpath d='M12 7.6v5.2' stroke='%23000' stroke-width='1.9' stroke-linecap='round'/%3E%3Ccircle cx='12' cy='16.4' r='1.15' fill='%23000'/%3E%3C/svg%3E");

  --dd-end-overflow: ellipsis;
  --dd-end-fade: none;
  --dd-end-fade-w: clamp(24px, 2em, 32px);
  --dd-end-fade-to: to right;
}
html[data-theme="light"] { color-scheme: light; }
html[data-theme="dark"]  { color-scheme: dark; }
/* THE END FADE, the app's (app/src/styles/system.css, "the end fade"). A name
   that does not fit fades out at its end where the browser has scroll
   timelines, and keeps its ellipsis where it has not or under reduced motion. */
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    :root { --dd-end-overflow: clip; --dd-end-fade: dd-end-fade linear both; }
  }
  :dir(rtl) { --dd-end-fade-to: to left; }
}
@keyframes dd-end-fade {
  from, to { mask-image: linear-gradient(var(--dd-end-fade-to), #000 calc(100% - var(--dd-end-fade-w)), transparent); }
}
/* THE CHOSEN ACCENT, the same blocks app/src/styles/system.css carries, so a
   person's accent (dd-accent in localStorage, written onto <html> by each
   page's pre-paint script) reaches the signed-out pages' ink button and
   welcome/'s own step 5 as it is picked. INK IS THE DEFAULT AND HAS NO BLOCK
   (the Tab pass, 2026-09-23): choosing it deletes the attribute and the key,
   so there is nothing to go stale. BLUE HAS ONE, because it is an explicit
   choice now rather than the absent default it was. Each block only
   reassigns --dd-accent to a hue token that is dark in light mode and light
   in dark mode, which is what keeps --dd-on-accent legible on it. */
:root[data-accent="blue"]   { --dd-accent: var(--dd-blue); }
:root[data-accent="teal"]   { --dd-accent: var(--dd-teal); }
:root[data-accent="green"]  { --dd-accent: var(--dd-green); }
:root[data-accent="amber"]  { --dd-accent: var(--dd-amber); }
:root[data-accent="orange"] { --dd-accent: var(--dd-orange); }
:root[data-accent="pink"]   { --dd-accent: var(--dd-pink); }
:root[data-accent="violet"] { --dd-accent: var(--dd-violet); }
* { box-sizing: border-box; }
html {

  background: var(--dd-canvas);
  scrollbar-width: thin;
  scrollbar-color: var(--dd-gray-300) transparent;

  overscroll-behavior: none;
}
body {
  margin: 0;
  font-family: var(--dd-font-sans);
  font-size: var(--dd-fs-sm);
  color: var(--dd-ink);
  background: var(--dd-canvas);
  -webkit-font-smoothing: antialiased;
}
[hidden] { display: none !important; }
::selection { background: light-dark(rgb(149 172 235 / .38), rgb(120 140 215 / .45)); color: inherit; }
a { color: inherit; }
:focus-visible {
  outline: 2px solid var(--dd-ink);
  outline-offset: 2px;
}
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="button"]):not([type="submit"]):focus-visible,
textarea:focus-visible,
[contenteditable]:focus-visible {
  outline: none;
}
body:not(.auth-body) { overflow: hidden; }
.side-head {
  display: flex; align-items: center; gap: 4px;

  height: var(--dd-topbar-h);
  flex: none;

  padding: 0 12px;
  margin: 0 -12px 10px;
  border-bottom: 1px solid var(--dd-hairline);
}
/* THE SHELL'S OWN CLASSES, KEPT FOR ONE READER (round 2 of the Tab pass,
   2026-09-23). Everything from .side-head down to .side-foot, and .topbar
   and .search-pill after them, is read by exactly one thing now: welcome/'s
   static preview of the shell, which is aria-hidden and inert. So these are
   drawings, not controls. The hover, active and cursor rules, the rail's
   collapse and glide transitions, and every selector for rail parts the
   preview does not draw (projects, chats, the subbar, the emoji picker) were
   the app's, carried here from the days this sheet was the app's too, and
   they went: an inert element is never hovered, and nothing here collapses. */
.ws-switch {
  flex: 1; min-width: 0;
  display: flex; align-items: center; gap: 8px;
  border: 0; background: transparent;
  font: inherit; color: inherit; text-align: left;
  padding: 4px 6px;
  border-radius: var(--dd-radius-m);
  max-width: 100%;
  overflow: hidden;
}
.ws-mark {
  width: 22px; height: 22px; flex: none;
  border-radius: var(--dd-radius-m);
  background: var(--dd-accent); color: var(--dd-on-accent);
  display: grid; place-items: center;
  font-weight: 700; font-size: var(--dd-fs-2xs);
  letter-spacing: -.02em;
}
.ws-name {
  min-width: 0; max-width: 150px;
  overflow: hidden; white-space: nowrap; text-overflow: var(--dd-end-overflow); animation: var(--dd-end-fade); animation-timeline: scroll(self inline);
  font-weight: 650; font-size: var(--dd-fs-sm); letter-spacing: -.01em;
}
.ws-caret {
  width: 14px; height: 14px; flex: none;
  stroke-width: 2; color: var(--dd-ink-quiet);
}
.rail-btn {
  width: 28px; height: 28px; flex: none;
  display: grid; place-items: center;
  border: 0; background: transparent;
  border-radius: var(--dd-radius-m);
  color: var(--dd-ink-quiet);
}
.rail-btn svg { width: 16px; height: 16px; stroke-width: 1.75; }
/* THE VECKA MARK (shared/brand.mjs). Three drawings, each filled with
   currentColor so the surface picks the colour. Every box keeps the
   designer's padding, so heights here are chosen by the visible artwork.
   A logo box h tall shows the icon 108/136 h and the lettering 100/136 h,
   and an icon box shows its drawing 407/512 h. */
.brand-icon, .brand-logo, .brand-wordmark { display: block; flex: none; }
.microlabel {
  font-size: var(--dd-fs-2xs);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--dd-ink-quiet);
}
.side-search { display: flex; align-items: center; gap: 4px; padding: 0 2px 12px; }
.side-search .search-pill { flex: 1; min-width: 0; width: auto; }
.side-nav { display: flex; flex-direction: column; gap: 1px; }
.side-nav svg { width: 16px; height: 16px; flex: none; stroke-width: 1.75; }
.side-foot { margin-top: auto; display: flex; flex-direction: column; gap: 2px; padding-top: 12px; border-top: 1px solid var(--dd-hairline); }
.topbar {
  flex: none;
  height: var(--dd-topbar-h);
  display: flex; align-items: center; gap: 8px;
  padding: 0 16px;
  border-bottom: 1px solid var(--dd-hairline);
  background: var(--dd-canvas);
  z-index: 60;
}
.topbar-page {
  display: flex; align-items: center; gap: 8px;
  font-size: var(--dd-fs-sm-);
  font-weight: 600;
  letter-spacing: -.005em;
}
.topbar-page svg { width: 15px; height: 15px; flex: none; stroke-width: 1.75; color: var(--dd-ink-light); }
.topbar-page { min-width: 0; }
.search-pill {
  display: flex; align-items: center; gap: 8px;
  font-family: inherit;
  height: 34px; width: 100%;
  padding: 0 14px;
  border: 1px solid var(--dd-border);
  border-radius: var(--dd-radius-m);
  /* The app's one input look (round 2): white panel, hairline, no shadow. */
  background: var(--dd-panel);
  color: var(--dd-ink-quiet);
  font-size: var(--dd-fs-sm-);
}
.search-pill > svg { width: 14px; height: 14px; flex: none; stroke-width: 1.75; color: var(--dd-ink-quiet); }
.search-pill-label {
  min-width: 0;
  overflow: hidden; text-overflow: var(--dd-end-overflow); animation: var(--dd-end-fade); animation-timeline: scroll(self inline); white-space: nowrap;
}
.search-pill kbd {
  margin-left: auto;
  font-size: var(--dd-fs-3xs);
  border: 1px solid var(--dd-border);
  border-radius: var(--dd-radius-s);
  padding: 1px 5px;
  background: var(--dd-panel);
}
main { padding: 32px 40px 40px; }
body:not([data-app]):not(.auth-body) main { flex: 1; min-height: 0; overflow-y: auto; }
.btn {
  display: inline-flex; align-items: center; gap: 7px;
  height: 34px; padding: 0 16px;
  border-radius: var(--dd-radius-m);
  border: 1px solid var(--dd-border);
  background: var(--dd-panel);
  color: var(--dd-ink);
  font: inherit; font-size: var(--dd-fs-sm-); font-weight: 550;
  cursor: pointer;

  text-decoration: none;

  transition-property: background, border-color, transform, opacity;
  transition-duration: var(--dd-dur-hover), var(--dd-dur-hover), var(--dd-dur-pop), var(--dd-dur);
  transition-timing-function: var(--dd-ease);
}
/* FLAT, the homepage's button exactly (round 3 of the Tab pass): no shadow
   at rest, hovered or pressed; hover is one grey step of fill and nothing
   else (the edge used to darken to a solid grey-300 as well), and a press
   gives a little under the hand rather than dropping a pixel. */
.btn:hover { background: var(--dd-gray-50); }
.btn:active {
  transform: scale(.97);
  transition-duration: var(--dd-dur-hover), var(--dd-dur-hover), 0s, var(--dd-dur);
}
.btn-primary { background: var(--dd-accent); color: var(--dd-on-accent); border-color: var(--dd-accent); }
.btn-primary:hover {
  background: color-mix(in srgb, var(--dd-accent) 88%, var(--dd-canvas));
  border-color: color-mix(in srgb, var(--dd-accent) 88%, var(--dd-canvas));
}
.btn svg { width: 16px; height: 16px; stroke-width: 1.75; }
.dd-swap { display: inline-grid; align-items: center; justify-items: center; }
.dd-swap > span {
  grid-area: 1 / 1;

  display: inline-flex; align-items: center; gap: 6px;
  opacity: 1;
  transform: translateY(0);
  filter: none;
  transition: opacity var(--dd-dur-pop) var(--dd-ease),
              transform var(--dd-dur-pop) var(--dd-ease),
              filter var(--dd-dur-pop) var(--dd-ease);
}
.dd-swap > span[data-on="false"] {
  opacity: 0;
  transform: translateY(3px);
}
@media (prefers-reduced-motion: no-preference) {
 .dd-swap > span[data-on="false"] { filter: blur(1.6px); }
}
.dd-swap > span[data-on="true"] { transition-delay: var(--dd-dur-pop); }
.check {
  width: 17px; height: 17px; flex: none;
  border: 1.5px solid var(--dd-gray-300);
  border-radius: var(--dd-radius-s);
  appearance: none; margin: 0; cursor: pointer;
  background: var(--dd-panel);
  transition: background var(--dd-dur) var(--dd-ease),
              border-color var(--dd-dur) var(--dd-ease),
              transform var(--dd-dur) var(--dd-ease);
}
.check:hover { border-color: var(--dd-gray-500); }
.check:active { transform: scale(.96); }
.check:checked {
  background: var(--dd-accent);
  border-color: var(--dd-accent);
}
.check::after {
  content: "";
  display: block; width: 100%; height: 100%;
  background: var(--dd-on-accent);
  -webkit-mask: var(--dd-tick) center / 10px 10px no-repeat;
  mask: var(--dd-tick) center / 10px 10px no-repeat;

  clip-path: inset(0 100% 0 0);
}
.check:checked::after {
  clip-path: inset(0 0% 0 0);
  transition: clip-path var(--dd-dur-draw) var(--dd-ease);
}
@media (max-width: 900px) {
 main { padding: 24px 20px 32px; }
}
@media (prefers-reduced-motion: reduce) {
 * { transition: none !important; animation: none !important; }
}
.input {
  font: inherit; font-size: var(--dd-fs-sm);
  color: var(--dd-ink);
  background: var(--dd-panel);
  border: 1px solid var(--dd-border);
  border-radius: var(--dd-radius-m);
  padding: 8px 12px;
  width: 100%;
  transition: border-color var(--dd-dur-hover) var(--dd-ease);
}
.input:focus { transition: border-color var(--dd-dur) var(--dd-ease); }
.input:hover { border-color: var(--dd-gray-300); }
.input:focus { border-color: var(--dd-gray-500); }
select.input { cursor: pointer; }
/* A FORM, NOT A CARD. The name is older than the card around it: every
   .login-card on every page now sits inside .auth-pane, whose card is the
   only frame, so the box this used to draw (and the override that took it
   away again inside the pane) are both gone. */
.login-card { width: 100%; display: flex; flex-direction: column; }
.login-field { position: relative; display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
.login-field .microlabel { padding-left: 1px; }
.login-error {
  display: flex; align-items: flex-start; gap: 9px;
  margin: 2px 0 14px;
  padding: 10px 12px;
  border-radius: var(--dd-radius-m);
  font-size: var(--dd-fs-sm-);
  line-height: 1.45;
  color: var(--dd-red);
  background: color-mix(in srgb, var(--dd-red) 7%, transparent);
  border: 1px solid color-mix(in srgb, var(--dd-red) 22%, transparent);
  overflow: hidden;
  animation: dd-callout-in 200ms var(--dd-ease);
}
.login-error::before {
  content: "";
  width: 15px; height: 15px; flex: none;
  margin-top: 1.5px;
  background: currentColor;
  -webkit-mask: var(--dd-alert) center / contain no-repeat;
  mask: var(--dd-alert) center / contain no-repeat;
}
@keyframes dd-callout-in {
  0% {
    opacity: 0; max-height: 0;
    padding-top: 0; padding-bottom: 0;
    margin-top: 0; margin-bottom: 0;
    border-top-width: 0; border-bottom-width: 0;
  }
  55% { max-height: 200px; }
  100% { opacity: 1; }
}
.login-submit { width: 100%; justify-content: center; }
.field-hint {
  display: block;
  min-height: 1.45em;
  margin: 6px 2px 0;
  font-size: var(--dd-fs-2xs);
  line-height: 1.45;
  color: var(--dd-ink-quiet);
  transition: color var(--dd-dur) var(--dd-ease);
}
.field-hint.bad { color: var(--dd-red); }
.field-hint.warn { color: var(--dd-amber); }
.input.is-bad,
.auth-pane .input.is-bad:hover,
.auth-pane .input.is-bad:focus {
  border-color: color-mix(in srgb, var(--dd-red) 60%, transparent);
}
@keyframes auth-shake {
  0%     { transform: translateX(0); }
  27.78% { transform: translateX(5px); }
  55.56% { transform: translateX(-5px); }
  77.78% { transform: translateX(3px); }
  100%   { transform: translateX(0); }
}
.auth-shake { animation: auth-shake 180ms var(--dd-ease); }
@media (prefers-reduced-motion: reduce) {
 .auth-shake { animation: none !important; transform: none !important; }
}
/* The recovery card's "Try a different address", in the card's one link
   register (round 2): centred under the centred line it answers, quiet,
   darkening on hover and underlined only then, like "Forgot your
   password?" and the page's foot. It sat right-aligned and always
   underlined, the one link on these pages that did either. */
.login-foot-link {
  display: block;
  margin: 6px 2px 0;
  padding: 0; border: 0; background: transparent;
  width: 100%; text-align: center;
  font: inherit; font-size: var(--dd-fs-2xs);
  color: var(--dd-ink-quiet);
  text-decoration: none;
  cursor: pointer;
}
.login-foot-link:hover { color: var(--dd-ink); text-decoration: underline; text-underline-offset: 3px; }
.login-error.login-note {
  color: var(--dd-green);
  background: color-mix(in srgb, var(--dd-green) 8%, transparent);
  border-color: color-mix(in srgb, var(--dd-green) 25%, transparent);
}
.login-error.login-note::before {
  -webkit-mask-image: var(--dd-tick);
  mask-image: var(--dd-tick);
}
/* The meter hangs from its password field's reserved caption line (kept
   for the caps-lock notice), so that field gives up its own bottom margin
   and the meter takes it below instead: the meter reads as the field's,
   and the confirm field gets the same air every other field has. */
.pw-strength { margin: 2px 2px 14px; }
.auth-pane .login-field:has(+ .pw-strength) { margin-bottom: 0; }
.pw-strength .pw-meter { display: flex; gap: 4px; margin-bottom: 6px; }
.pw-meter-bar {
  flex: 1; height: 4px; border-radius: 2px;
  background: var(--dd-gray-100);
  overflow: hidden;
}
.pw-meter-bar > i {
  display: block; height: 100%; border-radius: inherit;
  background: var(--dd-gray-300);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dd-dur) var(--dd-ease),
              background var(--dd-dur) var(--dd-ease);
}
.pw-meter-bar:nth-child(1) > i { transition-delay: 0ms; }
.pw-meter-bar:nth-child(2) > i { transition-delay: 40ms; }
.pw-meter-bar:nth-child(3) > i { transition-delay: 80ms; }
.pw-meter-bar:nth-child(4) > i { transition-delay: 120ms; }
.pw-meter-bar.filled > i { transform: scaleX(1); }
.pw-meter-bar.gate > i { background: var(--dd-gray-300); }
.pw-strength .pw-meter.level-weak   .pw-meter-bar.filled > i { background: var(--dd-red); }
.pw-strength .pw-meter.level-fair   .pw-meter-bar.filled > i { background: var(--dd-amber); }
.pw-strength .pw-meter.level-good   .pw-meter-bar.filled > i { background: var(--dd-blue); }
.pw-strength .pw-meter.level-strong .pw-meter-bar.filled > i { background: var(--dd-green); }
.pw-meter-label {
  display: block;
  min-height: 1.3em;
  font-size: var(--dd-fs-2xs);
  color: var(--dd-ink-quiet);
  transition: opacity var(--dd-dur) var(--dd-ease);
}
.pw-meter-label.fade { opacity: 0; }
.pw-reqs { list-style: none; margin: 9px 0 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.pw-strength .pw-meter + .pw-meter-label + .pw-reqs { margin-top: 8px; }
.pw-req {
  display: flex; align-items: center; gap: 7px;
  font-size: var(--dd-fs-2xs);
  color: var(--dd-ink-quiet);
  transition: color var(--dd-dur) var(--dd-ease);
}
.pw-req.met { color: var(--dd-ink); }
.pw-req-dot {
  width: 14px; height: 14px; flex: none;
  border: 1.5px solid var(--dd-gray-300);
  border-radius: 50%;
  position: relative;
  transition: background var(--dd-dur) var(--dd-ease), border-color var(--dd-dur) var(--dd-ease);
}
.pw-req-dot::after {
  content: "";
  position: absolute; inset: 0;
  background: var(--dd-on-accent);
  -webkit-mask: var(--dd-tick) center / 8px 8px no-repeat;
  mask: var(--dd-tick) center / 8px 8px no-repeat;
  opacity: 0; transform: scale(.5);
  transition: opacity var(--dd-dur) var(--dd-ease), transform var(--dd-dur) var(--dd-ease);
}
.pw-req.met .pw-req-dot { background: var(--dd-green); border-color: var(--dd-green); }
.pw-req.met .pw-req-dot::after { opacity: 1; transform: scale(1); }
.pw-sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
/* ============================================================================
   THE SIGNED-OUT SURFACE'S OWN VALUES (2026-09-23, the Tab pass).

   Not tokens of the product: nothing in the app reads them, and
   darkPalette.test.ts only compares the names both sheets declare, so these
   live under their own --auth- prefix. They are the numbers measured off
   tab.bot's sign-in and invite pages (see the "Tab pass" brief), turned into
   light-dark() pairs, because Tab has no dark scheme and ours has to look
   chosen rather than inverted.

     ground   Tab's sign-in page is #f8f8f6, a breath warmer and darker than
              the card, which is what lets a white card read as a card with
              only a hairline and a haze under it. Dark is the rail's own
              near-black, the darkest surface the product owns.
     card     white at .72, so the glow BLEEDS INTO the card (David, round 2,
              from a screenshot of tab.bot): the dome's arc passes behind the
              card's lower half and tints it periwinkle, fading to clean
              white above. Tab's card is .68 over a 12px backdrop blur; this
              one cannot blur (see THE GLOW), and does not need to, because
              both glow layers are already soft gradients with nothing sharp
              to show through. .72 rather than .68 is for the quiet ink: over
              the arc's brightest band the card still reads #f1f4fb, which
              keeps --auth-quiet-ink at 4.5:1. Round 1's .86 let almost
              nothing through. Everything a hand touches (fields, buttons,
              chips, tiles) stays opaque white, so the form reads crisp on a
              lit card rather than tinted with it. Dark is the card at .8
              over the near-black ground, enough for the arc's dim blue to
              reach it without turning the panel into a glow.
     edge     Tab's rgba(17,17,18,.09) a breath lighter at .08, drawn as the first layer of the
              card's shadow list (round 3) rather than as a border, so the
              edge and the lift are one soft thing and the card's box is its
              painted box. A tint, not a grey, so it sits the same on white
              and on the ground behind it.
     shadow   Tab's own wide, low, blue-slate haze (0 24px 70px at .11) with
              a one-pixel contact line under it. Dark has no blue to lift, so
              the haze turns to black and the contact line does the work.
     field    the homepage's waitlist field (home/home.css, .wl-input): white,
              a 1px alpha edge at --dd-border's strength, no shadow at rest.
              Hover takes the edge one alpha step up. Focus is a SURFACE
              change, never a ring and never an ink outline (no rings on
              text fields, a standing rule): the field lifts off the card on
              a small blue-slate shadow while its edge stays faint, the way a
              Tab card lifts off its ground.
     control  ONE SIZE for every control a signed-out visitor touches
              (round 2, 2026-09-23): 44px tall with a 12px corner, for the
              field, the ink primary, the white secondary, the select, the
              chips and the gateway's waitlist row alike, on these pages, the
              gateway and the homepage. Round 1 had fields at 48 and 44,
              buttons at 52, 48 and 40, and corners of 10, 12, 14 and 15 on
              one card. 12 is the one literal the component spec allows
              besides the radius tokens, and --dd-radius-m-lg is the name the
              homepage's sheet uses for it too.
   ============================================================================ */
:root {
  --dd-radius-m-lg: 12px;
  --auth-control-h: 44px;
  --auth-ground: light-dark(#f8f8f6, #0b0c0c);
  --auth-card: light-dark(rgb(255 255 255 / .72), rgb(21 22 22 / .8));
  --auth-paper: light-dark(#ffffff, #151616); /* the card's colour, opaque, to mix against */
  --auth-edge: light-dark(rgb(17 17 18 / .08), rgb(255 255 255 / .08));
  --auth-shadow:
    0 0 0 1px var(--auth-edge),
    0 1px 2px light-dark(rgb(40 45 70 / .04), rgb(0 0 0 / .5)),
    0 24px 70px light-dark(rgb(60 78 135 / .11), rgb(0 0 0 / .55));
  --auth-field: light-dark(#ffffff, #1b1c1d);
  --auth-field-edge: light-dark(rgb(17 17 18 / .08), rgb(255 255 255 / .10));
  --auth-field-edge-hover: light-dark(rgb(17 17 18 / .14), rgb(255 255 255 / .17));
  --auth-field-lift: 0 1px 2px light-dark(rgb(40 45 70 / .06), rgb(0 0 0 / .45)), 0 8px 20px -10px light-dark(rgb(45 55 90 / .26), rgb(0 0 0 / .7));
  /* A picked tile (the gateway's personas, welcome/'s chips): the field's
     white, lifted by the chip shadow and edged by one pixel of ink. */
  --auth-picked: 0 0 0 1px var(--dd-ink), 0 1px 3px light-dark(rgb(40 45 70 / .10), rgb(0 0 0 / .4));
  --auth-quiet-ink: light-dark(#6b6e76, #9a9da3);
  /* Tab's not-yet button, a cool slate with the words still white. */
  --auth-idle: light-dark(#b8bcc6, #34363b);
  --auth-idle-ink: light-dark(#ffffff, rgb(255 255 255 / .6));
}

.auth-body { min-height: 100vh; }
/* THREE ROWS ON THE GROUND: the lockup, the card, a quiet foot. The side
   gutter is 16px at every width, the phone's too: the card is a card on a
   phone as well now (it used to dissolve into a full-bleed sheet there,
   which only made sense on the sky, where an opaque slab left two seams). */
.auth-stage {
  min-height: 100vh; min-height: 100dvh;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  justify-items: center; align-items: center;
  row-gap: clamp(18px, 3vh, 34px);
  padding: clamp(22px, 3.6vh, 42px) 16px clamp(18px, 3vh, 32px);
  background: var(--dd-canvas);
  /* Above the glow, when there is one. Harmless when there isn't. */
  position: relative;
  z-index: 1;
}

/* ========================================================== THE GLOW

   Every front door (login, signup, forgot, reset, welcome and the demo
   gateway) stands on Tab's ground and Tab's glow, in place of the sky that
   stood here until 2026-09-23. David, of tab.bot: "white and black, but
   white majority", and "I like the glow that they have in the background
   too". The sky went whole: no page loads shared/sky.js any more (the
   homepage let go of it in the same pass), so there are no birds in the
   auth sky because there is no auth sky, and the .auth-sky rules that
   painted its first frame and its ink went in round 2.

   OPT-IN BY CLASS, for the reason the sky was. `auth-body` is also worn by
   internal/login.html, the operators' portal, and a staff tool wants a flat
   ground, so the public pages name `auth-glow` on their body and the portal
   does not.

   TWO LAYERS, both Tab's, both tokens in the :root block (--dd-glow and
   --dd-dome) so the marketing pages paint the same light:
     ::before  the wash, a periwinkle bloom centred just under the fold;
     ::after   the dome, a soft ring on a 1.5:1 ellipse whose centre sits on
               the bottom edge of the window, so its upper arc rises behind
               the card. Tab places it bottom -58% at 1600px wide, which on a
               900px window puts the centre within 11px of the edge; pinning
               the centre to the edge instead keeps that arc on screen on a
               phone too, where Tab's own numbers push the whole ellipse
               below the fold.
   Both are FIXED, so a page taller than the window (welcome/ on a phone, a
   long error at 320) scrolls its content over a glow that stays put. A fixed
   box adds nothing to the page's scrollable overflow, which is why an
   ellipse 180vw wide never scrolls the page sideways. Pseudo-elements, so no
   page carries markup for them and a failed stylesheet leaves nothing
   behind.

   WHY NOT A BACKDROP-FILTER ON THE CARD, which is the obvious next idea.
   backdrop-filter makes an element a containing block for `position: fixed`
   descendants, and demo/'s full-screen "Building your demo" IS a fixed
   descendant of the card. Frosting the card would silently shrink that
   overlay to the card's box. The card stays opaque, and the frosting lives
   on the overlay itself (demo.css), where it is safe. */
html:has(> body.auth-glow) { background: var(--auth-ground); }
.auth-glow { background: var(--auth-ground); }
.auth-glow .auth-stage { background: transparent; }
.auth-glow::before,
.auth-glow::after {
  content: "";
  position: fixed;
  z-index: 0;
  pointer-events: none;
}
.auth-glow::before { inset: 0; background: var(--dd-glow); }
.auth-glow::after {
  left: 50%; bottom: 0;
  width: min(1600px, 180vw);
  aspect-ratio: 1.5 / 1;
  transform: translate(-50%, 50%);
  border-radius: 50%;
  filter: blur(5px);
  background: var(--dd-dome);
}
/* ON A PHONE the dome is wider and its centre rises a fifth of the window,
   so its arc crosses the card's lower half the way it does at 1440 (the
   card is translucent so that crossing shows, see --auth-card). At 180vw
   with the centre on the edge, a 390x844 phone had the arc at the very
   foot of the window, under the card, and the card showed none of it. */
@media (max-width: 720px) {
  .auth-glow::after { width: 230vw; bottom: 20vh; }
}

.auth-mark {
  display: inline-flex; align-items: center; gap: 9px;
  text-decoration: none; color: var(--dd-ink);
}
/* The combined logo, 20px tall, which draws the icon 15.9px tall (the old
   V's 17 in a 24 box, optically, since the new mark is a filled mass) and
   the lettering 14.7px tall, at the legible floor for it. 82px wide. */
.auth-mark .brand-logo { height: 20px; width: auto; aspect-ratio: 555.211 / 136; }
/* The foot line: small and quiet, in the card's own quiet ink since the Tab
   pass (2026-09-23); its type is what it was before that pass. */
.auth-foot {
  display: flex; align-items: center; justify-content: center;
  gap: 6px; flex-wrap: wrap;
  min-height: 20px;
  font-size: var(--dd-fs-2xs);
  color: var(--auth-quiet-ink);
}
/* A button on the sign-in pages, a link on the gateway: no underline on
   either, so the two feet read the same. */
.auth-foot-link {
  border: 0; padding: 2px 3px; background: transparent;
  font: inherit; font-weight: 550; text-decoration: none;
  color: var(--dd-ink-light);
  cursor: pointer;
  border-radius: var(--dd-radius-s);
  transition: color var(--dd-dur-hover) var(--dd-ease);
}
.auth-foot-link:hover { color: var(--dd-ink); }

/* ============================================================ THE CARD

   Tab's sign-in card: white on the ground, the xl radius (24px; round 1
   drew Tab's own 28, round 2 put every signed-out card on the one token
   the spec gives cards of this rank), and one shadow list that is both its
   edge and its lift (round 3): a 1px alpha ring, then Tab's wide blue-slate
   haze. No border, so the edge is as soft as the haze under it. ONE COLUMN by default,
   440px, because login/, signup/, forgot/ and reset/ are one question at a
   time and Tab's sign-in is a single centred card; the preview that used to
   fill a right-hand cell there was a skeleton of the shell, bars where a
   week would be, and it went with the sky (see auth.js's note where
   mountHero stood).

   ITS CONTENT'S HEIGHT, like Tab's, centred on the ground. The views inside
   it change height when a visitor moves between panels or signup steps, and
   auth.js eases the views' wrapper between the two heights (show() and
   goToStep()), so the card's edges travel with it rather than jumping; an
   error line grows in the same way (dd-callout-in). A fixed floor was tried
   first and left short panels floating in the middle of a tall white card.
   Never a fixed height: it would clip into the pane's own scroll, which has
   no scrollbar. */
.auth-card {
  width: min(440px, 100%);
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  background: var(--auth-card);
  border-radius: var(--dd-radius-xl);
  box-shadow: var(--auth-shadow);
  overflow: hidden;
}
.auth-pane {
  min-width: 0; min-height: 0;
  display: flex; flex-direction: column;
  padding: 40px 40px 36px;
  background: transparent;

  overflow: hidden auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
}
.auth-pane::-webkit-scrollbar { width: 0; }
.auth-nav {
  flex: none;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  min-height: 30px;
}
/* A ghost square at the one control size (44), pulled back into the 30px
   nav row by its own negative margins so the row, and every heading under
   it, keeps its place; the chevron stays where it was. */
.auth-back {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--auth-control-h); height: var(--auth-control-h);
  margin: -7px 0 -7px -14px;
  border: 0; padding: 0; background: transparent;
  border-radius: var(--dd-radius-m);
  color: var(--dd-ink-quiet);
  cursor: pointer;
  transition: background var(--dd-dur-hover) var(--dd-ease), color var(--dd-dur-hover) var(--dd-ease);
}
.auth-back:hover { background: var(--dd-fill); color: var(--dd-ink); }
.auth-back svg { width: 17px; height: 17px; stroke-width: 1.75; }
.auth-nav-meta { margin-left: auto; display: flex; align-items: center; gap: 10px; }
.auth-form-col {
  width: 100%; max-width: 384px;
  margin: auto;
}

/* ------------------------------------------------ THE SPLIT CARD

   The two pages that show the product beside the form, demo/ (a filled
   week, the one thing a stranger came for) and welcome/ (the preview its
   steps ring), are Tab's invite card: one wide card, the form on white at
   the left, the picture in its own cell at the right. Keyed on the preview
   itself rather than a class, so the card is single wherever the preview is
   absent (a phone, a short window, JavaScript off on demo/).

   The split is one continuous curve from the phone fold up (the responsive
   pass, 2026-09-22): the form cell is 46% with a 340px floor and a 440px
   cap and its gutters ease from 28px to 44px with the window, so the form
   is never narrower than on a phone and no width between 721 and 1100 is a
   step. */
/* Its floor is the taller of the gateway's two panels (the persona step, a
   two-line question over four tiles), so moving between them never moves
   the card. On a 900px window the stage's row is the smaller number and
   both panels simply fill it. */
.auth-card:has(> .auth-hero) {
  width: min(1100px, 100%);
  min-height: min(720px, 100%);
}
@media (min-width: 721px) {
  .auth-card:has(> .auth-hero) { grid-template-columns: clamp(340px, 46%, 440px) minmax(0, 1fr); }
  .auth-card:has(> .auth-hero) > .auth-pane { padding: 34px clamp(28px, 3.93vw, 44px) 34px; }
}
/* A SHORT WINDOW (2026-09-10, the UI deep dive): a phone on its side is
   844 wide and 390 tall, with no room for the split card, which used to
   ride up over the wordmark. Under 520px of height the preview goes the way
   it does on a portrait phone, the card takes its own height, and the stage
   scrolls with the mark and the foot in flow. Height, not width: a
   1440x600 window keeps its preview because the card still fits. */
@media (max-height: 519px) and (min-width: 721px) {
 .auth-stage { align-content: start; align-items: start; }
 .auth-card, .auth-card:has(> .auth-hero) { width: min(440px, 100%); grid-template-columns: minmax(0, 1fr); height: auto; min-height: 0; }
 .auth-pane, .auth-card:has(> .auth-hero) > .auth-pane { overflow: visible; padding: 32px 36px 30px; }
}
/* THE PHONE. Still a card, 16px in from each edge with the same xl corner, and
   the gutters inside it easing down so a 320 screen keeps a 246px column.
   Content height, centred in the room between the lockup and the foot, and
   the stage scrolls when the form needs more than the window has. */
@media (max-width: 720px) {
 .auth-stage { padding-top: 22px; align-content: start; row-gap: 18px; }
 .auth-card, .auth-card:has(> .auth-hero) { width: 100%; height: auto; min-height: 0; }
 .auth-pane, .auth-card:has(> .auth-hero) > .auth-pane { overflow: visible; padding: 30px clamp(20px, 6vw, 28px) 26px; }
 .auth-form-col { max-width: 400px; }
}
.auth-views { position: relative; }
.auth-view { min-height: 0; }
@keyframes av-in { from { opacity: 0; filter: blur(1.6px); } }
.auth-view.av-in { animation: av-in 200ms var(--dd-ease) both; }
/* ------------------------------------------------------- THE WORDS

   The heading and the one quiet line under it, in Inter at the sizes they
   had before the Tab pass (2026-09-23), which changed the card around them
   and left the type alone. The single card centres both, the way Tab's
   sign-in does; its fields and their labels stay left, where a hand reads
   them. The split card (demo/, welcome/) reads left to right like Tab's
   invite card and leaves them left. */
.auth-title {
  margin: 0;
  font-size: 1.7rem;
  font-weight: 650;
  letter-spacing: -.022em;
  line-height: 1.15;
}
.auth-sub {
  margin: 8px 0 28px;
  font-size: var(--dd-fs-sm);
  line-height: 1.5;
  color: var(--dd-ink-quiet);
  /* The reset and forgot panels write the visitor's address into this line,
     and one long address is a single unbreakable word: it ran out of the
     pane (clipped at 768, past the column at 320). */
  overflow-wrap: anywhere;
}
.auth-card:not(:has(> .auth-hero)) :is(.auth-title, .auth-sub, .step-q) { text-align: center; }

/* ------------------------------------------------------- THE FIELDS

   The homepage's waitlist field, exactly at rest (round 3): a label above
   (the microlabel's sentence-case form, ink once its field has focus), a
   white field at the one control size (44px, the 12px corner) with a 1px
   alpha edge. No ring on focus, ever (a standing rule), and no ink outline
   either: hover takes the edge one alpha step up, and focus lifts the
   field off the card on a small blue-slate shadow (--auth-field-lift), a
   change of surface rather than a line. The caption line under each field
   stays reserved even when empty, which is what keeps the button from
   moving when a rejection lands there. */
.auth-pane .login-field { margin-bottom: 10px; gap: 8px; }
.auth-pane .input,
.auth-pane select.input {
  height: var(--auth-control-h);
  padding: 0 14px;
  border-radius: var(--dd-radius-m-lg);
  border-color: var(--auth-field-edge);
  background: var(--auth-field);
  transition: border-color var(--dd-dur) var(--dd-ease), box-shadow var(--dd-dur) var(--dd-ease);
}
.auth-pane .input::placeholder { color: var(--dd-gray-500); }
.auth-pane .input:hover { border-color: var(--auth-field-edge-hover); }
.auth-pane .input:focus { border-color: var(--auth-field-edge-hover); box-shadow: var(--auth-field-lift); }
.auth-pane .microlabel,
.auth-form-col .microlabel {
  text-transform: none;
  letter-spacing: 0;
  font-size: var(--dd-fs-2xs);
  font-weight: 500;
}
.auth-pane .login-field:focus-within .microlabel { color: var(--dd-ink); }
.auth-pane .login-field .microlabel { transition: color var(--dd-dur) var(--dd-ease); }
.auth-pane .field-hint { margin-top: 0; }

/* ------------------------------------------------------- THE BUTTON

   Tab's primary: ink, full width, at the one control size (44px, the
   12px corner; round 1 drew Tab's own 52 and 15, a size nothing else on
   the card shared; its words keep the size and weight they had before the
   Tab pass), and FLAT like the homepage's primary (round 3: no shadow at
   rest, hovered or pressed). INK IS THE
   ACCENT now, so this is simply the accent: near-black with white words in
   light, white with near-black words in dark, and a person's own accent
   choice (dd-accent on <html>) still reaches it. The dark half used to be a
   hard-coded bright blue, chosen when the accent was a pastel blue that
   read as disabled on a dark card; the ink accent has no such problem. */
.auth-pane .login-submit {
  height: var(--auth-control-h);
  margin-top: 8px;
  border-radius: var(--dd-radius-m-lg);
  font-size: var(--dd-fs-sm);
}
.auth-pane .login-submit.btn-primary:not(.idle):not(:disabled) {
  background: var(--dd-accent);
  border-color: var(--dd-accent);
  color: var(--dd-on-accent);
}
.auth-pane .login-submit.btn-primary:not(.idle):not(:disabled):hover {
  background: color-mix(in srgb, var(--dd-accent) 88%, var(--auth-paper));
  border-color: color-mix(in srgb, var(--dd-accent) 88%, var(--auth-paper));
}
/* NOT YET. Signup's Continue is idle until its step is answered (still
   clickable, a click says what is missing) and the gateway's buttons are
   born disabled. Both wear Tab's own waiting colour, a cool slate with the
   words still white: plainly the same button, plainly not ready. This is
   the one place the signed-out pages leave the app's disabled look (grey
   fill, grey words), on purpose and by David's call in round 2: on a lit
   card it is the Tab detail that reads as waiting rather than dead, and the
   bordered ghost before it read as dead. */
.auth-pane .login-submit.btn-primary.idle,
.auth-pane .login-submit.btn-primary:disabled {
  background: var(--auth-idle);
  border-color: var(--auth-idle);
  color: var(--auth-idle-ink);
}
.auth-pane .login-submit.btn-primary:disabled { cursor: default; }
.pw-wrap { position: relative; display: block; }
.auth-pane .login-field:has(.pw-eye) .input { padding-right: 46px; }
/* Centred in the 44px field: (44 - 28) / 2 from its bottom and right.
   8px inside the field's 12px corner, so its hover fill takes the small
   corner (12 less 8 floors at small): the medium one it had was rounder
   than the field's curve around it (the details pass). */
.pw-eye {
  position: absolute; right: 8px; bottom: 8px;
  width: 28px; height: 28px;
  display: grid; place-items: center;
  border: 0; padding: 0; background: transparent;
  border-radius: var(--dd-radius-s);
  color: var(--dd-ink-quiet);
  cursor: pointer;
  transition: color var(--dd-dur-hover) var(--dd-ease), background var(--dd-dur-hover) var(--dd-ease);
}
.pw-eye:hover { color: var(--dd-ink); background: var(--dd-fill); }
.pw-eye svg { width: 16px; height: 16px; stroke-width: 1.75; }
.login-forgot {
  display: block;
  margin: 18px auto 0;
  border: 0; padding: 2px 4px; background: transparent;
  font: inherit;
  font-size: var(--dd-fs-sm-); font-weight: 550;
  color: var(--auth-quiet-ink);
  cursor: pointer;
  transition: color var(--dd-dur-hover) var(--dd-ease);
}
.login-forgot:hover { color: var(--dd-ink); text-decoration: underline; text-underline-offset: 3px; }
.auth-div {
  display: flex; align-items: center; gap: 12px;
  margin: 24px 0 16px;
  font-size: var(--dd-fs-2xs);
  color: var(--auth-quiet-ink);
  white-space: nowrap;
}
.auth-div i { flex: 1; height: 1px; background: var(--auth-edge); }
/* The secondary is the homepage's secondary: a field's opaque white and
   its faint alpha edge, the primary's size and corner, and flat (round 3,
   no shadow at rest, hovered or pressed). Hover is one grey step of fill
   and nothing else, as on the homepage. */
.auth-alt-btn {
  width: 100%; height: var(--auth-control-h);
  justify-content: center;
  border-radius: var(--dd-radius-m-lg);
  border-color: var(--auth-field-edge);
  background: var(--auth-field);
  font-size: var(--dd-fs-sm-);
}
.auth-view > .auth-alt-btn { margin-top: 26px; }
.auth-view > .auth-div + .auth-alt-btn { margin-top: 0; }
.auth-alt-btn .auth-g { flex: none; margin-right: 8px; }
/* THE PREVIEW'S CELL, the right half of the split card. Where Tab's invite
   card puts a photograph, this cell is its own small piece of the page's
   glow: a cool paper a step off the card's white, with the periwinkle wash
   rising from under its bottom edge, so the product window floating in it
   reads as lit from the same place as the page around the card. No line
   divides it from the form (round 3): the step from the form's white to
   this paper is the seam, the way Tab's photograph meets its white. The glow is allowed
   here because this is a signed-out surface; inside the app it never is. */
.auth-hero {
  display: none;
  position: relative;
  overflow: hidden;
  --auth-hero-paper: light-dark(#f4f5f8, #101112);
  background:
    radial-gradient(90% 62% at 62% 112%, light-dark(rgb(149 172 235 / .30), rgb(110 132 210 / .14)), transparent 72%),
    var(--auth-hero-paper);

  --dd-dur-zoom: 320ms;
}
@media (min-width: 721px) {
 .auth-hero { display: block; }
}
/* ...except in a short window (the max-height rule beside .auth-card above):
   after the block above so the height wins over the width. */
@media (max-height: 519px) and (min-width: 721px) {
 .auth-hero { display: none; }
}
/* ---------------------------------------------------------------
   THE PREVIEW'S SHELL — THE FLOATING CANVAS (v8, 2026-08-30).

   The frame is the app's WINDOW, so its surface is the GROUND: the one
   tone darker than the canvas in light mode, which is what makes the
   card inside it read as a card before any shadow is asked to (the Tab
   pass adds a subtle one; the tone step still does most of the work). The three tokens
   are traced from app/src/styles/system.css rather than reached for,
   because auth.css is a standalone sheet that the app's system.css
   never loads — if the ground moves there, move it here too. Their dark
   halves are the OLD rail colour on purpose: dark already floated a
   lighter main area beside a near-black rail, so v8 changed dark's
   geometry and not its colour.

   Before this pass the preview drew the PRE-v8 shell — a boxed rail
   pane with its own background and a right border, beside a main pane
   whose topbar spanned the whole width. Both of those are gone from
   the product: the rail sits bare on the ground and the topbar is the
   card's first row. welcome/index.html's own head carries the anatomy
   this paints and why the preview holds no content (auth.js drew a twin
   of it on the four sign-in pages until the Tab pass took that cell away).

   THE TAB PASS (2026-09-23): the window floats on the pop shadow and sits
   on the preview cell's paper rather than on white, so it reads as a
   window lifted off a lit surface, the way Tab's own screenshots of the
   product sit on its page. ROUND 3 draws it as the round 3 app: the
   ground is #f5f5f3, nothing in it has a border (the window, the card and
   the ask box take their edge from their shadow's first ring), the active
   rail row is a white chip, the search field is filled, and the list's
   head and row rules are gone, so its rows part by whitespace.
   --------------------------------------------------------------- */
.hx-frame {
  --dd-ground:        light-dark(#f5f5f3, #0b0c0c);
  --dd-ground-wash-3: light-dark(#dfdfdb, #2a2b2c);
  box-shadow: var(--dd-shadow-pop);

  position: absolute;
  top: clamp(24px, 5%, 40px);
  bottom: clamp(18px, 3.6%, 30px);
  left: clamp(26px, 6.5%, 54px);
  right: -76px;
  display: flex;
  border-radius: var(--dd-radius-l);
  background: var(--dd-ground);
  overflow: hidden;

  transform-origin: 0 0;
  /* Its transition (the camera's glide) is written with the ring's, at
     .hx-frame.hx-ring below. */
}
.hx-cut {
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 132px; z-index: 3;
  pointer-events: none;
  background: var(--auth-hero-paper);
  -webkit-mask-image: linear-gradient(to top, #000 20%, transparent 100%);
  mask-image: linear-gradient(to top, #000 20%, transparent 100%);
}
@media (prefers-reduced-motion: no-preference) {
 .hx-cut {
     -webkit-backdrop-filter: blur(1.6px);
     backdrop-filter: blur(1.6px);
   }
}
.hx-app { position: relative; flex: 1; min-width: 0; display: flex; align-items: stretch; }
/* THE RAIL, BARE. No background of its own and no right border: what
   separates it from the card is the card's own edge and the ground
   showing between the two. The head keeps its row but loses the
   hairline under it — on the ground that line had nothing to divide. */
.hx-side {
  width: 184px; flex: none;
  display: flex; flex-direction: column;
  padding: 0 9px 10px;
}
.hx-side .side-head { height: 36px; padding: 0 2px; margin: 0 0 8px; border-bottom: 0; }
.hx-side .ws-switch { padding: 3px 5px; gap: 7px; }
.hx-side .ws-mark { width: 19px; height: 19px; font-size: var(--dd-fs-3xs); border-radius: var(--dd-radius-s); }
.hx-side .ws-name { font-size: var(--dd-fs-2xs); max-width: 106px; }
.hx-side .ws-caret { width: 12px; height: 12px; }
.hx-side .rail-btn { width: 22px; height: 22px; }
.hx-side .rail-btn svg { width: 13px; height: 13px; }
.hx-side .side-search { padding: 0 2px 10px; gap: 4px; }
.hx-side .search-pill { height: 26px; font-size: var(--dd-fs-3xs); padding: 0 8px; gap: 6px; border: 0; background: var(--dd-fill); }
.hx-side .search-pill > svg { width: 11px; height: 11px; }
.hx-side .search-pill kbd { font-size: var(--dd-fs-3xs); padding: 1px 3px; border: 0; background: transparent; }
.hx-side .side-nav > span {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 7px;
  border-radius: var(--dd-radius-m);
  font-size: var(--dd-fs-2xs); font-weight: 480;
  color: var(--dd-ink-light);
}
/* The active row is the round 3 rail's white chip on the chip lift. */
.hx-side .side-nav > span.active { background: var(--dd-panel); box-shadow: var(--dd-shadow-chip); color: var(--dd-ink); font-weight: 600; }
.hx-side .side-nav svg { width: 13px; height: 13px; flex: none; stroke-width: 1.75; }
.hx-side .side-foot { padding-top: 9px; gap: 1px; border-top: 0; }
.hx-side .hx-bar { background: var(--dd-ground-wash-3); }
/* The labelled record sections the real rail carries under its modules
   — Projects, Classes, calendars. A label bar, then rows of colour
   mark plus name. Bars and dots: the preview knows the SHAPE of that
   list and has no business inventing whose projects they are. */
.hx-side-records { display: flex; flex-direction: column; gap: 14px; padding: 12px 7px 0; }
.hx-rec-sec { display: flex; flex-direction: column; gap: 8px; }
.hx-rec { display: flex; align-items: center; gap: 8px; }
.hx-rec > .hx-bar { flex: 0 1 auto; min-width: 0; }
.hx-dot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--dd-gray-300); }
.hx-dot.c1 { background: var(--dd-blue); }
.hx-dot.c2 { background: var(--dd-green); }
.hx-dot.c3 { background: var(--dd-amber); }
.hx-dot.c4 { background: var(--dd-red); }
/* THE CARD. One frame around the whole main column, floated off the
   window's edges by the card gap, with NO left margin — the rail's own
   side padding is the left gutter, exactly as in .main-col. The topbar
   is its FIRST ROW, not a bar spanning rail and content, so the card's
   radius crops it: that is the single change that dates every older
   drawing of this shell. */
.hx-main {
  flex: 1; min-width: 0; min-height: 0;
  display: flex; flex-direction: column;
  margin: 9px 9px 9px 0;
  background: var(--dd-panel);
  border-radius: var(--dd-radius-l);
  box-shadow: var(--dd-shadow-card);
  overflow: clip;
}
/* The app gives .topbar z-index 60 to sit over its own scrollers. Here
   that would lift it out of the welcome wizard's dim (.hx-app::after,
   z-index 1) and leave one bright strip across a dimmed app. */
.hx-main .topbar { height: 34px; padding: 0 12px; justify-content: space-between; z-index: auto; border-bottom: 0; background: transparent; }
.hx-main .topbar-page { font-size: var(--dd-fs-2xs); gap: 6px; }
.hx-main .topbar-page svg { width: 13px; height: 13px; }
.hx-topbar-help { font-size: var(--dd-fs-3xs); color: var(--dd-ink-quiet); }
.hx-content {
  flex: 1; min-height: 0;
  padding: 15px 15px 0;
  display: flex; flex-direction: column; gap: 13px;
  overflow: hidden;
}
.hx-bar { display: block; height: 6px; border-radius: var(--dd-radius-s); background: var(--dd-gray-100); }
.hx-bar.wide { width: 68%; }
.hx-bar.mid { width: 46%; }
.hx-bar.narrow { width: 26%; }
.hx-bar.tiny { width: 32px; height: 5px; }
.hx-bar.strong { height: 8px; background: var(--dd-gray-200); }
/* Home's three shapes, in order: the greeting, the ask box, the day's
   list. Not a guess at a layout — the real /app Home reads greeting /
   Ask anything / Meetings top to bottom, and this is that rhythm with
   the words taken out. */
.hx-greet {
  flex: none; width: 62%; height: 12px;
  margin: 6px 0 2px;
  border-radius: var(--dd-radius-s);
  background: var(--dd-gray-200);
}
.hx-ask {
  flex: none; position: relative;
  height: 70px; padding: 11px 12px;
  border-radius: var(--dd-radius-l);
  background: var(--dd-panel);
  box-shadow: var(--dd-shadow-card);
}
.hx-ask-send {
  position: absolute; right: 9px; bottom: 9px;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: var(--dd-periwinkle-wash);
}
/* The list sits ON the card, a label and its rows, parted by whitespace
   alone (round 3 took the head's rule and the row rules away). */
.hx-panel { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.hx-panel-head {
  flex: none; height: 26px;
  display: flex; align-items: center; gap: 8px;
  padding: 0 2px;
}
.hx-row {
  flex: none; height: 31px;
  display: flex; align-items: center; gap: 12px;
  padding: 0 2px;
}
.hx-row > .hx-bar { flex: 0 1 auto; min-width: 0; background: var(--dd-gray-200); }
.hx-time { flex: none; width: 30px; height: 6px; border-radius: var(--dd-radius-s); background: var(--dd-gray-100); }
.hx-ring {
  border-radius: var(--dd-radius-m);
  transition: box-shadow 180ms var(--dd-ease), background var(--dd-dur) var(--dd-ease);
}
.hx-ring:not(.hx-frame) { box-shadow: 0 0 0 0 transparent; }
/* The spotlight on the region a step is about: the tiles' one selected
   look (round 3), the region's own white lifted on the chip shadow with
   one pixel of ink ON SCREEN as its edge. welcome/'s camera writes its
   zoom as --hx-z on the frame, and the ink edge divides by it, so it is a
   1px line at any camera distance rather than 1px of the miniature scaled
   up to two or three. The switcher is a ghost on the ground at rest, so
   picking it turns it into that white chip. */
.hx-ring.on:not(.hx-frame) {
  background: var(--dd-panel);
  box-shadow: 0 0 0 calc(1px / var(--hx-z, 1)) var(--dd-ink), var(--dd-shadow-chip);
}
.hx-app::after {
  content: "";
  position: absolute; inset: 0; z-index: 1;
  background: var(--dd-canvas);
  opacity: 0;
  pointer-events: none;
  transition: opacity 180ms var(--dd-ease);
}
.hx-frame.spot .hx-app::after { opacity: .58; }
.hx-frame.spot .hx-ring.on { position: relative; z-index: 2; }
/* When the subject is the whole app, the WINDOW is the region: it keeps
   its ground and simply rises, from the pop shadow to the dialog's. An
   ink edge there was clipped by the cell on the right (the window runs
   past it on purpose) and read as a black bracket down the left of the
   preview. The transition names the camera's glide as well, because the
   ring's own transition list above would otherwise replace the frame's
   and the zoom would jump instead of travelling. */
.hx-frame.hx-ring {
  border-radius: var(--dd-radius-l);
  transition: transform var(--dd-dur-zoom) var(--dd-ease-pane), box-shadow 180ms var(--dd-ease);
}
.hx-frame.hx-ring.on { box-shadow: var(--dd-shadow-dialog); }
.onboard-head {
  display: flex; align-items: center; justify-content: space-between;
  margin: 0 0 20px;
}
.onboard-progress { display: flex; gap: 6px; }
.onboard-progress i {
  width: 24px; height: 3px; border-radius: 2px;
  background: light-dark(rgb(17 17 18 / .1), rgb(255 255 255 / .12));
  transition: background var(--dd-dur-slow) var(--dd-ease);
}
.onboard-progress[data-step="1"] i:nth-child(-n+1),
.onboard-progress[data-step="2"] i:nth-child(-n+2),
.onboard-progress[data-step="3"] i:nth-child(-n+3) { background: var(--dd-ink); }
.onboard-stepnum {
  font-size: var(--dd-fs-2xs);
  color: var(--auth-quiet-ink);
  font-variant-numeric: tabular-nums;
}
.onboard-step.enter-r,
.onboard-step.enter-l { animation: av-in 200ms var(--dd-ease) both; }
.step-q {
  margin: 0 0 20px;
  font-size: 1.45rem;
  font-weight: 650;
  letter-spacing: -.02em;
  line-height: 1.2;
}
.onboard-head { margin-bottom: 28px; }
.auth-quiet {
  margin: 20px 0 0;
  text-align: center;
  font-size: var(--dd-fs-sm-);
  color: var(--auth-quiet-ink);
}
.auth-quiet-link {
  border: 0; padding: 0; background: transparent;
  font: inherit; font-weight: 600;
  color: var(--dd-ink);
  cursor: pointer;
}
.auth-quiet-link:hover { text-decoration: underline; text-underline-offset: 3px; }
/* Quiet links keep their text rhythm, with a full touch target in the
   surrounding air. */
@media (max-width: 720px), (hover: none) {
  .auth-mark, .auth-foot-link, .login-forgot, .auth-quiet-link, .auth-back { position: relative; }
  .auth-mark::after, .auth-foot-link::after, .login-forgot::after, .auth-quiet-link::after, .auth-back::after {
    content: ""; position: absolute; left: 50%; top: 50%;
    width: max(100%, 44px); height: max(100%, 44px);
    transform: translate(-50%, -50%);
  }
  /* The password eye draws 28px and sits 8px in from the 44px field's
     corner, so 8px of air all round is a 44px target, exactly the field's
     own height, that never reaches the text (the input keeps 46px clear). */
  .pw-eye::after { content: ""; position: absolute; inset: -8px; }
}
.login-submit {
  transition-property: background, border-color, color, transform, opacity;
  transition-duration: var(--dd-dur-hover), var(--dd-dur-hover), var(--dd-dur-hover), var(--dd-dur-pop), var(--dd-dur);
  transition-timing-function: var(--dd-ease);
}
.login-submit:active {
  transition-duration: var(--dd-dur-hover), var(--dd-dur-hover), var(--dd-dur-hover), 0s, var(--dd-dur);
}
/* ================================================== THE WAITLIST DOOR

   Signup's first step has two paths and shows one: the invite (the
   wizard's first question) or the waitlist (the no-code door). <html
   data-signup-path="waitlist"> is the whole switch. The head script of all
   four auth pages writes it before the first paint when the address
   carries ?waitlist=1, which is where the homepage's "Join the waitlist"
   buttons land, so that visitor never sees a frame asking for a code;
   auth.js flips it from the two quiet links. Hidden into display: none,
   never [hidden] (see THE GOOGLE DOOR below for the outage that rule
   came from), and each path is a plain block, so showing one is display:
   block with nothing to restore.

   The waitlist path is a form of the card's own shape, the way sign-in
   and recovery are: the question, one line of why, the field and the ink
   primary at the one control size (it was a 40px field beside a 40px
   white "Join" that rose in under a quiet line, the only inline row and
   the only secondary-looking submit on these pages), then the way back.
   The wizard's progress row stands down while it shows: the waitlist is
   not step 1 of 3 of anything. */
.signup-path[data-path="waitlist"] { display: none; }
html[data-signup-path="waitlist"] .signup-path[data-path="waitlist"] { display: block; }
html[data-signup-path="waitlist"] .signup-path[data-path="invite"],
html[data-signup-path="waitlist"] #signup-form .onboard-head { display: none; }
.waitlist .step-q { margin-bottom: 0; }
.waitlist .auth-sub { margin: 12px 0 26px; }
.waitlist-row .login-submit { margin-top: 2px; }
.waitlist .login-note { margin: 0; }
.gsignup { animation: av-in 200ms var(--dd-ease) both; }
.gsignup-id {
  display: flex; align-items: center; gap: 12px;
  margin: 4px 0 18px;
}
.gsignup-avatar {
  flex: none; width: 44px; height: 44px; border-radius: var(--dd-radius-full);
  object-fit: cover; background: var(--dd-gray-100);
  border: 1px solid var(--dd-border);
}
.gsignup-avatar-fb {
  display: grid; place-items: center;
  font-size: var(--dd-fs-md); font-weight: 600; color: var(--dd-ink-quiet);
}
.gsignup-who { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.gsignup-names { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.gsignup-email {
  font-size: var(--dd-fs-2xs); color: var(--dd-ink-quiet);
  padding-left: 1px; overflow: hidden; text-overflow: var(--dd-end-overflow); animation: var(--dd-end-fade); animation-timeline: scroll(self inline); white-space: nowrap;
}
.gsignup-opt {
  margin-left: 6px; font-size: 10px; font-weight: 500;
  color: var(--dd-ink-quiet); text-transform: uppercase; letter-spacing: 0.04em;
}
#gsignup-done { animation: av-in 200ms var(--dd-ease) both; }
.onboard-actions { display: flex; flex-direction: column; align-items: stretch; margin-top: 10px; }
.onboard-actions .login-submit { width: 100%; margin-top: 0; }
.auth-sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.welcome-steps { position: relative; }
.welcome-step.in { animation: av-in 200ms var(--dd-ease) both; }
.welcome-q {
  margin: 24px 0 6px;
  font-size: 1.35rem;
  font-weight: 650; letter-spacing: -.02em; line-height: 1.2;
}
.welcome-lede {
  margin: 0 0 20px;
  font-size: var(--dd-fs-sm); line-height: 1.5;
  color: var(--dd-ink-quiet);
}
.welcome-name-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 8px; }
.welcome-name-grid .login-field { margin-bottom: 0; }
.auth-pane .input[readonly] { color: var(--dd-ink-quiet); cursor: default; }
.auth-pane .input[readonly]:hover,
.auth-pane .input[readonly]:focus { border-color: var(--auth-field-edge); box-shadow: none; }
.welcome-switch {
  display: flex; align-items: flex-start; gap: 12px;
  margin: 18px 0 2px;
  cursor: pointer;
}
.welcome-switch .check { margin-top: 2px; }
.welcome-switch-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.welcome-switch-title { font-size: var(--dd-fs-sm-); font-weight: 550; color: var(--dd-ink); }
.welcome-switch-sub { font-size: var(--dd-fs-2xs); color: var(--dd-ink-quiet); line-height: 1.45; }
/* A field with a fixed prefix (welcome/'s handle, "vecka.ai/"), so it is
   drawn as the auth field is: the one control size and corner, the field's
   white and alpha edge, a step stronger on hover, and lifted off the card
   with the caret inside, exactly as THE FIELDS focus. */
.auth-prefix-field {
  display: flex; align-items: center;
  height: var(--auth-control-h); padding: 0 14px;
  font: inherit; font-size: var(--dd-fs-sm);
  color: var(--dd-ink);
  background: var(--auth-field);
  border: 1px solid var(--auth-field-edge);
  border-radius: var(--dd-radius-m-lg);
  transition: border-color var(--dd-dur) var(--dd-ease), box-shadow var(--dd-dur) var(--dd-ease);
}
.auth-prefix-field:hover { border-color: var(--auth-field-edge-hover); }
.auth-prefix-field:focus-within { border-color: var(--auth-field-edge-hover); box-shadow: var(--auth-field-lift); }
.auth-prefix-field.is-bad { border-color: color-mix(in srgb, var(--dd-red) 60%, transparent); }
.auth-prefix { flex: none; color: var(--dd-ink-quiet); white-space: nowrap; }
.auth-pane .auth-prefix-field .input,
.auth-pane .auth-prefix-field .input:hover,
.auth-pane .auth-prefix-field .input:focus {
  flex: 1; min-width: 0; height: auto; padding: 0;
  border: 0; background: transparent; border-radius: 0; box-shadow: none;
}
.auth-prefix-field .input:focus-visible { outline: none; }
/* ONE SELECTED LOOK for every tile a visitor picks from (round 3 of the
   Tab pass): these chips and the gateway's persona cards stay the field's
   white when picked and take the chip lift with one pixel of ink as their
   edge (--auth-picked), the treatment closest to Tab's, where a chosen
   thing rises rather than darkens. The accent swatches below draw the same
   1px of ink, held off the round swatch by a ring of paper. Round 2's
   picked chip was an ink BORDER over a grey-50 fill: a grey slab under a
   black frame, the heaviest thing on the card. EDGES ARE SHADOWS here, not
   borders, so rest (the field's faint alpha ring), hover (one step
   stronger, a grey step of fill) and picked all draw in the same place
   and nothing moves by a pixel between them. */
.auth-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.auth-chip {
  height: var(--auth-control-h);
  padding: 0 14px;
  border: 0;
  border-radius: var(--dd-radius-m-lg);
  background: var(--auth-field);
  box-shadow: 0 0 0 1px var(--auth-field-edge);
  font-weight: 500;
  color: var(--dd-ink-light);
  transition-property: background, box-shadow, transform, opacity;
}
.auth-chip svg { width: 15px; height: 15px; }
.auth-chip:hover { background: var(--dd-gray-50); box-shadow: 0 0 0 1px var(--auth-field-edge-hover); }
.auth-chip.active,
.auth-chip.active:hover {
  background: var(--auth-field);
  box-shadow: var(--auth-picked);
  color: var(--dd-ink);
  font-weight: 600;
}
.auth-reveal {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--dd-dur-fold) var(--dd-ease);
}
/* The child keeps 3px of padding so an open fold never clips a field's
   edge, and padding cannot collapse: a CLOSED fold was still 6px tall and
   showed the top of what it held (the second invite's label peeked out
   under the first field on welcome/'s last step). Hidden while closed, and
   hidden only once the fold has finished closing. */
.auth-reveal > * {
  min-height: 0; overflow: hidden; padding: 3px 0;
  visibility: hidden;
  transition: visibility 0s linear var(--dd-dur-fold);
}
.auth-reveal.open { grid-template-rows: 1fr; }
.auth-reveal.open > * { visibility: visible; transition-delay: 0s; }
.auth-reveal .welcome-field { margin-top: 16px; }
.welcome-accents { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 4px; }
.welcome-accent {
  position: relative;
  width: 22px; height: 22px; flex: none;
  padding: 0; border: 0;
  border-radius: var(--dd-radius-full);
  cursor: pointer;
  transition: box-shadow var(--dd-dur-hover) var(--dd-ease);
}
/* THE HIT TARGET, and it has to be a pseudo-element: the ink is a 22px circle
   in a row with 10px gaps, and border-radius takes part in hit-testing — the
   four corners of each swatch's own square are dead, and so are the gaps. The
   selection ring is drawn with box-shadow, which paints outside the box and
   adds no hit area at all. A near-miss then dies silently, because the group's
   delegated handler drops any click whose closest([role="radio"]) is null.
   inset -5px makes a 32px circle: exactly the row's pitch, so neighbours are
   tangent with nothing to arbitrate and the whole visual band is live. The ink,
   the per-accent colours and both edges stay on the element's own 22px box. */
.welcome-accent::after {
  content: "";
  position: absolute;
  inset: -5px;
  border-radius: inherit;
}
/* Ink is the default accent's own pair (--dd-accent's value in the tokens
   block), spelled out because on a page wearing another accent the token
   itself would paint this swatch in that accent. The swatches set only a
   fill: the selection edge below is ink for every one of them. */
.welcome-accent[data-accent="ink"]    { background: light-dark(#151516, #f5f5f4); }
.welcome-accent[data-accent="blue"]   { background: var(--dd-blue); }
.welcome-accent[data-accent="teal"]   { background: var(--dd-teal); }
.welcome-accent[data-accent="green"]  { background: var(--dd-green); }
.welcome-accent[data-accent="amber"]  { background: var(--dd-amber); }
.welcome-accent[data-accent="orange"] { background: var(--dd-orange); }
.welcome-accent[data-accent="pink"]   { background: var(--dd-pink); }
.welcome-accent[data-accent="violet"] { background: var(--dd-violet); }
/* The tiles' one selected look, drawn round: a 1px ink edge, held 2px off
   the swatch by a ring of the card's own paper so it reads as an edge and
   not as a thicker swatch. It used to be a 2px ring in the swatch's own
   colour, which on the ink swatch was the same heavy black ring the chips
   had. Hover is the same edge in grey. */
.welcome-accent:hover { box-shadow: 0 0 0 2px var(--auth-paper), 0 0 0 3px var(--dd-gray-300); }
.welcome-accent.active { box-shadow: 0 0 0 2px var(--auth-paper), 0 0 0 3px var(--dd-ink); }
.welcome-field { display: flex; flex-direction: column; gap: 7px; }
.welcome-field + .welcome-field { margin-top: 18px; }
.welcome-field .microlabel { padding-left: 1px; }
.welcome-invite-fields { display: flex; flex-direction: column; }
.welcome-invite-row {
  display: flex; align-items: center; justify-content: flex-end; gap: 10px;
  margin-top: 4px;
}
.welcome-invite-row > :first-child { margin-right: auto; }
.welcome-linkbtn {
  display: inline-flex; align-items: center; gap: 7px;
  height: var(--auth-control-h); padding: 0 10px;
  margin-inline: -10px;
  border: 0; background: transparent;
  border-radius: var(--dd-radius-m-lg);
  font: inherit; font-size: var(--dd-fs-sm-); font-weight: 550;
  color: var(--dd-ink-quiet);
  cursor: pointer;
  transition: background var(--dd-dur-hover) var(--dd-ease), color var(--dd-dur-hover) var(--dd-ease);
}
.welcome-linkbtn:hover { background: var(--dd-fill); color: var(--dd-ink); }
.welcome-linkbtn svg { width: 15px; height: 15px; flex: none; stroke-width: 1.75; }
.welcome-actions {
  display: flex; flex-direction: column; align-items: stretch; gap: 2px;
  margin-top: 26px;
}
.welcome-actions .login-submit { width: 100%; margin-top: 0; }
.welcome-skip {
  align-self: center;
  height: var(--auth-control-h); padding: 0 14px;
  border: 0; background: transparent;
  border-radius: var(--dd-radius-m-lg);
  font: inherit; font-size: var(--dd-fs-sm-); font-weight: 550;
  color: var(--dd-ink-quiet);
  cursor: pointer;
  transition: background var(--dd-dur-hover) var(--dd-ease), color var(--dd-dur-hover) var(--dd-ease);
}
.welcome-skip:hover { background: var(--dd-fill); color: var(--dd-ink); }
.welcome-note { margin: 10px 2px 0; font-size: var(--dd-fs-2xs); color: var(--dd-ink-quiet); }
.welcome-note.err { color: var(--dd-red); }
/* THE SIGNED-IN ERROR PAGE IS A TAB CARD (round 4): the app's ground under
   it (--dd-rail is this sheet's copy of it), the dialog's xl corner and
   haze, no border. It was the last surface still drawn in the outlined
   language, a hairline box with a rule across its middle, on the canvas
   white rather than the ground an app surface floats on. */
body.wslost {
  min-height: 100vh; min-height: 100dvh;
  display: grid; place-items: center;
  background: var(--dd-rail);
  padding: 24px;
}
.wslost-card {
  width: 100%; max-width: 420px;
  background: var(--dd-panel);
  border: 0;
  border-radius: var(--dd-radius-xl);
  box-shadow: var(--dd-shadow-dialog);
  padding: 32px 32px 24px;
  animation: av-in 200ms var(--dd-ease) both;
}
/* The Gather icon, a 22px box showing a 19 by 17.5 drawing. */
.wslost-logo { width: 22px; height: 22px; color: var(--dd-accent); margin-bottom: 14px; }
.wslost-card h1 {
  margin: 0 0 6px;
  font-size: var(--dd-fs-lg); font-weight: 600; letter-spacing: -0.01em;
  color: var(--dd-ink);
}
.wslost-sub {
  margin: 0 0 18px;
  font-size: var(--dd-fs-sm); line-height: 1.5;
  color: var(--dd-ink-quiet);
}
.wslost-label {
  display: block;
  font-size: var(--dd-fs-2xs); font-weight: 500;
  text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--dd-ink-quiet);
  margin-bottom: 6px;
}
/* The list is the card's second band, set off by air, not a rule. */
.wslost-list { padding-top: 6px; }
.wslost-row {
  display: flex; align-items: center; gap: 10px;
  padding: 8px; margin: 0 -8px;
  border-radius: var(--dd-radius-m);
  text-decoration: none;
  transition: background var(--dd-dur-hover) var(--dd-ease);
}
.wslost-row:hover { background: var(--dd-fill); }
.wslost-row:active { background: var(--dd-fill-hover); }
.wslost-mark {
  flex: none; width: 28px; height: 28px;
  display: grid; place-items: center;
  border-radius: var(--dd-radius-m);
  background: var(--dd-fill-hover); color: var(--dd-ink);
  font-size: var(--dd-fs-sm-); font-weight: 600;
}
.wslost-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.wslost-name {
  display: flex; align-items: center; gap: 6px;
  font-size: var(--dd-fs-sm); font-weight: 500; color: var(--dd-ink);
  overflow: hidden; text-overflow: var(--dd-end-overflow); animation: var(--dd-end-fade); animation-timeline: scroll(self inline); white-space: nowrap;
}
.wslost-tag {
  flex: none;
  font-style: normal; font-size: var(--dd-fs-2xs); font-weight: 500;
  color: var(--dd-ink-quiet);
  background: var(--dd-fill);
  padding: 1px 5px; border-radius: var(--dd-radius-s);
}
.wslost-addr {
  font-size: var(--dd-fs-xs); color: var(--dd-ink-quiet);
  overflow: hidden; text-overflow: var(--dd-end-overflow); animation: var(--dd-end-fade); animation-timeline: scroll(self inline); white-space: nowrap;
}
.wslost-go {
  flex: none; width: 16px; height: 16px;
  margin-left: auto;
  color: var(--dd-ink-quiet);
  stroke-width: 1.75;
  transition: color var(--dd-dur-hover) var(--dd-ease);
}
.wslost-row:hover .wslost-go { color: var(--dd-ink); }

/* ============================================================================
   THE BOOT COVER IS GONE FROM THE SIGNED-OUT PAGES (2026-09-01).

   It stood here for one day and earned its removal: it existed to hide these
   pages assembling themselves, and the two things they were assembling are now
   gone rather than hidden.

     * font-display: swap re-painted every string once Inter arrived, changing
       the metrics of every text box at once. It is `optional` now (see the
       @font-face block), so there is no second painting.
     * "Continue with Google" was unhidden by JavaScript, which is its own
       request and landed ~1.6s into a throttled load, dropping ~100px into a
       vertically centred card. server.js writes data-google onto <html> and
       .auth-gdoor below reads it in the same frame the markup is parsed.

   With both removed, a throttled production load of /login and /signup shows
   NOTHING MOVING that a user could see — measured, not assumed. A cover over a
   page that is already final in its first frame is just a blank screen with a
   circle on it, which is exactly what David asked these pages to stop doing:
   "the overall layout should still be visible. Just content can load."

   The app keeps its cover, because the app genuinely does assemble — but even
   there it covers the CONTENT REGION only, never the shell. See
   app/index.html.
   ============================================================================ */

/* ============================================================================
   THE GOOGLE DOOR IS CSS-GOVERNED, AND THAT IS THE WHOLE POINT (2026-09-01).

   "Continue with Google" must not appear on a deploy that has no Google set
   up. That used to be decided by JavaScript — first a fetch, then, once the
   answer moved into the served page, a synchronous read — and both were still
   LATER THAN THE FIRST PAINT, because auth.js is its own request. On a
   throttled load the door dropped in at ~1.6s and shoved the whole form up
   inside its vertically-centred card. David: "everything moves a bit because
   the Continue with Google button appears afterward."

   Now server.js writes data-google onto <html> as it serves the file
   (sendStatic), and the rule below decides in the SAME frame the markup is
   parsed. Nothing to unhide, so nothing to move.

   FAIL-SAFE BY DEFAULT: hidden unless the attribute positively says "on", so a
   document served without it — an old cached page, some future host that
   bypasses sendStatic — shows no dead button, which is the behaviour the
   fetch had. auth.js's fallback for that case sets the ATTRIBUTE rather than
   touching these elements, so there is exactly one mechanism.

   display, not [hidden]: this project has twice shipped a click outage from an
   author display rule quietly beating [hidden] (see the .dd-fb note). Two
   spellings of "not shown" on one element is the bug; this leaves one.

   NOT `display: revert`, which is what this said until 2026-09-02. revert
   goes back to the USER-AGENT value — block for a div, inline-block for a
   button — and this selector outranks the elements' own rules, so the
   divider lost its flex layout: its two hairlines (empty <i>s with flex: 1)
   collapsed to nothing and "or" sat at the left edge of the form as a stray
   label. David: "the placement of where 'or' is ... like a line to the
   left." Each element is unhidden into ITS OWN display, spelled out. */
.auth-gdoor { display: none; }
html[data-google="on"] .auth-div.auth-gdoor { display: flex; }
html[data-google="on"] .btn.auth-gdoor { display: inline-flex; }
