/* ============================================================
   LifeOs — design system matched to the reference brand:
   indigo → blue → cyan gradient, pink/amber/emerald/violet accents.
   Dark-first, light theme via [data-theme="light"] on <html>.
   Fonts: Outfit (UI), JetBrains Mono (data/labels/code-ish bits).
   ============================================================ */

:root {
  --indigo:  #6366f1;
  --blue:    #3b82f6;
  --cyan:    #22d3ee;
  --pink:    #ec4899;
  --violet:  #a78bfa;
  --amber:   #f59e0b;
  --emerald: #10b981;
  --danger:  #ef4444;

  --brand-grad: linear-gradient(115deg, var(--indigo) 0%, var(--blue) 42%, var(--cyan) 100%);

  /* Outfit carries no Devanagari glyphs, so Hindi text typed into any field would
     otherwise land on whatever the OS happened to substitute. Browsers resolve the
     stack per glyph: Latin still renders in Outfit, Devanagari in Noto Sans
     Devanagari, and the two are weight-matched so a mixed line stays even. */
  --font: 'Outfit', 'Noto Sans Devanagari', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  --radius: 16px;
  --radius-sm: 10px;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
}

[data-theme="dark"] {
  color-scheme: dark;
  --bg:        #0a0a12;
  --bg-alt:    #0e0e1a;
  --surface:   rgba(255, 255, 255, 0.035);
  --surface-2: rgba(255, 255, 255, 0.06);
  --line:      rgba(255, 255, 255, 0.09);
  --line-soft: rgba(255, 255, 255, 0.045);
  --grid:      rgba(255, 255, 255, 0.028);

  --text:     #eef1f8;
  --text-mid: #a9b1c7;
  --muted:    #6f7793;

  --nav-bg:   rgba(10, 10, 18, 0.72);
  --shadow:   0 24px 60px -18px rgba(0, 0, 0, 0.7);
}

[data-theme="light"] {
  color-scheme: light;
  --bg:        #fbfbfe;
  --bg-alt:    #f3f4fb;
  --surface:   #ffffff;
  --surface-2: #f6f7fd;
  --line:      rgba(15, 23, 42, 0.1);
  --line-soft: rgba(15, 23, 42, 0.06);
  --grid:      rgba(15, 23, 42, 0.035);

  --text:     #0f172a;
  --text-mid: #475569;
  --muted:    #7b879c;

  --nav-bg:   rgba(251, 251, 254, 0.78);
  --shadow:   0 20px 48px -20px rgba(15, 23, 42, 0.22);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  transition: background .4s var(--ease), color .4s var(--ease);
}

a { color: inherit; text-decoration: none; }
::selection { background: var(--indigo); color: #fff; }

/* belt-and-suspenders: some engines ignore color-scheme for <option> background */
select option { background-color: var(--bg-alt); color: var(--text); }
:focus-visible { outline: 2px solid var(--cyan); outline-offset: 3px; border-radius: 6px; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; }
}

.grad {
  background: var(--brand-grad);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}

/* ---------- aurora background (login) ---------- */
.aurora { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 0; }
.blob { position: absolute; border-radius: 50%; opacity: .5; will-change: transform; }
[data-theme="light"] .blob { opacity: .3; }
.blob--1 {
  width: 480px; height: 480px; top: -14%; left: -10%;
  background: radial-gradient(circle closest-side, var(--indigo), transparent 72%);
  animation: drift1 22s var(--ease) infinite alternate;
}
.blob--2 {
  width: 420px; height: 420px; bottom: -16%; right: -8%;
  background: radial-gradient(circle closest-side, var(--cyan), transparent 72%);
  animation: drift2 26s var(--ease) infinite alternate;
}
.blob--3 {
  width: 360px; height: 360px; bottom: 20%; left: 38%;
  background: radial-gradient(circle closest-side, var(--pink), transparent 72%);
  animation: drift3 30s var(--ease) infinite alternate;
}
@keyframes drift1 { to { transform: translate(100px, 60px) scale(1.15); } }
@keyframes drift2 { to { transform: translate(-90px, -60px) scale(0.9); } }
@keyframes drift3 { to { transform: translate(70px, -50px) scale(1.1); } }

.bg-grid {
  content: ''; position: absolute; inset: 0; pointer-events: none; z-index: 0;
  background-image:
    linear-gradient(var(--grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid) 1px, transparent 1px);
  background-size: 44px 44px;
  mask-image: radial-gradient(ellipse 80% 60% at 50% 0%, black 40%, transparent 100%);
}

/* ---------- app shell aurora (same wash as login, toned down + fixed) ---------- */
.app-aurora {
  position: fixed; inset: 0; overflow: hidden; pointer-events: none; z-index: 0;
}
.app-aurora .blob { opacity: .22; }
[data-theme="light"] .app-aurora .blob { opacity: .14; }

/* ---------- theme toggle ---------- */
.theme-btn {
  position: relative; width: 38px; height: 38px; flex: none;
  display: grid; place-items: center;
  border-radius: 50%; cursor: pointer;
  border: 1px solid var(--line);
  background: var(--surface-2); color: var(--text-mid);
  transition: transform .4s var(--ease), color .25s, border-color .25s;
}
.theme-btn:hover { transform: rotate(90deg) scale(1.08); color: var(--cyan); border-color: var(--cyan); }
.theme-btn svg { width: 17px; height: 17px; position: absolute; transition: opacity .3s, transform .4s var(--ease); }
[data-theme="dark"]  .ico-sun  { opacity: 1; transform: scale(1); }
[data-theme="dark"]  .ico-moon { opacity: 0; transform: scale(0.4) rotate(-90deg); }
[data-theme="light"] .ico-sun  { opacity: 0; transform: scale(0.4) rotate(90deg); }
[data-theme="light"] .ico-moon { opacity: 1; transform: scale(1); }

/* ---------- Auth (login) screen ---------- */
.auth-wrap {
  position: relative;
  /* dvh tracks the *visible* viewport: with 100vh alone, mobile browsers size
     against the chrome-less viewport and the page scrolls by the height of the
     address bar even when nothing overflows. vh stays first as the fallback. */
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  /* Clearance for the fixed .auth-footer, and the side gutter. Both are held
     in variables: the footer drops out of fixed positioning on phones and has
     to cancel the side gutter to stay full-bleed, and the split layout retunes
     the gutter twice on the way down. */
  --auth-gutter-b: 84px;
  --auth-pad-x: 20px;
  padding: 20px var(--auth-pad-x) calc(var(--auth-gutter-b) + env(safe-area-inset-bottom, 0px)) var(--auth-pad-x);
  overflow: hidden;
}

.auth-theme-btn { position: absolute; top: 22px; right: 22px; z-index: 2; }

.auth-card {
  position: relative; z-index: 1;
  width: 100%;
  max-width: 400px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  padding: 36px 32px;
}

.auth-logo {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 6px;
}

.auth-logo .mark {
  width: 38px;
  height: 38px;
  border-radius: 11px;
  background: var(--brand-grad);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-weight: 800;
  font-size: 15px;
  letter-spacing: -.02em;
  box-shadow: 0 10px 26px -8px rgba(99,102,241,.65);
}

.auth-logo h1 {
  font-size: 19px;
  margin: 0;
  font-weight: 800;
  letter-spacing: -0.02em;
}

.auth-sub { color: var(--text-mid); font-size: 13px; margin: 0 0 26px 0; }

/* ---------- Brand lockup ----------
   Sign-in, the lock screen and Forgot Password swap the letter tile for the
   inline SVG mark (Shared/_BrandMark) plus a two-tone wordmark. The pages still
   on .mark are the ones whose tile is a heading, not the product logo ("403" on
   Access Denied) — plus the reset/2FA screens, not yet converted. */
.auth-logo--brand { gap: 14px; align-items: center; margin-bottom: 4px; }

.brand-mark {
  width: 52px;
  height: 52px;
  flex: none;
  /* drop-shadow, not box-shadow: the glow has to follow the rounded tile, and
     the SVG's own corners are the only thing that knows that shape. */
  filter: drop-shadow(0 12px 26px rgba(99, 102, 241, .55));
  animation: brand-mark-in .7s var(--ease) both;
}
[data-theme="light"] .brand-mark { filter: drop-shadow(0 12px 24px rgba(99, 102, 241, .38)); }

/* transform-box keeps the origin in viewBox units, so the spark pivots on the
   tile's centre rather than the element's border box. Two animations on one
   element: the lap, and a hue cycle that carries the glow with it — the
   drop-shadow is declared inside the keyframe because hue-rotate runs after it
   in the filter chain and so tints the halo as well as the spark. */
.brand-mark__orbit {
  transform-box: view-box;
  transform-origin: 24px 24px;
  animation: brand-orbit 9s linear infinite, brand-spark 6s linear infinite;
}

.brand-lockup { min-width: 0; }

/* The ramp runs the full brand spectrum and repeats its first colour last, so
   sliding it produces a seamless loop. Two ramps, because the dark one is tuned
   for a near-black surface — cyan and indigo-400 land around 2:1 on the light
   theme's white card, which is unreadable at wordmark weight. */
[data-theme="dark"] .brand-wordmark {
  --wm-ramp: linear-gradient(100deg,
      #818cf8 0%, #38bdf8 20%, #22d3ee 38%, #a78bfa 58%, #f472b6 78%, #818cf8 100%);
}
[data-theme="light"] .brand-wordmark {
  --wm-ramp: linear-gradient(100deg,
      #4338ca 0%, #1d4ed8 20%, #0e7490 38%, #6d28d9 58%, #be185d 78%, #4338ca 100%);
}

/* Colour treatment only — callers own the type size. Background is 240% of the
   box so each letter sits under a different part of the ramp; brand-sheen then
   walks the whole spectrum through the word. */
.brand-wordmark {
  background: var(--wm-ramp);
  background-size: 240% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: brand-sheen 8s linear infinite;
}

/* display:block so the lock screen can use a <span> here — that page's h1 is
   already spent on the account name. */
.auth-logo .brand-wordmark {
  display: block;
  font-size: clamp(21px, 2.6vw, 27px);
  line-height: 1.1;
  margin: 0;
  font-weight: 800;
  letter-spacing: -0.03em;
  animation: brand-sheen 8s linear infinite, brand-mark-in .7s var(--ease) .08s both;
}

/* drop-shadow reads the painted glyphs' alpha, so the halo picks up whichever
   colours the ramp is currently sitting under. text-shadow would paint a flat
   slab behind clipped text instead. */
[data-theme="dark"] .brand-wordmark { filter: drop-shadow(0 4px 16px rgba(99, 102, 241, .45)); }

.brand-tagline {
  display: block;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--muted);
  margin-top: 3px;
  white-space: nowrap;
  animation: brand-mark-in .7s var(--ease) .16s both;
}

@keyframes brand-mark-in {
  from { opacity: 0; transform: translateY(-8px) scale(.82); }
  to   { opacity: 1; transform: none; }
}
@keyframes brand-orbit { to { transform: rotate(360deg); } }
@keyframes brand-spark {
  from { filter: drop-shadow(0 0 4px rgba(103, 232, 249, .95)) hue-rotate(0deg); }
  to   { filter: drop-shadow(0 0 4px rgba(103, 232, 249, .95)) hue-rotate(360deg); }
}
@keyframes brand-sheen { to { background-position: -240% 0; } }

/* ---------- Split login: branded showcase panel + form ----------
   Only the sign-in screen opts in (.auth-wrap--split). The other auth
   pages (forgot/reset/2FA/lock) keep the plain centred .auth-card. */
.auth-wrap--split { padding-top: 40px; }

/* The floating theme button sits at top:22 / right:22, so its lower edge is at
   60px. On wide screens the split is centred well clear of it, but once the
   viewport narrows to roughly the shell's own width the card grows into that
   corner — so buy the button its own gutter before they touch. */
@media (max-width: 1120px) {
  .auth-wrap--split { padding-top: 70px; }
}

.auth-split {
  position: relative; z-index: 1;
  width: 100%;
  max-width: 1040px;
  display: grid;
  grid-template-columns: 1.08fr 0.92fr;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 24px;
  box-shadow: var(--shadow);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  overflow: hidden;
}

/* The card loses its own chrome — the split shell provides it. The brand wash
   at the top keeps this column from reading as a flat panel next to the
   artwork-heavy one. */
.auth-split .auth-card {
  max-width: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  background:
    radial-gradient(ellipse 120% 70% at 50% -8%, rgba(99,102,241,.16), transparent 62%),
    radial-gradient(ellipse 90% 50% at 90% 108%, rgba(34,211,238,.1), transparent 60%);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  /* Padding tracks the viewport rather than stepping at a breakpoint: at 320px
     a flat 42px gutter would leave the inputs about 220px wide. */
  padding: clamp(28px, 4.4vw, 44px) clamp(20px, 4vw, 42px);
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.auth-welcome {
  margin: 18px 0 6px 0;
  font-size: clamp(22px, 2.4vw, 25px);
  font-weight: 800;
  letter-spacing: -0.025em;
  line-height: 1.2;
}
.auth-split .auth-sub { margin-bottom: 24px; }

/* Leading glyph inside a text field. The password variant also carries
   .pw-field, whose own rule owns the right-hand padding. */
.input-ico { position: relative; }
.input-ico__ico {
  position: absolute;
  left: 14px; top: 50%;
  transform: translateY(-50%);
  width: 16px; height: 16px;
  color: var(--muted);
  pointer-events: none;
  transition: color .2s;
}
/* .field wrapper included deliberately: the base `input[type="email"]` rule
   carries the same specificity as `.input-ico input` and is declared earlier,
   so without it the glyph sits on top of the text. */
.field .input-ico input { padding-left: 40px; }
.field .input-ico input:focus { padding-left: 39.5px; }
.input-ico:focus-within .input-ico__ico { color: var(--cyan); }

/* Reassurance line under the button — balances the column against the
   showcase panel instead of leaving dead space. */
.auth-secure {
  display: flex;
  align-items: center;
  justify-content: center;
  /* The label runs to roughly 260px, which is wider than the card's content
     box on a 320px phone — wrap rather than push the card out. */
  flex-wrap: wrap;
  text-align: center;
  gap: 8px;
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--line-soft);
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .02em;
  color: var(--muted);
}
.auth-secure svg { width: 14px; height: 14px; color: var(--emerald); flex: none; }

/* The panel stays dark in both themes so the artwork reads the same way. */
.auth-showcase {
  position: relative;
  isolation: isolate;
  display: flex;
  flex-direction: column;
  gap: 22px;
  padding: clamp(26px, 3.6vw, 40px) clamp(22px, 3.4vw, 38px);
  color: #eef1f8;
  background:
    radial-gradient(ellipse 90% 70% at 15% 0%, rgba(99,102,241,.55), transparent 60%),
    radial-gradient(ellipse 80% 60% at 100% 100%, rgba(236,72,153,.35), transparent 60%),
    linear-gradient(150deg, #131327 0%, #0b0b16 100%);
  border-right: 1px solid var(--line);
}

.showcase-head h2 {
  margin: 12px 0 8px 0;
  font-size: clamp(21px, 2.6vw, 27px);
  line-height: 1.2;
  font-weight: 800;
  letter-spacing: -0.025em;
  /* "Every rupee, accounted for" wraps mid-word on a 320px screen otherwise. */
  overflow-wrap: break-word;
}
.showcase-head p { margin: 0; font-size: 13.5px; line-height: 1.65; color: rgba(238,241,248,.68); }

.showcase-badge {
  display: inline-block;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: #a5f3fc;
  background: rgba(34,211,238,.12);
  border: 1px solid rgba(34,211,238,.28);
  border-radius: 999px;
  padding: 4px 11px;
}

.showcase-art {
  display: block;
  width: 100%;
  height: auto;
  max-height: 250px;
  object-fit: contain;
  filter: drop-shadow(0 22px 40px rgba(0,0,0,.45));
}

.showcase-features { list-style: none; margin: 0; padding: 0; display: grid; gap: 14px; }
.showcase-features li { display: flex; align-items: flex-start; gap: 12px; }
.showcase-features .ico {
  flex: none;
  width: 32px; height: 32px;
  display: grid; place-items: center;
  border-radius: 10px;
  color: #22d3ee;
  background: rgba(255,255,255,.06);
  border: 1px solid rgba(255,255,255,.12);
}
.showcase-features .ico svg { width: 16px; height: 16px; }
.showcase-features strong { display: block; font-size: 13.5px; font-weight: 600; line-height: 1.4; }
.showcase-features em {
  display: block; font-style: normal;
  font-size: 12.5px; line-height: 1.5;
  color: rgba(238,241,248,.6);
}

/* auto-fit rather than repeat(3, 1fr): once the panel stacks above the form its
   column is ~400px wide, and the longest value ("SSO-READY" at 16px mono) will
   not fit a third of that — so let the row break to two lines instead. */
.showcase-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(104px, 1fr));
  gap: 10px;
  padding-top: 20px;
  border-top: 1px solid rgba(255,255,255,.1);
}
.showcase-stats b {
  display: block;
  font-family: var(--mono);
  font-size: clamp(14px, 1.4vw, 16px);
  font-weight: 500;
  background: var(--brand-grad);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.showcase-stats span {
  font-size: 11px;
  letter-spacing: .02em;
  color: rgba(238,241,248,.55);
}

.showcase-quote {
  margin: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px;
  background: rgba(255,255,255,.05);
  border: 1px solid rgba(255,255,255,.1);
  border-radius: var(--radius-sm);
}
.showcase-quote img { width: 40px; height: 40px; border-radius: 50%; flex: none; }
.showcase-quote blockquote { margin: 0; font-size: 12.5px; line-height: 1.55; color: rgba(238,241,248,.85); }
.showcase-quote figcaption {
  margin-top: 4px;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .04em;
  color: rgba(238,241,248,.5);
}

/* Tablet: keep the panel, trim the tall bits so the fold still shows the form. */
@media (max-width: 980px) {
  .auth-split { grid-template-columns: 1fr; max-width: 460px; }
  .auth-showcase { border-right: 0; border-bottom: 1px solid var(--line); padding: 28px 28px 26px; gap: 18px; }
  .showcase-art, .showcase-quote { display: none; }
  .showcase-head h2 { font-size: 22px; }
  .auth-split .auth-card { padding: 32px 28px; }
}

/* Phone: the form is all that matters. */
@media (max-width: 560px) {
  .showcase-features, .showcase-stats { display: none; }
  .auth-showcase { padding: 24px 24px 22px; }
  .auth-wrap { --auth-pad-x: 14px; }
  .auth-wrap--split { padding-top: 64px; }
  .auth-split { border-radius: 18px; }
  .auth-split .auth-card { padding: 28px 22px; }
  /* Clear of the shell now that the shell starts at 64px. */
  .auth-theme-btn { top: 14px; right: 14px; }
}

/* iOS Safari zooms the whole page in when a focused control's text is under
   16px, which then leaves the login form scrolled sideways with no way back.
   Keyed on the pointer as well as the width, or a phone held in landscape
   (wider than 560px, still a touch device) would miss out.
   :not() carries its argument's weight, which is what lifts this over the
   equally-specific `input[type="email"]` base rule declared further down —
   a plain `.auth-wrap input` ties and loses on source order. Scoped to the
   auth screens so the app's denser 14px controls are untouched. */
@media (max-width: 560px), (pointer: coarse) {
  .auth-wrap input:not([type="checkbox"]):not([type="radio"]),
  .auth-wrap select,
  .auth-wrap textarea { font-size: 16px; }
}

@media (max-width: 380px) {
  .auth-wrap { --auth-pad-x: 10px; }
  .auth-split .auth-card { padding: 24px 18px; }
  .auth-showcase { padding: 20px 18px 18px; }
  .auth-logo h1 { font-size: 17px; }
}

/* Short landscape — a phone turned sideways, ~360px tall. Stacking (the rule
   above, which this width also matches) would put the password field a full
   screen below the fold. A landscape phone is short, not narrow, so spend the
   width instead: go back to two columns and cut the panel to its headline. */
@media (orientation: landscape) and (max-height: 560px) and (max-width: 980px) {
  .auth-split { grid-template-columns: 1fr 1fr; max-width: 760px; }
  .auth-showcase {
    border-right: 1px solid var(--line);
    border-bottom: 0;
    padding: 20px 22px;
    gap: 10px;
  }
  .showcase-features, .showcase-stats, .showcase-art, .showcase-quote { display: none; }
  .showcase-head h2 { font-size: 20px; margin-top: 10px; }
  .auth-split .auth-card { padding: 22px 24px; }
  .auth-welcome { margin-top: 10px; font-size: 20px; }
  .auth-split .auth-sub, .auth-secure { display: none; }
  /* The button only needs to clear the shell, and vertical room is the scarce
     one here — tuck it in so the shell can start higher. */
  .auth-theme-btn { top: 10px; right: 10px; }
  .auth-wrap--split { padding-top: 54px; }
}

.field { margin-bottom: 16px; }

/* Two fields side by side inside a modal; stacks on narrow screens. */
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.field-row .field { margin-bottom: 16px; }
@media (max-width: 520px) { .field-row { grid-template-columns: 1fr; gap: 0; } }

.field label {
  display: block;
  font-size: 12px;
  color: var(--text-mid);
  margin-bottom: 6px;
  font-weight: 600;
  letter-spacing: .02em;
}

/* Base styling for every text-like input/select/textarea in the app, not
   just ones wrapped in .field — a bare <input> in a .param-row (Recipient
   Email, Session Timeout, etc.) must look the same as one inside a form.
   input:not([type]) covers the read-only audit fields (Created Date/By,
   Last Updated/By), which omit the attribute and so default to text. */
input:not([type]),
input[type="text"],
input[type="email"],
input[type="password"],
input[type="tel"],
input[type="number"],
input[type="search"],
input[type="url"],
input[type="date"],
input[type="month"],
input[type="time"],
select,
textarea {
  width: 100%;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 11px 13px;
  color: var(--text);
  font-family: var(--font);
  font-size: 14px;
  outline: none;
  transition: border-color .2s, box-shadow .2s;
}

/* Read-only audit fields keep the normal input shape but read as inert:
   dimmed text, no focus affordance. Browsers otherwise grey them out
   inconsistently and drop the theme text colour. */
input:disabled,
input[readonly],
textarea:disabled,
textarea[readonly] {
  color: var(--text-mid);
  cursor: default;
  opacity: 1;
  -webkit-text-fill-color: var(--text-mid);
}

/* Native date/time widgets: the value text is drawn by the engine in its own
   shadow tree, so it only picks up the theme colour if we hand it down
   explicitly. The picker button is ~10px by default — too small to hit
   comfortably, hence the bump and the hover affordance. */
input[type="date"]::-webkit-datetime-edit,
input[type="month"]::-webkit-datetime-edit,
input[type="time"]::-webkit-datetime-edit {
  color: var(--text);
}
input[type="date"]::-webkit-calendar-picker-indicator,
input[type="month"]::-webkit-calendar-picker-indicator,
input[type="time"]::-webkit-calendar-picker-indicator {
  width: 17px;
  height: 17px;
  padding: 2px;
  border-radius: 6px;
  cursor: pointer;
  opacity: .55;
  transition: opacity .2s, background .2s;
}
input[type="date"]::-webkit-calendar-picker-indicator:hover,
input[type="month"]::-webkit-calendar-picker-indicator:hover,
input[type="time"]::-webkit-calendar-picker-indicator:hover {
  opacity: 1;
  background: var(--surface);
}
/* Disabled audit fields have no picker to open — don't advertise one. */
input:disabled::-webkit-calendar-picker-indicator,
input[readonly]::-webkit-calendar-picker-indicator {
  display: none;
}

/* Wrap an <input> in <div class="input-icon"> + a leading <svg> to render
   an icon inside the field, left of the typed/placeholder text. */
.input-icon {
  position: relative;
}
.input-icon svg {
  position: absolute;
  left: 13px;
  top: 50%;
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  color: var(--muted);
  pointer-events: none;
}
.input-icon input[type="email"] {
  padding-left: 38px;
}
.input-icon input[type="email"]:focus {
  padding-left: 37.5px;
}

@property --border-angle {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}
@keyframes border-spin { to { --border-angle: 360deg; } }

input[type="text"]:focus,
input[type="email"]:focus,
input[type="password"]:focus,
input[type="tel"]:focus,
input[type="number"]:focus,
input[type="search"]:focus,
input[type="url"]:focus,
input[type="date"]:focus,
input[type="month"]:focus,
input[type="time"]:focus,
select:focus,
textarea:focus,
.toolbar select:focus,
.toolbar input:focus {
  border: 1.5px solid transparent;
  padding: 10.5px 12.5px;
  /* opaque cover so only the border ring shows the animated gradient —
     --surface-2 is a translucent overlay, not a solid color, so it must
     NOT be used here or the gradient bleeds through the whole interior */
  background:
    linear-gradient(var(--bg-alt), var(--bg-alt)) padding-box,
    conic-gradient(from var(--border-angle), var(--indigo), var(--blue), var(--cyan), var(--emerald), var(--amber), var(--pink), var(--violet), var(--indigo)) border-box;
  box-shadow: 0 0 0 3px rgba(99,102,241,.14);
  animation: border-spin 2.5s linear infinite;
}
/* toolbar controls use tighter base padding (9px/12px) — keep the same
   compensation so focus doesn't nudge their size by a couple of px */
.toolbar select:focus, .toolbar input:focus { padding: 8.5px 11.5px; }

textarea { resize: vertical; min-height: 90px; line-height: 1.5; }
input::placeholder, textarea::placeholder { color: var(--muted); }

.pw-field { position: relative; }
.pw-field input { padding-right: 42px; }
.pw-field input:focus { padding-right: 41.5px; }
.pw-toggle {
  position: absolute; top: 50%; right: 4px; transform: translateY(-50%);
  width: 32px; height: 32px; display: grid; place-items: center;
  background: transparent; border: none; border-radius: 8px; cursor: pointer;
  color: var(--muted); transition: color .2s, background .2s;
}
.pw-toggle:hover { color: var(--text); background: var(--surface); }
.pw-toggle svg { width: 17px; height: 17px; }

/* Variants of .pw-field that reserve room for 2 or 3 stacked icon buttons
   (e.g. show/hide + generate + copy) instead of the default single toggle. */
.pw-field--2 input { padding-right: 76px; }
.pw-field--2 input:focus { padding-right: 75.5px; }
.pw-field--3 input { padding-right: 110px; }
.pw-field--3 input:focus { padding-right: 109.5px; }

/* Live password-requirements checklist (see wirePasswordPolicy in common.js). */
.pw-reqs { list-style: none; margin: 8px 0 0; padding: 0; display: grid; grid-auto-flow: column; grid-template-rows: repeat(3, auto); grid-template-columns: repeat(2, 1fr); gap: 4px 16px; }
.pw-reqs li { font-size: 12px; color: var(--muted); display: flex; align-items: center; gap: 7px; transition: color .2s; }
.pw-reqs li::before {
  content: ""; width: 14px; height: 14px; flex: none; border-radius: 50%;
  border: 1.5px solid var(--muted); background: transparent;
  background-position: center; background-repeat: no-repeat; background-size: 9px;
  transition: border-color .2s, background-color .2s;
}
.pw-reqs li.req--ok { color: #6fe3a5; }
.pw-reqs li.req--ok::before {
  border-color: #10b981; background-color: #10b981;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
}

/* Generic copy-to-clipboard affordance for fields that aren't passwords
   (username, email, dates, notes) — reuses the .pw-toggle button look. */
.copy-field { position: relative; }
.copy-field input, .copy-field select { padding-right: 42px; }
.copy-field input:focus, .copy-field select:focus { padding-right: 41.5px; }
.copy-field textarea { padding-right: 42px; }
.copy-field textarea:focus { padding-right: 41.5px; }
.copy-field textarea ~ .pw-toggle { top: 10px; transform: none; }

/* Inline copy affordance for plain table cells (not inside an <input>) —
   e.g. username/email/password columns in the Password Vault grid. */
.cell-value { display: flex; align-items: center; gap: 6px; }
.cell-copy-btn {
  flex: none; width: 24px; height: 24px; display: grid; place-items: center;
  background: transparent; border: none; border-radius: 6px; cursor: pointer;
  color: var(--muted); transition: color .2s, background .2s;
}
.cell-copy-btn:hover { color: var(--cyan); background: var(--surface); }
.cell-copy-btn svg { width: 14px; height: 14px; }
.pw-mask { font-family: var(--mono); letter-spacing: 2px; color: var(--text-mid); }

.row-between {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* "Remember me" and "Forgot password?" together need ~215px; on a 320px
     phone the card's content box is narrower than that, so let them stack. */
  flex-wrap: wrap;
  gap: 8px 14px;
  margin-bottom: 20px;
  font-size: 13px;
}

.checkbox-row {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--text-mid);
  cursor: pointer;
}
.checkbox-row input { accent-color: var(--indigo); width: 15px; height: 15px; cursor: pointer; }

.link { color: var(--violet); cursor: pointer; font-weight: 500; }
.link:hover { color: var(--cyan); text-decoration: underline; }

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  border: none;
  border-radius: 999px;
  padding: 12px 22px;
  font-family: var(--font);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  position: relative;
  transition: transform .25s var(--ease), box-shadow .3s, border-color .25s, color .25s, background-position .4s var(--ease);
}
.btn svg { width: 15px; height: 15px; flex: none; }
.btn:active { transform: translateY(1px) scale(.98); }

.btn-primary {
  background: var(--brand-grad); background-size: 180% auto; color: #fff;
  box-shadow: 0 10px 28px -8px rgba(99,102,241,.6);
}
.btn-primary:hover { transform: translateY(-2px); background-position: right center; box-shadow: 0 14px 34px -8px rgba(99,102,241,.8); }

.btn-block { width: 100%; }

.btn-ghost { background: transparent; color: var(--violet); border: 1.5px solid rgba(139,120,250,.45); }
.btn-ghost:hover { color: var(--cyan); border-color: var(--cyan); transform: translateY(-2px); box-shadow: 0 10px 26px -10px rgba(34,211,238,.5); }

.btn-danger { background: var(--danger); color: #fff; box-shadow: 0 10px 24px -8px rgba(239,68,68,.55); }
.btn-danger:hover { transform: translateY(-2px); box-shadow: 0 14px 30px -8px rgba(239,68,68,.7); }

.btn-sm { padding: 8px 16px; font-size: 12.5px; }

/* Busy state for a button whose form is posting (see wireSubmitLoader in
   common.js). The label is faded rather than removed so the button keeps its
   width and the layout does not jump when the spinner appears. */
/* Label is hidden with transparent text (not display:none) because it is a bare
   text node on most buttons; child icons are hidden separately. */
.btn.is-loading { pointer-events: none; opacity: .85; color: transparent; }
.btn.is-loading > * { visibility: hidden; }
.btn.is-loading::after {
  content: "";
  position: absolute; top: 50%; left: 50%;
  width: 16px; height: 16px; margin: -8px 0 0 -8px;
  border-radius: 50%;
  border: 2px solid rgba(255,255,255,.35);
  border-top-color: #fff;
  animation: btn-spin .6s linear infinite;
}
.btn-ghost.is-loading::after { border-color: rgba(139,120,250,.35); border-top-color: var(--violet); }
@keyframes btn-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .btn.is-loading::after { animation-duration: 1.6s; }
}

/* Inline loading state for JSON-driven lists — a spinner with a label, shown
   while the first fetch is in flight. Replaces bare "Loading…" text so a slow
   response reads as busy rather than empty. Reuses the btn-spin keyframe. */
.list-loading {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  padding: 32px 16px; color: var(--muted); font-size: 13px;
}
.list-loading::before {
  content: ""; width: 18px; height: 18px; flex: none;
  border-radius: 50%;
  border: 2px solid rgba(139,120,250,.3);
  border-top-color: var(--violet);
  animation: btn-spin .6s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .list-loading::before { animation-duration: 1.6s; }
}

/* ---------- full-screen blocking loader ----------
   A branded overlay dropped over the whole app while a request is in flight.
   Being fixed, full-bleed and on top, it swallows every click and keypress, so
   the user cannot interact with a half-loaded screen. Driven by
   showAppLoader()/hideAppLoader() in common.js. */
.app-loader-overlay {
  position: fixed; inset: 0; z-index: 4000;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 20px;
  background: rgba(8, 8, 18, .58);
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
  animation: app-loader-in .18s ease both;
}
.app-loader-overlay[hidden] { display: none; }

/* The logo sits inside a rotating multi-colour arc built from a conic gradient
   masked to a thin band. The arc reuses the btn-spin keyframe.

   The gap matters. This was a full 360° rainbow ring, and a complete ring of colour
   is rotationally symmetric — spinning it looks near-identical frame to frame, so
   the loader read as a static badge and users assumed the page had hung. Ending the
   gradient in transparent gives it a head and a tail, which is what makes the motion
   legible at all. */
.app-loader-overlay__spinner {
  position: relative; width: 108px; height: 108px;
  display: grid; place-items: center;
}
.app-loader-overlay__spinner::before {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  background: conic-gradient(from 0deg,
    transparent 0deg 24deg,
    var(--indigo) 66deg, var(--blue) 132deg, var(--cyan) 198deg,
    var(--emerald) 258deg, var(--amber) 312deg, var(--pink) 352deg,
    transparent 360deg);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 5px), #000 calc(100% - 4px));
          mask: radial-gradient(farthest-side, transparent calc(100% - 5px), #000 calc(100% - 4px));
  animation: btn-spin .85s linear infinite;
}
/* A faint full ring behind the arc, so the arc reads as travelling around a track
   rather than as a lone shape floating in the dark. */
.app-loader-overlay__spinner::after {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  border: 4px solid rgba(255, 255, 255, .1);
}
.app-loader-overlay__spinner .brand-mark {
  width: 60px; height: 60px;
  position: relative; z-index: 1;
  animation: app-loader-pulse 1.6s ease-in-out infinite;
}
.app-loader-overlay__label {
  color: #fff; font-size: 13px; font-weight: 500; letter-spacing: .3px;
  text-shadow: 0 1px 12px rgba(0,0,0,.4);
}

/* Second, independent signal of progress. The ring alone is ambiguous once it has
   been up for a few seconds — a bar sweeping left to right is unmistakably *running*
   rather than stuck. Same treatment as the splash screen's .logo-screen__bar. */
.app-loader-overlay__bar {
  width: 190px; height: 3px; border-radius: 3px;
  background: rgba(255, 255, 255, .14);
  overflow: hidden;
  margin-top: -6px;
}
.app-loader-overlay__bar span {
  display: block; height: 100%; width: 40%;
  border-radius: 3px;
  background: var(--brand-grad);
  animation: logo-bar 1.4s var(--ease) infinite;
}

@keyframes app-loader-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes app-loader-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.08); } }

/* Reduced motion still needs to say "working" — the point of the overlay is that the
   app has not hung. The travelling parts stay, slowed right down; the logo's scale
   pulse is the one that adds nothing and goes. */
@media (prefers-reduced-motion: reduce) {
  .app-loader-overlay__spinner::before { animation-duration: 2.4s; }
  .app-loader-overlay__spinner .brand-mark { animation: none; }
  .app-loader-overlay__bar span { animation-duration: 3s; }
}

.alert {
  padding: 11px 14px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  margin-bottom: 16px;
  border: 1px solid transparent;
  display: flex;
  align-items: center;
  gap: 8px;
}
/* asp-validation-summary renders a bare <ul> and tags the div
   `validation-summary-errors`, which the `.validation-summary ul` rule below
   never matches — so without this the browser default 40px list indent pushes
   the message out of a narrow alert. min-width lets the flex child shrink. */
.alert > ul { margin: 0; padding-left: 18px; min-width: 0; }
.alert > ul > li { overflow-wrap: anywhere; }
.alert:has(> ul) { align-items: flex-start; }

.alert-error { background: rgba(239,68,68,.1); border-color: rgba(239,68,68,.35); color: #ff8a8d; }
.alert-success { background: rgba(16,185,129,.1); border-color: rgba(16,185,129,.35); color: #6fe3a5; }
.hidden { display: none !important; }

.auth-footer {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 2;
  display: flex; align-items: center; justify-content: center; gap: 16px; flex-wrap: wrap;
  min-height: 38px;
  /* The home indicator on a notched iPhone sits over a bottom-pinned bar. */
  padding: 8px 20px calc(8px + env(safe-area-inset-bottom, 0px)) 20px;
  text-align: center;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--muted);
  background: var(--nav-bg);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  border-top: 1px solid var(--line-soft);
}
/* Each .clock-zone is nowrap and runs ~210px, so the three of them stop
   sharing a line at about 660px and the bar becomes three or four rows tall.
   A *fixed* bar of unpredictable height can't be reserved for with a constant
   gutter — at 320px it stood 111px tall and sat over the password field. So
   below 640px it rejoins the flow: it costs the form no room at all, and the
   wrap's bottom gutter drops to zero because the footer now occupies its own.
   The second query catches the same bar eating a fifth of a phone held in
   landscape, which is short rather than narrow and so misses the first. */
@media (max-width: 640px), (max-height: 560px) and (orientation: landscape) {
  .auth-wrap { --auth-gutter-b: 0px; flex-direction: column; }
  /* Auto block margins keep the card optically centred in whatever space is
     left above the footer, rather than jammed to the top by the new column. */
  .auth-wrap > .auth-split,
  .auth-wrap > .auth-card { margin-block: auto; }
  .auth-footer {
    position: static;
    align-self: stretch;
    /* cancel the wrap's side gutter so the bar still runs edge to edge */
    margin: 24px calc(-1 * var(--auth-pad-x)) 0 calc(-1 * var(--auth-pad-x));
    padding: 9px 14px calc(9px + env(safe-area-inset-bottom, 0px)) 14px;
    flex-direction: column;
    gap: 4px;
  }
}

.demo-hint {
  margin-top: 20px;
  padding: 12px 14px;
  background: var(--surface-2);
  border: 1px dashed var(--line);
  border-radius: var(--radius-sm);
  font-size: 12px;
  color: var(--text-mid);
  line-height: 1.7;
}
.demo-hint code {
  font-family: var(--mono);
  color: var(--cyan);
  background: rgba(34,211,238,.1);
  padding: 1px 6px;
  border-radius: 5px;
  /* A seeded admin address is one unbreakable token — without this it widens
     the whole card on a phone instead of wrapping. */
  overflow-wrap: anywhere;
}

/* Inline links on the auth screens (forgot password, back to sign in). */
.auth-link { color: var(--brand, #6366f1); font-weight: 600; text-decoration: none; }
.auth-link:hover { text-decoration: underline; }
.demo-hint a { color: var(--brand, #6366f1); font-weight: 600; text-decoration: none; }
.demo-hint a:hover { text-decoration: underline; }

/* ---------- App shell ---------- */
.app { display: flex; min-height: 100vh; }

.sidebar {
  width: 250px;
  flex-shrink: 0;
  background: var(--bg-alt);
  border-right: 1px solid var(--line-soft);
  display: flex;
  flex-direction: column;
  padding: 20px 14px;
  position: fixed;
  top: 0;
  left: 0;
  height: 100vh;
  /* The sidebar itself never scrolls — only <nav> does — so the brand and the
     user card stay pinned no matter how far down the nav list you are. */
  overflow: hidden;
  z-index: 30;
  transition: width .18s var(--ease), padding .18s var(--ease);
  /* Pin the sidebar to its own compositing layer so its nav labels and icons
     render identically whether or not a modal's backdrop-filter is sampling
     them. Without this their antialiasing flips as a modal opens or closes over
     the top, which reads as a flicker. The mobile breakpoint already transforms
     the sidebar off-canvas, so this only changes the desktop layout. */
  transform: translateZ(0);
}

.sidebar > nav {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  /* Room for the scrollbar so it doesn't sit on top of the nav labels. */
  margin-right: -6px;
  padding-right: 6px;
}

.sidebar .brand {
  flex: none;
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 4px 8px 20px 8px;
  border-bottom: 1px solid var(--line-soft);
  margin-bottom: 16px;
}

/* Same mark as the sign-in lockup, two sizes down. The entrance animation is
   dropped here: the sidebar is re-rendered on every full postback, so a logo
   that slid in each time would read as a flicker rather than a flourish. The
   orbit spark lives on an inner element and keeps running. */
.sidebar .brand .brand-mark {
  width: 38px;
  height: 38px;
  animation: none;
  filter: drop-shadow(0 8px 18px rgba(99, 102, 241, .5));
}
[data-theme="light"] .sidebar .brand .brand-mark { filter: drop-shadow(0 8px 16px rgba(99, 102, 241, .34)); }

/* Weighted up from 15/700: the wordmark is the product name sitting beside a
   38px mark, and at the old size it read as a caption next to it. */
.sidebar .brand strong { font-size: 19px; font-weight: 800; letter-spacing: -.025em; }

/* The glow is tuned for the 27px sign-in wordmark; at 15px it swamps the word. */
[data-theme="dark"] .sidebar .brand .brand-wordmark { filter: drop-shadow(0 2px 8px rgba(99, 102, 241, .35)); }

/* Signed-in user card, directly under the brand. Duplicates the topbar chip on
   purpose: the topbar scrolls out of view on wide pages, the sidebar never does. */
.sidebar-user {
  flex: none;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 8px 8px 10px;
  margin-bottom: 14px;
  border-radius: 12px;
  border: 1px solid var(--line-soft);
  background: var(--surface-2);
  min-width: 0;
  transition: border-color .2s, background .2s;
}
.sidebar-user:hover { border-color: var(--line); background: var(--surface); }
.sidebar-user.active { box-shadow: inset 0 0 0 1px rgba(99,102,241,.25); }

/* Two targets in one card: the identity opens the profile, the icon signs out. */
.sidebar-user__link {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  color: var(--text);
  text-decoration: none;
}

.sidebar-user__logout {
  flex: none;
  width: 30px; height: 30px;
  display: grid; place-items: center;
  padding: 0;
  border: 0; border-radius: 9px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  transition: color .2s, background .2s;
}
.sidebar-user__logout svg { width: 16px; height: 16px; }
.sidebar-user__logout:hover { color: var(--danger); background: rgba(239,68,68,.1); }

.sidebar-user__text { display: flex; flex-direction: column; min-width: 0; }
.sidebar-user__name {
  font-size: 13px; font-weight: 600; letter-spacing: -.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sidebar-user__email {
  font-size: 11px; color: var(--muted); font-family: var(--mono); margin-top: 1px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.nav-item {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 10px 13px;
  border-radius: 10px;
  color: var(--text-mid);
  font-size: 13.5px;
  font-weight: 500;
  cursor: pointer;
  margin-bottom: 3px;
  transition: color .2s, background .2s;
}

.nav-item .ic { width: 18px; height: 18px; flex: none; display: grid; place-items: center; }
.nav-item .ic svg { width: 100%; height: 100%; }

.nav-item:hover { background: var(--surface-2); color: var(--text); }
.nav-item.active {
  color: var(--text);
  background: linear-gradient(135deg, rgba(99,102,241,.18), rgba(34,211,238,.1));
  box-shadow: inset 0 0 0 1px rgba(99,102,241,.25);
}
.nav-item.active .ic { color: var(--cyan); }

/* --- Collapsible nav group (parent menu + submenu) --- */
.nav-group { margin-bottom: 3px; }
.nav-group summary { list-style: none; }
.nav-group summary::-webkit-details-marker { display: none; }

.nav-parent { width: 100%; }
.nav-parent__label { flex: 1; }
.nav-caret {
  width: 15px;
  height: 15px;
  flex: none;
  display: grid;
  place-items: center;
  color: var(--text-mid);
  transition: transform .2s var(--ease);
}
.nav-caret svg { width: 100%; height: 100%; }
.nav-group[open] > summary .nav-caret { transform: rotate(180deg); }

.nav-sub {
  margin: 2px 0 4px 0;
  padding-left: 12px;
  border-left: 1px solid var(--line-soft);
  margin-left: 18px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.nav-sub-item { font-size: 13px; padding: 8px 12px; }
.nav-sub-item .ic { width: 16px; height: 16px; }

/* Width the fixed sidebar occupies, and so the offset .main is pushed by. Read
   by the wide detail modal, which is the one overlay broad enough to reach back
   under the nav — see .modal-overlay:has(> .modal-detail). Overridden to the
   rail width and to 0 off-canvas alongside the .main rules that match. */
:root { --nav-w: 250px; }

.main {
  flex: 1; min-width: 0; display: flex; flex-direction: column;
  margin-left: var(--nav-w);
  transition: margin-left .18s var(--ease);
}

.topbar {
  height: 64px;
  border-bottom: 1px solid var(--line-soft);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 26px;
  background: var(--nav-bg);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  position: sticky; top: 0; z-index: 10;
  gap: 16px;
}

.topbar h2 { font-size: 16px; margin: 0; font-weight: 700; letter-spacing: -.01em; }
.topbar .meta { font-size: 11.5px; color: var(--muted); font-family: var(--mono); margin-top: 2px; }
.topbar-right { display: flex; align-items: center; gap: 14px; }

.user-menu { position: relative; }

.user-chip {
  display: flex; align-items: center; gap: 10px; font-size: 13px;
  background: transparent; border: 1px solid transparent; border-radius: 999px;
  padding: 5px 8px 5px 5px; cursor: pointer; color: var(--text); font-family: var(--font);
  text-align: left; transition: background .2s, border-color .2s;
}
.user-chip:hover { background: var(--surface-2); border-color: var(--line); }
.user-chip__chevron { width: 14px; height: 14px; color: var(--muted); transition: transform .2s; }
.user-chip[aria-expanded="true"] .user-chip__chevron { transform: rotate(180deg); }

.avatar {
  width: 32px; height: 32px;
  border-radius: 50%;
  background: var(--brand-grad);
  display: flex; align-items: center; justify-content: center;
  font-weight: 700; font-size: 12px; color: #fff;
  box-shadow: 0 6px 16px -4px rgba(99,102,241,.55);
  flex: none;
  overflow: hidden;
}

/* A profile photo replaces the initials inside the same circle. */
.avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }

.avatar-photo {
  border-radius: 50%;
  object-fit: cover;
  flex: none;
  border: 1px solid var(--line);
}

/* ---------- photo upload (contact form) ---------- */
.photo-upload {
  display: flex; align-items: center; gap: 16px;
  margin-bottom: 20px; padding-bottom: 20px;
  border-bottom: 1px solid var(--line-soft);
}
.photo-upload__preview { flex: none; }

/* ---------- radio group (birthday channel) ---------- */
.radio-group { display: flex; flex-wrap: wrap; gap: 8px; }
.radio-option {
  display: inline-flex; align-items: center; gap: 7px;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 999px;
  padding: 8px 14px; font-size: 13px; color: var(--text-mid);
  cursor: pointer; transition: color .2s, border-color .2s, background .2s;
}
.radio-option input { accent-color: var(--indigo); width: 14px; height: 14px; cursor: pointer; }
.radio-option:has(input:checked) { color: var(--text); border-color: var(--indigo); background: rgba(99,102,241,.12); font-weight: 600; }

/* ---------- 2-column form grid (contact form address/date rows) ---------- */
.form-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 14px; }
@media (max-width: 520px) { .form-grid-2 { grid-template-columns: 1fr; } }

/* ---------- 3-column form grid (12-col system, each field = 4 cols) ---------- */
.form-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0 16px; align-items: start; }
.form-grid-3 .field-full { grid-column: 1 / -1; }
.form-grid-3 .field-span-2 { grid-column: span 2; }
@media (max-width: 560px) { .form-grid-3 .field-span-2 { grid-column: auto; } }
@media (max-width: 860px) { .form-grid-3 { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .form-grid-3 { grid-template-columns: 1fr; } }

.user-dropdown {
  position: absolute; top: calc(100% + 10px); right: 0; z-index: 50;
  width: 220px;
  background: var(--bg-alt); border: 1px solid var(--line); border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
  padding: 6px;
  animation: dropdown-in .16s var(--ease);
}
@keyframes dropdown-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }

.user-dropdown__item {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 11px; border-radius: 8px;
  font-size: 13px; font-weight: 500; color: var(--text-mid);
  cursor: pointer; transition: background .15s, color .15s;
}
.user-dropdown__item .ic { width: 16px; height: 16px; flex: none; }
.user-dropdown__item:hover { background: var(--surface-2); color: var(--text); }
.user-dropdown__item--danger:hover { color: var(--danger); background: rgba(239,68,68,.08); }
.user-dropdown__sep { height: 1px; background: var(--line-soft); margin: 6px 4px; }

/* ---------- notification bell (events, todos, birthdays, anniversaries) ---------- */
.notif-menu { position: relative; }

.notif-btn {
  position: relative;
  width: 36px; height: 36px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface-2); border: 1px solid var(--line);
  color: var(--text-mid); cursor: pointer;
  transition: color .2s, border-color .2s, background .2s;
}
.notif-btn:hover { color: var(--cyan); border-color: var(--cyan); }
.notif-btn svg { width: 17px; height: 17px; }

.notif-badge {
  position: absolute; top: -3px; right: -3px;
  min-width: 17px; height: 17px; padding: 0 4px; box-sizing: border-box;
  border-radius: 999px; background: var(--danger); color: #fff;
  font-size: 10px; font-weight: 700; line-height: 15px; text-align: center;
  font-family: var(--font); box-shadow: 0 0 0 2px var(--bg);
}

.notif-dropdown {
  position: absolute; top: calc(100% + 10px); right: 0; z-index: 50;
  width: 320px; max-height: 440px;
  display: flex; flex-direction: column;
  background: var(--bg-alt); border: 1px solid var(--line); border-radius: var(--radius-sm);
  box-shadow: var(--shadow); overflow: hidden;
  animation: dropdown-in .16s var(--ease);
}
.notif-head {
  display: flex; align-items: center; gap: 8px;
  padding: 12px 14px; border-bottom: 1px solid var(--line-soft);
  font-size: 13px; font-weight: 700; color: var(--text);
}
.notif-head .ic { width: 16px; height: 16px; color: var(--amber); flex: none; }
.notif-list { overflow-y: auto; padding: 6px; }
.notif-item {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 10px; border-radius: 8px;
  text-decoration: none; transition: background .15s;
}
.notif-item:hover { background: var(--surface-2); }
.notif-item__body { min-width: 0; flex: 1; }
.notif-item__name {
  font-size: 13px; font-weight: 600; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.notif-item__meta { font-size: 11.5px; color: var(--muted); margin-top: 2px; }
.notif-when {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 11px; font-weight: 700; white-space: nowrap; flex: none;
}
.notif-when--today { color: var(--emerald); }
.notif-when--soon { color: var(--amber); }
.notif-when--later { color: var(--muted); }
.notif-when--past { color: var(--danger); }

/* A row that has already gone by — an overdue todo, or today's event once its end
   time has passed. Kept in the list and crossed out rather than dropped, so the
   red cross is the whole signal: struck-through title, red marker, red timing. */
.notif-item--past .notif-item__name { text-decoration: line-through; color: var(--text-mid); }
.notif-cross {
  display: inline-flex; align-items: center; justify-content: center;
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--danger); color: #fff;
  font-size: 9px; font-weight: 700; line-height: 1;
}
.notif-empty { padding: 26px 14px; text-align: center; color: var(--muted); font-size: 12.5px; }
.notif-foot {
  display: block; padding: 11px 14px; border-top: 1px solid var(--line-soft);
  font-size: 12.5px; font-weight: 600; color: var(--indigo); text-align: center;
  text-decoration: none; transition: background .15s;
}
.notif-foot:hover { background: var(--surface-2); }

.content { padding: 26px 26px 68px 26px; overflow-y: auto; position: relative; }

/* ---------- fixed footer (app shell) ---------- */
.app-footer {
  position: fixed; left: 250px; right: 0; bottom: 0; z-index: 20;
  min-height: 38px;
  display: flex; align-items: center; justify-content: center; gap: 16px;
  padding: 8px 20px;
  background: var(--nav-bg);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  border-top: 1px solid var(--line-soft);
  flex-wrap: wrap;
}
.app-footer__brand { font-family: var(--mono); font-size: 10.5px; color: var(--muted); letter-spacing: .02em; }

/* clock.js renders a .clock-track holding two identical .clock-copy rows. Only
   the first is shown here — the duplicate is what closes the marquee loop on
   phones, where the three zones cannot fit on one line (see the 860px block). */
.clock { display: flex; align-items: center; justify-content: center; max-width: 100%; overflow: hidden; font-family: var(--mono); font-size: 11.5px; color: var(--text-mid); }
.clock-track { display: flex; align-items: center; }
.clock-copy { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; justify-content: center; }
.clock-copy--dup { display: none; }
@keyframes clock-marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }
.clock-zone { white-space: nowrap; }
.clock-zone b { font-weight: 700; margin-right: 5px; }
.clock-sep { color: var(--line); }

/* ---------- Cards / stats ---------- */
.grid-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 16px;
  margin-bottom: 22px;
}

.stat-card {
  position: relative; overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 20px;
  transition: transform .3s var(--ease), border-color .3s, box-shadow .3s;
}
.stat-card { --c: var(--indigo); }
.stat-card::before {
  content: ''; position: absolute; inset: 0 0 auto 0; height: 3px;
  background: linear-gradient(90deg, var(--c), transparent);
  transform: scaleX(0); transform-origin: left;
  transition: transform .45s var(--ease);
}
.stat-card:hover { transform: translateY(-4px); border-color: color-mix(in srgb, var(--c) 45%, transparent); box-shadow: 0 20px 40px -20px color-mix(in srgb, var(--c) 60%, transparent); }
.stat-card:hover::before { transform: scaleX(1); }

.stat-card .label { font-size: 11.5px; color: var(--muted); margin-bottom: 9px; text-transform: uppercase; letter-spacing: .06em; font-weight: 600; }
.stat-card .value { font-size: 25px; font-weight: 800; letter-spacing: -.02em; }
.stat-card .sub { font-size: 12px; color: var(--text-mid); margin-top: 7px; }

/* ---------- profile ---------- */
.profile-header { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.profile-header__avatar {
  width: 64px; height: 64px; flex: none;
  border-radius: 50%;
  background: var(--brand-grad);
  display: flex; align-items: center; justify-content: center;
  font-size: 22px; font-weight: 800; color: #fff;
  box-shadow: 0 10px 28px -8px rgba(99,102,241,.55);
}

.panel {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 22px;
  margin-bottom: 20px;
}

.panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 18px;
  flex-wrap: wrap;
  gap: 12px;
}

.panel-head h3 { margin: 0; font-size: 15.5px; font-weight: 700; letter-spacing: -.01em; }
.panel-head p { margin: 4px 0 0 0; font-size: 12.5px; color: var(--muted); }

.toolbar { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.toolbar select, .toolbar input {
  min-width: 150px;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 9px 12px; color: var(--text); font-family: var(--font); font-size: 13px; outline: none;
  transition: border-color .2s;
}

/* ---------- custom dropdown (replaces native <select> popup styling) ---------- */
.cs-select { position: relative; display: inline-block; min-width: 150px; }
.field .cs-select, .param-row .cs-select { display: block; width: 100%; }

.cs-trigger {
  width: 100%;
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 9px 12px; color: var(--text); font-family: var(--font); font-size: 13px;
  cursor: pointer; text-align: left;
  transition: border-color .2s, box-shadow .2s;
}
.field .cs-trigger, .param-row .cs-trigger { padding: 11px 13px; font-size: 14px; }
.cs-trigger:hover { border-color: color-mix(in srgb, var(--indigo) 45%, var(--line)); }
.cs-select--disabled .cs-trigger { opacity: .5; cursor: not-allowed; }

.cs-trigger__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cs-trigger__chevron { width: 14px; height: 14px; flex: none; color: var(--muted); transition: transform .2s; }
.cs-select--open .cs-trigger__chevron { transform: rotate(180deg); }

.cs-select--open .cs-trigger,
.cs-trigger:focus-visible {
  border: 1.5px solid transparent;
  padding: 7.5px 10.5px;
  background:
    linear-gradient(var(--bg-alt), var(--bg-alt)) padding-box,
    conic-gradient(from var(--border-angle), var(--indigo), var(--blue), var(--cyan), var(--emerald), var(--amber), var(--pink), var(--violet), var(--indigo)) border-box;
  box-shadow: 0 0 0 3px rgba(99,102,241,.14);
  animation: border-spin 2.5s linear infinite;
  outline: none;
}
.field .cs-select--open .cs-trigger, .param-row .cs-select--open .cs-trigger,
.field .cs-trigger:focus-visible, .param-row .cs-trigger:focus-visible {
  padding: 9.5px 11.5px;
}

.cs-listbox {
  position: absolute; top: calc(100% + 8px); left: 0; right: 0; z-index: 60;
  max-height: 260px; overflow-y: auto;
  background: var(--bg-alt); border: 1px solid var(--line); border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
  padding: 6px;
  animation: dropdown-in .16s var(--ease);
}
.cs-listbox.hidden { display: none; }
.cs-listbox::-webkit-scrollbar { width: 8px; }
.cs-listbox::-webkit-scrollbar-track { background: transparent; }
.cs-listbox::-webkit-scrollbar-thumb { background: var(--line); border-radius: 999px; }
.cs-listbox::-webkit-scrollbar-thumb:hover { background: var(--muted); }

.cs-search-wrap {
  position: sticky; top: -6px; z-index: 1;
  margin: -6px -6px 4px; padding: 6px;
  background: var(--bg-alt);
}
.cs-search-wrap.hidden { display: none; }
.cs-search {
  width: 100%; box-sizing: border-box;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 8px;
  padding: 8px 10px; color: var(--text); font-family: var(--font); font-size: 13px;
  outline: none; transition: border-color .2s, box-shadow .2s;
}
.cs-search::placeholder { color: var(--muted); }
.cs-search:focus {
  border-color: color-mix(in srgb, var(--indigo) 55%, var(--line));
  box-shadow: 0 0 0 3px rgba(99,102,241,.14);
}

.cs-empty { padding: 9px 11px; font-size: 13px; color: var(--muted); text-align: center; }
.cs-empty.hidden { display: none; }

.cs-option {
  padding: 9px 11px; border-radius: 8px; font-size: 13px; color: var(--text-mid);
  cursor: pointer; transition: background .12s, color .12s;
}
.cs-option--hidden { display: none; }
.cs-option:hover, .cs-option--active { background: var(--surface-2); color: var(--text); }
.cs-option--selected { color: var(--text); font-weight: 600; background: rgba(99,102,241,.12); }
.cs-option--selected.cs-option--active { background: rgba(99,102,241,.18); }

/* ---------- Table ---------- */
table { width: 100%; border-collapse: collapse; font-size: 13.5px; }

/* Head rows pin while their table is on screen. The window is the scrollport on
   a normal page, so the default offset clears the 64px sticky topbar; the two
   overrides below re-zero it for tables that scroll inside their own box.
   border-collapse drops a sticky cell's own border, hence the inset shadow. */
thead th {
  text-align: left;
  color: var(--muted);
  font-weight: 600;
  font-size: 10.5px;
  font-family: var(--mono);
  text-transform: uppercase;
  letter-spacing: .06em;
  padding: 11px 12px;
  position: sticky;
  top: var(--thead-top, 64px);
  z-index: 3;
  /* --surface is a translucent tint in dark theme, so painting it alone lets
     rows scroll through the pinned head. Composite it over an opaque base to
     match the panel exactly while staying solid. */
  background:
    linear-gradient(var(--thead-tint, var(--surface)), var(--thead-tint, var(--surface))),
    var(--thead-base, var(--bg));
  box-shadow: inset 0 -1px 0 var(--line);
}
.modal { --thead-top: 0px; --thead-tint: var(--bg-alt); --thead-base: var(--bg-alt); }
.table-scroll { --thead-top: 0px; }

/* Caps a long grid so it scrolls in place instead of stretching the page. */
.table-scroll {
  max-height: var(--table-scroll-max, min(58vh, 580px));
  overflow: auto;
  scrollbar-width: thin;
  scrollbar-color: rgba(99, 102, 241, .55) transparent;
}
.table-scroll::-webkit-scrollbar { width: 10px; height: 10px; }
.table-scroll::-webkit-scrollbar-track { background: transparent; }
.table-scroll::-webkit-scrollbar-thumb {
  background: rgba(99, 102, 241, .45);
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: content-box;
}
.table-scroll::-webkit-scrollbar-thumb:hover { background: rgba(99, 102, 241, .7); background-clip: content-box; }
tbody td { padding: 13px 12px; border-bottom: 1px solid var(--line-soft); }
tbody tr { transition: background .15s; }
tbody tr:hover { background: var(--surface-2); }
tbody tr:last-child td { border-bottom: none; }

.badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 11px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  font-family: var(--mono);
}
.badge::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.badge-blue  { background: rgba(59,130,246,.14); color: #7fb0ff; }
.badge-green { background: rgba(16,185,129,.14); color: #4fdb9c; }
.badge-amber { background: rgba(245,158,11,.14); color: #f6bc55; }
.badge-red   { background: rgba(239,68,68,.14); color: #ff8a8d; }
/* Neutral state — "No", "Inactive", and other unremarkable values. */
.badge-slate { background: rgba(148,163,184,.14); color: #9fb0c6; }

.row-actions { display: flex; gap: 8px; }
/* Centre a table column, header and cells alike — used for compact columns like
   Order and Action where left-alignment leaves the content stranded far left. */
.col-center { text-align: center; }
.col-center .row-actions { justify-content: center; }

/* App-wide: centre the action-button column and its header in every data grid,
   so the buttons sit under the "Action" heading instead of hugging the left edge.
   Scoped deliberately — `td >` and `:has(td > .row-actions)` mean only real table
   action columns are touched, leaving the detail-modal secret actions (a .row-actions
   inside a modal <div>) and the file-card overlays with their own layout. The header
   rule keys off :last-child, and Action is the last column in every button grid. */
td > .row-actions { justify-content: center; }
table:has(td > .row-actions) thead th:last-child { text-align: center; }
.icon-btn {
  background: var(--surface-2);
  border: 1px solid var(--line);
  color: var(--text-mid);
  border-radius: 999px;
  padding: 6px 13px;
  cursor: pointer;
  font-family: var(--font);
  font-size: 12px;
  font-weight: 500;
  transition: color .2s, border-color .2s, transform .2s;
}
.icon-btn:hover { color: var(--cyan); border-color: var(--cyan); transform: translateY(-1px); }
.icon-btn.danger:hover { color: var(--danger); border-color: var(--danger); }

/* Kept visible rather than hidden: a button that vanishes reads as a missing
   feature, one that is dimmed and carries a title says why it is unavailable. */
.icon-btn:disabled { opacity: .45; cursor: not-allowed; }
.icon-btn:disabled:hover { color: var(--text-mid); border-color: var(--line); transform: none; }

/* Icon-only variant — same pill, sized for a glyph instead of a word. Used where a
   cell carries two actions per secret (the vault grid's Reveal/Copy) and worded
   buttons pushed the column wider than the data in it. */
.icon-btn--icon { width: 28px; height: 28px; padding: 0; display: inline-grid; place-items: center; }
.icon-btn--icon svg { width: 15px; height: 15px; }

.empty-state { text-align: center; padding: 46px 20px; color: var(--muted); font-size: 13px; }

/* ---------- error state (404 / 403 / 500 shown inside the shell) ---------- */
.error-state { --c: var(--indigo); text-align: center; padding: 54px 24px; }
.error-state__code {
  font-family: var(--mono);
  font-size: 62px; font-weight: 900; letter-spacing: -.04em; line-height: 1;
  color: var(--c);
}
.error-state__title { margin: 16px 0 8px; font-size: 18px; font-weight: 700; letter-spacing: -.01em; }
.error-state__msg { margin: 0 auto; max-width: 460px; font-size: 13px; color: var(--muted); }
.error-state__path {
  margin-top: 18px; font-size: 12px; color: var(--muted);
  /* A long query string is the normal case here, so it wraps rather than
     stretching the panel past the content column. */
  word-break: break-all;
}
.error-state__actions { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; margin-top: 26px; }

@media (max-width: 620px) {
  .error-state { padding: 38px 16px; }
  .error-state__code { font-size: 48px; }
}

/* ---------- expense report (donut chart) ---------- */
.report-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 22px;
  align-items: start;
}
.report-grid > .panel { margin: 0; }
@media (max-width: 1100px) {
  .report-grid { grid-template-columns: 1fr; }
}

/* Monthly bar chart — 12 equal columns, bars scaled against the tallest month. */
.bar-chart {
  display: grid;
  /* Auto columns rather than a fixed repeat(12, 1fr): the report pages all plot a
     full twelve months, but the dashboard's expense trend is six, and a fixed
     twelve-track grid left those six bunched into the left half of the panel. */
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 10px;
  height: 260px;
  padding: 14px 4px 4px;
}

.bar-chart__col {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  min-width: 0;
  height: 100%;
}

/* The track owns the flexible space so bars can grow from the baseline up. */
.bar-chart__track { flex: 1; width: 100%; display: flex; align-items: flex-end; min-height: 0; }

.bar-chart__bar {
  width: 100%;
  min-height: 3px;
  border-radius: 8px 8px 3px 3px;
  background: linear-gradient(180deg, var(--indigo) 0%, var(--cyan) 100%);
  box-shadow: 0 8px 20px -10px rgba(99, 102, 241, .8);
  transition: filter .2s var(--ease), transform .2s var(--ease);
}
.bar-chart__col:hover .bar-chart__bar { filter: brightness(1.18); transform: scaleY(1.02); transform-origin: bottom; }
.bar-chart__bar.is-empty { background: var(--surface-2); box-shadow: none; }

.bar-chart__value {
  font-size: 11px;
  font-family: var(--mono);
  color: var(--text-mid);
  white-space: nowrap;
  min-height: 14px;
}

.bar-chart__label {
  font-size: 11.5px;
  color: var(--muted);
  font-weight: 600;
  letter-spacing: .03em;
}

@media (max-width: 900px) {
  .bar-chart { height: 220px; gap: 5px; }
  .bar-chart__value { font-size: 9.5px; }
  .bar-chart__label { font-size: 10px; }
}

.chart-layout {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 34px;
  padding: 8px 4px 4px;
}

.donut { position: relative; width: 220px; height: 220px; flex: none; }
.donut__svg { width: 100%; height: 100%; transform: rotate(0deg); }
.donut__ring { stroke: var(--surface-2); }
.donut__slice { transition: stroke-width .2s var(--ease); }
.donut__slice:hover { stroke-width: 3.9; }

.donut__center {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}
.donut__total { font-size: 24px; font-weight: 800; letter-spacing: -.02em; }
.donut__caption { font-size: 11.5px; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; font-weight: 600; margin-top: 3px; }

.chart-legend { list-style: none; margin: 0; padding: 0; flex: 1; min-width: 240px; display: flex; flex-direction: column; gap: 4px; }
.chart-legend__item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 10px;
  border-radius: 10px;
  font-size: 13px;
  transition: background .2s;
}
.chart-legend__item:hover { background: var(--surface-2); }
.chart-legend__dot { width: 11px; height: 11px; border-radius: 3px; flex: none; }
.chart-legend__name { flex: 1; font-weight: 500; }
.chart-legend__pct { color: var(--text-mid); font-variant-numeric: tabular-nums; min-width: 46px; text-align: right; }
.chart-legend__amt { font-family: var(--mono); font-size: 12.5px; min-width: 96px; text-align: right; }

/* One row per site, so a real vault runs to dozens and stretches the panel far
   past both the donut beside it and the panel it shares the row with. Cap it and
   scroll in place, same treatment as .table-scroll and .dash-feed. The floor keeps
   the donut from overhanging the list when there are only a handful of sites. */
.chart-legend {
  max-height: var(--legend-scroll-max, 340px);
  overflow-y: auto;
  overflow-x: hidden;
  /* Gutter for the bar so it doesn't sit on top of the count column. */
  padding-right: 4px;
  scrollbar-width: thin;
  scrollbar-color: rgba(99, 102, 241, .55) transparent;
}
.chart-legend::-webkit-scrollbar { width: 10px; }
.chart-legend::-webkit-scrollbar-track { background: transparent; }
.chart-legend::-webkit-scrollbar-thumb {
  background: rgba(99, 102, 241, .45);
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: content-box;
}
.chart-legend::-webkit-scrollbar-thumb:hover {
  background: rgba(99, 102, 241, .7);
  background-clip: content-box;
}

/* ---------- files (drive) ---------- */
.storage-bar { height: 8px; border-radius: 999px; background: var(--surface-2); overflow: hidden; }
.storage-bar__fill { height: 100%; background: var(--brand-grad); border-radius: 999px; transition: width .3s var(--ease); }

/* Inside a table cell the 8px bar crowds the row, and the number beside it is
   carrying the precision anyway. */
.storage-bar--slim { height: 5px; }

/* A drive filling up is worth seeing before it is full, so the bar leaves the brand
   gradient behind at 75% and again at 90%. */
.storage-bar__fill.is-warning { background: var(--amber, #f59e0b); }
.storage-bar__fill.is-critical { background: var(--danger, #ef4444); }

/* Amber alert: used where an action is permitted but consequential — an
   administrator working inside someone else's drive, most of all. */
.alert-warning { border-left-color: var(--amber, #f59e0b); background: rgba(245, 158, 11, .10); }

.breadcrumb { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; font-size: 13.5px; }
.breadcrumb__item {
  color: var(--text-mid); cursor: pointer; padding: 4px 8px; border-radius: 6px;
  transition: background .15s, color .15s;
}
.breadcrumb__item:last-child { color: var(--text); font-weight: 600; cursor: default; }
.breadcrumb__item:not(:last-child):hover { background: var(--surface-2); color: var(--text); }
.breadcrumb__sep { color: var(--muted); }

.dropzone { position: relative; border-radius: var(--radius-sm); transition: background .15s; }
.dropzone__hint {
  display: none; position: absolute; inset: 0; z-index: 5;
  align-items: center; justify-content: center;
  background: rgba(99,102,241,.08);
  border: 2px dashed var(--indigo); border-radius: var(--radius-sm);
  font-size: 14px; font-weight: 600; color: var(--indigo);
  pointer-events: none;
}
.dropzone--active .dropzone__hint { display: flex; }

.file-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 14px; }
.file-card {
  position: relative;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 16px 14px; text-align: center; cursor: pointer;
  transition: border-color .2s, transform .2s, box-shadow .2s;
}
.file-card:hover { border-color: color-mix(in srgb, var(--indigo) 45%, var(--line)); transform: translateY(-3px); box-shadow: 0 14px 30px -16px rgba(99,102,241,.5); }
.file-card--folder { cursor: pointer; }

.file-card__icon {
  width: 100%; height: 64px; margin-bottom: 10px;
  display: flex; align-items: center; justify-content: center;
}
.file-card__icon svg { width: 40px; height: 40px; color: var(--c, var(--indigo)); }
.file-card--folder .file-card__icon svg { width: 48px; height: 48px; }

.file-thumb { width: 64px; height: 64px; object-fit: cover; border-radius: 8px; border: 1px solid var(--line); }

.file-icon { position: relative; display: inline-flex; color: var(--c, var(--muted)); }
.file-icon svg { width: 44px; height: 44px; }
.file-icon__ext {
  position: absolute; bottom: 2px; left: 50%; transform: translateX(-50%);
  background: var(--c, var(--muted)); color: #fff;
  font-size: 8.5px; font-weight: 700; font-family: var(--mono);
  padding: 1px 5px; border-radius: 4px; letter-spacing: .02em;
}

.file-card__name {
  font-size: 12.5px; font-weight: 500; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Actions overlay the tile rather than being appended to it. Appending made the
   card grow on hover, which shoved every tile after it around the grid; and with
   five actions on a 150px tile the row could not fit on one line however it wrapped.
   Covering the tile keeps the grid still and gives the buttons the full width.
   Note this must NOT be nested inside .row-actions — that one is nowrap, and an
   inner nowrap flex row overflows the card no matter what this rule says. */
.file-card__actions {
  display: none; flex-wrap: wrap; justify-content: center; align-content: center; gap: 5px;
  position: absolute; inset: 0; padding: 10px;
  /* --bg-alt, not --surface-2: the surface tokens are near-transparent overlays
     (rgba white at 6% in dark), so anything mixed from them lets the file name and
     type badge show straight through the buttons. --bg-alt is opaque in both themes. */
  background: var(--bg-alt);
  border-radius: inherit;
}
.file-card:hover .file-card__actions, .file-card:focus-within .file-card__actions { display: flex; }

/* The overlay is tight, so its buttons trade a little padding for fitting. */
.file-card__actions .icon-btn { padding: 4px 8px; font-size: 11.5px; }
.file-card__actions .inline-form { display: contents; }

/* Share-link cell: an icon that copies, with the full URL revealed on hover. */
.link-chip {
  position: relative;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 10px; border-radius: 999px;
  background: var(--surface-2); border: 1px solid var(--line);
  color: var(--text-mid); font: inherit; font-size: 12px; cursor: pointer;
  transition: color .15s, border-color .15s;
}
.link-chip:hover { color: var(--text); border-color: color-mix(in srgb, var(--indigo) 45%, var(--line)); }
.link-chip svg { width: 14px; height: 14px; flex: none; }

/* The URL itself, as a tooltip. Dropped below the chip and anchored left: the Link
   column sits near the start of a wide table, so there is room to the right and
   below, but none above (the header row) or to the left (the panel edge). Wrapped
   rather than ellipsised — a truncated URL would defeat the point of showing it. */
.link-chip::after {
  content: attr(data-url);
  position: absolute; top: calc(100% + 8px); left: 0;
  width: max-content; max-width: 340px;
  white-space: normal; word-break: break-all; text-align: left;
  background: var(--bg-alt); border: 1px solid var(--line); border-radius: 8px;
  box-shadow: var(--shadow);
  padding: 6px 9px; font-family: var(--mono); font-size: 11.5px; line-height: 1.5; color: var(--text-mid);
  opacity: 0; pointer-events: none; transition: opacity .15s var(--ease);
  z-index: 30;
}
.link-chip:hover::after, .link-chip:focus-visible::after { opacity: 1; }

/* Without this the tooltip is clipped by the scroll container the table sits in. */
.panel:has(.link-chip) { overflow: visible; }

.code-checks { display: flex; flex-wrap: wrap; gap: 8px; }
.code-check {
  display: inline-flex; align-items: center; gap: 7px;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 999px;
  padding: 6px 13px 6px 10px; font-family: var(--mono); font-size: 12.5px; color: var(--text-mid);
  cursor: pointer; transition: color .2s, border-color .2s;
}
.code-check:has(input:checked) { color: var(--text); border-color: var(--indigo); background: rgba(99,102,241,.1); }
.code-check input { accent-color: var(--indigo); width: 14px; height: 14px; cursor: pointer; }

/* ---------- Modal ---------- */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(5,7,10,.6);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
  padding: 20px;
  /* Ease in the dark tint only. The earlier version faded the overlay's own
     opacity, but fading a backdrop-filter layer makes the browser toggle the
     blur on and off across the fade, so the text and icons behind it flickered
     as the modal opened — and again as it closed. Holding the filter at full
     strength and animating just the background colour keeps the backdrop
     steady; the modal card still animates in via .modal's own keyframes. */
  animation: overlay-in .18s var(--ease);
}
@keyframes overlay-in { from { background: rgba(5,7,10,0); } to { background: rgba(5,7,10,.6); } }

.modal {
  width: 100%;
  max-width: 460px;
  background: var(--bg-alt);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 26px;
  animation: modal-in .22s var(--ease);
}
@keyframes modal-in { from { opacity: 0; transform: translateY(10px) scale(.98); } to { opacity: 1; transform: none; } }

/* Idle auto-lock warning: the border is a countdown ring that fills with the
   brand's multi-colour sweep clockwise as the grace period drains.
   --idle-progress (0% → 100%) is the elapsed share of the warning window,
   driven from common.js on every tick.
   Stacked layers, topmost first: the inner card colour on padding-box so only
   the border ring shows a gradient; then a two-layer mask over the *remaining*
   share of the ring (--line is translucent, so it needs the opaque --bg-alt
   arc under it or the colours bleed through the un-elapsed part); then the
   full logo gradient underneath, which the mask uncovers as the countdown
   runs. Masking one static gradient — rather than stretching the colours to
   --idle-progress — keeps every hue in the same place while the arc grows. */
#idleLockModal .modal {
  border: 3px solid transparent;
  background:
    linear-gradient(var(--bg-alt), var(--bg-alt)) padding-box,
    conic-gradient(transparent var(--idle-progress, 0%), var(--line) 0) border-box,
    conic-gradient(transparent var(--idle-progress, 0%), var(--bg-alt) 0) border-box,
    conic-gradient(var(--indigo), var(--blue), var(--cyan), var(--emerald),
                   var(--amber), var(--pink), var(--violet), var(--indigo)) border-box;
  transition: box-shadow .3s var(--ease);
}

/* Corner dismiss, injected into every modal by modalClose.js. Sticky rather than
   absolute because .modal-wide / .modal-xl scroll their own content — absolute
   would scroll the ✕ out of reach on the long forms. Floated so it takes the
   heading's right edge without needing a header wrapper the markup doesn't have. */
.modal-close {
  position: sticky;
  top: 0;
  float: right;
  z-index: 1;
  width: 30px;
  height: 30px;
  margin: -6px -6px 0 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--danger);
  border-radius: 8px;
  background: var(--danger);
  color: #fff;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  transition: background .15s var(--ease), color .15s var(--ease), border-color .15s var(--ease), box-shadow .15s var(--ease);
}
/* Solid at rest so the way out is unmissable; hover only deepens it. */
.modal-close:hover { background: #dc2626; border-color: #dc2626; color: #fff; box-shadow: 0 0 0 4px rgba(239, 68, 68, .18); }
.modal-close:focus-visible { outline: 2px solid var(--danger); outline-offset: 2px; }

.modal h3 { margin: 0 0 4px 0; font-size: 17px; font-weight: 700; letter-spacing: -.01em; }
.modal .modal-sub { font-size: 12.5px; color: var(--muted); margin: 0 0 18px 0; }
.modal-actions { display: flex; justify-content: center; gap: 10px; margin-top: 20px; }

/* Wider modal + two-column field layout, used by forms with many fields. */
.modal.modal-wide { max-width: 720px; }
/* Capped short of the viewport on purpose. At calc(100vh - 40px) a long form —
   an edit modal carrying audit fields and a documents block — grew until it met
   the overlay's own padding, so it read as a full-height panel pinned to the top
   of the screen rather than the centred card the same form shows when adding.
   Leaving ~7vh clear above and below keeps the framing identical either way; the
   extra content scrolls inside the card. The px term stops a very tall monitor
   from stretching the card instead of just centring it. */
.modal.modal-wide { max-height: min(86vh, 900px); overflow-y: auto; }

/* A secondary block inside a modal, for a concern that posts separately from the
   main form — e.g. the policy document uploads, which need their own multipart
   post and so can't live inside the details form. */
.modal-section { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--line-soft); }
.modal-section h4 { margin: 0; font-size: 14px; font-weight: 700; letter-spacing: -.01em; }
.modal-section > p { font-size: 12.5px; color: var(--muted); margin: 4px 0 14px 0; }

/* ---------- Two-pane form modals ----------
   An edit modal that carries files was one long scrolling column: details, then
   the submit row, then the document list and its upload below the fold. Split in
   two, each half fits the card. The strip reuses the .tool-tab visuals from the
   photo editor and the bank account tabs; modalTabs.js does the switching. */
.modal-tabs { margin-bottom: 18px; }
.modal-pane[hidden] { display: none; }

/* The section that opens a pane needs no rule above it — the tab strip already
   separates it. Later sections in the same pane keep their divider. */
.modal-pane.modal-section,
.modal-pane > .modal-section:first-child { margin-top: 0; padding-top: 0; border-top: none; }

/* Splitting the modal shortens the details pane but does not always shorten it
   enough — a record with audit fields still runs ~60px past the cap once the tab
   strip is above it — so the action row pins to the foot of the card instead of
   scrolling out of reach. The negative offsets cancel the card's own 26px padding
   so nothing shows underneath the row while the pane scrolls behind it.

   Only inside a tabbed modal (:has), so every untabbed form — every Add modal
   included — keeps its plain action row, and only above the sheet breakpoint,
   below which .modal is repadded for the safe-area inset and these offsets would
   no longer line up. */
@media (min-width: 861px) {
  .modal:has(.modal-tabs) .modal-pane > .modal-actions {
    position: sticky;
    bottom: -26px;
    margin-bottom: -26px;
    padding: 14px 0 26px;
    background: var(--bg-alt);
    border-top: 1px solid var(--line-soft);
  }
}
.field-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px 18px; }
.field-grid .field-full { grid-column: 1 / -1; }
@media (max-width: 560px) { .field-grid { grid-template-columns: 1fr; } }

/* Extra-wide modal + 3-up field layout, for forms with many short fields
   (Contacts). Steps down to 2 columns then 1 as the viewport narrows — these
   rules follow the .field-grid ones above so they win at equal specificity. */
.modal.modal-xl { max-width: 1200px; max-height: calc(100vh - 40px); overflow-y: auto; }
.field-grid--3 { grid-template-columns: repeat(3, 1fr); }
@media (max-width: 980px) { .field-grid--3 { grid-template-columns: 1fr 1fr; } }
@media (max-width: 620px) { .field-grid--3 { grid-template-columns: 1fr; } }

/* A field that wants more room than one column but shouldn't push the fields
   beside it onto their own row — e.g. a notes box sharing a row with a date.
   Reverts to a single column once the grid itself is down to one. */
.field-grid--3 .field-span-2 { grid-column: span 2; }
@media (max-width: 620px) { .field-grid--3 .field-span-2 { grid-column: auto; } }

/* Monospace inline value, e.g. policy / reference numbers. */
.mono { font-family: "JetBrains Mono", ui-monospace, monospace; font-size: 12.5px; }

/* ---------- Read-only record details modal ----------
   Built by detailModal.js and shared by every list page's View action. Rows
   re-use .field so the vault/licence reveal handlers, which scope their lookup
   to `.field, tr`, resolve to the value inside the modal rather than the row
   behind it — hence the margin reset here rather than a separate class. */
.detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px 18px; }
.detail-grid .detail-item { margin-bottom: 0; min-width: 0; }
.detail-item--wide { grid-column: 1 / -1; }
@media (max-width: 560px) { .detail-grid { grid-template-columns: 1fr; } }

/* Field-heavy records (Tenants carries ~50 fields across nine groups) open on a
   card wider than any of the form modals — read-only values are short, so the
   width buys columns rather than roomier inputs, and the record reads without
   pages of scrolling. Capped against the viewport so it never outgrows the
   screen; detailModal.js picks this class over .modal-wide by field count. */
.modal.modal-detail { max-width: min(1500px, calc(100vw - var(--nav-w) - 60px)); max-height: calc(100vh - 40px); overflow-y: auto; }

/* Every other modal is narrow enough to clear the fixed sidebar on its own; this
   one is not, so its overlay reserves the nav's width and the card centres over
   the content area rather than lying across the menu. Keyed off the card, not
   the overlay's id, so it holds wherever a wide detail card is opened. */
.modal-overlay:has(> .modal-detail) { padding-left: calc(var(--nav-w) + 20px); }

/* Same 3 → 2 → 1 step-down as .field-grid--3, so a record and its edit form
   break at identical widths. */
.detail-grid--3 { grid-template-columns: repeat(3, 1fr); }
@media (max-width: 980px) { .detail-grid--3 { grid-template-columns: 1fr 1fr; } }
@media (max-width: 620px) { .detail-grid--3 { grid-template-columns: 1fr; } }

/* Scrolling cards get the same thin themed bar as .table-scroll — the default
   one is a wide grey slab against the card's own dark surface. */
.modal.modal-wide, .modal.modal-xl, .modal.modal-detail {
  scrollbar-width: thin;
  scrollbar-color: rgba(99, 102, 241, .55) transparent;
}
.modal.modal-wide::-webkit-scrollbar,
.modal.modal-xl::-webkit-scrollbar,
.modal.modal-detail::-webkit-scrollbar { width: 10px; }
.modal.modal-wide::-webkit-scrollbar-track,
.modal.modal-xl::-webkit-scrollbar-track,
.modal.modal-detail::-webkit-scrollbar-track { background: transparent; }
.modal.modal-wide::-webkit-scrollbar-thumb,
.modal.modal-xl::-webkit-scrollbar-thumb,
.modal.modal-detail::-webkit-scrollbar-thumb {
  background: rgba(99, 102, 241, .45);
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: content-box;
}
.modal.modal-wide::-webkit-scrollbar-thumb:hover,
.modal.modal-xl::-webkit-scrollbar-thumb:hover,
.modal.modal-detail::-webkit-scrollbar-thumb:hover {
  background: rgba(99, 102, 241, .7);
  background-clip: content-box;
}

.detail-label {
  display: block;
  margin-bottom: 5px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--muted);
}

.detail-value { display: block; font-size: 13.5px; color: var(--text); overflow-wrap: anywhere; }
.detail-value--mono { font-family: "JetBrains Mono", ui-monospace, monospace; font-size: 12.5px; }
.detail-value--multiline { white-space: pre-wrap; }
.detail-value--link { color: var(--indigo); text-decoration: none; }
.detail-value--link:hover { text-decoration: underline; }

/* Heading for the first group, which sits above the divider rather than after
   one — later groups use the shared .modal-section rule. */
.detail-section__lead { margin: 0 0 12px 0; font-size: 14px; font-weight: 700; letter-spacing: -.01em; }
.detail-section { margin-bottom: 14px; }

.detail-secret-actions { margin-top: 6px; }

/* ---------- Settings / parameter groups ---------- */
.settings-grid { display: grid; grid-template-columns: 230px 1fr; gap: 22px; align-items: start; }

.settings-nav {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 12px; position: sticky; top: 90px;
}
.settings-nav .nav-item { color: var(--text-mid); }

.param-row {
  display: grid;
  grid-template-columns: 1fr 1.2fr;
  gap: 18px;
  align-items: center;
  padding: 16px 0;
  border-bottom: 1px solid var(--line-soft);
}
.param-row:last-child { border-bottom: none; }
.param-row .param-label { font-size: 13.5px; font-weight: 600; }
.param-row .param-desc { font-size: 12px; color: var(--muted); margin-top: 3px; }

.switch { position: relative; display: inline-block; width: 44px; height: 25px; }
.switch input { opacity: 0; width: 0; height: 0; }
.slider {
  position: absolute; cursor: pointer; inset: 0;
  background: var(--line); border-radius: 999px; transition: .2s;
}
.slider:before {
  content: ""; position: absolute; height: 19px; width: 19px; left: 3px; top: 3px;
  background: #fff; border-radius: 50%; transition: .2s;
  box-shadow: 0 2px 6px rgba(0,0,0,.3);
}
.switch input:checked + .slider { background: var(--brand-grad); }
.switch input:checked + .slider:before { transform: translateX(19px); }

.toast {
  position: fixed;
  bottom: 24px;
  right: 24px;
  background: var(--bg-alt);
  border: 1px solid var(--line);
  border-left: 3px solid var(--emerald);
  border-radius: var(--radius-sm);
  padding: 13px 18px;
  font-size: 13px;
  box-shadow: var(--shadow);
  z-index: 200;
  animation: toast-in .2s var(--ease);
}
.toast.error { border-left-color: var(--danger); }
@keyframes toast-in { from { opacity:0; transform: translateY(10px);} to { opacity:1; transform:none; } }

/* ---------- Photo editor ---------- */
.editor-grid { display: grid; grid-template-columns: 1fr 320px; gap: 22px; align-items: start; }

.stage {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  min-height: 420px;
  padding: 18px;
  border-radius: var(--radius-sm);
  background:
    linear-gradient(45deg, var(--surface-2) 25%, transparent 25%, transparent 75%, var(--surface-2) 75%) 0 0 / 20px 20px,
    linear-gradient(45deg, var(--surface-2) 25%, transparent 25%, transparent 75%, var(--surface-2) 75%) 10px 10px / 20px 20px,
    var(--bg-alt);
  border: 1px solid var(--line);
  overflow: hidden;
}
.stage__frame { position: relative; line-height: 0; max-width: 100%; }
.stage canvas {
  display: block;
  max-width: 100%;
  max-height: 58vh;
  border-radius: 4px;
  box-shadow: var(--shadow);
  touch-action: none;
}
.stage--pick canvas { cursor: crosshair; }

.crop-box {
  position: absolute;
  border: 1.5px solid var(--cyan);
  box-shadow: 0 0 0 9999px rgba(6, 8, 18, .62);
  cursor: move;
  touch-action: none;
}
.crop-box::before, .crop-box::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  border-color: rgba(255,255,255,.28); border-style: solid;
}
.crop-box::before { border-width: 0 1px; left: 33.33%; right: 33.33%; }
.crop-box::after  { border-width: 1px 0; top: 33.33%; bottom: 33.33%; }
.crop-handle {
  position: absolute; width: 12px; height: 12px;
  background: var(--cyan); border-radius: 2px;
  touch-action: none;
}
.crop-handle[data-h="nw"] { top: -6px; left: -6px; cursor: nwse-resize; }
.crop-handle[data-h="ne"] { top: -6px; right: -6px; cursor: nesw-resize; }
.crop-handle[data-h="sw"] { bottom: -6px; left: -6px; cursor: nesw-resize; }
.crop-handle[data-h="se"] { bottom: -6px; right: -6px; cursor: nwse-resize; }
.crop-handle[data-h="n"]  { top: -6px; left: 50%; margin-left: -6px; cursor: ns-resize; }
.crop-handle[data-h="s"]  { bottom: -6px; left: 50%; margin-left: -6px; cursor: ns-resize; }
.crop-handle[data-h="w"]  { left: -6px; top: 50%; margin-top: -6px; cursor: ew-resize; }
.crop-handle[data-h="e"]  { right: -6px; top: 50%; margin-top: -6px; cursor: ew-resize; }

.stage-empty {
  text-align: center; color: var(--muted); font-size: 13px;
  padding: 60px 24px; max-width: 420px;
}
.stage-empty svg { width: 44px; height: 44px; color: var(--indigo); margin-bottom: 14px; }
.stage-empty h4 { margin: 0 0 6px 0; font-size: 15px; color: var(--text); font-weight: 700; }
.stage-empty p { margin: 0 0 18px 0; line-height: 1.6; }

.stage-bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-top: 14px;
  font-family: var(--mono); font-size: 11.5px; color: var(--text-mid);
}
.stage-bar b { color: var(--text); font-weight: 600; }

.tool-tabs { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 16px; }
.tool-tab {
  flex: 1 1 auto;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 8px 10px; font: inherit; font-size: 12px; font-weight: 600;
  color: var(--text-mid); background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-sm); cursor: pointer; transition: all .15s var(--ease);
}
.tool-tab svg { width: 14px; height: 14px; }
.tool-tab:hover { color: var(--text); border-color: color-mix(in srgb, var(--indigo) 45%, var(--line)); }
.tool-tab--active { color: var(--text); border-color: var(--indigo); background: rgba(99,102,241,.12); }

.tool-panel { display: none; }
.tool-panel--active { display: block; }
.tool-note { font-size: 11.5px; color: var(--muted); line-height: 1.55; margin: 0 0 14px 0; }

.chip-row { display: flex; flex-wrap: wrap; gap: 7px; margin-bottom: 14px; }
.chip {
  padding: 6px 11px; font: inherit; font-size: 11.5px; font-weight: 600;
  color: var(--text-mid); background: var(--surface); border: 1px solid var(--line);
  border-radius: 999px; cursor: pointer; transition: all .15s var(--ease);
}
.chip:hover { color: var(--text); border-color: var(--cyan); }
.chip--active { color: var(--text); border-color: var(--indigo); background: rgba(99,102,241,.14); }

.slider-row { margin-bottom: 14px; }
.slider-row__head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 6px; }
.slider-row__head label { font-size: 12px; font-weight: 600; color: var(--text-mid); }
.slider-row__head output { font-family: var(--mono); font-size: 11px; color: var(--muted); }
input[type="range"] {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 4px; border-radius: 999px;
  background: var(--surface-2); border: 1px solid var(--line); cursor: pointer;
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 15px; height: 15px; border-radius: 50%;
  background: var(--brand-grad); border: none; cursor: pointer;
  box-shadow: 0 2px 8px -2px rgba(99,102,241,.9);
}
input[type="range"]::-moz-range-thumb {
  width: 15px; height: 15px; border-radius: 50%;
  background: var(--indigo); border: none; cursor: pointer;
}

.dim-row { display: grid; grid-template-columns: 1fr auto 1fr; gap: 8px; align-items: end; }
.dim-row .field { margin-bottom: 0; }
.dim-lock {
  height: 40px; width: 34px; display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-sm);
  color: var(--muted); cursor: pointer; transition: all .15s var(--ease);
}
.dim-lock svg { width: 14px; height: 14px; }
.dim-lock--on { color: var(--indigo); border-color: var(--indigo); background: rgba(99,102,241,.12); }

.swatch-row { display: flex; flex-wrap: wrap; gap: 7px; align-items: center; margin-bottom: 14px; }
.swatch {
  width: 26px; height: 26px; padding: 0; border-radius: 7px;
  border: 1px solid var(--line); cursor: pointer; transition: transform .15s var(--ease);
}
.swatch:hover { transform: scale(1.12); }
.swatch--active { box-shadow: 0 0 0 2px var(--bg-alt), 0 0 0 4px var(--indigo); }
.swatch--none {
  background:
    linear-gradient(45deg, var(--surface-2) 25%, transparent 25%, transparent 75%, var(--surface-2) 75%) 0 0 / 10px 10px,
    linear-gradient(45deg, var(--surface-2) 25%, transparent 25%, transparent 75%, var(--surface-2) 75%) 5px 5px / 10px 10px,
    var(--bg-alt);
}
input[type="color"] {
  width: 26px; height: 26px; padding: 0; background: none;
  border: 1px solid var(--line); border-radius: 7px; cursor: pointer;
}
input[type="color"]::-webkit-color-swatch-wrapper { padding: 2px; }
input[type="color"]::-webkit-color-swatch { border: none; border-radius: 5px; }

.tool-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 16px; }
.tool-actions .btn { flex: 1 1 0; justify-content: center; }

.size-result {
  margin-top: 14px; padding: 12px 13px; border-radius: var(--radius-sm);
  background: var(--surface); border: 1px solid var(--line);
  font-size: 12px; color: var(--text-mid); line-height: 1.6;
}
.size-result b { color: var(--text); }
.size-result--warn { border-color: rgba(245,158,11,.4); background: rgba(245,158,11,.08); }

@media (max-width: 1100px) {
  .editor-grid { grid-template-columns: 1fr; }
}

/* ---------- Photo editor: stage overlay, zoom and panels ----------
   ID-scoped so the shared .stage rules above keep working unchanged on the
   other canvas pages (Passport Photo). */

/* Zooming needs the stage to scroll rather than clip, and the canvas to escape
   the max-width/max-height clamp that fits it by default. */
#stage { overflow: auto; }
#stageFrame { margin: auto; }
#viewCanvas, #drawLayer { max-width: none; max-height: none; }

.draw-layer {
  position: absolute; top: 0; left: 0;
  box-shadow: none !important;
  background: transparent;
  pointer-events: none;
}
.stage--draw .draw-layer { pointer-events: auto; cursor: crosshair; }
.stage--text .draw-layer { pointer-events: auto; cursor: grab; }
.stage--text .draw-layer:active { cursor: grabbing; }
.stage--pick .draw-layer { pointer-events: none; }

/* Crop guide overlays. The default (thirds) is the ::before/::after pair above;
   these variants re-point the same two pseudo-elements. */
.crop-box--none::before, .crop-box--none::after { display: none; }
.crop-box--center::before { border-width: 0 1px; left: 50%; right: 50%; }
.crop-box--center::after { border-width: 1px 0; top: 50%; bottom: 50%; }
.crop-box--grid::before, .crop-box--grid::after { border: none; }
.crop-box--grid::before {
  left: 0; right: 0;
  background-image:
    repeating-linear-gradient(to right, rgba(255,255,255,.22) 0 1px, transparent 1px 25%),
    repeating-linear-gradient(to bottom, rgba(255,255,255,.22) 0 1px, transparent 1px 25%);
}

.zoom-bar {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  margin-top: 10px;
}
.zoom-bar__label {
  min-width: 54px; text-align: center;
  font-family: var(--mono); font-size: 11.5px; color: var(--muted);
}

.shortcuts-card {
  margin-top: 12px; padding: 14px 15px;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface);
}
.shortcuts-card__grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 9px 18px;
}
.shortcuts-card__grid > div { display: flex; align-items: center; gap: 5px; font-size: 12px; color: var(--text-mid); }
.shortcuts-card__grid span { margin-left: 5px; color: var(--muted); }
kbd {
  display: inline-block; min-width: 20px; padding: 2px 6px;
  border: 1px solid var(--line); border-bottom-width: 2px; border-radius: 4px;
  background: var(--bg-alt); color: var(--text);
  font-family: var(--mono); font-size: 10.5px; text-align: center; line-height: 1.5;
}

.restore-bar {
  display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap;
  margin-bottom: 18px; padding: 13px 16px;
  border: 1px solid rgba(99,102,241,.4); border-radius: var(--radius-sm);
  background: rgba(99,102,241,.09);
}
.restore-bar__title { font-size: 13.5px; font-weight: 700; }
.restore-bar__text { font-size: 12px; color: var(--muted); margin-top: 3px; }
.restore-bar__actions { display: flex; gap: 8px; }

.tool-group {
  margin: 20px 0 11px; font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .07em; color: var(--muted);
}
.tool-group:first-child { margin-top: 0; }

.histogram {
  display: block; width: 100%; height: 70px;
  margin-bottom: 14px; border-radius: var(--radius-sm);
  background: var(--bg-alt); border: 1px solid var(--line);
}

.filter-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 9px; }
.filter-tile {
  display: flex; flex-direction: column; gap: 6px; align-items: stretch;
  padding: 0; border: none; background: none; cursor: pointer;
}
.filter-tile__thumb {
  display: block; height: 58px; border-radius: var(--radius-sm);
  background: var(--surface-2) center / cover no-repeat;
  border: 2px solid transparent; transition: border-color .15s, transform .15s;
}
.filter-tile:hover .filter-tile__thumb { transform: translateY(-2px); }
.filter-tile--active .filter-tile__thumb { border-color: var(--indigo); }
.filter-tile__label { font-size: 11px; color: var(--muted); text-align: center; }
.filter-tile--active .filter-tile__label { color: var(--text); font-weight: 600; }

.batch-list {
  max-height: 220px; overflow-y: auto;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--bg-alt);
}
.batch-item {
  display: grid; grid-template-columns: 1fr auto auto; gap: 10px; align-items: center;
  padding: 9px 12px; border-bottom: 1px solid var(--line); font-size: 12px;
}
.batch-item:last-child { border-bottom: none; }
.batch-item__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.batch-item__meta { color: var(--muted); font-family: var(--mono); font-size: 11px; }
.batch-item__status { color: var(--text-mid); font-size: 11px; white-space: nowrap; }

@media (max-width: 640px) {
  .filter-grid { grid-template-columns: repeat(2, 1fr); }
  .shortcuts-card__grid { grid-template-columns: 1fr; }
}

/* ---------- Logo / splash screen ---------- */
.logo-screen {
  position: relative;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 26px;
  background: var(--bg);
  padding: 20px 20px 84px 20px;
  overflow: hidden;
}

.logo-screen__theme-btn { position: absolute; top: 22px; right: 22px; z-index: 2; }

.logo-screen__brand {
  position: relative; z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  text-align: center;
  animation: logo-rise .7s var(--ease) both;
}

.logo-screen__mark {
  position: relative;
  width: 92px;
  height: 92px;
  border-radius: 26px;
  background: var(--brand-grad);
  display: grid;
  place-items: center;
  color: #fff;
  font-weight: 900;
  font-size: 34px;
  letter-spacing: -.03em;
  box-shadow: 0 26px 60px -16px rgba(99, 102, 241, .75);
  animation: logo-float 4.5s var(--ease) infinite alternate;
}

.logo-screen__mark::after {
  content: '';
  position: absolute; inset: -10px;
  border-radius: 34px;
  border: 1px solid var(--indigo);
  opacity: 0;
  animation: logo-pulse 2.6s var(--ease) infinite;
}

.logo-screen__title {
  margin: 0;
  font-size: 32px;
  font-weight: 800;
  letter-spacing: -.03em;
}

.logo-screen__sub {
  margin: 0;
  color: var(--text-mid);
  font-size: 14px;
}

.logo-screen__bar {
  position: relative; z-index: 1;
  width: 190px;
  height: 3px;
  border-radius: 3px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  overflow: hidden;
}

.logo-screen__bar span {
  display: block;
  height: 100%;
  width: 40%;
  border-radius: 3px;
  background: var(--brand-grad);
  animation: logo-bar 1.4s var(--ease) infinite;
}

.logo-screen__status {
  position: relative; z-index: 1;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted);
}

.logo-screen__skip { position: relative; z-index: 1; }

@keyframes logo-rise { from { opacity: 0; transform: translateY(14px) scale(.96); } to { opacity: 1; transform: none; } }
@keyframes logo-float { to { transform: translateY(-10px); } }
@keyframes logo-pulse { 0% { opacity: .55; transform: scale(1); } 100% { opacity: 0; transform: scale(1.28); } }
@keyframes logo-bar { 0% { transform: translateX(-110%); } 100% { transform: translateX(360%); } }

@media (max-width: 860px) {
  .settings-grid { grid-template-columns: 1fr; }
  .sidebar { z-index: 50; transform: translateX(-100%); transition: transform .25s var(--ease); }
  .sidebar.open { transform: translateX(0); }
  /* Off-canvas: the nav takes no layout width, so .main and the wide detail
     modal both go full-bleed. */
  :root { --nav-w: 0px; }
  .param-row { grid-template-columns: 1fr; }
  .app-footer { left: 0; min-height: 0; padding: 6px 0; flex-direction: column; gap: 2px; }
  /* Three zone clocks wrap to three lines on a phone and eat a chunk of the
     viewport. Keep them on one row and pan that row automatically instead of
     asking for a swipe. The track is two identical copies wide, so sliding it
     by 50% lands copy two exactly where copy one started and the loop closes
     invisibly. Edges are masked so text enters and leaves as a fade. */
  .clock {
    width: 100%;
    justify-content: flex-start;
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 12px, #000 calc(100% - 12px), transparent);
    mask-image: linear-gradient(90deg, transparent 0, #000 12px, #000 calc(100% - 12px), transparent);
  }
  .clock-track { animation: clock-marquee 22s linear infinite; }
  /* The 14px sides double as the gap between the two copies, so the spacing at
     the seam matches the spacing anywhere else in the loop. */
  .clock-copy { flex-wrap: nowrap; padding: 0 14px; }
  .clock-copy--dup { display: flex; }
  /* Hold it still while it is being read. */
  .clock:hover .clock-track,
  .clock:active .clock-track { animation-play-state: paused; }

  .content { padding-bottom: 72px; }
}

/* Perpetual motion in the chrome is exactly what this setting is for. Drop the
   marquee and hand the row back as a manual side-scroll. */
@media (max-width: 860px) and (prefers-reduced-motion: reduce) {
  .clock { overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
  .clock::-webkit-scrollbar { display: none; }
  .clock-track { animation: none; }
  .clock-copy--dup { display: none; }
}

/* ---------- Server-rendered pages (Expenses reference pattern) ---------- */

/* Stat-card accent modifiers (replace the former inline --c style attribute). */
.stat-card--indigo { --c: var(--indigo); }
.stat-card--cyan { --c: var(--cyan); }
.stat-card--amber { --c: var(--amber); }
.stat-card--violet { --c: var(--violet); }
.stat-card--green { --c: var(--emerald); }
.stat-card--red { --c: var(--danger); }

/* Two-column add/edit form; full-width members span both tracks. */
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; }
.form-grid .validation-summary,
.form-grid .photo-upload,
.form-grid .field-full,
.form-grid .form-actions { grid-column: 1 / -1; }
@media (max-width: 620px) { .form-grid { grid-template-columns: 1fr; } }

/* Read-only audit metadata shown when editing: a full-width strip of four
   equal columns (12-col system, each field spans 3), divided from the
   editable fields above. Collapses to 2, then 1 column on narrow screens. */
.form-grid .audit-grid {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0 16px;
  margin-top: 6px;
  padding-top: 18px;
  border-top: 1px solid var(--line);
}
@media (max-width: 860px) { .form-grid .audit-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 520px) { .form-grid .audit-grid { grid-template-columns: 1fr; } }

.form-actions { display: flex; justify-content: center; gap: 10px; margin-top: 6px; }

/* Validation messages (server-side ModelState + native client-side constraints). */
.field-error { display: block; margin-top: 5px; color: var(--danger); font-size: 12px; }
/* Neutral sibling of .field-error: explains what a control did without reading as a
   validation failure. */
.field-hint { display: block; margin-top: 5px; color: var(--muted); font-size: 12px; }
.validation-summary { color: var(--danger); font-size: 13px; margin-bottom: 12px; }
.validation-summary ul { margin: 0; padding-left: 18px; }
.validation-summary-valid { display: none; }

/* Required marker.

   Driven off data-val-required, which the tag helpers emit onto the input whenever the
   model carries [Required]. Marking labels by hand instead would mean every future change
   to a [Required] attribute needing a matching edit in the markup, and the two would
   drift — a field would claim to be optional and then refuse to save. This cannot. */
/* Descendant, not direct child. customSelect.js moves every <select> inside a
   .cs-select wrapper for theming, and the password-style fields nest their input in
   .pw-field, so a `>` selector matches plain inputs and silently misses every dropdown —
   which is the one place a required marker matters most, since a dropdown gives no other
   hint that "— Not set —" will be rejected. */
.field:has(input[data-val-required]) > label::after,
.field:has(select[data-val-required]) > label::after,
.field:has(textarea[data-val-required]) > label::after,
.field > label.label-required::after {
  content: " *";
  color: var(--danger);
  font-weight: 600;
}

/* The clear-the-stored-value checkbox is a label too, and it must not inherit the marker
   from a required field it happens to sit inside. */
.field .checkbox-row::after { content: none !important; }

/* Written by validate.js. The message span is styled by .field-error already; these
   two carry the state so a field can be marked without the markup changing shape,
   and so an empty message span occupies no vertical space. */
.field-validation-error { display: block; margin-top: 5px; color: var(--danger); font-size: 12px; }
.field-validation-valid:empty { display: none; }

.input-validation-error,
.input-validation-error:focus {
  border-color: var(--danger);
  outline-color: var(--danger);
}

/* On an enhanced dropdown the class lands on the .cs-select wrapper, so the outline has
   to be drawn on the trigger the user can actually see. */
.cs-select.input-validation-error .cs-trigger {
  border-color: var(--danger);
  outline-color: var(--danger);
}

/* Flash messages from TempData. */
.alert {
  border: 1px solid var(--line);
  border-left-width: 3px;
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  margin-bottom: 18px;
  font-size: 14px;
}
.alert-success { border-left-color: var(--emerald); background: rgba(16, 185, 129, .10); }
.alert-error { border-left-color: var(--danger); background: rgba(239, 68, 68, .10); }

/* Inline note under a table cell's primary text. */
.cell-note { margin-top: 2px; color: var(--text-mid); font-size: 11px; }

/* Placeholder for a table cell with nothing in it. */
.cell-muted { color: var(--muted); }

/* ---------- Price Calculator ---------- */

/* Base rate reads as one sentence — item, quantity, unit, price — so the fields
   are sized to their content rather than split evenly. The note sits underneath
   on its own row. */
.calc-base-grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr minmax(120px, auto) 1fr;
  gap: 14px 18px;
  align-items: end;
}
.calc-base-grid .field { margin-bottom: 0; }
.calc-base-grid__note { grid-column: 1 / -1; }
@media (max-width: 860px) { .calc-base-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .calc-base-grid { grid-template-columns: 1fr; } }

.calc-summary { margin-top: 14px; }
.calc-audit { margin-top: 18px; }

.whatif-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 720px) { .whatif-grid { grid-template-columns: 1fr; } }

.whatif-box {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 16px 18px;
}
.whatif-q { font-size: 12.5px; color: var(--muted); font-weight: 600; margin-bottom: 10px; }
.whatif-row { display: flex; gap: 8px; align-items: center; }
.whatif-row input { max-width: 130px; }
.whatif-unit { font-size: 13px; color: var(--text-mid); font-weight: 600; }

/* Unit picker sitting inline beside a what-if figure — sized to its content so it
   reads as part of the sentence rather than a full-width form control. The rule
   targets the themed wrapper customSelect.js builds around the native select. */
.whatif-row .cs-select,
.whatif-a .cs-select { width: auto; min-width: 92px; }
.whatif-a .cs-select { display: inline-block; vertical-align: middle; margin-left: 6px; }
.whatif-a .cs-trigger { padding-top: 6px; padding-bottom: 6px; }

.whatif-a { margin-top: 12px; font-size: 14px; color: var(--text-mid); }
.whatif-a strong {
  font-size: 20px;
  font-weight: 800;
  color: var(--text);
  letter-spacing: -.01em;
  margin-left: 4px;
}

.quick-table { margin-top: 18px; }
.quick-table table { width: 100%; }

/* ---------- Public price calculator (/cal) ----------
   The signed-out twin. It borrows the login screen's aurora rather than the app
   shell — the sidebar would hand a stranger the whole feature list — but its
   content is the app's ordinary panels and stat cards, so only the page frame
   below is new. Wider than .auth-card, which is sized for a single form. */
.public-wrap {
  position: relative;
  /* dvh for the same reason as .auth-wrap: with 100vh alone a mobile browser
     sizes against the chrome-less viewport and the page scrolls by the height
     of the address bar even when nothing overflows. */
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--bg);
  padding: 22px 20px calc(40px + env(safe-area-inset-bottom, 0px));
  overflow: hidden;
}

.public-page { position: relative; z-index: 1; width: 100%; max-width: 1000px; margin: 0 auto; }

.public-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 24px;
}

.public-brand { display: flex; align-items: center; gap: 13px; min-width: 0; }
.public-brand .brand-mark { width: 42px; height: 42px; }
.public-brand .brand-wordmark {
  display: block;
  font-size: 21px;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.15;
}
.public-head__sub { font-size: 12px; color: var(--muted); margin-top: 1px; }

/* The app's own ≤860px block is not scoped to .content, so the panels, stat
   cards and tables on this page already collapse with the rest of the app. Only
   the pieces that assume the app shell — or that this page uses differently —
   are retuned below. */
@media (max-width: 860px) {
  .public-wrap { padding: 16px 14px calc(32px + env(safe-area-inset-bottom, 0px)); }

  .public-head { margin-bottom: 18px; }
  .public-brand { gap: 11px; }
  .public-brand .brand-mark { width: 36px; height: 36px; }
  .public-brand .brand-wordmark { font-size: 19px; }

  /* .toolbar goes width:100% at this width so a page's filter bar stacks. This
     page's toolbar is a single small button, and full width turns it into a
     stray row of its own under the heading — so it keeps its content width and
     stays beside the title.

     The heading block needs a flex-basis of 0, not auto, and it is not a detail:
     .panel-head wraps, and wrapping breaks lines on each item's *unshrunk* size.
     At `auto` that is the description set on a single line, which alone overflows
     a phone — so the heading claims the whole line and the button is pushed onto
     the next one before any shrinking gets a chance to happen. */
  .public-wrap .panel-head > div:first-child { flex: 1 1 0; min-width: 0; }
  .public-wrap .panel-head .toolbar { width: auto; flex: none; }

  /* Touch targets. The inline unit picker is deliberately short on desktop so it
     sits inside the sentence; a finger needs more than 31px of it. */
  .public-wrap .whatif-a .cs-trigger,
  .public-wrap .whatif-row .cs-trigger { padding-top: 10px; padding-bottom: 10px; }
  .public-wrap .btn-sm { padding-top: 9px; padding-bottom: 9px; }

  /* iOS zooms the viewport when focusing an input under 16px. The app's guard is
     scoped to .content and .modal, neither of which is on this page, and this
     page is form boxes end to end. */
  .public-wrap input:not([type="checkbox"]):not([type="radio"]),
  .public-wrap select,
  .public-wrap textarea { font-size: 16px; }

  /* The what-if boxes are capped at 130px so they read as part of a sentence on
     a wide screen. Once the two boxes stack, that cap leaves each row two thirds
     empty, so let the input take the space the unit picker does not. */
  .public-wrap .whatif-row { flex-wrap: nowrap; }
  .public-wrap .whatif-row input { max-width: none; flex: 1 1 auto; min-width: 0; }
}

@media (max-width: 520px) {
  /* The two cards fill the app's `1fr 1fr` row exactly, so nothing is spanned
     here — an odd card would need `grid-column: 1 / -1` to avoid sitting
     half-width beside a gap. */

  /* The answer and its unit picker are one sentence — "I get 12.5 KG" — and it
     wraps mid-phrase at this width unless the strong and the select are allowed
     to fall together onto their own line. */
  .public-wrap .whatif-a { display: flex; align-items: center; flex-wrap: wrap; gap: 4px; }
  .public-wrap .whatif-a strong { margin-left: 0; }
  .public-wrap .whatif-a .cs-select { margin-left: 0; }
}

/* Keep the row delete form from breaking the actions layout. */
.inline-form { display: inline; margin: 0; }

/* Buttons that submit native forms but must look like the links they replace. */
.user-dropdown__item--button {
  width: 100%;
  background: none;
  border: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.sidebar-user form { margin: 0; }
.user-chip__email { margin-top: 1px; }

/* ==========================================================================
   Responsive / mobile
   --------------------------------------------------------------------------
   The shell is desktop-first: a fixed 250px sidebar with .main offset by the
   same amount. Below 860px the sidebar becomes an off-canvas drawer — the
   .sidebar / .sidebar.open transform lives with the settings breakpoint
   further up; the pieces below are the trigger, the scrim, and the per-page
   density changes that go with them.
   ========================================================================== */

/* Hamburger — the only way to reach the nav once the sidebar goes off-canvas,
   so it is hidden on desktop and revealed at the same breakpoint. */
.menu-btn {
  display: none;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  flex: none;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  color: var(--text-mid);
  cursor: pointer;
  transition: color .15s, border-color .15s, background .15s;
}
.menu-btn:hover { color: var(--text); border-color: var(--line); background: var(--surface); }
.menu-btn svg { width: 19px; height: 19px; }

/* ---------- icon rail ----------
   `data-nav="rail"` shrinks the sidebar to icons only; hovering it floats the
   full-width nav back over the page. The sidebar is position:fixed and .main
   keeps its narrow offset, so nothing behind it reflows on hover — only the
   toggle changes the layout. Desktop only: below 860px the sidebar is already
   an off-canvas drawer, where a rail would mean two competing collapse modes. */
/* Compounded with .menu-btn so the mobile `.menu-btn { display: inline-flex }`
   rule further down cannot win this back on narrow screens. */
.menu-btn.rail-btn { display: none; }

@media (min-width: 861px) {
  .menu-btn.rail-btn { display: inline-flex; }

  /* The collapsed width only — the rail's :hover expansion overlays the content
     rather than reflowing it, so the offset must not follow it. */
  html[data-nav="rail"] { --nav-w: 68px; }
  html[data-nav="rail"] .sidebar { width: 68px; padding-left: 10px; padding-right: 10px; }
  html[data-nav="rail"] .sidebar:hover {
    width: 250px;
    padding-left: 14px;
    padding-right: 14px;
    box-shadow: 18px 0 40px -18px rgba(0, 0, 0, .6);
  }

  /* Collapsed: fold everything but the icons away. Nav labels are bare text
     nodes inside .nav-item, so font-size:0 is what actually hides them — the
     icons survive because .ic carries an explicit px size. */
  html[data-nav="rail"] .sidebar:not(:hover) .brand strong,
  html[data-nav="rail"] .sidebar:not(:hover) .sidebar-user__text,
  html[data-nav="rail"] .sidebar:not(:hover) .sidebar-user__logout,
  html[data-nav="rail"] .sidebar:not(:hover) .nav-caret,
  html[data-nav="rail"] .sidebar:not(:hover) .nav-sub { display: none; }

  html[data-nav="rail"] .sidebar:not(:hover) .brand,
  html[data-nav="rail"] .sidebar:not(:hover) .sidebar-user,
  html[data-nav="rail"] .sidebar:not(:hover) .nav-item {
    justify-content: center;
    gap: 0;
    padding-left: 0;
    padding-right: 0;
  }

  html[data-nav="rail"] .sidebar:not(:hover) .nav-item { font-size: 0; }
  html[data-nav="rail"] .sidebar:not(:hover) .sidebar-user__link { flex: none; }

  /* The scrollbar gutter would knock the centred icons off-axis. */
  html[data-nav="rail"] .sidebar:not(:hover) > nav { margin-right: 0; padding-right: 0; }
}

/* Groups the hamburger with the page title so .topbar keeps its
   space-between split between "left" and .topbar-right. */
.topbar-left { display: flex; align-items: center; gap: 12px; min-width: 0; }
.topbar-left h2 { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Dismiss layer behind the open drawer. Sits above .content but below
   .sidebar (z-index 50 at this breakpoint). Kept in the DOM and toggled by
   opacity/visibility so it can transition in both directions. */
.sidebar-scrim {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: rgba(5, 7, 10, .6);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  opacity: 0;
  visibility: hidden;
  transition: opacity .25s var(--ease), visibility .25s var(--ease);
}
.sidebar-scrim.show { opacity: 1; visibility: visible; }

/* Stops the page behind the drawer from scrolling while it is open. */
body.nav-open { overflow: hidden; }

/* Horizontal scroll container for wide data tables. Applied automatically by
   wrapTables() in common.js, so every page — server-rendered or JS-rendered,
   present or future — gets it without touching the markup. */
.table-scroll {
  width: 100%;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
}

@media (max-width: 860px) {
  .menu-btn { display: inline-flex; }

  /* Below this width the table would otherwise crush its columns to
     unreadable widths; give it a floor and let .table-scroll pan. */
  .table-scroll > table { min-width: 720px; }

  .topbar { padding: 0 14px; gap: 10px; }
  .topbar h2 { font-size: 15px; }
  /* The long-form date ("Saturday, 18 July 2026") crowds out the title. */
  .topbar .meta { display: none; }
  .topbar-right { gap: 8px; }

  /* Collapse the user chip to just its avatar — the name and email are both
     available on the profile page the chip links to. */
  .user-chip { padding: 4px; gap: 0; }
  .user-chip > div:not(.avatar) { display: none; }
  .user-chip__chevron { display: none; }

  .content { padding: 16px 14px 96px 14px; }
  .panel { padding: 16px; }
  .panel-head { align-items: flex-start; }

  /* Filter bars go full-width and stack rather than overflowing. The inline
     min-width some pages set on the search box has to be overridden. */
  .toolbar { width: 100%; }
  .toolbar form { width: 100%; }
  .toolbar input, .toolbar select { flex: 1 1 100%; min-width: 0 !important; }

  .grid-cards { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin-bottom: 16px; }
  .stat-card { padding: 15px; }
  .stat-card .value { font-size: 21px; }

  /* Modals become bottom sheets: full-width, anchored to the bottom edge and
     capped so the form scrolls inside rather than running off-screen.

     The height is pinned in dvh, not vh. A phone browser measures vh against
     the tallest possible viewport — the one it only reaches once the URL bar
     has scrolled away — so a bottom-anchored 100vh overlay hangs below the
     visible area and takes the sheet's action row with it, under the toolbar.
     dvh tracks the live viewport instead. The vh line stays as the fallback
     for engines without dvh; the dvh line overrides it where supported. */
  .modal-overlay {
    padding: 0;
    align-items: flex-end;
    height: 100vh;
    height: 100dvh;
    overscroll-behavior: contain;
  }
  /* The wide detail card reserves the sidebar's width on desktop. There is no
     sidebar in the layout at this width, and the sheet is full-bleed, so the
     reservation would only push the card off-centre. */
  .modal-overlay:has(> .modal-detail) { padding-left: 0; }
  /* .modal-detail is in this list deliberately: left out, it kept its desktop
     width cap, centred alignment and all-round radius, and read as a floating
     card clipped by the screen instead of a sheet. */
  .modal,
  .modal.modal-wide,
  .modal.modal-xl,
  .modal.modal-detail {
    max-width: 100%;
    max-height: 92vh;
    max-height: 92dvh;
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    /* env() clears the home indicator / gesture bar so the last button in a
       scrolled-to-bottom sheet is still tappable. */
    padding: 20px 16px calc(24px + env(safe-area-inset-bottom, 0px));
    border-radius: var(--radius) var(--radius) 0 0;
  }
  .modal-actions { flex-direction: column-reverse; }
  .modal-actions .btn { width: 100%; justify-content: center; }

  .report-grid, .editor-grid { grid-template-columns: 1fr; }

  /* iOS Safari zooms the viewport when focusing an input whose text is under
     16px. Bumping form controls to 16px here keeps entry from jumping. */
  .content input, .content select, .content textarea,
  .modal input, .modal select, .modal textarea { font-size: 16px; }
}

@media (max-width: 520px) {
  .grid-cards { grid-template-columns: 1fr 1fr; }
  .row-actions { flex-wrap: wrap; }
  .panel-head h3 { font-size: 14.5px; }
}

/* ============================================================
   Dashboard — the cross-module landing page.
   Three list primitives (alerts, occasions, activity feed) plus the
   module snapshot grid and the meter reused by the budget and storage
   stat cards. Everything else on the page reuses the shared panel,
   stat-card, bar-chart and donut primitives above.
   ============================================================ */

.dash-greeting { margin: 0 0 20px; }
.dash-greeting h2 { margin: 0; font-size: 22px; font-weight: 800; letter-spacing: -.02em; }
.dash-greeting p { margin: 5px 0 0; font-size: 13px; color: var(--muted); }
.dash-greeting__urgent { color: #ff8a8d; font-weight: 700; }

/* ---- Movement pill: an arrow plus a magnitude, sat beside a stat value ----
   Direction and sentiment are deliberately separate. The arrow follows the
   sign of the change, but the colour follows whether that change is welcome:
   spending more month-on-month points up and reads red, so a single
   "up is green" rule would tell the wrong story here. */
.trend-pill {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  flex: none;
  padding: 3px 8px 3px 6px;
  border-radius: 999px;
  font-size: 11.5px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: .01em;
  line-height: 1;
  white-space: nowrap;
  border: 1px solid transparent;
}
.trend-pill svg { width: 12px; height: 12px; flex: none; }
.trend-pill--good {
  color: #34d399;
  background: color-mix(in srgb, var(--emerald) 14%, transparent);
  border-color: color-mix(in srgb, var(--emerald) 30%, transparent);
}
.trend-pill--bad {
  color: #ff8a8d;
  background: color-mix(in srgb, var(--danger) 14%, transparent);
  border-color: color-mix(in srgb, var(--danger) 30%, transparent);
}
.trend-pill--flat {
  color: var(--muted);
  background: var(--surface-2);
  border-color: var(--line);
}

/* The pill gets its own line under the value rather than sharing a row with it
   or with the caption. At the 210px this card grid can shrink to there is room
   for exactly one of them per line — sharing either row just pushed the other
   into an uneven wrap. */
.stat-card__trend { margin-top: 9px; }

/* Progress meter inside a stat card. Sits under .sub, so the card grows
   rather than the bar overlapping the value. */
.dash-meter {
  margin-top: 11px;
  height: 5px;
  border-radius: 999px;
  background: var(--surface-2);
  overflow: hidden;
}
.dash-meter__fill {
  height: 100%;
  border-radius: inherit;
  background: var(--c, var(--indigo));
  transition: width .5s var(--ease);
}
.dash-meter__fill.is-over { background: var(--danger); }

/* The month the rest of the page is reporting on. Without this every bar reads
   as equally current and the eye has to fall back to the labels to find "now". */
.bar-chart__col.is-current .bar-chart__bar {
  background: linear-gradient(180deg, var(--violet) 0%, var(--indigo) 100%);
  box-shadow: 0 10px 24px -10px rgba(167, 139, 250, .85);
}
.bar-chart__col.is-current .bar-chart__label {
  color: var(--text);
  font-weight: 700;
}
.bar-chart__col.is-current .bar-chart__value { color: var(--text); }

/* Staggered reveal for the headline cards. Scoped to the dashboard rather than
   .grid-cards at large: every module page opens on the same primitive, and a
   cascade there would delay the numbers people navigated straight to. */
.dash-cards > .stat-card {
  animation: dash-card-in .45s var(--ease) both;
  animation-delay: calc(var(--i, 0) * 55ms);
}
@keyframes dash-card-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
/* The global reduce rule collapses the duration but not the delay, which on its
   own would hold the later cards at opacity 0 for a beat before they snap in. */
@media (prefers-reduced-motion: reduce) {
  .dash-cards > .stat-card { animation-delay: 0ms !important; }
}

/* ---- Shared row scaffold for the three dashboard lists ---- */
.dash-alerts, .dash-events, .dash-feed { list-style: none; margin: 0; padding: 0; }

/* Both of these grow with the data — the feed to 12 rows, the alert list to 40 —
   which otherwise stretches its panel far past the one beside it and pushes the
   rest of the page down. Cap them and scroll in place, same approach and same
   scrollbar treatment as .table-scroll. Occasions is left uncapped: it tops out
   at 8 rows by construction. */
.dash-alerts, .dash-feed {
  max-height: var(--dash-scroll-max, 440px);
  overflow-y: auto;
  /* Explicit, because overflow-y:auto on its own forces the other axis from
     visible to auto — and the 2px translateX these rows lean on hover was then
     enough to raise a horizontal scrollbar across the whole panel. Titles and
     meta already ellipsis against min-width:0, so there is nothing to reach. */
  overflow-x: hidden;
  scrollbar-width: thin;
  scrollbar-color: rgba(99, 102, 241, .55) transparent;
}
.dash-alerts::-webkit-scrollbar, .dash-feed::-webkit-scrollbar { width: 10px; }
.dash-alerts::-webkit-scrollbar-track, .dash-feed::-webkit-scrollbar-track { background: transparent; }
.dash-alerts::-webkit-scrollbar-thumb, .dash-feed::-webkit-scrollbar-thumb {
  background: rgba(99, 102, 241, .45);
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: content-box;
}
.dash-alerts::-webkit-scrollbar-thumb:hover, .dash-feed::-webkit-scrollbar-thumb:hover {
  background: rgba(99, 102, 241, .7);
  background-clip: content-box;
}

.dash-alert__link, .dash-event__link, .dash-feed__link {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 11px 12px;
  border-radius: var(--radius-sm);
  color: inherit;
  text-decoration: none;
  transition: background .18s var(--ease), transform .18s var(--ease);
}
.dash-alert__link:hover, .dash-event__link:hover, .dash-feed__link:hover {
  background: var(--surface-2);
  transform: translateX(2px);
}

.dash-alert__icon, .dash-event__icon, .dash-feed__icon {
  flex: none;
  width: 30px;
  height: 30px;
  display: grid;
  place-items: center;
  border-radius: 9px;
  background: var(--surface-2);
  font-size: 15px;
}

.dash-alert__body, .dash-event__body, .dash-feed__body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* Long names truncate rather than pushing the badge off the row. */
.dash-alert__title, .dash-event__name, .dash-feed__title {
  font-size: 13.5px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.dash-alert__detail, .dash-event__meta, .dash-feed__meta {
  font-size: 12px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---- Needs attention ---- */
/* A left rule carries the severity so the row reads as urgent at a glance,
   without tinting the whole background of forty rows. */
.dash-alert { border-left: 2.5px solid transparent; border-radius: var(--radius-sm); }
.dash-alert--overdue  { border-left-color: var(--danger); }
.dash-alert--duesoon  { border-left-color: var(--amber); }
.dash-alert--upcoming { border-left-color: var(--line); }

.dash-alert__source {
  display: inline-block;
  margin-right: 6px;
  padding: 1px 6px;
  border-radius: 5px;
  background: var(--surface-2);
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--text-mid);
}

.dash-alert__amount {
  flex: none;
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--text-mid);
}
.dash-alert__when { flex: none; }

/* ---- Activity feed ---- */
.dash-feed__at {
  flex: none;
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--muted);
}

/* ---- Module snapshot ---- */
.dash-modules {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
  gap: 12px;
}
.dash-module {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 15px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: inherit;
  text-decoration: none;
  transition: transform .2s var(--ease), border-color .2s var(--ease), box-shadow .2s var(--ease);
}
.dash-module:hover {
  transform: translateY(-3px);
  border-color: rgba(99, 102, 241, .45);
  box-shadow: 0 16px 34px -20px rgba(99, 102, 241, .8);
}
.dash-module__icon { font-size: 17px; }
.dash-module__count { font-size: 22px; font-weight: 800; letter-spacing: -.02em; }
.dash-module__label { font-size: 12.5px; font-weight: 600; }
.dash-module__noun {
  font-size: 11px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: .05em;
}

@media (max-width: 620px) {
  /* Amount and timestamp are the first things to go — the title, source and
     severity badge are what make a row actionable on a phone. */
  .dash-alert__amount, .dash-feed__at { display: none; }
  .dash-modules { grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); }
  .dash-greeting h2 { font-size: 19px; }
}

/* ---------- file picker (every input[type=file], enhanced by filePicker.js) ----------
   The native input is not hidden — it is stretched over the drop zone at zero
   opacity. Keeping it in the layout is what makes click, keyboard focus, native
   drag-and-drop and `required` validation all keep working; a display:none input
   would make the browser throw "not focusable" the moment a required upload form
   was submitted empty. */
.filepick { display: block; min-width: 240px; }

/* In a toolbar the picker grows with every file previewed, so the row's default
   centring would float the Upload button and its quota note halfway down the
   list. Pin everything to the top instead and drop it by half a drop-zone so it
   still reads as one row. */
.toolbar .filepick { flex: 1 1 260px; max-width: 420px; }
.toolbar:has(.filepick) { align-items: flex-start; }
.toolbar:has(.filepick) > :not(.filepick) { margin-top: 15px; }

.filepick__drop {
  position: relative;
  display: flex; align-items: center; gap: 11px;
  padding: 12px 14px;
  border: 1.5px dashed var(--line); border-radius: var(--radius-sm);
  background: var(--surface-2);
  transition: border-color .2s, background .2s;
}
.filepick__drop:hover { border-color: color-mix(in srgb, var(--indigo) 45%, var(--line)); }
.filepick__drop:focus-within {
  border-color: var(--indigo); border-style: solid;
  box-shadow: 0 0 0 3px rgba(99, 102, 241, .14);
}
.filepick--dragging .filepick__drop {
  border-color: var(--indigo); background: rgba(99, 102, 241, .08);
}

/* Beats `.toolbar input` / the global input rules — this element is a hit area,
   not a control anyone should see. */
.filepick__input {
  position: absolute; inset: 0; width: 100%; height: 100%;
  min-width: 0; padding: 0; margin: 0; border: 0; background: none;
  opacity: 0; cursor: pointer; z-index: 1;
}
.filepick__input:disabled { cursor: not-allowed; }
/* Family Documents disables the picker while "remove the attached file" is ticked —
   the zone has to read as unavailable, not merely stop responding. */
.filepick:has(.filepick__input:disabled) .filepick__drop { opacity: .5; }
.filepick:has(.filepick__input:disabled) .filepick__drop:hover { border-color: var(--line); }

.filepick__icon { display: flex; color: var(--indigo); flex: none; }
.filepick__icon svg { width: 20px; height: 20px; }
.filepick__text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.filepick__cta { font-size: 13px; font-weight: 600; color: var(--text); }
.filepick__hint {
  font-size: 11.5px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.filepick__list { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.filepick__list:empty { display: none; }

.filepick__item {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface);
}

.filepick__thumb {
  position: relative; flex: none;
  width: 44px; height: 44px; border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-alt); border: 1px solid var(--line);
  overflow: hidden; color: var(--c, var(--muted));
}
.filepick__thumb img { width: 100%; height: 100%; object-fit: cover; }
.filepick__thumb svg { width: 22px; height: 22px; }
.filepick__ext {
  position: absolute; bottom: 2px; left: 50%; transform: translateX(-50%);
  background: var(--c, var(--muted)); color: #fff;
  font-size: 8px; font-weight: 700; font-family: var(--mono);
  padding: 1px 4px; border-radius: 4px; letter-spacing: .02em;
  max-width: 90%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.filepick__meta { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.filepick__name {
  font-size: 13px; font-weight: 500; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.filepick__size { font-size: 11.5px; color: var(--muted); font-family: var(--mono); }

.filepick__remove {
  flex: none; display: flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; padding: 0;
  border: 1px solid var(--line); border-radius: 7px;
  background: transparent; color: var(--muted); cursor: pointer;
  transition: color .15s, border-color .15s, background .15s;
}
.filepick__remove:hover { color: var(--danger); border-color: var(--danger); background: rgba(239, 68, 68, .1); }
.filepick__remove svg { width: 13px; height: 13px; }

/* ============================================================
   Events calendar (/Events)
   A hand-built month grid — the app ships no JS dependencies, so
   there is no calendar library to lean on. Colours come from the
   same tokens as everything else, so it themes for free.
   ============================================================ */

.badge-violet { background: rgba(167,139,250,.14); color: #c0aaff; }

.cal-nav { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.cal-title { margin: 0; font-size: 15.5px; font-weight: 700; letter-spacing: -.01em; }
/* The month steppers are square so ‹ › do not sit on differently sized hit areas. */
.cal-nav .icon-btn { min-width: 30px; justify-content: center; font-size: 15px; line-height: 1; }

.cal-legend { display: flex; gap: 14px; flex-wrap: wrap; margin: 0 0 14px 0; }
.cal-key {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 11.5px; color: var(--text-mid); font-family: var(--mono);
}
.cal-key::before { content: ''; width: 9px; height: 9px; border-radius: 3px; background: var(--k); }
.cal-key--event { --k: var(--indigo); }
.cal-key--birthday { --k: var(--amber); }
.cal-key--anniversary { --k: var(--violet); }

/* Seven columns cannot usefully compress to a phone, so the grid keeps its minimum
   and scrolls sideways in here rather than squeezing the page body. */
.cal-scroll { overflow-x: auto; margin: 0 -4px; padding: 0 4px 4px; }

.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(122px, 1fr));
  gap: 1px;
  min-width: 860px;
  background: var(--line-soft);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.cal-dow {
  background: var(--surface-2);
  padding: 9px 10px;
  font-size: 10.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted); font-family: var(--mono); text-align: center;
}

.cal-day {
  background: var(--surface);
  min-height: 116px;
  padding: 7px 8px 9px;
  display: flex; flex-direction: column; gap: 6px;
  transition: background .15s;
}
.cal-day:hover { background: var(--surface-2); }
/* Leading/trailing days from the neighbouring months: present for context, receded
   so the month in view still reads as a block. */
.cal-day--outside { opacity: .42; }
.cal-day--weekend .cal-day__num { color: var(--pink); }

.cal-day--today { background: color-mix(in srgb, var(--indigo) 12%, transparent); }
.cal-day--today .cal-day__num {
  background: var(--brand-grad); color: #fff;
  width: 23px; height: 23px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
}

.cal-day__head { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.cal-day__num { font-size: 12.5px; font-weight: 700; font-family: var(--mono); color: var(--text); }

/* Revealed on hover so 42 plus-signs do not compete with the content; still
   keyboard-reachable, which is why it un-hides on focus too. */
.cal-day__add {
  opacity: 0;
  width: 19px; height: 19px; border-radius: 6px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 14px; line-height: 1; text-decoration: none;
  color: var(--muted); border: 1px solid var(--line);
  transition: opacity .15s, color .15s, border-color .15s;
}
.cal-day:hover .cal-day__add,
.cal-day__add:focus-visible { opacity: 1; }
.cal-day__add:hover { color: var(--cyan); border-color: var(--cyan); }

.cal-day__items { display: flex; flex-direction: column; gap: 4px; min-width: 0; }

.cal-chip {
  --c: var(--indigo);
  display: flex; align-items: baseline; gap: 5px;
  width: 100%; min-width: 0;
  padding: 4px 7px;
  border: none; border-left: 3px solid var(--c);
  border-radius: 5px;
  background: color-mix(in srgb, var(--c) 15%, transparent);
  color: var(--text);
  font-family: var(--font); font-size: 11.5px; font-weight: 500;
  text-align: left; cursor: pointer;
  transition: background .15s, transform .15s;
}
.cal-chip:hover { background: color-mix(in srgb, var(--c) 28%, transparent); transform: translateX(2px); }
.cal-chip__icon { flex: none; font-size: 11px; }
.cal-chip__text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cal-chip__time { flex: none; font-family: var(--mono); font-size: 9.5px; color: var(--text-mid); }
/* Day two onwards of a multi-day event — same colour, hollow, so the start day is
   still identifiable at a glance. */
.cal-chip--cont { background: transparent; border-left-style: dashed; }

.cal-chip--event { --c: var(--indigo); }
.cal-chip--birthday { --c: var(--amber); }
.cal-chip--anniversary { --c: var(--violet); }

.cal-more { font-size: 10.5px; color: var(--muted); font-family: var(--mono); padding-left: 3px; }

.cal-row--today td { background: color-mix(in srgb, var(--indigo) 9%, transparent); }

/* ---------- Banking Vault ---------- */

/* Account tiles on the Banking index — the same visual role .file-card plays for
   Files, but carrying a financial summary instead of a thumbnail. */
.bank-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); gap: 16px; }
.bank-tile {
  position: relative;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 18px; display: flex; flex-direction: column; gap: 10px;
  transition: transform .2s var(--ease), border-color .2s, box-shadow .2s;
}
.bank-tile:hover { transform: translateY(-3px); border-color: color-mix(in srgb, var(--indigo) 45%, var(--line)); box-shadow: 0 16px 32px -18px rgba(99,102,241,.5); }
.bank-tile__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.bank-tile__name { font-size: 15px; font-weight: 700; letter-spacing: -.01em; }
.bank-tile__bank { font-size: 12px; color: var(--muted); margin-top: 2px; }
.bank-tile__lock { color: var(--amber); flex: none; }
.bank-tile__lock svg { width: 18px; height: 18px; display: block; }
.bank-tile__number { font-family: var(--mono); font-size: 12.5px; color: var(--text-mid); letter-spacing: .04em; }
.bank-tile__balance { font-size: 23px; font-weight: 800; letter-spacing: -.02em; }
.bank-tile__balance--negative { color: var(--danger); }
.bank-tile__chips { display: flex; flex-wrap: wrap; gap: 6px; }
.bank-tile__counts {
  display: flex; flex-wrap: wrap; gap: 8px 14px; font-size: 11.5px; color: var(--text-mid);
  border-top: 1px solid var(--line-soft); padding-top: 10px;
}
.bank-tile__counts b { color: var(--text); font-family: var(--mono); font-weight: 600; }
.bank-tile__actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: auto; padding-top: 4px; }

/* Reveal-gated secret field inside a form: the input sits above a read-only
   masked preview that carries the same Reveal/Copy affordance the vault tables
   use, plus a "clear stored value" checkbox — hidden until there is something to
   clear (toggled by banking.js when a row is loaded into the form for editing). */
.secret-field { margin-bottom: 16px; }
.secret-field__stored { margin-top: 6px; font-size: 12px; color: var(--text-mid); display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.secret-field__clear { margin-top: 6px; }

/* Dual-series (credit/debit) monthly bars, sharing .bar-chart's grid/track. */
.bar-chart__track--dual { gap: 3px; }
.bar-chart__bar--credit { background: linear-gradient(180deg, var(--emerald) 0%, #34d399 100%); box-shadow: 0 8px 20px -10px rgba(16,185,129,.7); }
.bar-chart__bar--debit { background: linear-gradient(180deg, var(--danger) 0%, #f87171 100%); box-shadow: 0 8px 20px -10px rgba(239,68,68,.7); }
.chart-legend__dot--credit { background: var(--emerald); }
.chart-legend__dot--debit { background: var(--danger); }

/* Ledger register rows needing a visual flag: one whose stated balance disagrees
   with the running total, and the carried-forward / adjustment rows that aren't
   ordinary transactions. */
.ledger-row--outofstep { background: color-mix(in srgb, var(--danger) 9%, transparent); }
.ledger-row--adjustment, .ledger-row--opening { background: color-mix(in srgb, var(--indigo) 7%, transparent); font-style: italic; }

@media (max-width: 640px) {
  .cal-day { min-height: 96px; }
  .cal-nav { width: 100%; }
}

/* ---------- Bank Vault lock screen ---------- */

.vault-lock { max-width: 560px; margin: 40px auto; text-align: center; }

.vault-lock__icon {
  width: 64px; height: 64px; margin: 0 auto 18px;
  display: grid; place-items: center;
  border-radius: 18px;
  background: rgba(99, 102, 241, 0.12);
  border: 1px solid rgba(99, 102, 241, 0.35);
  color: var(--indigo, #6366f1);
}
.vault-lock__icon svg { width: 30px; height: 30px; }

.vault-lock__lead { color: var(--muted); font-size: 14px; line-height: 1.6; margin: 0 auto 22px; max-width: 44ch; }

.vault-lock__facts {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px;
  margin: 0 0 24px; text-align: center;
}
.vault-lock__facts > div {
  padding: 12px 8px; border: 1px solid var(--line); border-radius: 12px;
}
.vault-lock__facts strong { display: block; font-size: 14px; }
.vault-lock__facts span { display: block; margin-top: 3px; color: var(--muted); font-size: 11px; line-height: 1.4; }

.vault-lock__to { color: var(--muted); font-size: 13px; margin: 0 0 12px; }
.vault-lock__form { margin-bottom: 20px; }

/* Live resend cooldown under the send button. Mono seconds so the width doesn't jump. */
.vault-lock__cooldown { margin: 10px 0 0; color: var(--muted); font-size: 12.5px; }
.vault-lock__cooldown strong { font-family: var(--mono); color: var(--text); }

.vault-lock__verify { border-top: 1px solid var(--line); padding-top: 16px; text-align: left; }
.vault-lock__verify > summary { cursor: pointer; font-weight: 700; margin-bottom: 12px; text-align: center; }

/* Wide tracking so a six-digit code reads as discrete digits, not a number. */
.vault-lock__code {
  font-size: 26px; letter-spacing: 12px; text-align: center;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

.vault-lock__note {
  margin: 22px 0 0; padding-top: 16px; border-top: 1px solid var(--line);
  color: var(--muted); font-size: 12px; line-height: 1.6;
}

@media (max-width: 520px) {
  .vault-lock__facts { grid-template-columns: 1fr; }
  .vault-lock__code { letter-spacing: 8px; font-size: 22px; }
}

/* The attempt-limit tile is a warning, not a fact — it is the one number with a
   consequence attached, so it reads differently from the other three. */
.vault-lock__facts--warn {
  border-color: rgba(239, 68, 68, 0.4) !important;
  background: rgba(239, 68, 68, 0.06);
}
.vault-lock__facts--warn strong { color: var(--danger); }

/* Topbar countdown to the vault re-lock, shown on every banking page while the
   vault is open. Emerald until the last minute, then it flips to amber and pulses
   (vault-timer.js toggles --warn). Mono digits so the width doesn't jitter each tick. */
.vault-timer {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 12px; border-radius: 999px;
  font-family: var(--mono); font-size: 12.5px; font-weight: 600;
  letter-spacing: .3px; line-height: 1; white-space: nowrap;
  color: var(--emerald);
  background: color-mix(in srgb, var(--emerald) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--emerald) 30%, transparent);
}
.vault-timer svg { width: 14px; height: 14px; flex: none; }
.vault-timer__time { font-variant-numeric: tabular-nums; }

.vault-timer--warn {
  color: var(--amber);
  background: color-mix(in srgb, var(--amber) 14%, transparent);
  border-color: color-mix(in srgb, var(--amber) 34%, transparent);
  animation: vault-timer-pulse 1s ease-in-out infinite;
}
@keyframes vault-timer-pulse { 50% { opacity: .55; } }

@media (max-width: 560px) {
  .vault-timer { padding: 5px 9px; }
  .vault-timer svg { display: none; }
}

/* ---------- Password Vault: repeatable backup-password rows ----------
   One credential can hold several recovery secrets, so the add/edit form carries a
   list rather than a single field. Each row is label + secret + a remove button; the
   button is bottom-aligned so it sits level with the inputs rather than their labels. */
.vault-backup-list { display: grid; gap: 10px; margin-bottom: 12px; }

.vault-backup-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr) auto;
  gap: 0 12px; align-items: end;
  padding: 12px; border: 1px solid var(--line-soft); border-radius: 12px;
}
.vault-backup-row .field { margin: 0; }
.vault-backup-row > .icon-btn { margin-bottom: 2px; }

@media (max-width: 700px) {
  .vault-backup-row { grid-template-columns: 1fr; gap: 10px 0; align-items: stretch; }
  .vault-backup-row > .icon-btn { justify-self: start; margin-bottom: 0; }
}

/* One backup secret inside the vault table's Backup column. Several can stack in a
   single cell, so they need a divider — without one the masks run together. */
.vault-backup-cell + .vault-backup-cell {
  margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--line-soft);
}

/* ---- Birthday celebration (Dashboard only) ------------------------------------
   Fires once per session when the signed-in user's own birthday is today or within
   the coming week. Driven by birthday.js; sits above everything (5000, over modals
   at 100) but stays pointer-events:none so only the card catches clicks — the party
   never traps the page underneath it. */
.bday {
  position: fixed; inset: 0; z-index: 5000; overflow: hidden; pointer-events: none;
  opacity: 0; transition: opacity .4s var(--ease);
}
.bday.is-on { opacity: 1; }
.bday.is-off { opacity: 0; }

.bday__layer { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }

.bday__card {
  position: absolute; left: 50%; top: 24%;
  transform: translate(-50%, -30px) scale(.92); opacity: 0;
  pointer-events: auto; text-align: center;
  width: min(92vw, 440px); padding: 26px 34px;
  background: var(--bg-alt); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow);
  transition: transform .5s var(--ease), opacity .5s var(--ease);
}
.bday.is-on .bday__card { transform: translate(-50%, 0) scale(1); opacity: 1; }

.bday__emoji { font-size: 54px; line-height: 1; animation: bday-pop .9s var(--ease) both; }
.bday__title { margin: 12px 0 4px; font-size: 22px; font-weight: 800; letter-spacing: -.01em; }
.bday__sub { margin: 0 0 18px; color: var(--muted); font-size: 13.5px; }

@keyframes bday-pop {
  0%   { transform: scale(0) rotate(-25deg); }
  60%  { transform: scale(1.25) rotate(8deg); }
  100% { transform: scale(1) rotate(0); }
}

/* Confetti raining down. Colour, size, position and timing are set inline per piece. */
.bday-confetti {
  position: absolute; top: -6vh; will-change: transform;
  animation-name: bday-fall; animation-timing-function: linear; animation-iteration-count: infinite;
}
@keyframes bday-fall {
  0%   { transform: translateY(-10vh) rotate(0); }
  100% { transform: translateY(112vh) rotate(720deg); }
}

/* Balloons drifting up, tethered by a thin string. */
.bday-balloon {
  position: absolute; bottom: -24vh; width: 34px; height: 44px; will-change: transform;
  border-radius: 50% 50% 50% 50% / 55% 55% 45% 45%;
  box-shadow: inset -6px -8px 0 rgba(0,0,0,.12);
  animation-name: bday-rise; animation-timing-function: ease-in; animation-iteration-count: infinite;
}
.bday-balloon::after {
  content: ''; position: absolute; left: 50%; top: 100%;
  width: 1px; height: 42px; background: rgba(148,163,184,.5); transform: translateX(-50%);
}
@keyframes bday-rise {
  0%   { transform: translateY(0) translateX(0); opacity: 0; }
  10%  { opacity: 1; }
  100% { transform: translateY(-140vh) translateX(24px); opacity: 0; }
}

/* Cake, crackers and flowers floating up from the bottom. */
.bday-float {
  position: absolute; bottom: -12vh; will-change: transform;
  animation-name: bday-drift; animation-timing-function: ease-in; animation-iteration-count: infinite;
}
@keyframes bday-drift {
  0%   { transform: translateY(0) rotate(-10deg); opacity: 0; }
  12%  { opacity: 1; }
  100% { transform: translateY(-122vh) rotate(15deg); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .bday__emoji { animation: none; }
  .bday-confetti, .bday-balloon, .bday-float { display: none; }
}

/* ── Policy import review ────────────────────────────────────────────────────
   The dry run the import dialog shows before anything is written: one card per
   record in the file, carrying its verdict and — for a policy number that
   already exists — the field-by-field diff and the tick that turns a skip into
   an overwrite. Built by policies.js. */

/* The summary counts are labels, not filters, unlike every other .chip. */
.chip--static { cursor: default; }
.chip--static:hover { color: var(--text-mid); border-color: var(--line); }

.import-row {
  padding: 14px; margin-bottom: 10px;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-sm);
}

.import-row-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 12px; margin-bottom: 10px;
}
.import-row-head strong { font-size: 13px; }

/* Diffs are dense and read as a comparison, not as a data table — the register's
   full table styling would overpower a three-column list inside a card. */
.import-row table { margin: 0; font-size: 12px; }
.import-row table th { font-size: 10.5px; }

.import-errors {
  margin: 0 0 10px 0; padding-left: 18px;
  font-size: 12px; color: #ff8a8d; line-height: 1.6;
}
.import-errors > li { overflow-wrap: anywhere; }

.import-choice {
  display: flex; align-items: center; gap: 8px; margin-top: 12px;
  padding-top: 10px; border-top: 1px solid var(--line-soft);
  font-size: 12.5px; font-weight: 600; color: var(--text-mid); cursor: pointer;
}
.import-choice:hover { color: var(--text); }
.import-choice input { width: 15px; height: 15px; margin: 0; cursor: pointer; }

/* The export field picker. One row per field the module offers as a choice; the
   fields a record is matched on are never listed, because a file without them
   could not be imported back. Built by Views/Shared/_ImportExport.cshtml. */
.export-fields { display: grid; gap: 2px; margin: 4px 0 14px 0; }
.export-field {
  margin-top: 0; padding-top: 8px; padding-bottom: 8px;
  border-top: none; border-bottom: 1px solid var(--line-soft);
}
.export-field:last-child { border-bottom: none; }
.export-field .badge { margin-left: 8px; }

/* ---------- single-policy report (/Policies/ReportV2) ----------
   A picker rail beside one policy read end to end. The rail is its own scrollport
   so the detail column, which is far taller, scrolls the page on its own. */
.pv2 {
  display: grid;
  grid-template-columns: 320px minmax(0, 1fr);
  gap: 22px;
  align-items: start;
}

/* .content carries overflow-y:auto, which makes it a scroll container even though
   it has no height of its own and so never scrolls — and a sticky child inside a
   scroll container that does not scroll can never pin. The rail needs the window
   to be its scrollport, which is the same assumption the sticky table heads make
   (thead th { top: 64px }), so the wrapper's overflow is released while this
   report is the page on screen. */
.content:has(.pv2) { overflow: visible; }

/* Pinned beside the detail column, which runs several screens long. The offset is
   the shell's own geometry — the 64px sticky topbar plus .content's 26px top
   padding — so the rail sits level with the hero at rest and slides under the
   topbar, not behind it, once the page scrolls. The height stops short of the
   fixed footer for the same reason. */
.pv-rail {
  position: sticky;
  top: 90px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  height: calc(100vh - 152px);
  min-height: 300px;
  padding: 18px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.pv-rail__head h3 { margin: 0; font-size: 16px; font-weight: 700; letter-spacing: -.01em; }
.pv-rail__head p { margin: 3px 0 0; font-size: 12px; color: var(--muted); }

.pv-rail__totals {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  padding: 12px;
  background: var(--surface-2);
  border-radius: var(--radius-sm);
}
.pv-rail__totals strong { display: block; font-size: 15px; font-weight: 700; letter-spacing: -.01em; }
.pv-rail__label {
  display: block;
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .06em;
  font-weight: 600; color: var(--muted); margin-bottom: 2px;
}

.pv-rail__search input { width: 100%; }

/* The list, not the card, owns the overflow â€” a sticky rail that grew with the
   register would push its own last policy off the bottom of the viewport. */
.pv-rail__list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 0 -6px;
  padding: 2px 6px;
  scrollbar-width: thin;
  scrollbar-color: rgba(99, 102, 241, .55) transparent;
  /* Reserve the gutter so the cards do not reflow the moment the list overflows. */
  scrollbar-gutter: stable;
}
.pv-rail__list::-webkit-scrollbar { width: 10px; }
.pv-rail__list::-webkit-scrollbar-track { background: transparent; }
.pv-rail__list::-webkit-scrollbar-thumb {
  background: rgba(99, 102, 241, .45);
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: content-box;
}
.pv-rail__list::-webkit-scrollbar-thumb:hover { background: rgba(99, 102, 241, .7); background-clip: content-box; }

.pv-pick {
  display: block;
  padding: 11px 12px;
  border: 1px solid var(--line);
  border-left: 3px solid transparent;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  transition: border-color .2s var(--ease), transform .2s var(--ease), background .2s;
}
.pv-pick:hover { transform: translateX(2px); border-color: color-mix(in srgb, var(--indigo) 45%, transparent); }
.pv-pick.is-selected {
  border-left-color: var(--indigo);
  background: color-mix(in srgb, var(--indigo) 12%, transparent);
  border-color: color-mix(in srgb, var(--indigo) 40%, transparent);
}
/* A lapsed or surrendered policy still belongs in the rail, just not at full weight. */
.pv-pick.is-dim { opacity: .62; }
.pv-pick.is-dim:hover, .pv-pick.is-dim.is-selected { opacity: 1; }

.pv-pick__top { display: flex; align-items: center; gap: 8px; justify-content: space-between; }
.pv-pick__title {
  font-size: 13.5px; font-weight: 600; letter-spacing: -.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pv-pick__meta {
  display: flex; gap: 8px; align-items: baseline;
  margin-top: 4px; font-size: 11.5px; color: var(--muted);
  overflow: hidden; white-space: nowrap;
}
.pv-pick__no { font-family: var(--mono); font-size: 10.5px; overflow: hidden; text-overflow: ellipsis; }
.pv-pick__figures {
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  margin-top: 7px; font-size: 11.5px; color: var(--text-mid);
  font-variant-numeric: tabular-nums;
}

/* ---- detail column ---- */
.pv-detail { min-width: 0; }

.pv-hero {
  display: flex;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
  background:
    linear-gradient(115deg, color-mix(in srgb, var(--indigo) 16%, transparent) 0%, transparent 62%),
    var(--surface);
}

.pv-hero__mark {
  width: 58px; height: 58px; flex: none;
  display: flex; align-items: center; justify-content: center;
  font-size: 27px; line-height: 1;
  border-radius: 18px;
  background: var(--surface-2);
  border: 1px solid var(--line);
}

.pv-hero__body { flex: 1; min-width: 220px; }
.pv-hero__body h2 { margin: 0; font-size: 22px; font-weight: 800; letter-spacing: -.02em; }
.pv-hero__sub { margin: 3px 0 0; font-size: 13px; color: var(--text-mid); }
.pv-hero__no { font-family: var(--mono); font-size: 11.5px; color: var(--muted); margin-left: 10px; }
.pv-hero__actions { display: flex; gap: 8px; flex-wrap: wrap; }

.pv-chips { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 11px; }
.pv-chip {
  display: inline-flex; align-items: center;
  padding: 3px 10px; border-radius: 999px;
  background: var(--surface-2); border: 1px solid var(--line-soft);
  font-size: 11.5px; color: var(--text-mid);
}

.pv-metrics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 16px;
  margin-bottom: 22px;
}

/* ---- meters (term elapsed, premiums paid) ---- */
.pv-meter { margin: 0 0 18px; }
.pv-meter__head {
  display: flex; align-items: baseline; justify-content: space-between;
  font-size: 12.5px; color: var(--text-mid); margin-bottom: 7px;
}
.pv-meter__head strong { font-size: 15px; color: var(--text); font-variant-numeric: tabular-nums; }

.pv-meter__track {
  height: 10px; border-radius: 999px;
  background: var(--surface-2); overflow: hidden;
}
.pv-meter__fill {
  height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, var(--indigo), var(--cyan));
  transition: width .6s var(--ease);
}
.pv-meter__fill--emerald { background: linear-gradient(90deg, var(--emerald), var(--cyan)); }

.pv-meter__foot {
  display: flex; flex-wrap: wrap; gap: 6px 14px; justify-content: space-between;
  margin-top: 7px; font-size: 11.5px; color: var(--muted);
}

.pv-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 14px;
  padding-top: 16px;
  border-top: 1px solid var(--line-soft);
}
.pv-facts--stack { border-top: none; padding-top: 4px; }
.pv-facts strong { display: block; font-size: 15px; font-weight: 700; letter-spacing: -.01em; }
.pv-facts__label {
  display: block;
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .06em;
  font-weight: 600; color: var(--muted); margin-bottom: 3px;
}
.pv-facts__note { display: block; font-size: 11.5px; color: var(--text-mid); margin-top: 2px; }

.pv-subhead {
  margin: 20px 0 10px;
  font-size: 12px; text-transform: uppercase; letter-spacing: .06em;
  font-weight: 700; color: var(--muted);
}

.pv-note { margin: 10px 0 0; font-size: 12px; color: var(--muted); line-height: 1.6; }
.pv-note--warn { color: var(--amber); }
.pv-audit {
  margin: 18px 0 0; padding-top: 12px;
  border-top: 1px solid var(--line-soft);
  font-size: 11.5px; color: var(--muted);
}

/* ---- timeline ---- */
.pv-timeline { list-style: none; margin: 4px 0 0; padding: 0; position: relative; }
/* The rail behind the dots stops short of the last one so it does not dangle. */
.pv-timeline::before {
  content: ''; position: absolute; left: 6px; top: 10px; bottom: 22px;
  width: 2px; background: var(--line);
}

.pv-timeline__item {
  position: relative;
  display: flex; align-items: flex-start; gap: 12px;
  padding: 9px 0 9px 26px;
}
.pv-timeline__dot {
  position: absolute; left: 0; top: 14px;
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--bg); border: 2px solid var(--line);
}
.pv-timeline__item.is-past .pv-timeline__dot { background: var(--muted); border-color: var(--muted); }
.pv-timeline__item.is-next .pv-timeline__dot {
  background: var(--amber); border-color: var(--amber);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--amber) 22%, transparent);
}
.pv-timeline__item.is-past { opacity: .72; }

.pv-timeline__body { flex: 1; min-width: 0; }
.pv-timeline__label { display: flex; align-items: center; gap: 8px; font-size: 13.5px; font-weight: 600; }
.pv-timeline__date { font-size: 12px; color: var(--text-mid); margin-top: 1px; }
.pv-timeline__side { text-align: right; flex: none; }
.pv-timeline__amt { font-family: var(--mono); font-size: 12.5px; }
.pv-timeline__note { font-size: 11.5px; color: var(--muted); }

/* ---- nomination list (sits beside the donut) ---- */
.pv-nominees { list-style: none; margin: 0; padding: 0; flex: 1; min-width: 240px; display: flex; flex-direction: column; gap: 4px; }
.pv-nominees > li {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 10px; border-radius: 10px;
  font-size: 13px; transition: background .2s;
}
.pv-nominees > li:hover { background: var(--surface-2); }
.pv-nominees__body { flex: 1; min-width: 0; }
.pv-nominees__detail { font-size: 11.5px; color: var(--muted); }
.pv-nominees__figures { text-align: right; flex: none; }
.pv-nominees__pct { display: block; font-variant-numeric: tabular-nums; }
.pv-nominees__amt { display: block; font-family: var(--mono); font-size: 11.5px; color: var(--muted); }

/* ---- documents ---- */
.pv-docs { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.pv-docs > li {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 11px; border-radius: var(--radius-sm);
  background: var(--surface-2); font-size: 12.5px;
}
.pv-docs__name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
.pv-docs__size { font-family: var(--mono); font-size: 11px; color: var(--muted); }

/* Below the split the rail becomes an ordinary block above the detail, capped so
   it never costs more than a screenful before the policy itself. */
@media (max-width: 1080px) {
  .pv2 { grid-template-columns: 1fr; }
  .pv-rail { position: static; height: auto; min-height: 0; }
  .pv-rail__list { max-height: 320px; }
}

@media (max-width: 620px) {
  .pv-hero { gap: 14px; }
  .pv-hero__body h2 { font-size: 19px; }
  .pv-hero__actions { width: 100%; }
  .pv-hero__actions .btn { flex: 1; justify-content: center; }
  .pv-timeline__side { display: none; }
}
