/* LaingLogic portal: shared stylesheet. Flat: solid fills, one pixel hairlines, small radii, no
   depth tricks. The palette and both themes are the agency site's; Onest for everything,
   Silkscreen only on the "//" labels on page and section titles. The one shadow is on things
   that float: a dropdown, a dialog, a toast. Tokens per the platform naming contract, section
   10. The white mark only under this surface's own light theme. */

@font-face {
  font-family: 'Onest';
  font-style: normal;
  font-weight: 300 900;
  font-display: swap;
  src: url('../assets/fonts/onestVarLatin.woff2') format('woff2');
}
@font-face {
  font-family: 'Silkscreen';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../assets/fonts/silkscreen400Latin.woff2') format('woff2');
}
@font-face {
  font-family: 'Silkscreen';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('../assets/fonts/silkscreen700Latin.woff2') format('woff2');
}

/* Tokens. Every colour is a solid. In dark the card is the agency demo card's 2% wash as a hex;
   in light the card is white on the agency ground and the rail carries the 4% wash, because the
   pinned light amber measures 4.24:1 on the wash and 5.26:1 on white. Text tiers on the card:
   muted 8.6:1 dark and 8.0:1 light, faint 5.6:1 dark and 5.7:1 light; every pill's text clears
   4.5:1 on its tint; input borders clear 3:1 on the input fill. Hairlines between surfaces are
   deliberately quiet. */
:root {
  --bg: #050a14;
  --card: #0a0f19;
  --rail: #0a0f19;
  --raised: #101a2c;
  --surface: #0a0f19;
  --blue: #4da6ff;
  --blue-deep: #2b8ae6;
  --blue-light: #4da6ff;          /* alias the meter block reads */
  --amber: #f5b342;
  --green: #43d67c;
  --red: #ff5050;
  --warn: #ff7b72;
  --text: #e6edf7;
  --muted: #a3aec2;
  --faint: #7f8ba0;
  --line: #1c2536;
  --line-strong: #2a3447;
  --tint: #111827;                /* hover fill on the card and the rail */
  --accent-tint: #0f2440;         /* the current item and a pressed chip */
  --blue-tint: #0f2440;
  --green-tint: #0d2a1c;
  --amber-tint: #2b2412;
  --red-tint: #2e1418;
  --input-bg: #050a14;
  --input-border: #5c687b;
  --input-focus: #4da6ff;
  --scrim: rgba(5, 10, 20, 0.6);
  --shadow-float: 0 8px 24px rgba(0, 0, 0, 0.45), 0 1px 2px rgba(0, 0, 0, 0.3);
  --font-ui: 'Onest', -apple-system, BlinkMacSystemFont, sans-serif;
  --font-pixel: 'Silkscreen', monospace;
  --font-mono: ui-monospace, 'SFMono-Regular', Menlo, Consolas, 'Liberation Mono', monospace;
  /* Motion: one ease-out for everything that answers a press or appears, one curve for things
     that travel. --ease and --spring are kept as aliases for the rules that use them. Colour fades
     in fast and out slow; a press goes down quickly and comes back softly; an exit is quicker than
     its entrance. */
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-move: cubic-bezier(0.32, 0.72, 0, 1);
  --ease: var(--ease-out);
  --spring: var(--ease-out);
  --d-hover-in: 70ms;
  --d-hover-out: 160ms;
  --d-press: 90ms;
  --d-release: 160ms;
  --d-menu-in: 160ms;
  --d-menu-out: 100ms;
  --d-dialog-in: 200ms;
  --d-dialog-out: 120ms;
  --d-scrim-in: 200ms;
  --d-scrim-out: 160ms;
  --d-toast-in: 240ms;
  --d-toast-out: 180ms;
  --d-reveal: 200ms;
  --d-move: 220ms;
  --tint-press: #172033;          /* a row or tab held down, one step past the hover tint */
  --r-sm: 4px;
  --r-md: 6px;
  --r-lg: 8px;
  --sidebar-w: 232px;
  --content-max: 1180px;
  color-scheme: dark;
}
[data-theme="light"] {
  --bg: #eef1f6;
  --card: #ffffff;
  --rail: #e4e7ec;
  --raised: #ffffff;
  --surface: #ffffff;
  --blue: #1962af;
  --blue-deep: #14528f;
  --blue-light: #1962af;
  --amber: #825b00;
  --green: #166a3c;
  --red: #b82c2c;
  --warn: #b03a30;
  --text: #1a1a2e;
  --muted: #4a5065;
  --faint: #5a6174;
  --line: #d5dae2;
  --line-strong: #b9c0cc;
  --tint: #dde1e8;
  --tint-press: #d9dee6;
  --accent-tint: #d6e6f7;
  --blue-tint: #d6e6f7;
  --green-tint: #d4ecdd;
  --amber-tint: #f9f2e2;
  --red-tint: #fce9e9;
  --input-bg: #ffffff;
  --input-border: #7a8292;
  --input-focus: #1962af;
  --scrim: rgba(26, 26, 46, 0.45);
  --shadow-float: 0 8px 24px rgba(10, 30, 61, 0.18), 0 1px 2px rgba(10, 30, 61, 0.12);
  color-scheme: light;
}

/* Reset and base */
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
[hidden] { display: none !important; }
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
/* The agency site's scrollbar, rule for rule (Website/CSS/shared.css): the page takes the thin
   native bar, every other scroller the 9px webkit bar, both shown only while in use
   (portal.js puts html.ll-scrolling on). #f5b342 on dark, #f0a500 on light. */
html { scrollbar-color: transparent transparent; scrollbar-width: thin; }
html.ll-scrolling { scrollbar-color: #f5b342 transparent; }
html[data-theme="light"].ll-scrolling { scrollbar-color: #f0a500 transparent; }
::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: transparent;
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
html.ll-scrolling::-webkit-scrollbar-thumb,
html.ll-scrolling ::-webkit-scrollbar-thumb {
  background: linear-gradient(180deg, #f5b342, #f0a500);
  background-clip: padding-box;
}
html.ll-scrolling::-webkit-scrollbar-thumb:hover,
html.ll-scrolling ::-webkit-scrollbar-thumb:hover {
  background: linear-gradient(180deg, #ffd98c, #f5b342);
  border: 1px solid transparent;
  background-clip: padding-box;
}
:root[data-theme="light"].ll-scrolling::-webkit-scrollbar-thumb,
:root[data-theme="light"].ll-scrolling ::-webkit-scrollbar-thumb {
  background: linear-gradient(180deg, #f0a500, #d99400);
  background-clip: padding-box;
}
:root[data-theme="light"].ll-scrolling::-webkit-scrollbar-thumb:hover,
:root[data-theme="light"].ll-scrolling ::-webkit-scrollbar-thumb:hover {
  background: linear-gradient(180deg, #f5b342, #f0a500);
  background-clip: padding-box;
}
::-webkit-scrollbar-corner { background: transparent; }
body {
  font-family: var(--font-ui); font-size: 14px; line-height: 1.5;
  background: var(--bg); color: var(--text);
  -webkit-font-smoothing: antialiased; min-height: 100vh;
  font-variant-numeric: tabular-nums;
}
::selection { background: rgba(245,179,66,0.32); color: #fff; }
[data-theme="light"] ::selection { background: rgba(245,179,66,0.5); color: #1a1a2e; }
img, svg { display: block; max-width: 100%; }
button { font-family: inherit; color: inherit; }
/* No tap highlight on a field: iOS draws a dark box over it for the length of the tap. The caret
   and the focus styles already answer the tap. */
input, textarea, select { font-family: inherit; font-size: inherit; color: inherit; -webkit-tap-highlight-color: transparent; }
a { color: var(--blue); text-decoration: none; text-underline-offset: 2px; }
@media (hover: hover) and (pointer: fine) { a:hover { text-decoration: underline; } }
:focus { outline: none; }
:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; border-radius: var(--r-sm); }
/* #main takes focus after a route change so a screen reader starts there; a
   ring round the whole region showed as a blue rule below the rail. */
.main:focus-visible { outline: none; }
code, .mono { font-family: var(--font-mono); font-size: 0.92em; }
.muted { color: var(--muted); }
.sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; clip-path: inset(50%); overflow: hidden; white-space: nowrap; }
.skip-link { position: absolute; left: -9999px; top: 8px; z-index: 10002; background: var(--raised); color: var(--text); padding: 10px 16px; border-radius: var(--r-md); border: 1px solid var(--line-strong); }
.skip-link:focus { left: 8px; }
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* Cursors: a pointer on everything that acts; not-allowed on a control that cannot; progress on
   one that is working; text stays on text. A label is pointed when pressing it toggles or opens
   something. */
a[href], button, [role="button"], [role="tab"], [role="menuitem"], [role="option"], [role="switch"],
[role="checkbox"], [role="radio"], summary, select, input[type="checkbox"], input[type="radio"],
input[type="file"], input[type="submit"], label:has(> input[type="checkbox"], > input[type="radio"]) { cursor: pointer; }
:disabled, [aria-disabled="true"], label:has(> input:disabled) { cursor: not-allowed; }
.is-busy, [aria-busy="true"]:is(button, a) { cursor: progress; }

/* Silkscreen "//" labels (the only Silkscreen use) and brand amber, on page and section titles only. */
.page-label { font-family: var(--font-pixel); font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: #f5b342 !important; }
[data-theme="light"] .page-label { color: #825b00 !important; }

/* Veil (portal chrome edition) */
#llLoader { display: none; }
html.ll-loading #llLoader { display: block; position: fixed; inset: 0; z-index: 10001; background: #050a14; opacity: 1; transition: opacity 0.25s var(--ease); }
html[data-theme="light"].ll-loading #llLoader { background: #eef1f6; }
html.ll-loading #llLoader.done { opacity: 0; pointer-events: none; }

/* The mark stage (LL.stage): signing in and signing out, the mark plays alone on the ground, as
   the no-script interstitial does (html.ts INTERSTITIAL_CSS). It is opaque from its first frame
   and what it covers is hidden at once, from assistive technology too. It reaches half a screen
   past the top and bottom, so a phone's toolbar strips never show the page underneath, and
   nothing scrolls while it is up; the gutter stays, so nothing shifts. */
html.ll-staged { overflow: hidden; overscroll-behavior: none; scrollbar-gutter: stable; }
body.is-staged > :not(.signin-stage) { visibility: hidden; }
body > .signin-stage {
  position: fixed; top: -50vh; right: 0; bottom: -50vh; left: 0; z-index: 5; display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: 24px; text-align: center; background: var(--bg); color: var(--muted); touch-action: none; overscroll-behavior: none;
  transition: opacity 0.35s var(--ease), transform 0.35s var(--ease);
}
body > .signin-stage.leave { opacity: 0; transform: scale(0.98); }
.ll-mark { position: relative; width: 88px; height: 88px; margin: 0 auto 22px; display: flex; align-items: center; justify-content: center; animation: llmPop 0.9s var(--spring) both; }
/* Plain boxes, not SVG, so the transforms run on the compositor and the mark keeps moving
   while the page is busy. The 34-unit logo at 80px; it loops, resting whole between breaks. */
.llm-tile { position: relative; display: block; width: 80px; height: 80px; box-sizing: border-box; border-radius: 20px; border: 2.35px solid #2a3447; background: #0a0f19; animation: llmShake 1.5s linear infinite; }
.llm-sq { position: absolute; width: 24.7px; height: 24.7px; border-radius: 2.35px; will-change: transform; animation: 1.5s linear infinite; }
.llm-sq1 { left: 9.4px; top: 9.4px; background: #4da6ff; animation-name: llmFrag1; }
.llm-sq2 { left: 41.2px; top: 9.4px; background: #56d9e4; opacity: 0.85; animation-name: llmFrag2; }
.llm-sq3 { left: 9.4px; top: 41.2px; background: #4da6ff; opacity: 0.4; animation-name: llmFrag3; }
.llm-sq4 { left: 41.2px; top: 41.2px; animation-name: llmFrag4; }
/* The window flickers by colour on its own box inside the moving square, so no element carries a
   transform and a changing fill together. */
.llm-win { position: absolute; inset: 0; border-radius: inherit; background: #f5b342; animation: llmWindow 9s steps(1) 2.2s infinite; }
.signin-title { font-size: 20px; font-weight: 700; letter-spacing: -0.02em; color: var(--text); opacity: 0; animation: llmRise 0.4s var(--ease) both 1.08s; }
.signin-note { margin-top: 12px; font-size: 13px; color: var(--muted); opacity: 0; animation: llmRise 0.4s var(--ease) both 1.25s; }
/* Signing out: the same mark in 0.9 s; the note shows only if the sign-in page is slow to come. */
.signin-stage.is-quick .ll-mark { animation-duration: 0.54s; }
.signin-stage.is-quick .llm-tile, .signin-stage.is-quick .llm-sq { animation-duration: 0.9s; }
.signin-stage.is-quick .signin-title { animation-duration: 0.3s; animation-delay: 0.3s; }
.signin-stage.is-quick .signin-note { animation-delay: 2s; }
/* Underlined, so the link is not told apart by colour alone (WCAG 1.4.1). */
.signin-note a { color: var(--text); text-decoration: underline; text-underline-offset: 3px; }
@keyframes llmPop { 0% { transform: scale(0.8); opacity: 0; } 18% { opacity: 1; } 32% { transform: scale(1.04); } 50%, 100% { transform: scale(1); opacity: 1; } }
@keyframes llmFrag1 { 0%, 22% { transform: translate(0,0) rotate(0deg); animation-timing-function: cubic-bezier(0.34,1.3,0.64,1); } 44%, 58% { transform: translate(-13.2px,-13.2px) rotate(-9deg); animation-timing-function: cubic-bezier(0.55,0,0.45,1); } 76% { transform: translate(0.9px,0.9px) rotate(1.5deg); animation-timing-function: ease-out; } 84%, 100% { transform: translate(0,0) rotate(0deg); } }
@keyframes llmFrag2 { 0%, 22% { transform: translate(0,0) rotate(0deg); animation-timing-function: cubic-bezier(0.34,1.3,0.64,1); } 44%, 58% { transform: translate(13.2px,-13.2px) rotate(8deg); animation-timing-function: cubic-bezier(0.55,0,0.45,1); } 76% { transform: translate(-0.9px,0.9px) rotate(-1.3deg); animation-timing-function: ease-out; } 84%, 100% { transform: translate(0,0) rotate(0deg); } }
@keyframes llmFrag3 { 0%, 22% { transform: translate(0,0) rotate(0deg); animation-timing-function: cubic-bezier(0.34,1.3,0.64,1); } 44%, 58% { transform: translate(-13.2px,13.2px) rotate(7deg); animation-timing-function: cubic-bezier(0.55,0,0.45,1); } 76% { transform: translate(0.9px,-0.9px) rotate(-1.2deg); animation-timing-function: ease-out; } 84%, 100% { transform: translate(0,0) rotate(0deg); } }
@keyframes llmFrag4 { 0%, 22% { transform: translate(0,0) rotate(0deg); animation-timing-function: cubic-bezier(0.34,1.3,0.64,1); } 44%, 58% { transform: translate(13.2px,13.2px) rotate(-8deg); animation-timing-function: cubic-bezier(0.55,0,0.45,1); } 76% { transform: translate(-0.9px,-0.9px) rotate(1.3deg); animation-timing-function: ease-out; } 84%, 100% { transform: translate(0,0) rotate(0deg); } }
@keyframes llmShake { 0%, 44% { transform: translate(0,0) rotate(0deg); } 47% { transform: translate(-1px,0.6px) rotate(-1.4deg); } 50% { transform: translate(1.1px,-0.5px) rotate(1.2deg); } 53% { transform: translate(-1.1px,-0.5px) rotate(-1deg); } 56% { transform: translate(1px,0.5px) rotate(0.8deg); } 58% { transform: translate(-0.6px,0.3px) rotate(-0.4deg); } 61%, 100% { transform: translate(0,0) rotate(0deg); } }
/* 94.5% closes the second blink. */
@keyframes llmWindow { 0%, 88%, 100% { background-color: #f5b342; } 90%, 93% { background-color: var(--llm-dim, #433926); } 91.5%, 94.5% { background-color: #f5b342; } }
@keyframes llmRise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
[data-theme="light"] .llm-tile { background: #ffffff; border-color: #b9c0cc; }
[data-theme="light"] .llm-win { --llm-dim: #f7e9d0; }

/* Logo pair: the white mark only under this surface's own light theme */
.light-only { display: none; }
[data-theme="light"] .dark-only { display: none; }
[data-theme="light"] .light-only { display: block; }

/* The brand mark: one element, two background layers, only one ever drawn: the lit file at full
   size over the dim twin at 0x0. A dip swaps the two sizes, so the whole mark repaints at once and
   nothing animates inside an image (a stacked dim copy doubles the tile's anti-aliased edge).
   The static pair is built by Agency/scripts/markFiles.py. Size comes from the element's class;
   the white pair only under this surface's own light theme. */
.logo-mark {
  display: block; flex: 0 0 auto;
  background: url('../assets/logo-lit.svg?v=20260911') 0 0 / 100% 100% no-repeat, url('../assets/logo-dim.svg?v=20260911') 0 0 / 0 0 no-repeat;
  animation: llMarkWindow 9s steps(1, end) infinite;
}
[data-theme="light"] .logo-mark { background-image: url('../assets/logo-light-lit.svg?v=20260911'), url('../assets/logo-light-dim.svg?v=20260911'); }
@keyframes llMarkWindow {
  0%, 88%, 100% { background-size: 100% 100%, 0 0; }
  90%, 93% { background-size: 0 0, 100% 100%; }
  91.5%, 94.5% { background-size: 100% 100%, 0 0; }
}

/* Shell */
.shell { position: relative; z-index: 1; display: flex; min-height: 100vh; }
.sidebar {
  position: sticky; top: 0; height: 100vh; flex: 0 0 var(--sidebar-w);
  display: flex; flex-direction: column;
  border-right: 1px solid var(--line);
  background: var(--rail);
  z-index: 10;
}
.brand { padding: 18px 16px 12px; }
.brand-link { display: flex; align-items: center; gap: 10px; color: inherit; }
@media (hover: hover) and (pointer: fine) { .brand-link:hover { text-decoration: none; } }
.brand-mark { width: 28px; height: 28px; border-radius: 6px; flex: 0 0 auto; }
.brand-text { min-width: 0; display: flex; align-items: baseline; gap: 6px; }
.brand-word { font-weight: 800; font-size: 15px; letter-spacing: -0.01em; line-height: 1.1; }
/* The wordmark is one colour (the theme's text), Logic thinner by weight only. */
.brand-word .logic { color: var(--text); font-weight: 400; }
.brand-tag { font-size: 12px; color: var(--muted); }
/* The way back to lainglogic.com, on the sign-in page alone and only for a visitor who came from
   the site (portal.js bindBackToSite): a small quiet square with a left arrow and no words (the
   name is in the label and the tooltip), 44px on a touch screen. */
.back-site { width: 30px; min-height: 30px; padding: 0; gap: 0; }
.back-site svg { width: 15px; height: 15px; }
@media (hover: none) { .back-site { width: 44px; } }

.nav { flex: 1 1 auto; padding: 4px 10px; overflow-y: auto; }
.tab-list { list-style: none; display: flex; flex-direction: column; gap: 2px; }
.tab-link {
  position: relative; display: flex; align-items: center; gap: 10px;
  min-height: 36px; padding: 7px 10px; border-radius: var(--r-md);
  color: var(--muted); font-weight: 500; font-size: 13.5px;
}
@media (hover: hover) and (pointer: fine) {
  .tab-link:hover { background: var(--tint); color: var(--text); text-decoration: none; }
}
.tab-link[aria-current="page"] { color: var(--blue); background: var(--accent-tint); font-weight: 600; }
.tab-ic { width: 16px; height: 16px; flex: 0 0 auto; color: var(--faint); }
.tab-link[aria-current="page"] .tab-ic { color: var(--blue); }
@media (hover: hover) and (pointer: fine) { .tab-link:hover .tab-ic { color: var(--text); } }

.session-panel { padding: 12px 14px 14px; border-top: 1px solid var(--line); }
/* The signed-in account: plain lines. */
.session-card { display: flex; flex-direction: column; gap: 1px; min-width: 0; font-size: 12.5px; line-height: 1.45; }
.session-name { font-weight: 600; color: var(--text); }
.session-email { color: var(--muted); overflow-wrap: anywhere; }
.session-note { color: var(--faint); }
.session-panel .session-card { margin-bottom: 10px; }
.session-actions { display: flex; align-items: center; gap: 8px; margin-top: 10px; }

/* Main column and the page header: one label, one title, one line, and at most one primary
   action on the right (portal.js writes the title and the line per tab). */
.main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.page-head { width: 100%; max-width: var(--content-max); margin: 0 auto; padding: 28px 40px 0; }
.page-head-row { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; }
.page-head .page-label { margin-bottom: 6px; }
.hero-title { font-size: 28px; font-weight: 700; letter-spacing: -0.02em; line-height: 1.15; color: var(--text); }
.hero-sub { font-size: 13.5px; color: var(--muted); margin-top: 4px; max-width: 640px; }
.page-head-actions { flex: 0 0 auto; display: flex; gap: 8px; }

.content { width: 100%; max-width: var(--content-max); margin: 0 auto; padding: 20px 40px 56px; flex: 1 0 auto; min-width: 0; }
/* The page head and the content fill at least one screen, so the footer always starts below the
   fold however short the tab. One LARGE viewport, never the dynamic one: a height that follows the
   phone's toolbar moves the footer while the page scrolls. A view-as bar above the shell takes its
   measured height off. */
.main-body { display: flex; flex-direction: column; flex: 1 0 auto; min-width: 0; min-height: 100vh; }
body[data-viewing="1"] .main-body { min-height: calc(100vh - var(--view-bar-h, 48px)); }
@supports (height: 1lvh) {
  .main-body { min-height: 100lvh; }
  body[data-viewing="1"] .main-body { min-height: calc(100lvh - var(--view-bar-h, 48px)); }
}
/* A tab switch happens hundreds of times a day and does not animate. */
.tab-panel { display: flex; flex-direction: column; gap: 16px; }
/* min(): a column is never wider than the screen, and min-width 0 lets a field shrink with it. */
.grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); gap: 16px; align-items: start; }
.grid-2 > * { min-width: 0; }

/* Section title (a plain heading between cards) */
.section-title { font-size: 14px; font-weight: 600; letter-spacing: -0.01em; margin-bottom: 10px; }

/* Card */
.panel { position: relative; border-radius: var(--r-lg); background: var(--card); border: 1px solid var(--line); }
.panel-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 16px; border-bottom: 1px solid var(--line); min-height: 48px; }
.panel-head .page-label { display: none; }
.panel-title { font-size: 14px; font-weight: 600; letter-spacing: -0.01em; }
.panel-body { padding: 16px; }
.panel-note { font-size: 12.5px; color: var(--muted); margin-top: 12px; }
.panel[aria-disabled="true"] .panel-body { color: var(--muted); }
/* The dropdown session line is for phone width only. */
.nav-session { display: none; }

/* Upload meter: the cs component, flat (sync warning in CLAUDE.md): a plain mono block on the
   page ground, no blinking cursor. */
.upload-meter{position:relative;display:flex;align-items:center;flex-wrap:wrap;gap:3px 8px;
  margin-top:10px;padding:8px 12px;
  font-family:ui-monospace,"SFMono-Regular",Menlo,Consolas,"Liberation Mono",monospace;
  font-size:12px;line-height:1.5;border-radius:6px;
  background:var(--input-bg);
  border:1px solid var(--line);
  overflow:hidden;white-space:nowrap;text-overflow:ellipsis;color:var(--text)}
.upload-meter > *{position:relative}
.upload-meter .um-prompt{color:var(--blue);font-weight:700}
.upload-meter .um-body{color:var(--text)}
.upload-meter .um-body.um-warn{color:var(--warn)}
.upload-meter .um-note{color:var(--muted)}
.upload-meter .um-cursor{display:none}
.upload-meter .um-line{display:inline-flex;align-items:center;gap:6px;min-width:0}
.upload-meter .um-line-note > .um-prompt{display:none}
@media (max-width:540px){
  .upload-meter{row-gap:5px}
  .upload-meter .um-line{flex:1 1 100%}
  .upload-meter .um-line-note > .um-prompt{display:inline-block}
}
/* end component */
.session-panel .upload-meter, .stat .upload-meter, .meter-wrap .upload-meter { margin-top: 0; white-space: normal; }
.session-panel .upload-meter .um-line, .stat .upload-meter .um-line, .meter-wrap .upload-meter .um-line { flex: 1 1 100%; align-items: flex-start; }
.session-panel .upload-meter .um-line-note > .um-prompt, .stat .upload-meter .um-line-note > .um-prompt, .meter-wrap .upload-meter .um-line-note > .um-prompt { display: inline-block; }
.session-panel .upload-meter .um-body, .session-panel .upload-meter .um-note, .stat .upload-meter .um-body, .stat .upload-meter .um-note { min-width: 0; overflow-wrap: anywhere; }
.upload-meter .um-dim { color: var(--muted); }

/* Buttons: solid white primary on dark, solid ink primary on light, outline secondary, quiet
   tertiary. A press scales down; nothing lifts. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  font-weight: 600; font-size: 13px; line-height: 1;
  padding: 0 14px; min-height: 36px; border-radius: var(--r-md); border: 1px solid transparent;
  cursor: pointer; white-space: nowrap; text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}
@media (hover: hover) and (pointer: fine) { .btn:hover { text-decoration: none; } }
.btn svg { width: 14px; height: 14px; }
/* Disabled by colour, not opacity: the faint tier clears AA on every fill. A disabled button
   keeps its pointer events so the cursor can say not-allowed; the browser sends it no click. A
   label or link standing in for a button (view as locks them) keeps them too, and portal.js
   swallows its activation. */
.btn[disabled], .btn[aria-disabled="true"], .btn.is-disabled { color: var(--faint); background: transparent; border-color: var(--line-strong); cursor: not-allowed; }

.btn-quiet[disabled], .btn-quiet[aria-disabled="true"] { border-color: transparent; }
.btn-primary[disabled], .btn-primary[aria-disabled="true"], .btn-primary.is-disabled { background: transparent; color: var(--faint); border-color: var(--line-strong); }
.btn-primary { background: #ffffff; color: #050a14; }
[data-theme="light"] .btn-primary { background: #1a1a2e; color: #ffffff; }
/* A disabled primary in light: the rule above outranks .btn-primary[disabled] by order, so
   without this a disabled primary draws as a live navy button. */
[data-theme="light"] .btn-primary:is([disabled], [aria-disabled="true"], .is-disabled) { background: transparent; color: var(--faint); border-color: var(--line-strong); }
@media (hover: hover) and (pointer: fine) { [data-theme="light"] .btn-primary:is([disabled], [aria-disabled="true"], .is-disabled):hover { background: transparent; color: var(--faint); border-color: var(--line-strong); } }
@media (hover: hover) and (pointer: fine) {
  .btn-primary:hover { background: #e6edf7; }
  [data-theme="light"] .btn-primary:hover { background: #2a2a4e; }
}
.btn-outline { background: transparent; color: var(--text); border-color: var(--input-border); }
.btn-quiet { background: transparent; color: var(--muted); }
.btn-danger { background: transparent; color: var(--red); border-color: var(--line-strong); }
/* Not a fourth button colour: the working palette is blue and amber lives on the labels. The
   class stays for the markup that carries it. */
.btn-amber { background: transparent; color: var(--text); border-color: var(--line-strong); }
@media (hover: hover) and (pointer: fine) {
  .btn-outline:hover, .btn-amber:hover { background: var(--tint); border-color: var(--faint); }
  .btn-quiet:hover { background: var(--tint); color: var(--text); }
  .btn-danger:hover { background: var(--red-tint); border-color: var(--red); }
  /* A disabled button does not answer a hover. */
  .btn:is([disabled], [aria-disabled="true"], .is-disabled):hover { color: var(--faint); background: transparent; border-color: var(--line-strong); }
  .btn-quiet:is([disabled], [aria-disabled="true"]):hover { border-color: transparent; }
}
.btn-sm { padding: 0 10px; min-height: 30px; font-size: 12.5px; }
.btn-quiet.btn-remove { color: var(--red); }
.btn-block { width: 100%; }
/* Two-tap: the first tap arms the button for 4s; the second inside that window acts. */
.btn.is-armed, .btn-amber.is-armed { background: var(--red-tint); color: var(--red); border-color: var(--red); }
/* Touch: every pressable thing is at least 44px tall. */
@media (hover: none) { .btn, .btn-sm { min-height: 44px; } }

/* Interaction motion: colour fades in fast (70ms) and out slow (160ms), so a pointer sweeping a
   list leaves a short trail, never a flash; a press goes down in 90ms and back over 160ms. Rows
   and tabs take a deeper tint when held instead of a scale: a full-width row that shrinks reads
   as a wobble. Focus is instant, and nothing here runs at rest. */
.btn, .chip, .tab-link, .drawer-close, .nav-toggle, a.stat,
.input, .select, .textarea, .table tbody tr {
  transition-property: background-color, border-color, color, transform;
  transition-duration: var(--d-hover-out), var(--d-hover-out), var(--d-hover-out), var(--d-release);
  transition-timing-function: var(--ease-out);
  -webkit-tap-highlight-color: transparent;
}
@media (hover: hover) and (pointer: fine) {
  :is(.btn, .chip, .tab-link, .drawer-close, .nav-toggle, a.stat, .input, .select, .textarea, .table tbody tr):hover {
    transition-duration: var(--d-hover-in), var(--d-hover-in), var(--d-hover-in), var(--d-release);
  }
}
:is(.input, .select, .textarea):focus { transition-duration: 0ms; }
:is(.btn, .chip, .drawer-close, .nav-toggle, .tab-link, a.stat):active { transition-duration: 0ms, 0ms, 0ms, var(--d-press); }
.btn:active:not(:disabled):not([aria-disabled="true"]) { transform: scale(0.97); }
:is(.btn-sm, .chip, .drawer-close, .nav-toggle):active:not(:disabled):not([aria-disabled="true"]) { transform: scale(0.96); }
.btn-block:active:not(:disabled):not([aria-disabled="true"]) { transform: scale(0.985); }
:is(.tab-link, a.stat):active { background: var(--tint-press); }

/* Inputs */
.field { display: flex; flex-direction: column; gap: 6px; }
.field-label { font-size: 12.5px; font-weight: 500; color: var(--muted); }
.field-error { font-size: 12px; color: var(--red); }
.field-error[hidden] { display: none; }
.hint { font-size: 12px; color: var(--faint); }
.input, .select, .textarea {
  width: 100%; min-height: 36px; padding: 7px 10px; font-size: 14px; line-height: 1.4; color: var(--text);
  background: var(--input-bg); border: 1px solid var(--input-border); border-radius: var(--r-md);
  -webkit-appearance: none; appearance: none;
}
@media (hover: hover) and (pointer: fine) { .input:hover, .select:hover, .textarea:hover { border-color: var(--faint); } }
.input:focus, .select:focus, .textarea:focus { outline: none; border-color: var(--input-focus); box-shadow: 0 0 0 3px var(--accent-tint); }
.input::placeholder, .textarea::placeholder { color: var(--faint); }
.input[aria-invalid="true"], .select[aria-invalid="true"] { border-color: var(--red); }
.input[readonly], .input:disabled, .select:disabled, .textarea:disabled { background: var(--card); color: var(--muted); border-color: var(--line-strong); }
.textarea { resize: vertical; min-height: 96px; line-height: 1.5; }
.select { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%237f8ba0' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 10px center; padding-right: 30px; cursor: pointer; }
select.select option { background: var(--raised); color: var(--text); }
/* Autofill: a filled field never gains a background. The browser's own fill is clipped to the
   glyphs (important, because a page's background shorthand resets the clip), no shadow stands in
   for it, and the words take a light blue tinge instead, caret too. Every kind of field, every
   state. */
input:-webkit-autofill, textarea:-webkit-autofill, select:-webkit-autofill,
input:-webkit-autofill:hover, textarea:-webkit-autofill:hover, select:-webkit-autofill:hover,
input:-webkit-autofill:focus, textarea:-webkit-autofill:focus, select:-webkit-autofill:focus {
  -webkit-background-clip: text !important; background-clip: text !important; box-shadow: none;
  -webkit-text-fill-color: color-mix(in srgb, var(--text) 75%, var(--blue)); caret-color: color-mix(in srgb, var(--text) 75%, var(--blue));
}
/* The standard pseudo-class: Firefox tints a filled field with a filter instead. */
input:autofill, textarea:autofill, select:autofill {
  filter: none; box-shadow: none;
  -webkit-text-fill-color: color-mix(in srgb, var(--text) 75%, var(--blue)); caret-color: color-mix(in srgb, var(--text) 75%, var(--blue));
}
.check { display: flex; align-items: center; gap: 10px; min-height: 36px; cursor: pointer; user-select: none; }
.check input, .upload-file-pick input { position: relative; width: 16px; height: 16px; flex: 0 0 auto; -webkit-appearance: none; appearance: none; border: 1px solid var(--input-border); border-radius: var(--r-sm); background: var(--input-bg); cursor: pointer; }
.check input, .upload-file-pick input {
  transition: background-color 120ms var(--ease-out), border-color 120ms var(--ease-out), transform var(--d-release) var(--ease-out);
  -webkit-tap-highlight-color: transparent;
}
.check input:checked, .upload-file-pick input:checked { background-color: var(--blue); border-color: var(--blue); }
/* The tick is its own layer so it can grow in: from 0.6 and clear to full over the release
   curve, while the box fills. */
.check input::after, .upload-file-pick input::after {
  content: ''; position: absolute; inset: 0;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 16 16' fill='none' stroke='%23050a14' stroke-width='2.5'%3E%3Cpath d='M3 8.5l3.5 3.5 6.5-7'/%3E%3C/svg%3E") center / 10px no-repeat;
  opacity: 0; transform: scale(0.6);
  transition: opacity 120ms var(--ease-out), transform var(--d-release) var(--ease-out);
}
[data-theme="light"] .check input::after, [data-theme="light"] .upload-file-pick input::after { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 16 16' fill='none' stroke='%23ffffff' stroke-width='2.5'%3E%3Cpath d='M3 8.5l3.5 3.5 6.5-7'/%3E%3C/svg%3E"); }
.check input:checked::after, .upload-file-pick input:checked::after { opacity: 1; transform: none; }
.check input:active:not(:disabled), .upload-file-pick input:active:not(:disabled) { transform: scale(0.92); transition-duration: 0ms, 0ms, var(--d-press); }
.check input:focus-visible, .upload-file-pick input:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
.check input:disabled { border-color: var(--line-strong); background: var(--card); cursor: not-allowed; }
.check-list { display: flex; flex-direction: column; gap: 0; }
.toolbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.toolbar .input { max-width: 320px; }
/* A list's search: the magnifier inside the field, 320px at most, full width on a phone. */
.list-search { display: flex; align-items: center; gap: 8px 12px; flex-wrap: wrap; }
.search-box { position: relative; display: block; flex: 0 1 320px; min-width: 0; }
.search-ic { position: absolute; left: 10px; top: 50%; width: 14px; height: 14px; transform: translateY(-50%); color: var(--faint); pointer-events: none; }
.search-box .input { padding-left: 32px; }
.list-search-count { font-size: 12.5px; }
@media (max-width: 640px) { .search-box { flex-basis: 100%; } }
/* A searched list's last row shown drops its rule as a last row does; the no-match row takes no hover. */
.table tbody tr.is-last td { border-bottom: none; }
@media (hover: hover) and (pointer: fine) { .table tbody tr.search-none:hover { background: transparent; } }
@media (hover: none) { .check { min-height: 44px; } }

/* Tables: dense, left aligned, numbers right aligned, sentence case heads */
/* No sideways scroll: long values wrap in their cells instead. */
.table-wrap { overflow-x: visible; }
.table { width: 100%; border-collapse: collapse; font-size: 13px; }
.table th { text-align: left; font-size: 12px; color: var(--muted); font-weight: 500; padding: 8px 12px; border-bottom: 1px solid var(--line-strong); white-space: nowrap; }
.table td { padding: 9px 12px; border-bottom: 1px solid var(--line); vertical-align: middle; }
.table tbody tr:last-child td { border-bottom: none; }
@media (hover: hover) and (pointer: fine) { .table tbody tr:hover { background: var(--tint); } }
.table .num { text-align: right; font-variant-numeric: tabular-nums; }
.table .actions { white-space: nowrap; text-align: right; }
.table .actions .btn { margin-left: 4px; }
@media (max-width: 540px) {
  .table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .table, .table tbody, .table tr, .table td { display: block; width: 100%; }
  .table tr { padding: 10px 0; border-bottom: 1px solid var(--line-strong); }
  /* In a card a stacked row keeps the card's inset. */
  .panel .table tr { padding-left: 14px; padding-right: 14px; }
  .table td { border: none; padding: 4px 0; }
  .table td[data-label]::before { content: attr(data-label); display: block; font-size: 12px; color: var(--faint); margin-bottom: 1px; }
  .table .actions { white-space: normal; display: flex; flex-wrap: wrap; gap: 6px; padding-top: 8px; text-align: left; }
  .table .actions .btn { margin: 0; }
}

/* Status pills and filter chips: a word in a pill, a colour behind it */
.badge { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; font-weight: 500; line-height: 1.4; padding: 1px 8px; border-radius: 999px; border: 1px solid var(--line-strong); color: var(--muted); background: transparent; white-space: nowrap; }
.badge-blue { color: var(--blue); border-color: transparent; background: var(--blue-tint); }
.badge-cyan { color: var(--muted); border-color: var(--line-strong); background: transparent; }
.badge-amber { color: var(--amber); border-color: transparent; background: var(--amber-tint); }
.badge-green { color: var(--green); border-color: transparent; background: var(--green-tint); }
.badge-red { color: var(--red); border-color: transparent; background: var(--red-tint); }
.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chip { display: inline-flex; align-items: center; gap: 6px; min-height: 32px; font-size: 12.5px; font-weight: 500; padding: 0 12px; border-radius: 999px; border: 1px solid var(--line-strong); color: var(--muted); background: transparent; cursor: pointer; }
@media (hover: hover) and (pointer: fine) { .chip:hover { color: var(--text); background: var(--tint); } }
.chip[aria-pressed="true"] { color: var(--blue); border-color: var(--blue); background: var(--accent-tint); }
.chip .dot { display: none; }
@media (hover: none) { .chip { min-height: 44px; } }

/* Notices, empty state, toast */
.notice { padding: 10px 12px; border-radius: var(--r-md); font-size: 13px; border: 1px solid var(--line-strong); background: var(--card); color: var(--text); }
/* A notice under a button row or a field keeps its distance; one that opens a panel sits flush. */
.notice:not(:first-child) { margin-top: 12px; }
.notice-error { color: var(--red); border-color: var(--red); background: var(--red-tint); }
.notice-info { color: var(--text); border-color: var(--blue); background: var(--blue-tint); }
.empty { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; text-align: left; padding: 24px 0; color: var(--muted); }
.empty-title { font-weight: 600; color: var(--text); font-size: 14px; }
.empty p { font-size: 13px; max-width: 420px; }
.toasts { position: fixed; right: 16px; bottom: 16px; z-index: 12000; display: flex; flex-direction: column; gap: 8px; max-width: 360px; }
.toast { display: flex; align-items: flex-start; gap: 10px; padding: 10px 12px; border-radius: var(--r-lg); background: var(--raised); border: 1px solid var(--line-strong); box-shadow: var(--shadow-float); font-size: 13px; color: var(--text); animation: toastIn var(--d-toast-in) var(--ease-out); }
/* In from below its own height, out a little quicker than it came. */
@keyframes toastIn { from { opacity: 0; transform: translateY(100%); } to { opacity: 1; transform: none; } }
.toast.out { opacity: 0; transform: translateY(6px) scale(0.98); transition: opacity var(--d-toast-out) var(--ease-out), transform var(--d-toast-out) var(--ease-out); }
.toast .t-bar { width: 3px; align-self: stretch; border-radius: 2px; background: var(--blue); flex: 0 0 auto; }
.toast.err .t-bar { background: var(--red); }
.toast.ok .t-bar { background: var(--green); }
.toast.warn .t-bar { background: var(--amber); }
@media (max-width: 540px) { .toasts { left: 10px; right: 10px; bottom: calc(10px + env(safe-area-inset-bottom, 0px)); max-width: none; } }

/* Dialog (create and edit forms; the markup keeps its drawer names). The one translucent layer
   in the portal is the scrim behind it. */
.drawer-overlay { position: fixed; inset: 0; z-index: 11000; background: var(--scrim); animation: fadeIn var(--d-scrim-in) var(--ease-out); }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes fadeOut { to { opacity: 0; } }
/* Closing plays the entrance backwards, quicker: LL.leave holds the nodes for the length of it. */
.drawer-overlay.is-leaving { animation: fadeOut var(--d-scrim-out) var(--ease-out) forwards; pointer-events: none; }
.drawer.is-leaving { animation: dialogOut var(--d-dialog-out) var(--ease-out) forwards; pointer-events: none; }
@keyframes dialogOut { to { transform: translate(-50%, -50%) scale(0.98); opacity: 0; } }
.drawer { position: fixed; left: 50%; top: 50%; z-index: 11001; transform: translate(-50%, -50%); width: min(480px, calc(100vw - 32px)); max-height: calc(100vh - 32px); display: flex; flex-direction: column; background: var(--raised); border: 1px solid var(--line-strong); border-radius: var(--r-lg); box-shadow: var(--shadow-float); animation: dialogIn var(--d-dialog-in) var(--ease-out); transform-origin: center; }
@keyframes dialogIn { from { transform: translate(-50%, -50%) scale(0.97); opacity: 0; } to { transform: translate(-50%, -50%) scale(1); opacity: 1; } }
.drawer > form { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.drawer-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 16px; border-bottom: 1px solid var(--line); }
.drawer-head .page-label { display: none; }
.drawer-title { font-size: 16px; font-weight: 600; letter-spacing: -0.01em; }
.drawer-close { width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; border-radius: var(--r-md); border: 1px solid transparent; background: transparent; color: var(--muted); cursor: pointer; }
@media (hover: hover) and (pointer: fine) { .drawer-close:hover { color: var(--text); background: var(--tint); } }
.drawer-close svg { width: 13px; height: 13px; }
.drawer-body { flex: 1 1 auto; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 14px; }
.drawer-actions { display: flex; justify-content: flex-end; gap: 8px; padding: 12px 16px; border-top: 1px solid var(--line); }
/* The root alone: overflow on body too would make it the sticky rail's scroller. */
html.ll-noscroll { overflow: hidden; }

/* Loading: a region reads in its own shape: skeleton bars where the content will be, and one
   quiet line saying what is being read. The content that replaces them arrives with a short fade
   and a 3px rise, a list's rows 25ms apart. A working button shows a spinner in its icon's place
   and keeps its width and label. Only transform and opacity move. */
.skeleton { display: block; border-radius: var(--r-sm); background: var(--tint); animation: skeletonPulse 1.4s var(--ease-out) infinite; }
@keyframes skeletonPulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.55; } }
.sk-line { height: 10px; max-width: 100%; }
.sk-val { height: 20px; width: 48px; }
.sk-pill { width: 56px; height: 18px; border-radius: 999px; }
.sk-btn { width: 64px; height: 30px; border-radius: var(--r-md); }
.sk-stack { display: flex; flex-direction: column; gap: 8px; }
/* One line of words to come: inline in the words' own element, so the line box is the text's
   and nothing moves when the words land. */
.skeleton.sk-text { display: inline-block; vertical-align: middle; height: 0.72em; max-width: 100%; }
/* A file row's checkbox to come (the uploads list): a 16px square. */
.skeleton.sk-icon { display: inline-block; width: 16px; height: 16px; }
/* Known words waiting on their styles (the content form's first step): the
   words themselves, transparent, on a bar the exact shape of each line. */
.sk-words { color: transparent !important; background: var(--tint); border-radius: var(--r-sm); -webkit-box-decoration-break: clone; box-decoration-break: clone; animation: skeletonPulse 1.4s var(--ease-out) infinite; }
.load-line { display: flex; align-items: center; gap: 8px; min-height: 20px; margin: 0 0 10px; font-size: 12.5px; color: var(--faint); }
.load-line .btn-spin { width: 12px; height: 12px; color: var(--blue); }
.panel-head .load-line, .activity-foot .load-line, .panel-body > .load-line:only-child { margin: 0; }
/* Inside a skeleton card the line is the card's first row, so it leaves with
   the bars and nothing below it moves. */
.panel > .load-line { margin: 0; padding: 12px 16px 0; }
/* The quiet line takes no room the content will not also have: over a table's empty head
   corner, or in a panel's head beside its title. */
.panel.is-skel { position: relative; }
.panel.is-skel > .load-line { position: absolute; top: 9px; right: 16px; z-index: 1; padding: 0; }
.panel-head .load-line.in-head { margin: 0 12px 0 auto; min-width: 0; flex: 0 1 auto; overflow: hidden; }
/* One line, never a second: a head that wrapped to fit the words would be taller while it read,
   and the body under it would move when they went. */
.panel-head .load-line.in-head > span:last-child { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* The head's own title keeps its line while the quiet line sits beside it:
   the line is the one that gives way. */
.panel-head:has(> .load-line.in-head) > .panel-title { flex: 0 0 auto; white-space: nowrap; }
.panel-head:has(> .load-line.in-head) { flex-wrap: nowrap; }
.is-refreshing { opacity: 0.55; transition: opacity var(--d-hover-out) var(--ease-out); }
@keyframes llIn { from { opacity: 0; transform: translateY(3px); } }
.ll-in { animation: llIn var(--d-reveal) var(--ease-out) backwards; }
.ll-in-rows > * { animation: llIn var(--d-reveal) var(--ease-out) backwards; }
.ll-in-rows > :nth-child(2) { animation-delay: 25ms; }
.ll-in-rows > :nth-child(3) { animation-delay: 50ms; }
.ll-in-rows > :nth-child(4) { animation-delay: 75ms; }
.ll-in-rows > :nth-child(5) { animation-delay: 100ms; }
.ll-in-rows > :nth-child(6) { animation-delay: 125ms; }
.ll-in-rows > :nth-child(7) { animation-delay: 150ms; }
.ll-in-rows > :nth-child(n+8) { animation-delay: 175ms; }
@keyframes llSpin { to { transform: rotate(360deg); } }
.btn-spin { display: inline-block; width: 14px; height: 14px; flex: 0 0 auto; border-radius: 50%; border: 1.5px solid currentColor; border-right-color: transparent; animation: llSpin 0.7s linear infinite, llIn 120ms var(--ease-out) backwards; }
.btn-sm .btn-spin { width: 12px; height: 12px; }
.is-busy > svg { display: none; }
.is-busy.no-icon { position: relative; color: transparent !important; }
.is-busy.no-icon > .btn-spin { position: absolute; left: 50%; top: 50%; margin: -7px 0 0 -7px; color: var(--spin-ink); }
.btn-sm.is-busy.no-icon > .btn-spin { margin: -6px 0 0 -6px; }

/* Shell error (session could not load) */
.shell-error { max-width: 520px; margin: 40px auto; }

/* Footer: the agency footer, exactly. Website/CSS/pageIndex.css "Footer" and its light block,
   rule for rule, with the agency's own tones rather than this surface's tokens, so the portal
   footer computes the same as lainglogic.com's (tests/portal/tests/13-footer.spec.js reads both).
   The logo's glow and the icons' lift are this footer's licensed exception to the flat rules
   (portal/DESIGN.md). No phone override: the agency has none. */
.dash-footer {
  text-align: center; padding: 48px 24px 32px; color: rgba(255,255,255,0.2);
  font-size: 13px; line-height: normal; letter-spacing: normal; font-variant-numeric: normal;
  border-top: 1px solid rgba(255,255,255,0.04); position: relative; z-index: 1;
}
.dash-footer a:has(> .footer-logo) { display: inline-block; }
.dash-footer .footer-logo {
  display: inline-block; aspect-ratio: 1 / 1; width: 48px; height: auto; margin-bottom: 14px;
  filter: drop-shadow(0 0 6px rgba(77,166,255,0.4));
}
.dash-footer .footer-social { display: flex; justify-content: center; gap: 10px; margin-bottom: 24px; }
.dash-footer .footer-social a { display: flex; align-items: center; justify-content: center; width: 34px; height: 34px; border-radius: 50%; border: 1px solid rgba(255,255,255,0.08); color: rgba(255,255,255,0.3); text-decoration: none; transition: color 0.25s, border-color 0.25s, transform 0.25s; }
.dash-footer .footer-social a:hover { color: #4da6ff; border-color: rgba(77,166,255,0.3); transform: translateY(-2px); }
.dash-footer .footer-social a svg { width: 14px; height: 14px; }
.dash-footer .footer-links { display: flex; justify-content: center; align-items: center; gap: 0; flex-wrap: wrap; row-gap: 4px; margin-bottom: 24px; }
.dash-footer .footer-links a { color: rgba(255,255,255,0.55); text-decoration: none; transition: color 0.2s; font-size: clamp(11px, 3vw, 13px); display: inline-block; padding: 5px 0; }
.dash-footer .footer-links a:hover { color: rgba(255,255,255,0.6); }
.dash-footer .footer-sep { color: rgba(255,255,255,0.3); margin: 0 clamp(4px, 1.5vw, 10px); }
.dash-footer .address { color: rgba(255,255,255,0.48); margin-bottom: 8px; }
.dash-footer .address a { color: inherit; text-decoration: none; text-underline-offset: 3px; }
@media (hover: hover) and (pointer: fine) { .dash-footer .address a:hover { text-decoration: underline; } }
.dash-footer .footer-copy { color: rgba(255,255,255,0.46); }
[data-theme="light"] .dash-footer { color: rgba(0,0,0,0.6); border-color: rgba(0,0,0,0.09); }
[data-theme="light"] .dash-footer .footer-logo { filter: drop-shadow(0 0 6px rgba(43,127,212,0.3)); }
[data-theme="light"] .dash-footer .address { color: rgba(0,0,0,0.6); }
[data-theme="light"] .dash-footer .footer-social a { border-color: rgba(0,0,0,0.1); color: rgba(0,0,0,0.6); }
[data-theme="light"] .dash-footer .footer-social a:hover { color: #1a66b5; border-color: rgba(77,166,255,0.3); }
[data-theme="light"] .dash-footer .footer-links a { color: rgba(0,0,0,0.6); }
[data-theme="light"] .dash-footer .footer-links a:hover { color: #1a1a2e; }
[data-theme="light"] .dash-footer .footer-sep { color: rgba(0,0,0,0.45); }
[data-theme="light"] .dash-footer .footer-copy { color: rgba(0,0,0,0.6); }

/* Nav dropdown under 900: the sidebar becomes one bar (brand, the nav button, log out). The tab
   list is the same list, rendered as a menu floating under its button (raised fill, one
   hairline, the float shadow), animating transform and opacity only, from the button, so it
   never pushes the page. portal.js owns the open state, the keyboard path and the focus trap.
   The Dashboard's base.css carries the same rules at 860. */
.nav-toggle { display: none; }
@media (max-width: 900px) {
  .shell { flex-direction: column; }
  .sidebar { position: sticky; top: 0; height: auto; flex: none; flex-direction: row; align-items: center; gap: 4px; padding: 0 12px; min-height: 52px; border-right: none; border-bottom: 1px solid var(--line); z-index: 50; }
  .brand { padding: 8px 8px 8px 0; flex: 0 0 auto; }
  .brand-text { display: none; }
  .session-panel { margin-left: auto; padding: 8px 0; border-top: none; }
  .session-panel .upload-meter, .session-panel .session-card { display: none; }
  .session-actions { margin-top: 0; }
  .nav { position: relative; flex: 0 0 auto; padding: 0; overflow: visible; }
  .nav-toggle {
    display: inline-flex; align-items: center; gap: 8px;
    min-height: 36px; padding: 0 8px 0 12px; max-width: 220px;
    border: 1px solid var(--line-strong); border-radius: var(--r-md);
    background: transparent; color: var(--text);
    font: inherit; font-size: 13.5px; font-weight: 600; line-height: 1; cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .nav-toggle:active { background: var(--tint-press); }
  .nav-toggle-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .nav-toggle svg { width: 14px; height: 14px; flex: 0 0 auto; color: var(--muted); transition: transform 0.12s var(--ease); }
  .nav-toggle[aria-expanded="true"] svg { transform: rotate(180deg); }
  .tab-list {
    position: absolute; top: calc(100% + 6px); left: 0; z-index: 70;
    width: min(280px, calc(100vw - 24px));
    flex-direction: column; gap: 2px; padding: 4px;
    background: var(--raised); border: 1px solid var(--line-strong); border-radius: var(--r-lg);
    box-shadow: var(--shadow-float);
    transform-origin: top left;
    opacity: 0; transform: scale(0.96) translateY(-4px); visibility: hidden; pointer-events: none;
    transition: opacity var(--d-menu-out) var(--ease-out), transform var(--d-menu-out) var(--ease-out), visibility 0s linear var(--d-menu-out);
  }
  .nav.is-open .tab-list {
    opacity: 1; transform: none; visibility: visible; pointer-events: auto;
    transition: opacity var(--d-menu-in) var(--ease-out), transform var(--d-menu-in) var(--ease-out), visibility 0s;
  }
  .tab-link { min-height: 40px; padding: 0 10px; }
  .nav-session { display: block; margin-top: 4px; padding: 8px 6px 4px; border-top: 1px solid var(--line); }
  .nav-session .upload-meter { margin: 0; white-space: normal; font-size: 11.5px; padding: 6px 10px; }
  .nav-session .session-card { padding: 4px 6px; }
  .nav-session .upload-meter .um-line { flex: 1 1 100%; }
  .nav-session .upload-meter .um-line-note > .um-prompt { display: inline-block; }
  .page-head { padding: 20px 16px 0; }
  .content { padding: 16px 16px 40px; }
  /* The bar sits above the column here, so the column is one screen less the bar: its measured
     height (portal.js sizeBar writes --bar-h; 61px on a touch screen, whose targets are taller),
     52px, its min-height, with the scripts off. So the footer starts exactly at the fold. */
  .main-body { min-height: calc(100vh - var(--bar-h, 52px)); }
  body[data-viewing="1"] .main-body { min-height: calc(100vh - var(--bar-h, 52px) - var(--view-bar-h, 48px)); }
  @supports (height: 1lvh) {
    .main-body { min-height: calc(100lvh - var(--bar-h, 52px)); }
    body[data-viewing="1"] .main-body { min-height: calc(100lvh - var(--bar-h, 52px) - var(--view-bar-h, 48px)); }
  }
  .panel-head, .panel-body { padding-left: 14px; padding-right: 14px; }
}
@media (max-width: 900px) and (hover: none) { .nav-toggle, .tab-link { min-height: 44px; } }
@media (max-width: 540px) {
  .hero-title { font-size: 24px; }
  .page-head-row { flex-direction: column; align-items: flex-start; }
  .grid-2 { grid-template-columns: minmax(0, 1fr); }
  .toolbar .input { max-width: none; }
  .drawer { width: calc(100vw - 16px); max-height: calc(100vh - 16px); }
  .drawer-actions { flex-direction: column-reverse; }
  .drawer-actions .btn { width: 100%; }
}
@media (max-width: 480px) {
  .content { padding: 12px 12px 36px; }
  .page-head { padding-left: 12px; padding-right: 12px; }
  .brand-mark { width: 26px; height: 26px; }
  .tab-link { font-size: 13px; padding: 7px 10px; }
}

/* Reduced motion: nothing fades or slides */
@media (prefers-reduced-motion: reduce) {
  /* Everything settles at once, as in the Dashboard (base.css). */
  *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }
  .toast, .drawer, .drawer-overlay, .skeleton, .sk-words, .ll-in, .ll-in-rows > * { animation: none; }
  :is(.btn, .chip, .drawer-close, .nav-toggle, .check input, .upload-file-pick input):active { transform: none !important; }
  .check input::after, .upload-file-pick input::after { transform: none; }
  .btn-spin, .load-line .btn-spin { animation: llSpin 2.4s linear infinite !important; }
  html.ll-loading #llLoader { transition: none; }
  .logo-mark { animation: none; }
  .ll-mark, .llm-tile, .signin-title, .signin-note { animation: none; opacity: 1; }
  /* Not opacity on the squares: two of them are 0.85 and 0.4 by design. */
  .llm-sq, .llm-sq4, .llm-win { animation: none; }
  body > .signin-stage { transition: none; }
}

/* Viewing as a client: a solid amber-tinted band across the top */
.view-bar {
  position: sticky; top: 0; z-index: 60; box-sizing: border-box; min-height: 48px;
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 12px;
  padding: 8px 16px;
  background: var(--amber-tint);
  border-bottom: 1px solid var(--amber);
  color: var(--text); font-size: 13px;
}
.view-bar[hidden] { display: none; }
.view-bar-text { margin: 0; }
#viewBarWho { font-weight: 600; }
@media (max-width: 540px) { .view-bar { justify-content: space-between; text-align: left; } }
/* The sidebar is sticky at top 0 and so is the bar; --view-bar-h is written by
   portal.js from the bar's MEASURED height (it wraps at phone width). */
body[data-viewing="1"] .sidebar { top: var(--view-bar-h, 48px); }
@media (min-width: 901px) {
  body[data-viewing="1"] .sidebar { height: calc(100vh - var(--view-bar-h, 48px)); }
}

/* Sign-in. The ground is lainglogic.com's hero orbs, the home hero's own rules, a step dimmer.
   On it, one centred column: the title block, then the step. The email step is the field alone,
   its words centred on a fine underline whose ends fade out; the code step has its label above
   the six squares, Remember me under them and Sign in centred below. An error is written in
   the step's button for a moment (login.js). The orbs are this page's licensed exception to the
   portal's no-glow rule (portal/DESIGN.md). */

body.login {
  --top: 72px;                         /* the brand bar */
  --code-w: 352px;                     /* the six squares: 52px each with 8px between */
  --side: clamp(16px, 3vw, 32px);      /* the brand bar's side gutters */
  --email-w: 400px;                    /* the email line, end to end */
  --email-inset: 20px;                 /* the words' distance from each end of the line */
  --line-edge: rgba(255, 255, 255, 0.62); /* the email line's fine underline */
  --hint: rgba(230, 237, 247, 0.9);    /* the example address and the dot: 4.62:1 at the brightest the orbs get behind it (light 0.72, 4.94:1) */
  position: relative; display: flex; flex-direction: column; min-height: 100vh; overflow-x: clip;
}
/* White would vanish on the light ground, so the line takes the foreground ink,
   softened the same way; both clear 3:1 on their ground (WCAG 1.4.11). */
[data-theme="light"] body.login { --line-edge: rgba(26, 26, 46, 0.55); --hint: rgba(26, 26, 46, 0.72); }

/* Arrival on a revisit (the head block's ll-loading, which a sign-out lands with): the bar, the
   column and the footer fade in from the ground over 0.35s, the mark stage's leave played the
   other way; the light stays lit. A session's first page opens with the orbs' intro instead and
   reads from its first frame. Reduced motion settles it at once (portal.css). */
body.login > :is(.auth-top, .auth-shell, .dash-footer) { animation: authIn 0.35s var(--ease) backwards; }
html.ll-first body.login > :is(.auth-top, .auth-shell, .dash-footer) { animation: none; }
@keyframes authIn { from { opacity: 0; } }

/* The brand bar */
.auth-top { position: relative; z-index: 2; height: var(--top); flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 0 var(--side); }
.auth-brand { display: flex; align-items: center; gap: 10px; min-width: 0; }
.auth-logo { width: 30px; height: 30px; border-radius: 6px; }
.auth-word { font-size: 16px; letter-spacing: -0.02em; line-height: 1; }
.brand-laing { font-weight: 800; color: var(--text); }
.brand-logic { font-weight: 400; color: var(--text); }
.auth-tag { font-size: 12px; color: var(--muted); line-height: 1; }
/* The way back to lainglogic.com on the bar's right, in the foreground tone like everything on the light. */
.auth-top .back-site { flex: 0 0 auto; color: var(--text); }

/* The light: lainglogic.com's hero orbs, a step dimmer. The .hero-orbs group and its five
   .hero-gradient orbs from Website/CSS/pageIndex.css, every declaration as written there (only
   the body class changed), including which carry will-change and which stop under reduced
   motion. .auth-orbs stands where the home page's .hero does, clipped, and makes the light a step
   dimmer at a steady 0.8. z-index 0, under the brand bar (2) and the column (1).
   tests/portal/tests/01-login.spec.js compares every orb with the home page's. */
.auth-orbs { position: absolute; top: 0; left: 0; width: 100%; height: 100vh; overflow: hidden; pointer-events: none; z-index: 0; opacity: 0.8; }

body.login .hero-gradient {
  position: absolute;
  width: 700px;
  height: 430px;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) rotate(-48deg);
  background: radial-gradient(
    ellipse at 25% 25%,
    #7cc4ff 0%, #4da6ff 15%, #2b8ae6 30%, #1a5bb5 45%, #0f3d80 60%, #0a1e3d 80%, transparent 100%
  );
  border-radius: 50%;
  filter: blur(78px);
  opacity: 0.8;
  pointer-events: none;
  z-index: 0;
  animation: gradientDrift1 30s linear infinite;
  will-change: transform;
}

body.login .hero-gradient-2 {
  position: absolute;
  width: 420px;
  height: 300px;
  top: 35%;
  left: 60%;
  transform: translate(-50%, -50%) rotate(20deg);
  background: radial-gradient(
    ellipse at 60% 30%,
    #c2f1f6 0%, #7ce4ee 20%, #2fbccb 40%, #0b4d57 70%, transparent 100%
  );
  border-radius: 50%;
  filter: blur(85px);
  opacity: 0.6;
  pointer-events: none;
  z-index: 0;
  animation: gradientDrift2 36s linear infinite;
  will-change: transform;
}

body.login .hero-gradient-3 {
  position: absolute;
  width: 330px;
  height: 235px;
  top: 60%;
  left: 37%;
  transform: translate(-50%, -50%) rotate(65deg);
  background: radial-gradient(
    circle at 50% 50%,
    #5fb8ff 0%, #1c6ecf 35%, #0b2a5e 70%, transparent 100%
  );
  border-radius: 50%;
  filter: blur(90px);
  opacity: 0.45;
  pointer-events: none;
  z-index: 0;
  animation: gradientDrift3 40s linear infinite;
  will-change: transform;
}

body.login .hero-gradient-4 {
  position: absolute;
  width: 250px;
  height: 250px;
  top: 62%;
  left: 59%;
  transform: translate(-50%, -50%);
  background: radial-gradient(
    circle, #ffd98c 0%, #f5b342 45%, transparent 78%
  );
  border-radius: 50%;
  filter: blur(62px);
  opacity: 0.35;
  pointer-events: none;
  z-index: 0;
  animation: gradientDrift4 26s linear infinite;
}

body.login .hero-gradient-5 {
  position: absolute;
  width: 300px;
  height: 300px;
  top: 33%;
  left: 38%;
  transform: translate(-50%, -50%);
  background: radial-gradient(
    circle, #b8e0ff 0%, #4da6ff 40%, transparent 75%
  );
  border-radius: 50%;
  filter: blur(68px);
  opacity: 0.5;
  pointer-events: none;
  z-index: 0;
  /* drift2 is transform-only, so this orb's own opacity holds (drift3/4
     animate opacity and would pin it low). */
  animation: gradientDrift2 32s linear infinite;
}

/* Raised 6vh with the column, as the home hero's are. */
body.login .hero-orbs { position: absolute; inset: -6vh 0 6vh; z-index: 0; transform-origin: 50% 50%; pointer-events: none; }
/* A session's first page (the head block's ll-first): the home hero's first-visit intro from
   Website/CSS/pageIndex.css. The orb group swings open from a seed and
   settles while a deep-blue flash goes off behind it. */
html.ll-first body.login .hero-orbs {
  will-change: transform, opacity; backface-visibility: hidden;
  animation: heroBang 2.4s cubic-bezier(0.33, 1, 0.68, 1) 0s both;
}
html.ll-first body.login .auth-orbs::before {
  content: ''; position: absolute; top: calc(50% - 6vh); left: 50%; width: 560px; height: 560px;
  transform: translate(-50%, -50%) scale(0.4); opacity: 0; z-index: 1;
  border-radius: 50%; pointer-events: none; filter: blur(36px);
  background: radial-gradient(circle, rgba(95,180,255,0.5) 0%, rgba(30,100,200,0.4) 38%, rgba(15,60,140,0.16) 60%, transparent 76%);
  animation: heroFlash 1.1s ease-out 0.2s forwards;
}

[data-theme="light"] body.login .hero-gradient {
  opacity: 0.55;
  background: radial-gradient(ellipse at 25% 25%, #b8daf5 0%, #8ec4ef 15%, #6aade4 30%, #4d96d4 45%, #3a82c2 60%, #2b6fb3 80%, transparent 100%);
}
[data-theme="light"] body.login .hero-gradient-2 {
  opacity: 0.48;
  background: radial-gradient(ellipse at 60% 30%, #cdeef3 0%, #a3e2ea 20%, #6cc9d6 40%, #3fa5b4 70%, transparent 100%);
}
[data-theme="light"] body.login .hero-gradient-3 {
  opacity: 0.42;
  background: radial-gradient(circle at 50% 50%, #a8d4f5 0%, #7bbde8 35%, #4d9fd4 70%, transparent 100%);
}
[data-theme="light"] body.login .hero-gradient-4 {
  opacity: 0.38;
  background: radial-gradient(circle, #ffe2a6 0%, #efae33 45%, transparent 78%);
}
[data-theme="light"] body.login .hero-gradient-5 {
  opacity: 0.38;
  background: radial-gradient(circle, #d0ebfc 0%, #8ec8f0 40%, transparent 75%);
}
/* Website/CSS/shared.css heroFlash and heroBang, verbatim. */
@keyframes heroFlash {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.4); }
  30%  { opacity: 0.6; transform: translate(-50%, -50%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(1.6); }
}
@keyframes heroBang {
  0%   { opacity: 0; transform: scale(0.1) rotate(-200deg); }
  16%  { opacity: 1; }
  70%  { transform: scale(1.1) rotate(-6deg); }
  100% { opacity: 1; transform: scale(1) rotate(0deg); }
}
/* Website/CSS/shared.css gradientDrift1 to 4, verbatim. */
@keyframes gradientDrift1 {
  0%   { transform: translate(-50%, -50%) rotate(-48deg) scale(1); }
  25%  { transform: translate(-47%, -53%) rotate(-30deg) scale(1.04); }
  50%  { transform: translate(-50%, -50%) rotate(-15deg) scale(1.06); }
  75%  { transform: translate(-53%, -47%) rotate(-35deg) scale(1.02); }
  100% { transform: translate(-50%, -50%) rotate(-48deg) scale(1); }
}
@keyframes gradientDrift2 {
  0%   { transform: translate(-50%, -50%) rotate(20deg) scale(1); }
  25%  { transform: translate(-53%, -47%) rotate(35deg) scale(1.05); }
  50%  { transform: translate(-50%, -50%) rotate(50deg) scale(1.08); }
  75%  { transform: translate(-47%, -53%) rotate(35deg) scale(1.03); }
  100% { transform: translate(-50%, -50%) rotate(20deg) scale(1); }
}
@keyframes gradientDrift3 {
  0%   { transform: translate(-50%, -50%) rotate(65deg) scale(1); opacity: 0.45; }
  25%  { transform: translate(-46%, -54%) rotate(50deg) scale(1.06); opacity: 0.5; }
  50%  { transform: translate(-50%, -50%) rotate(38deg) scale(1.1); opacity: 0.55; }
  75%  { transform: translate(-54%, -46%) rotate(52deg) scale(1.04); opacity: 0.48; }
  100% { transform: translate(-50%, -50%) rotate(65deg) scale(1); opacity: 0.45; }
}
@keyframes gradientDrift4 {
  0%   { transform: translate(-50%, -50%) scale(1); opacity: 0.3; }
  33%  { transform: translate(-48%, -52%) scale(1.1); opacity: 0.45; }
  66%  { transform: translate(-52%, -48%) scale(1.05); opacity: 0.35; }
  100% { transform: translate(-50%, -50%) scale(1); opacity: 0.3; }
}

/* The column, on the centre axis, in the middle of the first screen */
.auth-shell { position: relative; z-index: 1; flex: 0 0 auto; display: flex; align-items: center; min-height: calc(100vh - var(--top)); padding: 24px 20px 72px; }
/* The footer starts below the fold: one LARGE viewport tall is the tallest the first screen gets,
   so the footer stays below it in every toolbar state (svh let it show when Safari's toolbar
   retracted). On a desktop lvh and vh are the same. */
@supports (height: 1lvh) { .auth-shell { min-height: calc(100lvh - var(--top)); } }
/* The column stands above the middle of the screen, at about 44%: the margin box is what the
   shell centres, so the bottom margin lifts the column by half its own height. */
.auth-col { width: 100%; max-width: 520px; margin: 0 auto 15vh; text-align: center; }
.auth-head { position: relative; display: flex; flex-direction: column; align-items: center; padding-bottom: 28px; }
.auth-head .page-label { margin-bottom: 14px; }
/* On the light theme's orbs the pinned #825b00 falls below 4.5:1, so this
   page's label takes the next amber step down. */
[data-theme="light"] body.login .auth-head .page-label { color: #6b4a00 !important; }
/* The dark theme's amber falls to 3:1 where the home hero's light is brightest,
   so on this page the label takes the pale end of the same amber (#ffe8b5). */
[data-theme="dark"] body.login .auth-head .page-label { color: #ffe8b5 !important; }
.auth-title { font-size: clamp(40px, 5vw, 64px); font-weight: 800; letter-spacing: -0.035em; line-height: 1.02; color: var(--text); text-wrap: balance; }
/* Everything on the first screen stands on the light, so it takes the
   foreground tone rather than grey; size and weight carry the hierarchy (Remember
   me as well, since the orbs span the whole screen). */
.auth-sub { margin: 14px auto 0; font-size: 15px; line-height: 1.55; color: var(--text); max-width: 400px; text-wrap: balance; }
.auth-sent { margin: 8px auto 0; font-size: 13.5px; line-height: 1.5; color: var(--text); max-width: 460px; }
.auth-sent:empty { display: none; }
.auth-sent strong { font-weight: 600; overflow-wrap: anywhere; }
/* With the scripts off this is where the server's sentence goes (login.js
   hides it otherwise): plain red words under the intro, above the field. */
.auth-error { margin: 12px auto 0; max-width: 440px; color: var(--red); font-size: 13.5px; font-weight: 500; line-height: 1.5; }

/* The step: its label, its field, and its one button centred below */
.field-line { display: flex; margin: 0 auto; text-align: left; }
body.login .field-label { display: grid; align-items: center; font-size: 13px; font-weight: 500; line-height: 1.25; color: var(--text); }


/* The email step: the field alone, its words centred, on one fine underline drawn in the row's
   own background so it never moves the layout. Each end fades out over its outer tenth; the
   middle keeps the full tone, which clears 3:1. Focus draws the line 2px and blue; the shake
   turns it red; both keep the fades. */
.field-line-email {
  --edge: var(--line-edge); --edge-h: 1px;
  justify-content: center; gap: 0; max-width: var(--email-w); min-height: 44px;
  /* The words start 20px along the line and stop 20px short of its end, so they sit over its full
     tone, clear of the faded ends. */
  padding: 0 var(--email-inset);
  background: linear-gradient(90deg, transparent, var(--edge) 10%, var(--edge) 90%, transparent) left bottom / 100% var(--edge-h) no-repeat;
}
.field-line-email:focus-within { --edge: var(--input-focus); --edge-h: 2px; }
/* The words sit 8px down the 44px line, so they are about 10px above the
   underline (8 on a phone) rather than floating in the middle of it. */
.line-input {
  flex: 1 1 auto; min-width: 0; height: 44px; padding: 8px 0 0; text-align: center;
  background: none; border: 0; border-radius: 0;
  font-size: 13px; font-weight: 500; letter-spacing: -0.01em; line-height: 1.2; color: var(--text); caret-color: var(--blue);
  -webkit-appearance: none; appearance: none;
}
.line-input:focus { outline: none; }
/* The example address: the hint tone, measured across the whole drift cycle
   against the brightest the orbs get behind it (see --hint), and a lighter
   weight than a typed address, so it never reads as filled in. */
.line-input::placeholder { color: var(--hint); opacity: 1; font-weight: 400; }
/* It goes the moment the field is tapped, so an empty field never looks filled in; the page's own
   focus on arrival leaves it showing (login.js adds is-tapped). */
.line-input.is-tapped:focus::placeholder { color: transparent; }

/* The code step: six squares where the field is, progressive enhancement over the one real
   input; with JS the squares are six real inputs, the first #loginCode itself (iOS paste).
   "Six-digit code" sits above them in a column exactly as wide as the squares, so the label's
   left edge is the first square's. */
.field-line-code { flex-direction: column; align-items: stretch; gap: 10px; width: var(--code-w); max-width: 100%; }
body.login .field-line-code .field-label { justify-items: start; }
.code-wrap { position: relative; width: 100%; min-width: 0; }
.input-code { width: 100%; font-family: var(--font-mono); font-size: 24px; letter-spacing: 0.32em; text-align: center; padding-left: 0.32em; min-height: 52px; }
.code-cells { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 8px; }
.code-cells[hidden] { display: none; }
.code-wrap.has-cells .input-code { letter-spacing: normal; padding-left: 0; font-size: 22px; }
.code-cell {
  width: 100%; min-width: 0; height: 52px; padding: 0;
  border-radius: var(--r-md); border: 1px solid var(--input-border); background: var(--input-bg);
  font-family: var(--font-mono); font-size: 22px; text-align: center; color: var(--text);
  caret-color: var(--blue); -webkit-appearance: none; appearance: none;
}
.code-cell.is-filled { border-color: var(--faint); }
.code-cell:focus { outline: none; border-color: var(--input-focus); box-shadow: 0 0 0 3px var(--accent-tint); }

/* The shake: the agency contact form's supportBtnShake, verbatim from Website/CSS/shared.css at
   its 0.5s ease-in-out, on the email line or the six squares as one block, red for that half
   second. Transform only. */
@keyframes supportBtnShake { 0% { transform: translateX(0); } 20% { transform: translateX(-3px); } 40% { transform: translateX(3px); } 60% { transform: translateX(-2px); } 80% { transform: translateX(1px); } 100% { transform: translateX(0); } }
.field-line-email.is-shaking, .code-cells.is-shaking { animation: supportBtnShake 0.5s ease-in-out both; }
.field-line-email.is-shaking { --edge: var(--red); --edge-h: 2px; }
.code-cells.is-shaking .code-cell { transition: none; border-color: var(--red); box-shadow: 0 0 0 3px var(--red-tint); }

/* The step's one button, centred under the field. */
/* 28px under the email line, the same air as above it; 20px under Remember
   me on the code step, which sits between the squares and the button. */
.step-actions { display: flex; justify-content: center; margin-top: 28px; }
#verifyForm .step-actions { margin-top: 20px; }
.step-actions > .btn-step { min-height: 48px; padding: 0 28px; border-radius: var(--r-md); font-size: 14px; }

/* A step's button when it refuses: the content form's validating button and animation
   (form/form.css .btn-submit.validating, timerFill and returnToBlue). While an error shows, Send
   code or Sign in turns into a red pill and a darker bar fills it over 2.5s; after the 2.8s hold
   it settles back by opacity over 0.65s. Its words are the error's short form, 13px, one line,
   above the bar. The form's own reds, both themes. */
body.login { --red-line: #5a2323; }
[data-theme="light"] body.login { --red-line: #e8b4b4; }
.step-actions > .btn-step.validating {
  position: relative; background: var(--red-tint); border: 1px solid var(--red-line); color: var(--red);
  clip-path: inset(0 round var(--r-md)); pointer-events: none; transform: none; transition: none;
  font-size: 13px; white-space: nowrap;
}
.step-actions > .btn-step.validating::before {
  content: ''; position: absolute; top: 0; left: 0; bottom: 0;
  width: 100%; transform-origin: left center; transform: scaleX(0);
  background: var(--red-line);
  animation: timerFill 2.5s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
.step-actions > .btn-step.validating .btn-label { position: relative; }
@keyframes timerFill { to { transform: scaleX(1); } }
.step-actions > .btn-step.returning { animation: returnToBlue 0.65s cubic-bezier(0.22, 1, 0.36, 1) forwards; pointer-events: none; }
@keyframes returnToBlue { from { opacity: 0.55; } to { opacity: 1; } }

/* The second factor's steps share the code step's squares. The set-up step opens on a choice, two
   buttons the width of the squares with a line under each; its passcode rows stack 14px apart. A
   second, quiet action sits 10px under a step's button; the passkey step is one button and a
   note; the done block reads as a short title and one line. */
#setupForm .field-line-code + .field-line-code { margin-top: 14px; }
.step-actions-alt { margin-top: 10px; }
.step-actions-alt[hidden], .step-actions-alt > .btn-step[hidden] { display: none; }
.step-actions > .btn-outline.btn-step { color: var(--text); }
.auth-choice { display: flex; flex-direction: column; align-items: center; }
.auth-choice[hidden] { display: none; }
.auth-choice > .btn-step { width: var(--code-w); max-width: 100%; min-height: 48px; border-radius: var(--r-md); font-size: 14px; }
.auth-choice-hint { margin: 8px 0 18px; max-width: var(--code-w); font-size: 13px; line-height: 1.45; color: var(--text); text-align: center; }
.auth-choice-hint:last-child { margin-bottom: 0; }
#setupFields[hidden] { display: none; }
.auth-passkey { text-align: center; }
.auth-passkey .step-actions { margin-top: 0; }
.auth-passkey .step-actions > .btn-step[hidden] { display: none; }
.auth-note { margin: 0 auto; max-width: 400px; font-size: 14px; line-height: 1.5; color: var(--text); }
.auth-note[hidden] { display: none; }
.auth-note-quiet { margin-top: 14px; font-size: 12.5px; opacity: 0.85; }
.auth-done { text-align: center; }
.auth-done-title { font-size: 18px; font-weight: 700; letter-spacing: -0.02em; color: var(--text); }
.auth-done-text { margin: 8px auto 0; max-width: 400px; font-size: 14px; line-height: 1.5; color: var(--text); }

/* Under the step */
/* The code step's two other ways on: ask for a new code, or use another email. */
.auth-alts { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 4px 8px; margin-top: 16px; }
.auth-alts > form { display: flex; }
/* The two quiet actions stand on the light too: the foreground tone, not grey. */
.auth-alts .btn-quiet { color: var(--text); }
/* Remember me: in the code form, close under the squares and above Sign in, on the squares' left
   edge like the label above them. The label is its checkbox's target: 24px floor (WCAG 2.5.8). */
body.login .auth-remember { width: var(--code-w); max-width: 100%; min-height: 24px; margin: 16px auto 0; justify-content: flex-start; font-size: 13px; color: var(--text); }


@media (prefers-reduced-motion: reduce) {
  body.login .hero-gradient, body.login .hero-gradient-2, body.login .hero-gradient-3, body.login .hero-gradient-4 { animation: none; }
  html.ll-first body.login .hero-orbs, html.ll-first body.login .auth-orbs::before { animation: none; }
  .code-cell { transition: none; }
  /* The pill still turns red for the hold; the bar and the settle do not move. */
  .step-actions > .btn-step.validating::before { animation: none; transform: scaleX(1); }
  .step-actions > .btn-step.returning { animation: none; }
  /* No movement: the field still turns red for the half second. */
  .field-line-email.is-shaking, .code-cells.is-shaking { animation: none; }
}

/* Phones */
@media (max-width: 640px) {
  body.login { --top: 64px; --code-w: 306px; }
  .auth-shell { padding: 16px 16px 56px; }
  .auth-title { font-size: 40px; }
  .auth-sub { font-size: 14.5px; }
  /* The typed address and the code step's label are 16px on a phone, the smallest an iPhone
     does not zoom into on a tap. */
  body.login .field-label { font-size: 16px; }
  .line-input { font-size: 16px; }
  /* Six 46px squares with 6px between them: 306px, the label above them. */
  .code-cells { gap: 6px; }
  .code-cell { height: 46px; font-size: 20px; }
  .input-code { min-height: 46px; }
  body.login .hero-gradient { width: 420px; height: 300px; filter: blur(60px); }
  body.login .hero-gradient-2 { width: 280px; height: 200px; filter: blur(70px); }
  body.login .hero-gradient-3 { width: 220px; height: 160px; filter: blur(70px); }
  body.login .hero-gradient-4 { width: 130px; height: 130px; filter: blur(50px); }
}
@media (max-width: 380px) { .code-cells { gap: 5px; } }
