/* ═══════════════════════════════════════════════════════════════════════════════════════════
   THE SIGN-IN DOOR. One door for every product: / (Liaison or Conduit, staff), /portal/login and
   /portal/reset (Portal, partners), and Labs and Keep when they ship. The approved spec is
   .claude/skills/liaison-design/references/sign-in.md; the working mock is .claude/skills/liaison-design/specimens/sign-in.html.

   Load order, in every door page: /design/tokens.css, /design/components.css, then
   this file. Colour, type, space, corners, glass and the controls all come from those two; this
   file only arranges them. Which product a door is comes from data-workspace on <html>, and the
   tokens repaint every role from it. signin.js draws the door and runs it.

   NO RAW COLOURS AND NO RAW SIZES. Every colour is a role (var(--text-2), var(--accent-glow)),
   every type style a --type-* pair, every gap and padding a --space-* step. The only literal
   measures are the door's own, from sign-in.md, named once in the block below. Stroke widths
   (1px, 2px rings) are written as the components file writes them. tests/signin-door-styles.test.js
   holds all of this.
   ═══════════════════════════════════════════════════════════════════════════════════════════ */

/* Self-hosted, as .claude/skills/liaison-design/references/typography.md says: the same files every product serves,
   from this app's own /fonts/. No Google Fonts call. Each weight of DM Mono is its own file. */
@font-face{font-family:'Plus Jakarta Sans';font-style:normal;font-weight:200 800;font-display:swap;
  src:url('/fonts/PlusJakartaSans-Variable-latin.woff2') format('woff2-variations'),
      url('/fonts/PlusJakartaSans-Variable-latin.woff2') format('woff2')}
@font-face{font-family:'DM Mono';font-style:normal;font-weight:400;font-display:swap;
  src:url('/fonts/DMMono-Regular-latin.woff2') format('woff2')}
@font-face{font-family:'DM Mono';font-style:normal;font-weight:500;font-display:swap;
  src:url('/fonts/DMMono-Medium-latin.woff2') format('woff2')}
/* Google Sans Medium, for Google's own "Continue with Google" button only (components.css .btn-google;
   Google's branding rules name it). Open Font License, from Google Fonts, served from /fonts/ like the rest. */
@font-face{font-family:'Google Sans';font-style:normal;font-weight:500;font-display:swap;
  src:url('/fonts/GoogleSans-Medium-latin.woff2') format('woff2')}

/* The door's own measures, from sign-in.md. The only literal sizes in this file. */
:root{
  --door-width:380px;              /* the column: lockup, card, footer */
  --door-mark:48px;                /* the product mark in the lockup */
  --door-by-mark:16px;             /* the Liaison mark in "by Liaison" */
  --door-watermark:620px;          /* the product mark drawn large behind everything */
  --door-watermark-opacity:.09;    /* 9% in dark */
  --door-code-box:56px;            /* each of the six code boxes */
  --door-icon-tile:72px;           /* the passkey and signed-in icon tiles */
  --door-icon:36px;                /* the icon inside one */
}
html[data-theme="light"]{--door-watermark-opacity:.12}   /* 12% in light */

/* The top of every door is one colour: the product's glow over its ground, the colour the middle of
   the top edge already had (measured 2026-09-27). iOS paints the opaque status bar in theme-color,
   and signin.js keeps every theme-color equal to this, so the bar reads as the top of the picture. */
html{--door-top:color-mix(in srgb,var(--accent-glow) 24%,var(--ground))}
html[data-theme="light"]{--door-top:color-mix(in srgb,var(--accent-glow) 28%,var(--ground))}

*,*::before,*::after{box-sizing:border-box}
[hidden]{display:none !important}   /* .btn sets display, which would otherwise beat the attribute */
html{background:var(--ground);-webkit-text-size-adjust:100%}
body{margin:0;min-height:100vh;min-height:100dvh;background:var(--ground);color:var(--text-1);
  font:var(--type-body);letter-spacing:var(--track-body);-webkit-font-smoothing:antialiased;overflow-x:hidden}
button,input{font-family:inherit}
a,button,input{-webkit-tap-highlight-color:transparent}
h1,p{margin:0}

/* ── The atmosphere: soft glows in the workspace colour, and the watermark. Fixed behind
   everything, never clickable, never in the way of a scroll. ── */
.door-atmos{position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden;
  background:
    radial-gradient(circle at 15% 12%,color-mix(in srgb,var(--accent-glow) 34%,transparent),transparent 42%),
    radial-gradient(circle at 95% 70%,color-mix(in srgb,var(--accent-glow) 20%,transparent),transparent 40%),
    linear-gradient(var(--ground),var(--ground)) var(--door-top)}
/* Its background colour, under the ground it paints, is the top colour: Safari 26 ignores theme-color
   and tints its status area from the background colour of a fixed element at the top. And the top
   edge itself is that colour right across, with the glows and the watermark fading in below it. */
.door-atmos::after{content:'';position:absolute;left:0;right:0;top:0;height:calc(var(--space-16) * 2);
  background:linear-gradient(to bottom,var(--door-top),transparent)}
.door-watermark{position:absolute;width:var(--door-watermark);height:var(--door-watermark);
  left:calc(var(--door-watermark) * -.3);top:calc(var(--door-watermark) * -.2);
  opacity:var(--door-watermark-opacity);filter:saturate(1.2);user-select:none;-webkit-user-select:none}

/* ── THE LAYOUT RULE. The page is a column at least as tall as the screen. The door takes
   margin:auto, so it sits centred while it fits; when a screen grows (an error, a long invite)
   the column grows instead, and the page scrolls. The lockup stays at the top of the door and
   the footer stays under the card: nothing is pushed off screen and nothing is covered. ── */
.door-page{position:relative;z-index:1;min-height:100vh;min-height:100dvh;display:flex;flex-direction:column;align-items:center;
  padding:max(env(safe-area-inset-top),var(--space-10)) var(--gutter) max(env(safe-area-inset-bottom),var(--space-4))}
.door{width:100%;max-width:var(--door-width);display:grid;gap:var(--space-6);margin:auto 0;flex-shrink:0}
.door-foot{flex-shrink:0;padding-top:var(--space-6);text-align:center;color:var(--text-3)}
.door-foot a{color:var(--text-3)}
.door-foot a:focus-visible{outline:2px solid var(--accent-fg);outline-offset:2px;border-radius:var(--radius-xs)}

/* ── Lockup: the product mark and name; "by Liaison" under every product but Liaison. ── */
.door-lockup{display:grid;justify-items:center;gap:var(--space-2);text-align:center}
.door-brand{display:flex;align-items:center;gap:var(--space-3)}
/* The mark sits on nothing: no filter, no shadow, no background. iOS Safari clips a drop-shadow on
   an image to the image's rectangle, which drew a darker box behind the Liaison eclipse. */
.door-mark{width:var(--door-mark);height:var(--door-mark)}
.door-name{font:var(--type-title-1);letter-spacing:var(--track-title-1);color:var(--text-1)}
.door-by{display:flex;align-items:center;gap:var(--space-1);color:var(--text-3)}
.door-by img{width:var(--door-by-mark);height:var(--door-by-mark)}

/* ── The card: .card.glass-card, with the extra-large corner the spec asks for. ── */
.door-card{border-radius:var(--radius-xl);padding:var(--space-6) var(--space-5) var(--space-5)}
.door-screen h1{font:var(--type-title-2);letter-spacing:var(--track-title-2);color:var(--text-1)}
.door-sub{margin:var(--space-1) 0 var(--space-5);color:var(--text-2)}
.door-sub b{color:var(--text-1);font:var(--type-body-strong)}
.door-stack{display:grid;gap:var(--space-3)}
.door-alt{display:grid;gap:var(--space-1);margin-top:var(--space-4);justify-items:center}
.door-screen.is-center{text-align:center}

/* An alert sits at the top of the card, under the title, in its status tint, with an icon. */
.door-alert{display:flex;gap:var(--space-2);align-items:flex-start;margin-bottom:var(--space-4);
  padding:var(--space-3) var(--space-4);border-radius:var(--radius-md);font:var(--type-callout);letter-spacing:var(--track-callout);text-align:left}
.door-alert .icon{width:var(--space-5);height:var(--space-5);margin-top:var(--space-0-5)}
.door-alert strong{display:block;color:var(--text-1);font:var(--type-body-strong)}
.door-alert a{display:block;margin-top:var(--space-2);color:inherit;text-decoration:underline}
.door-alert[data-kind="danger"]{background:var(--danger-tint);color:var(--danger-fg)}
.door-alert[data-kind="warning"]{background:var(--warning-tint);color:var(--warning-fg)}
.door-alert[data-kind="info"]{background:var(--info-tint);color:var(--info-fg)}

/* Password with show and hide. The eye is a 44px target inside the field's right edge. */
.door-pw{position:relative;display:block}
.door-pw .input{padding-right:calc(var(--tap) + var(--space-2))}
.door-eye{position:absolute;right:var(--space-0-5);top:50%;transform:translateY(-50%);width:var(--tap);height:var(--tap);
  border:0;background:transparent;color:var(--text-3);display:flex;align-items:center;justify-content:center;cursor:pointer;border-radius:var(--radius-md)}
.door-eye:focus-visible{outline:2px solid var(--accent-fg);outline-offset:-2px}
.door-forgot{justify-self:end;margin-top:calc(-1 * var(--space-1))}

/* The "or" divider, then Google (its own button, components.css .btn-google), then the quiet links. */
.door-or{display:flex;align-items:center;gap:var(--space-3);margin:var(--space-4) 0 var(--space-3);color:var(--text-3)}
.door-or::before,.door-or::after{content:'';flex:1;border-top:1px solid var(--line-2)}
.door-links{display:flex;justify-content:center;gap:var(--space-1);flex-wrap:wrap;margin-top:var(--space-2)}

/* ── Six boxes drawn over one real input. The input takes the typing, the paste and the phone's
   one-time-code offer; the boxes only show what it holds. ── */
.door-code{position:relative}
/* It also carries .input, which keeps it at 16px so an iPhone does not zoom when it takes focus. */
.door-code-input{position:absolute;inset:0;width:100%;height:100%;min-height:0;opacity:0;border:0;padding:0;
  caret-color:transparent;cursor:text;color:transparent;background:transparent}
.door-boxes{display:grid;grid-template-columns:repeat(6,1fr);gap:var(--space-2);pointer-events:none}
.door-boxes span{height:var(--door-code-box);border-radius:var(--radius-md);background:var(--surface-2);box-shadow:inset 0 0 0 1px var(--line-3);
  display:flex;align-items:center;justify-content:center;font:var(--type-title-2);letter-spacing:var(--track-title-2);
  font-variant-numeric:tabular-nums;color:var(--text-1);transition:box-shadow .15s}
.door-code.is-focus .door-boxes span.is-cur{box-shadow:inset 0 0 0 2px var(--accent-fg),0 0 0 3px var(--accent-tint)}
.door-code.is-bad .door-boxes span{box-shadow:inset 0 0 0 1.5px var(--danger-fg)}
.door-code.is-bad{animation:door-shake .35s}
@keyframes door-shake{20%,60%{transform:translateX(calc(-1 * var(--space-1-5)))}40%,80%{transform:translateX(var(--space-1-5))}}
@media (prefers-reduced-motion:reduce){.door-code.is-bad{animation:none}}
.door-code-help{margin-top:var(--space-2)}
.door-resend{display:flex;justify-content:center;gap:var(--space-2);color:var(--text-3);margin-top:var(--space-3);min-height:var(--tap);align-items:center}

/* Live checks under a new password. */
.door-checks{display:grid;gap:var(--space-1);margin:0;padding:0;list-style:none}
.door-checks li{display:flex;align-items:center;gap:var(--space-2);color:var(--text-3);font:var(--type-footnote);letter-spacing:var(--track-footnote)}
.door-checks li .icon{width:var(--space-4);height:var(--space-4)}
.door-checks li.is-ok{color:var(--success-fg)}

/* Icon tiles: the passkey offer (accent) and signed in (success). */
.door-tile{width:var(--door-icon-tile);height:var(--door-icon-tile);border-radius:var(--radius-lg);display:flex;align-items:center;justify-content:center;
  background:var(--accent-tint);color:var(--accent-fg);margin:0 auto var(--space-3)}
.door-tile .icon{width:var(--door-icon);height:var(--door-icon)}
.door-tile.is-ok{border-radius:50%;background:var(--success-tint);color:var(--success-fg)}
.door-done-sub{color:var(--text-2)}
