/* Cronenta — design tokens. Source of truth: docs/brand-kit-v1.0 (04-dev-tokens).
   Light-only by decision (#113); dark [data-theme] block intentionally not ported. */

@font-face { font-family: 'Poppins'; font-weight: 500; font-style: normal; font-display: swap;
  src: url('/static/fonts/poppins-latin-500.woff2') format('woff2'); }
@font-face { font-family: 'Poppins'; font-weight: 600; font-style: normal; font-display: swap;
  src: url('/static/fonts/poppins-latin-600.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-weight: 400; font-style: normal; font-display: swap;
  src: url('/static/fonts/inter-latin-400.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-weight: 600; font-style: normal; font-display: swap;
  src: url('/static/fonts/inter-latin-600.woff2') format('woff2'); }

:root {
  color-scheme: light;

  /* Brand ramps */
  --cr-teal-50: #E8F5F4;
  --cr-teal-100: #C6E8E6;
  --cr-teal-200: #93D4D1;
  --cr-teal-300: #5CBCB8;
  --cr-teal-400: #2BA5A0;
  --cr-teal-500: #159C98;
  --cr-teal-600: #0C807D;
  --cr-teal-700: #0A6E70;
  --cr-teal-800: #08585B;
  --cr-teal-900: #06373B;
  --cr-sand-50: #FAF5EF;
  --cr-sand-100: #F3E7D8;
  --cr-sand-200: #E6D0B5;
  --cr-sand-300: #D5B48D;
  --cr-sand-400: #BE946C;
  --cr-sand-500: #A57B55;
  --cr-sand-600: #866143;
  --cr-sand-700: #664A33;
  /* #472: third forecast-bar family colour (G2 readiness), outside the
     teal/sand ramps since it needs to sit visually distinct from both. */
  --cr-forecast-purple: #CBB8E8;

  /* Neutrals */
  --cr-ink-900: #16232B;
  --cr-ink-800: #1D282E;
  --cr-ink-700: #2C3A42;
  --cr-ink-600: #46555E;
  --cr-slate-500: #63727B;
  --cr-slate-400: #94A3AA;
  --cr-slate-300: #C2CDD2;
  --cr-slate-200: #DFE7E9;
  --cr-slate-100: #EDF3F3;
  --cr-slate-150: #E4EBEB;
  --cr-paper: #FBFAFA;
  --cr-white: #FFFFFF;

  /* Semantic */
  --cr-success: #1F9D6B;
  --cr-warning: #C98A1E;
  --cr-danger: #C2453C;
  --cr-info: #2B7FB8;

  /* Roles (components use these, not the raw ramps) */
  --cr-primary: var(--cr-teal-600);
  --cr-primary-hover: var(--cr-teal-700);
  --cr-primary-active: var(--cr-teal-800);
  --cr-primary-subtle: var(--cr-teal-50);
  --cr-on-primary: #FFFFFF;
  --cr-accent: var(--cr-sand-400);
  --cr-text: var(--cr-ink-900);
  --cr-text-muted: var(--cr-slate-500);
  --cr-text-inverse: #FFFFFF;
  --cr-surface: #FFFFFF;
  --cr-surface-subtle: var(--cr-paper);
  --cr-surface-inverse: var(--cr-ink-800);
  --cr-border: var(--cr-slate-200);
  --cr-focus: var(--cr-teal-400);
  --cr-gradient-brand: linear-gradient(135deg, #159C98 0%, #0A6E70 100%);

  /* Tints — values owned by the kit (04-dev-tokens), mirrored here (approved in the #113 mockup rounds) */
  --cr-success-tint: #E4F4ED;
  --cr-danger-tint: #F9E9E8;
  --cr-neutral-tint: var(--cr-slate-100);
  --cr-sand-tint: var(--cr-sand-50);
  --cr-sand-text: var(--cr-sand-600);
  --cr-info-tint: #E5F1F8;
  --cr-warning-tint: #F8EEDC;
  --cr-success-text: #187A53;
  --cr-danger-text: #B64038;
  --cr-info-text: #256FA1;
  --cr-warning-text: #8C6014;

  /* Type */
  --cr-font-display: 'Poppins', system-ui, sans-serif;
  --cr-font-body: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --cr-font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
  --cr-text-xs: 12px;
  --cr-text-sm: 14px;
  --cr-text-base: 16px;
  --cr-text-lg: 18px;
  --cr-text-xl: 20px;
  --cr-text-2xl: 24px;
  --cr-text-3xl: 30px;
  --cr-text-4xl: 38px;
  --cr-text-5xl: 48px;
  --cr-text-6xl: 60px;
  --cr-text-7xl: 76px;

  /* Space */
  --cr-space-0: 0px;
  --cr-space-1: 4px;
  --cr-space-2: 8px;
  --cr-space-3: 12px;
  --cr-space-4: 16px;
  --cr-space-5: 20px;
  --cr-space-6: 24px;
  --cr-space-8: 32px;
  --cr-space-10: 40px;
  --cr-space-12: 48px;
  --cr-space-16: 64px;
  --cr-space-20: 80px;
  --cr-space-24: 96px;
  --cr-space-32: 128px;

  /* Radius */
  --cr-radius-none: 0px;
  --cr-radius-sm: 6px;
  --cr-radius-md: 10px;
  --cr-radius-lg: 14px;
  --cr-radius-xl: 20px;
  --cr-radius-2xl: 28px;
  --cr-radius-full: 9999px;

  /* Elevation */
  --cr-shadow-xs: 0 1px 2px rgba(22,35,43,.06);
  --cr-shadow-sm: 0 1px 3px rgba(22,35,43,.08), 0 1px 2px rgba(22,35,43,.05);
  --cr-shadow-md: 0 4px 12px rgba(22,35,43,.08), 0 2px 4px rgba(22,35,43,.04);
  --cr-shadow-lg: 0 12px 28px rgba(22,35,43,.10), 0 4px 8px rgba(22,35,43,.05);
  --cr-shadow-xl: 0 24px 48px rgba(22,35,43,.12);
  --cr-shadow-teal: 0 8px 24px rgba(14,138,134,.24);

  --motion-fast: 120ms;
  --motion-base: 200ms;
  --easing-standard: cubic-bezier(0.2, 0, 0, 1);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--cr-surface-subtle);
  color: var(--cr-text);
  font-family: var(--cr-font-body);
  font-size: var(--cr-text-sm);
  line-height: 1.55;
}

header {
  display: flex;
  align-items: center;
  gap: var(--cr-space-3);
  max-width: 60rem;
  margin: 0 auto;
  padding: var(--cr-space-4);
  border-bottom: 1px solid var(--cr-border);
}

header nav { margin-left: auto; display: flex; align-items: center; gap: var(--cr-space-6); }
header nav a { color: var(--cr-text-muted); text-decoration: none; font-size: var(--cr-text-base); font-weight: 600; }
header nav a:hover { color: var(--cr-text); }
header svg { width: 120px; height: auto; display: block; } /* lockup floor: BRAND-SPEC 7.3, >=110px wide */

main { max-width: 60rem; margin: var(--cr-space-8) auto 0; padding: 0 var(--cr-space-4) var(--cr-space-12); }

footer { max-width: 60rem; margin: 0 auto; padding: 0 var(--cr-space-4) var(--cr-space-6); text-align: center; }
footer .hint { font-size: var(--cr-text-xs); }
footer a { color: var(--cr-text-muted); }

/* Portal shell — persistent left sidebar, logged-in pages only (see base.html). */

.shell { display: flex; min-height: 100vh; }

.sidebar { width: 236px; flex: none; background: var(--cr-surface); border-right: 1px solid var(--cr-border); display: flex; flex-direction: column; }
.sidebar-brand { padding: 22px 20px 16px; display: flex; align-items: center; gap: var(--cr-space-3); }
.sidebar-brand a { display: block; line-height: 0; }
.sidebar-brand svg { width: 120px; height: auto; display: block; } /* lockup floor: BRAND-SPEC 7.3, >=110px wide */
.sidebar-nav { flex: 1; padding: 6px 12px; display: flex; flex-direction: column; gap: 18px; }

.nav-group { display: flex; flex-direction: column; gap: 2px; }
.nav-group-label { font-family: var(--cr-font-display); font-size: 11px; font-weight: 500; text-transform: uppercase; letter-spacing: .14em; color: var(--cr-text-muted); padding: 6px 10px 4px; }

.sidebar-nav a, .nav-logout {
  display: flex; align-items: center; gap: 10px; width: 100%;
  min-height: 44px; /* LAY-5 touch target */
  padding: 8px 10px; border-radius: var(--cr-radius-sm);
  background: none; border: none; color: var(--cr-text); text-decoration: none;
  font-family: var(--cr-font-body); font-weight: 400; font-size: var(--cr-text-sm);
  cursor: pointer; text-align: left;
  transition: background var(--motion-fast) var(--easing-standard), color var(--motion-fast) var(--easing-standard);
}
.sidebar-nav a:hover, .nav-logout:hover { background: var(--cr-neutral-tint); color: var(--cr-text); text-decoration: none; }
.sidebar-nav a.current { background: var(--cr-primary-subtle); color: var(--cr-primary-hover); font-weight: 600; }
.sidebar-nav a svg, .nav-logout svg { width: 17px; height: 17px; flex: none; stroke: currentColor; fill: none; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; color: var(--cr-text-muted); }
.sidebar-nav a.current svg { color: var(--cr-primary); }

.sidebar-foot { border-top: 1px solid var(--cr-border); padding: 14px 16px; display: flex; align-items: center; gap: 10px; }
.tenant-avatar { width: 34px; height: 34px; border-radius: var(--cr-radius-full); background: var(--cr-primary-subtle); color: var(--cr-primary-hover); display: flex; align-items: center; justify-content: center; font-family: var(--cr-font-display); font-weight: 600; font-size: var(--cr-text-sm); flex: none; }
.tenant-meta { flex: 1; min-width: 0; }
.tenant-name { font-weight: 600; font-size: var(--cr-text-sm); color: var(--cr-text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tenant-sub { font-size: var(--cr-text-xs); color: var(--cr-text-muted); }

/* Top bar — mobile-only header carrying the drawer toggle (see .sidebar drawer below). */
.topbar { display: none; align-items: center; gap: var(--cr-space-3); height: 56px; flex: none;
  padding: 0 var(--cr-space-4); background: var(--cr-surface); border-bottom: 1px solid var(--cr-border);
  position: sticky; top: 0; z-index: 30; }
.topbar-tenant { font-weight: 600; color: var(--cr-text); }
.topbar-brand { display: block; line-height: 0; }
.topbar-brand svg { width: 120px; height: auto; display: block; } /* lockup floor: BRAND-SPEC 7.3, same asset/size as .sidebar-brand */
.burger { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; /* LAY-5 */
  background: none; border: 1px solid var(--cr-border); border-radius: var(--cr-radius-sm);
  color: var(--cr-text); font-size: var(--cr-text-lg); line-height: 1; cursor: pointer; padding: 0; }
.burger:hover { background: var(--cr-neutral-tint); }

.content { flex: 1; min-width: 0; background: var(--cr-surface-subtle); padding: var(--cr-space-8) var(--cr-space-10) var(--cr-space-12); }
.content-inner { max-width: 56rem; }

/* View header row (title + status chip) and placeholder cards — placeholder.html, home.html. */
.vhead { display: flex; align-items: center; gap: var(--cr-space-3); flex-wrap: wrap; margin-bottom: var(--cr-space-5); }
.vhead h1 { margin: 0; }
.vhead .sp { flex: 1; }
.chip { display: inline-block; font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  border-radius: var(--cr-radius-full); padding: 2px 10px; text-decoration: none; }
.chip-soon { background: var(--cr-sand-tint); color: var(--cr-sand-text); }

.ph { border: 1.5px dashed var(--cr-border); border-radius: var(--cr-radius-lg); padding: 34px 28px;
  display: flex; flex-direction: column; align-items: flex-start; gap: 10px; background: var(--cr-surface);
  margin: 0 0 var(--cr-space-6); }
.ph h2 { margin: 0; font-size: var(--cr-text-base); }
.ph p { margin: 0; color: var(--cr-text-muted); max-width: 58ch; }
.ph-slim { padding: var(--cr-space-3) var(--cr-space-4); }

h1 { font-family: var(--cr-font-display); font-weight: 600; font-size: var(--cr-text-2xl); letter-spacing: -0.015em; margin: 0 0 var(--cr-space-5); color: var(--cr-text); }
h2 { font-family: var(--cr-font-display); font-weight: 600; font-size: var(--cr-text-lg); letter-spacing: -0.01em; margin: var(--cr-space-8) 0 var(--cr-space-3); color: var(--cr-text); }

table { border-collapse: collapse; width: 100%; background: var(--cr-surface); border: 1px solid var(--cr-border); border-radius: var(--cr-radius-md); box-shadow: var(--cr-shadow-xs); font-size: 13.5px; }
th, td { text-align: left; padding: 10px var(--cr-space-4); border-bottom: 1px solid var(--cr-border); font-size: var(--cr-text-sm); }
th { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .07em; color: var(--cr-text-muted); }
td { font-variant-numeric: tabular-nums; }
tr:last-child td { border-bottom: none; }

.metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--cr-space-3); margin-bottom: var(--cr-space-8); }
.metric { background: var(--cr-surface); border: 1px solid var(--cr-border); border-radius: var(--cr-radius-md); padding: 14px var(--cr-space-4); box-shadow: var(--cr-shadow-sm); }
.metric b { font-family: var(--cr-font-display); font-weight: 600; font-size: var(--cr-text-2xl); font-variant-numeric: tabular-nums; letter-spacing: -0.01em; display: block; margin-bottom: 2px; }
.metric span { font-size: var(--cr-text-xs); color: var(--cr-text-muted); }
.metric-sand { background: var(--cr-sand-tint); border-color: transparent; }
.metric-sand b { color: var(--cr-sand-text); }

.notice-banner { background: var(--cr-primary-subtle); color: var(--cr-text); border-radius: var(--cr-radius-md); padding: var(--cr-space-4) var(--cr-space-6); margin-bottom: var(--cr-space-6); font-size: var(--cr-text-sm); }
.notice-banner a { font-weight: 600; }
/* issue 602: the one-time API key display and its Copy button. */
.api-key { display: block; font-size: var(--cr-text-sm); word-break: break-all; margin: var(--cr-space-3) 0; padding: var(--cr-space-3) var(--cr-space-4); background: var(--cr-surface); border: 1px solid var(--cr-border); border-radius: var(--cr-radius-md); }
.notice-banner .btn-row { justify-content: flex-start; margin-top: var(--cr-space-2); }

.error { color: var(--cr-danger); font-size: var(--cr-text-sm); }

form.inline { display: inline; }

a { color: var(--cr-primary); }
a:hover { text-decoration: underline; }

button {
  background: var(--cr-primary);
  color: var(--cr-on-primary);
  border: none;
  border-radius: var(--cr-radius-sm);
  padding: var(--cr-space-2) var(--cr-space-4);
  font-family: var(--cr-font-body);
  font-weight: 600;
  font-size: var(--cr-text-sm);
  cursor: pointer;
  transition: background var(--motion-fast) var(--easing-standard);
}
button:hover { background: var(--cr-primary-hover); }

button { min-height: 44px; } /* LAY-5: bare buttons in inline forms included */
input[type="checkbox"], input[type="radio"] { min-height: 0; width: 20px; height: 20px; }
input, select, textarea {
  border: 1px solid var(--cr-border);
  border-radius: var(--cr-radius-sm);
  min-height: 44px; /* LAY-5 touch target */
  padding: var(--cr-space-2) var(--cr-space-3);
  font-family: var(--cr-font-body);
  font-size: var(--cr-text-base);
  background: var(--cr-surface);
  color: var(--cr-text);
}
input:focus, select:focus, textarea:focus { border-color: var(--cr-teal-400); }

textarea { resize: vertical; min-height: 4.5rem; }

.panel-card { background: var(--cr-surface); border: 1px solid var(--cr-border); border-radius: var(--cr-radius-lg); padding: var(--cr-space-6); margin-bottom: var(--cr-space-8); box-shadow: var(--cr-shadow-xs); }
/* flex + centered so a status pill (e.g. Settings -> Payments, #166 task 7)
   sits on the heading's baseline instead of dropping to text flow. */
.panel-card h2 { margin: 0 0 var(--cr-space-5); display: flex; align-items: center; gap: 10px; }

/* issue 602: full gap between the create-key form and the key table/empty
   line below it, matching the gap other panel-card sections already use.
   Direct-child selector: the card's only direct-child <form> is the create
   form -- the per-row Revoke <form>s live inside the table, and must not
   pick up this margin or their table cell grows and the pill misaligns. */
#api-keys > form { margin-bottom: var(--cr-space-6); }

/* #29 MVP: Home "clients missing a G2 date" nudge card. Rows reuse .dayrow's
   bordered-row look (border/radius/padding) and .daylist's stack -- only the
   hover colour and the trailing action text are new. */
.panel-card.g2-nudge { border-left: 3px solid var(--cr-warning-text); }
.g2-nudge-badge {
  background: var(--cr-warning-tint); color: var(--cr-warning-text);
  text-transform: uppercase; letter-spacing: 0.04em; font-weight: 700;
  font-size: var(--cr-text-xs); padding: var(--cr-space-1) var(--cr-space-2);
  border-radius: var(--cr-radius-full);
}
.dayrow.g2-nudge-row:hover { border-color: var(--cr-primary); }
.g2-nudge-row .g2-nudge-action { color: var(--cr-primary); font-weight: 600; font-size: var(--cr-text-sm); }
.g2-nudge-foot { margin-top: var(--cr-space-2); font-size: var(--cr-text-sm); }

.field { display: flex; flex-direction: column; gap: var(--cr-space-2); margin-bottom: var(--cr-space-5); }
/* #242: first + last name side by side where one Name input used to be. */
.name-row { display: flex; gap: 10px; }
.name-row .field { flex: 1; }
/* LAY-2: multi-column field rows stack below 600px (phone tier). Gap 0:
   each stacked .field keeps its own margin-bottom, so a row gap would
   double the space between First and Last name against every other
   field pair. */
@media (max-width: 599px) {
  .name-row { flex-direction: column; gap: 0; }
}
/* Pairs short, related fields into two columns at tablet width and up
   (LAY-2's "two-column layouts allowed"); a plain stack below 600px. */
.field-2up { margin-bottom: var(--cr-space-5); }
.field-2up .field { margin-bottom: var(--cr-space-4); }
.field-2up .field:last-child { margin-bottom: 0; }
@media (min-width: 600px) {
  .field-2up { display: grid; grid-template-columns: 1fr 1fr; gap: var(--cr-space-4); }
  .field-2up .field { margin-bottom: 0; }
}
.field label { font-weight: 600; font-size: var(--cr-text-sm); color: var(--cr-text); }
.field input, .field select, .field textarea { width: 100%; }
/* #342: same trap as .scopelist label[hidden] -- the unqualified .field
   `display: flex` rule above would otherwise defeat the `hidden` attribute
   shell.js (and the server, on first render) sets on the campaign form's
   Subject field. */
.field[hidden] { display: none; }

/* Message label + its Preview link on the same row (campaign/notification
   forms). */
.label-row { display: flex; justify-content: space-between; align-items: baseline; }

.field + .btn-row { margin-top: var(--cr-space-1); }
.btn-row + .btn-row { margin-top: var(--cr-space-4); }

.hint { font-size: var(--cr-text-xs); color: var(--cr-text-muted); margin: 0 0 var(--cr-space-4); }
.field .hint { margin-bottom: 0; }
.hint.flash { margin: 0 0 var(--cr-space-6); }
/* .hint margin variants -- CSP is style-src 'self' with no unsafe-inline
   (see app.py), so a style="" attribute is silently dropped by real
   browsers; these replace two former inline overrides (calendar.html). */
.hint-inline { margin: 0; }
.hint-note { margin: 10px 0 0; }

.field-checkbox { display: flex; align-items: center; gap: var(--cr-space-2); margin-bottom: var(--cr-space-6); font-weight: 600; font-size: var(--cr-text-sm); }
.field-checkbox input { width: auto; }
/* A checkbox wrapped in .field pairs tightly with the hint below it --
   the standalone margin is for checkboxes that end a form (issue 383). */
.field > .field-checkbox { margin-bottom: var(--cr-space-1); }

.consent { display: flex; gap: 9px; align-items: flex-start; font-size: 13.5px; font-weight: 400; margin: 18px 0 22px; color: var(--cr-text); }
.consent input { accent-color: var(--cr-primary); margin-top: 2px; }
.fine.consent-fine { margin: -18px 0 22px; padding-left: 28px; }

.btn-row { display: flex; justify-content: flex-end; gap: var(--cr-space-3); }
.btn-pill {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--cr-space-2);
  min-height: 44px; /* LAY-5 touch target */
  background: var(--cr-primary); color: var(--cr-on-primary);
  border: none; border-radius: var(--cr-radius-full);
  padding: 8px 18px; font-family: var(--cr-font-body); font-weight: 600; font-size: 13px;
  cursor: pointer; text-decoration: none;
}
.btn-pill:hover { background: var(--cr-primary-hover); text-decoration: none; }

/* Status pills — dot + label. */
.pill { display: inline-flex; align-items: center; gap: 6px; font-size: var(--cr-text-xs); font-weight: 600; padding: 2px 10px; border-radius: var(--cr-radius-full); }
.pill::before { content: ""; width: 6px; height: 6px; border-radius: var(--cr-radius-full); background: currentColor; }
.pill-ok { background: var(--cr-success-tint); color: var(--cr-success-text); }
.pill-bad { background: var(--cr-danger-tint); color: var(--cr-danger-text); }
.pill-muted { background: var(--cr-neutral-tint); color: var(--cr-ink-600); }
.pill-brand { background: var(--cr-primary-subtle); color: var(--cr-primary-hover); }
/* Setup-incomplete status -- neither ok nor bad, so it gets the warning
   tint rather than being forced into success/danger (#166 task 7, Settings
   -> Payments panel, State B). */
.pill-warn { background: var(--cr-warning-tint); color: var(--cr-warning-text); }

/* Receipt page -- letterhead card for /payments/{id}/receipt (#443 approved
   mockup). Printed via @media print below: the app shell disappears and
   only the card is left on the page. */
.receipt { background: var(--cr-surface); border: 1px solid var(--cr-border); border-radius: var(--cr-radius-md); max-width: 560px; margin: 0 auto; padding: var(--cr-space-8) var(--cr-space-10); }
.r-head { display: flex; justify-content: space-between; align-items: flex-start; border-bottom: 2px solid var(--cr-primary); padding-bottom: var(--cr-space-5); margin-bottom: var(--cr-space-6); }
.r-biz { font-family: var(--cr-font-display); font-weight: 600; font-size: var(--cr-text-xl); color: var(--cr-text); }
.r-biz small { display: block; font-family: var(--cr-font-body); font-weight: 400; font-size: var(--cr-text-xs); color: var(--cr-text-muted); margin-top: var(--cr-space-1); }
.r-no { text-align: right; font-size: 13px; color: var(--cr-text-muted); flex: none; }
.r-no b { display: block; font-family: var(--cr-font-display); font-size: 15px; color: var(--cr-text); letter-spacing: .02em; }
.r-rows { font-size: var(--cr-text-sm); }
.r-row { display: flex; justify-content: space-between; padding: var(--cr-space-2) 0; border-bottom: 1px solid var(--cr-border); }
.r-row span:first-child { color: var(--cr-text-muted); }
.r-total { display: flex; justify-content: space-between; padding: var(--cr-space-4) 0 var(--cr-space-1); font-family: var(--cr-font-display); font-weight: 600; font-size: var(--cr-text-lg); }
.r-foot { margin-top: var(--cr-space-6); font-size: var(--cr-text-xs); color: var(--cr-text-muted); text-align: center; }
.receipt-actions { max-width: 560px; margin: var(--cr-space-5) auto 0; }
.receipt-back { max-width: 560px; margin: 0 auto var(--cr-space-3); font-size: var(--cr-text-sm); }

@media print {
  .sidebar, .topbar, .drawer-backdrop, .page-toast, .receipt-actions, .receipt-back { display: none !important; }
  .content { padding: 0; }
  .receipt { border: none; box-shadow: none; margin: 0; max-width: none; }
}

/* Booking-flow classes (Task 2 templates) */
.chips { display: flex; gap: var(--cr-space-2); flex-wrap: wrap; }
.chips label {
  position: relative; border: 1px solid var(--cr-border); border-radius: var(--cr-radius-full);
  padding: 7px 18px; cursor: pointer; font-size: 13.5px; display: inline-flex; align-items: center; gap: 7px;
}
.chips input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.chips label:has(input:checked) { background: var(--cr-primary-subtle); border-color: var(--cr-teal-400); color: var(--cr-primary-hover); font-weight: 600; }
.chips label:has(input:focus-visible) { outline: 2px solid var(--cr-focus); outline-offset: 2px; }

.slot-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--cr-space-2); margin-bottom: var(--cr-space-4); }
.slot {
  position: relative; border: 1px solid var(--cr-border); border-radius: var(--cr-radius-sm);
  min-height: 44px; /* LAY-5 touch target */
  padding: 9px 0; text-align: center; cursor: pointer; font-weight: 600; font-size: 13.5px;
  color: var(--cr-primary); display: flex; align-items: center; justify-content: center; gap: var(--cr-space-2);
  font-variant-numeric: tabular-nums;
}
.slot input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; accent-color: var(--cr-primary); margin: 0; }
.slot:has(input:checked) { background: var(--cr-primary); color: var(--cr-on-primary); border-color: var(--cr-primary); }
.slot:has(input:focus-visible) { outline: 2px solid var(--cr-focus); outline-offset: 2px; }

.cal { flex: 1 1 300px; max-width: 360px; }
.cal-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--cr-space-3); }
.cal-head .month { font-family: var(--cr-font-display); font-weight: 600; font-size: var(--cr-text-lg); }
.cal-head a, .cal-head span.off {
  width: 44px; height: 44px; display: grid; place-items: center; border-radius: var(--cr-radius-full); /* LAY-5 */
  text-decoration: none; color: var(--cr-primary); font-size: var(--cr-text-xl);
}
.cal-head a:hover { background: var(--cr-primary-subtle); }
.cal-head span.off { color: var(--cr-slate-500); }

.dow, .days { display: grid; grid-template-columns: repeat(7, 1fr); text-align: center; }
.dow span { font-size: 11px; font-weight: 600; color: var(--cr-text-muted); padding: 4px 0; }
.day { padding: 2px; }
.day a, .day span {
  display: grid; place-items: center; width: 44px; height: 44px; margin: 0 auto; /* LAY-5 */
  border-radius: var(--cr-radius-full); font-variant-numeric: tabular-nums; font-size: 13.5px; text-decoration: none;
}
.day a { color: var(--cr-primary-hover); background: var(--cr-primary-subtle); font-weight: 600; }
.day a:hover { background: var(--cr-teal-100); }
.day a.sel { background: var(--cr-primary); color: var(--cr-on-primary); }
.day span { color: var(--cr-slate-500); }

/* Booking page header media -- profile picture, licence line, page images
   strip (book_page.html, #223 Task 5). Renders above the stepper below,
   in every state (unbookable included) since it's tenant identity, not
   part of the booking flow itself. */
.book-head { display: flex; align-items: center; gap: var(--cr-space-4); margin: 0 0 var(--cr-space-4); }
.book-head h1 { margin: 0; }
/* #458 F2: contain on every image surface that used to crop -- the avatar
   letterboxes inside its circle on a neutral ground rather than cropping a
   portrait down to an unrecognisable centre. */
.book-avatar { width: 64px; height: 64px; border-radius: var(--cr-radius-full); object-fit: contain; background: var(--cr-slate-100); flex: none; }
.book-licence { font-size: var(--cr-text-xs); color: var(--cr-text-muted); margin: 2px 0 0; }
/* auto-fit, not a fixed 3 columns -- at most 4 images (page-settings caps
   it), and this collapses to fewer/narrower columns on a 360px phone (LAY-4)
   instead of squeezing three fixed columns into it. */
.book-images { display: flex; gap: var(--cr-space-3); margin: 0 0 var(--cr-space-4); overflow-x: auto; }
.book-images img { flex: none; height: 170px; aspect-ratio: 4 / 3; object-fit: contain; background: var(--cr-slate-100); border-radius: var(--cr-radius-md); display: block; }

/* Single-page booking stepper (book_page.html, Task 3). */
.typepick { display: flex; align-items: center; gap: var(--cr-space-3); margin: 0 0 var(--cr-space-4); flex-wrap: wrap; }
.typepick .flabel { font-weight: 600; font-size: 14px; }
.typepick select { width: auto; max-width: 100%; min-width: 0; flex: 1 1 auto; }
.hmsg{white-space:pre-line;margin:0 0 4px}
.fine{font-size:12.5px;color:var(--cr-text-muted);margin:0 0 16px}
.steps{display:flex;gap:18px;margin:0 0 18px;color:var(--cr-text-muted);font-size:14px}
.steps .cur{color:var(--cr-text);font-weight:600}
.pickedcard{display:flex;align-items:center;gap:var(--cr-space-3);border:1px solid var(--cr-border);border-radius:var(--cr-radius-md);padding:var(--cr-space-3) var(--cr-space-4);margin:0 0 var(--cr-space-4)}
.pickedcard .x{margin-left:auto;font-size:20px;text-decoration:none;color:var(--cr-text-muted)}
.greetrow { display: flex; justify-content: space-between; align-items: center; gap: var(--cr-space-3); margin-bottom: var(--cr-space-3); }
.greetrow p { margin: 0; }
.maskrow { display: flex; align-items: center; gap: var(--cr-space-3); margin: var(--cr-space-1) 0 0; }
.pickedcard.stack { flex-direction: column; align-items: stretch; }
input[data-masked] { display: none; }
.slot-grid a.slot{display:block;text-align:center;text-decoration:none}
/* Anchor-based slots carry no <input>, so the radio-driven
   .slot:has(input:checked) rule above can never match them -- same
   selected treatment, applied via the .sel class the template sets
   instead (matches the .day a.sel precedent above). */
.slot-grid a.slot.sel{background:var(--cr-primary);color:var(--cr-on-primary);border-color:var(--cr-primary)}

/* Slot picker layout + appointment/confirmation cards (book_page.html, book_confirmed.html) */
.picker { display: flex; gap: var(--cr-space-8); flex-wrap: wrap; }
.apptcard { border-bottom: 1px solid var(--cr-border); padding: 0 0 var(--cr-space-4); margin-bottom: var(--cr-space-5); }
.apptcard h1 { font-size: 21px; margin: 0 0 2px; }
.apptcard .with { color: var(--cr-text-muted); font-size: 13.5px; }
.times { flex: 1 1 260px; }
.times h2 { font-size: var(--cr-text-lg); margin: 2px 0 2px; }
.tz { font-size: var(--cr-text-xs); color: var(--cr-text-muted); margin-bottom: var(--cr-space-3); }

.confirm { text-align: center; padding: 14px 0 6px; }
.tick { width: 52px; height: 52px; margin: 0 auto var(--cr-space-4); border-radius: var(--cr-radius-full); background: var(--cr-success-tint); display: grid; place-items: center; }
.tick svg { width: 26px; height: 26px; stroke: var(--cr-success); stroke-width: 2.4; fill: none; stroke-linecap: round; stroke-linejoin: round; }
.confirm h1 { font-size: 22px; margin: 0 0 6px; }
/* Danger-recoloured .tick variant (book_paid_wait.html's refunded/failed
   states) -- same anatomy as .tick above, swapped to the danger
   tint/text tokens instead of success. .tick's anatomy is unscoped from
   .confirm (not .confirm .tick) so it renders correctly wherever it's used,
   e.g. the design gallery's standalone confirmation-icons row (#166 review
   round 1 -- a .confirm-scoped rule left the gallery's tick invisible). */
.tick.tick-danger { background: var(--cr-danger-tint); }
.tick.tick-danger svg { stroke: var(--cr-danger-text); }
/* Loading spinner (book_paid_wait.html's pending state) -- the loading-state
   companion to .tick above. */
.spin { width: 34px; height: 34px; border-radius: 50%; border: 3px solid var(--cr-teal-50); border-top-color: var(--cr-primary); margin: 0 auto var(--cr-space-4); animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spin { animation: none; } }
.detail { display: inline-block; margin-top: var(--cr-space-4); border: 1px solid var(--cr-border); border-radius: var(--cr-radius-md); padding: 14px 26px; text-align: left; }
.cmsg { white-space: pre-line; }

/* Notification preferences page (#538: preferences.html) -- one bordered
   card holding full-width tappable checkbox rows, public/unauthenticated
   like .confirm/.receipt above but left-aligned, not centered. */
.card { background: var(--cr-surface); border: 1px solid var(--cr-border); border-radius: var(--cr-radius-lg); padding: var(--cr-space-6) var(--cr-space-5); max-width: 32rem; margin: 0 auto; box-sizing: border-box; }
.card h1 { font-size: var(--cr-text-xl); margin: 0 0 6px; }
.card .rows { gap: var(--cr-space-2); margin: 0 0 var(--cr-space-5); }
.row { display: flex; align-items: flex-start; gap: var(--cr-space-3); padding: 14px; border: 1px solid var(--cr-border); border-radius: var(--cr-radius-md); cursor: pointer; min-height: 44px; box-sizing: border-box; }
.row:has(input:checked) { border-color: var(--cr-primary); background: var(--cr-primary-subtle); }
.row input { width: var(--cr-space-5); height: var(--cr-space-5); margin: 2px 0 0; accent-color: var(--cr-primary); flex: none; }
.row:has(input:focus-visible) { outline: 2px solid var(--cr-focus); outline-offset: 2px; }
.row .txt { display: flex; flex-direction: column; gap: 2px; }
.row .name { font-weight: 500; font-size: var(--cr-text-sm); }
.row .desc { color: var(--cr-text-muted); font-size: var(--cr-text-xs); }
/* Full-width primary CTA variant of .btn-pill -- every other .btn-pill use
   sits inline in a .btn-row next to siblings; this page has one lone save
   action instead, so it earns its own width/size instead of bending the
   shared inline default. */
.btn-pill.btn-block { display: block; width: 100%; padding: 13px var(--cr-space-5); min-height: var(--cr-space-12); font-size: var(--cr-text-base); text-align: center; }
.toast { display: flex; align-items: center; gap: var(--cr-space-3); background: var(--cr-success-tint); color: var(--cr-success-text); border-radius: var(--cr-radius-md); padding: var(--cr-space-3) 14px; font-size: var(--cr-text-sm); font-weight: 500; margin: 0 0 var(--cr-space-4); }

code {
  font-family: var(--cr-font-mono);
  font-size: var(--cr-text-sm);
  background: var(--cr-neutral-tint);
  color: var(--cr-text);
  padding: .1em .35em;
  border-radius: var(--cr-radius-sm);
}

:focus-visible { outline: 2px solid var(--cr-focus); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; }
}

/* Reveal panels -- Rename, New calendar, off hours Add/Edit. The trigger
   button (data-reveal, shell.js) NEVER moves: the panel is a separate
   sibling below it, opened in place and closed only by its own Cancel
   (data-conceal). Replaced the <details>/<summary> version, whose open
   state reflowed the trigger inside its flex row. */
.reveal-panel { margin: 0 0 var(--cr-space-3); }
.reveal-panel form { display: flex; align-items: center; gap: var(--cr-space-2); flex-wrap: wrap; }

/* This week strip (calendar.html) -- Mon-Sun bookings, colour-coded per
   calendar. Named .wk-day rather than .day: .day is already the public
   booking month-grid's day-number button (book_page.html). */
.wkwrap { overflow-x: auto; }
/* Columns float down to an 80px floor instead of a flat 760px min-width, so
   tablet's narrower content column shows more of the week before .wkwrap's
   scroll kicks in (LAY-7) -- was 3-4 days visible at 768px, now ~5. */
.wk7 { display: grid; grid-template-columns: repeat(7, minmax(80px, 1fr)); gap: var(--cr-space-2); }
.wk-day { border: 1px solid var(--cr-border); border-radius: var(--cr-radius-md); padding: var(--cr-space-2); min-height: 96px; background: var(--cr-surface); }
.wk-day .dh { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .08em; color: var(--cr-text-muted); margin-bottom: 6px; }
.ev { border-radius: var(--cr-radius-sm); padding: 3px 7px; font-size: 11.5px; font-weight: 500; margin-bottom: 5px; border-left: 3px solid; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ev-teal { background: var(--cr-primary-subtle); color: var(--cr-primary-hover); border-left-color: var(--cr-primary); }
.ev-red { background: var(--cr-danger-tint); color: var(--cr-danger-text); border-left-color: var(--cr-danger); }
.ev-blue { background: var(--cr-info-tint); color: var(--cr-info-text); border-left-color: var(--cr-info); }
.ev-graphite { background: var(--cr-neutral-tint); color: var(--cr-ink-700); border-left-color: var(--cr-ink-700); }
.ev-amber { background: var(--cr-warning-tint); color: var(--cr-warning-text); border-left-color: var(--cr-warning); }

/* #411: Google Calendar busy blocks on the Home views -- display-only,
   striped so blocked time reads as not-a-booking at a glance. */
.ev-gbusy { background: repeating-linear-gradient(45deg, var(--cr-slate-100), var(--cr-slate-100) 5px, var(--cr-slate-150) 5px, var(--cr-slate-150) 10px); color: var(--cr-slate-500); border-left: 3px dashed var(--cr-ink-700); cursor: default; }
/* flex-wrap: the row's five children (t/n/ty/sp/glabel) don't all fit on
   one line at 360px -- VER-3's responsive check caught the overflow the
   gallery demo below exposes; wrapping to a second line beats a fixed
   min-width shrink race between the label pill and the muted "from Google
   Calendar" text. */
.gbusy-row { background: repeating-linear-gradient(45deg, var(--cr-slate-100), var(--cr-slate-100) 6px, var(--cr-slate-150) 6px, var(--cr-slate-150) 12px); border-style: dashed; cursor: default; color: var(--cr-slate-500); flex-wrap: wrap; row-gap: var(--cr-space-1); }
.glabel { font-size: 11px; color: var(--cr-slate-500); border: 1px solid var(--cr-border); border-radius: var(--cr-radius-full); padding: 1px 8px; background: var(--cr-surface); }

/* Week grid (calendar.html, #216 direction pivot) -- availability IS the
   grid: one row per interval mark, 7 weekday columns, a checkbox per cell
   (checked = enabled). Cards stack one per row now (no more side-by-side
   2-up layout -- owner call after live testing rejected the two-part UI). */
.stimes-head { display: flex; align-items: center; gap: var(--cr-space-2); margin: var(--cr-space-4) 0 var(--cr-space-3); padding-top: var(--cr-space-3); border-top: 1px solid var(--cr-border); flex-wrap: wrap; }
.stimes-head .flabel { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .08em; color: var(--cr-text-muted); }
.stimes-head select { width: auto; }
.wg-head, .wg-row { display: grid; grid-template-columns: 46px repeat(7, 1fr); column-gap: 4px; align-items: center; user-select: none; } /* #222: drags must not select the row labels */
.wg-head { font-size: 10.5px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; color: var(--cr-ink-600); padding-bottom: var(--cr-space-2); }
.wg-day { text-align: center; }
/* #222: day headers are column-toggle buttons with a tri-state (shell.js
   stamps data-state after every change). */
button.wg-day { border: 1px solid transparent; background: none; font: inherit; color: inherit;
  min-height: 44px; /* LAY-5 touch target */
  text-transform: inherit; letter-spacing: inherit; padding: 4px 0; border-radius: 6px; cursor: pointer; }
button.wg-day:hover { border-color: var(--cr-border); }
button.wg-day[data-state="some"] { background: var(--cr-neutral-tint); }
/* #254 UX012: one "offered" colour trio per calendar colour, shared by
   cells, day headers and the legend. Teal has a kit mid tint; the other
   four have none, so their fill derives from the strong colour itself
   (color-mix over tokens -- no new hexes). */
.wg-teal { --wg-on-bg: var(--cr-teal-200); --wg-on-border: var(--cr-primary); }
.wg-red { --wg-on-bg: color-mix(in srgb, var(--cr-danger) 30%, var(--cr-white)); --wg-on-border: var(--cr-danger); }
.wg-blue { --wg-on-bg: color-mix(in srgb, var(--cr-info) 30%, var(--cr-white)); --wg-on-border: var(--cr-info); }
.wg-graphite { --wg-on-bg: color-mix(in srgb, var(--cr-ink-700) 30%, var(--cr-white)); --wg-on-border: var(--cr-ink-700); }
.wg-amber { --wg-on-bg: color-mix(in srgb, var(--cr-warning) 30%, var(--cr-white)); --wg-on-border: var(--cr-warning); }
/* "all" takes the calendar's own colour trio -- same token mapping as the
   wg-cell:has(input:checked) rule below. */
/* Ink, not the colour's own text tone: the mid-tint fill only reaches
   AA (4.5:1) for small text against ink -- axe caught teal-700 on
   teal-200 at 3.6:1. */
button.wg-day[data-state="all"] { background: var(--wg-on-bg); color: var(--cr-text); border-color: var(--wg-on-border); }
/* Grid renders only the calendar's window rows now (#216 owner round) --
   no more 24h scroll. max-height/overflow-y is a native, no-JS "scroll only
   past ~20 rows" behaviour: it simply has no effect while content is
   shorter than 720px (roughly 20 of the rows below), and only kicks in once
   a sub-hour interval's row count actually exceeds that. */
.wg-body { max-height: 720px; overflow-y: auto; touch-action: none; } /* #222: drag paints, page must not scroll */
.wg-row { min-height: 36px; }
.wg-label { font-size: 10.5px; font-variant-numeric: tabular-nums; color: var(--cr-text-muted); text-align: right; padding-right: 6px; }
/* Disabled/unchecked cells read muted; enabled/checked cells fill with the
   calendar's own colour tint, reusing the ev- and sw- colour token mapping
   already established above rather than inventing a second one. */
/* #254 UX012: the old border was 1.25:1 against the card; slate-500 is 4.97:1. */
.wg-cell { display: block; height: 34px; margin: 1px 0; border: 1px solid var(--cr-slate-500); border-radius: 2px; cursor: pointer; background: var(--cr-neutral-tint); }
.wg-cell input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.wg-cell:has(input:focus-visible) { outline: 2px solid var(--cr-focus); outline-offset: 1px; }
.wg-cell:has(input:checked) { background: var(--wg-on-bg); border-color: var(--wg-on-border); }

.wg-legend { display: flex; gap: var(--cr-space-4); align-items: center; font-size: var(--cr-text-xs); color: var(--cr-ink-600); margin-bottom: var(--cr-space-2); }
.wg-legend span { display: inline-flex; align-items: center; gap: var(--cr-space-1); }
.wg-legend .sw-box { width: var(--cr-space-3); height: var(--cr-space-3); border: 1px solid var(--cr-slate-500); background: var(--cr-neutral-tint); flex: none; }
.wg-legend .sw-box.on { background: var(--wg-on-bg); border-color: var(--wg-on-border); }
/* #153 lesson: the display rule must not defeat [hidden]. */
.grid-toast { margin-top: var(--cr-space-3); display: flex; justify-content: center; }
.grid-toast[hidden] { display: none; }
.grid-toast .toast-pill { /* not .pill -- that class is the status pill with a ::before dot */  background: var(--cr-surface-inverse); color: var(--cr-text-inverse); border-radius: var(--cr-radius-full); padding: var(--cr-space-2) var(--cr-space-4); font-size: var(--cr-text-sm); box-shadow: var(--cr-shadow-md); display: inline-flex; align-items: center; gap: var(--cr-space-3); }
/* #323 UX029: page-level save confirmation, rendered once by base.html from
   the notice query param. Same pill look as .toast-pill, fixed bottom centre
   so it is visible wherever the restored scroll position lands. */
/* Centred on the CONTENT column, not the viewport: the fixed 236px sidebar
   pushes the visual centre right by half its width on desktop; the mobile
   drawer overlays instead, so below 600px plain viewport centring is right. */
.page-toast { position: fixed; left: calc(50% + 118px); transform: translateX(-50%); bottom: var(--cr-space-6); z-index: 40; background: var(--cr-surface-inverse); color: var(--cr-text-inverse); border-radius: var(--cr-radius-full); padding: var(--cr-space-2) var(--cr-space-4); font-size: var(--cr-text-sm); box-shadow: var(--cr-shadow-md); max-width: min(85vw, 34rem); text-align: center; }
@media (max-width: 599px) { .page-toast { left: 50%; } }
.page-toast[hidden] { display: none; }
/* #323 UX030: live character counter under a capped textarea. */
.char-count { font-size: var(--cr-text-xs); color: var(--cr-text-muted); text-align: right; margin: var(--cr-space-1) 0 0; }
.char-count.near-limit { color: var(--cr-warning-text); font-weight: 600; }

/* Calendar screen (calendar.html) — calendar cards, colour swatches, off hours. */
.cal-card-head { display: flex; align-items: center; gap: var(--cr-space-3); flex-wrap: wrap; margin-bottom: var(--cr-space-4); }
.cal-card-head h2 { margin: 0; font-size: 15px; }
.cal-card-head .sp { flex: 1; }
.cal-card-head form.inline { display: inline-flex; align-items: center; gap: var(--cr-space-2); }
.cal-card-head input[type="text"] { width: 12rem; }
.chip-df { background: var(--cr-primary-subtle); color: var(--cr-primary-hover); }
.chip-muted { background: var(--cr-surface-subtle); color: var(--cr-text-muted); border: 1px solid var(--cr-border); }

.btn-pill.mini { padding: 5px 14px; font-size: 12.5px; min-width: 44px; }
.btn-pill.btn-danger { background: var(--cr-danger); }
.btn-pill.btn-danger:hover { background: var(--cr-danger); filter: brightness(0.88); }
.btn-pill[disabled] { background: var(--cr-neutral-tint); color: var(--cr-text-muted); cursor: not-allowed; }
.btn-pill[disabled]:hover { background: var(--cr-neutral-tint); }

/* Outline/ghost pill -- an additive modifier (kept off the base .btn-pill.btn-danger
   rule above on purpose, so calendar_sync.html/appointments.html's filled danger
   buttons are untouched). Matches the mockup's secondary-action buttons: Rename,
   Delete, off hours Add/Edit/Delete. */
.btn-pill.outline { background: none; color: var(--cr-primary); border: 1px solid var(--cr-border); }
.btn-pill.outline:hover { background: var(--cr-primary-subtle); color: var(--cr-primary-hover); }
.btn-pill.outline.btn-danger { color: var(--cr-danger); }
.btn-pill.outline.btn-danger:hover { background: var(--cr-danger-tint); color: var(--cr-danger); }
/* Mockup treats disabled as aria-disabled (opacity .55 on the same-coloured
   button, not a colour swap) -- keep the danger-outline's red, just fade it. */
.btn-pill.outline[disabled] { opacity: .55; cursor: not-allowed; }
.btn-pill.outline[disabled]:hover { background: none; }

/* Segmented control -- groups tightly-related toggle/nav actions (calendar
   view switcher, date nav) into one bar instead of separate .btn-pill.mini's
   each carrying their own border/radius/gap. Padding can't shrink a .mini
   below its 44px LAY-5 floor, so on a crowded phone toolbar the fix is
   removing the chrome between segments, not shrinking the segments (#420). */
.segmented { display: inline-flex; border: 1px solid var(--cr-border); border-radius: var(--cr-radius-full); overflow: hidden; }
.segmented a, .segmented button { display: flex; align-items: center; justify-content: center;
  min-height: 44px; min-width: 44px; padding: 0 var(--cr-space-3); box-sizing: border-box;
  font-family: var(--cr-font-body); font-weight: 600; font-size: 13px;
  color: var(--cr-primary); text-decoration: none; border: none; border-right: 1px solid var(--cr-border);
  background: none; cursor: pointer; }
.segmented a:last-child, .segmented button:last-child { border-right: none; }
.segmented a:hover, .segmented button:hover { background: var(--cr-primary-subtle); color: var(--cr-primary-hover); }
.segmented a.current, .segmented button.current { background: var(--cr-primary); color: var(--cr-on-primary); }
.segmented a.current:hover, .segmented button.current:hover { background: var(--cr-primary); }

/* Check chips -- multi-select groups (#373, approved mockup: option A).
   The native checkbox stays in the DOM for keyboard and screen readers;
   the pill and the ring-that-fills are purely visual. */
.check-chips { display: flex; flex-wrap: wrap; gap: var(--cr-space-2); margin-bottom: var(--cr-space-5); }
.check-chips label { cursor: pointer; user-select: none; display: inline-flex; align-items: center;
  gap: var(--cr-space-2); font-size: var(--cr-text-sm); font-weight: 500;
  background: var(--cr-surface); border: 1px solid var(--cr-border); border-radius: var(--cr-radius-full);
  padding: 10px 18px 10px 10px; /* 22px ring + 2x10 + borders = 44px (LAY-5) */
  box-shadow: var(--cr-shadow-sm); transition: border-color .15s ease; }
.check-chips label:hover { border-color: var(--cr-primary); }
.check-chips input { position: absolute; opacity: 0; pointer-events: none; min-height: 0; width: 0; height: 0; }
.check-chips label:has(input:focus-visible) { outline: 2px solid var(--cr-primary); outline-offset: 2px; }
.check-chips .ind { width: 22px; height: 22px; border-radius: var(--cr-radius-full);
  border: 1.5px solid var(--cr-slate-300); display: inline-flex; align-items: center;
  justify-content: center; flex: none; transition: background .15s ease, border-color .15s ease; }
.check-chips .ind svg { width: 12px; height: 12px; stroke: var(--cr-on-primary); stroke-width: 3;
  fill: none; stroke-linecap: round; stroke-linejoin: round; opacity: 0; }
.check-chips label:has(input:checked) { border-color: var(--cr-primary); }
.check-chips label:has(input:checked) .ind { background: var(--cr-primary); border-color: var(--cr-primary); }
.check-chips label:has(input:checked) .ind svg { opacity: 1; }

/* #415: card methods lock until Stripe is connected -- same aria-disabled
   fade convention as .btn-pill.outline[disabled]. */
.check-chips label.chip-locked { opacity: .55; cursor: not-allowed; box-shadow: none; }
.check-chips label.chip-locked:hover { border-color: var(--cr-border); }
.check-chips label.chip-locked .lock { width: 13px; height: 13px; flex: none;
  stroke: var(--cr-slate-500); fill: none; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round; }

.radio { display: flex; flex-wrap: wrap; gap: var(--cr-space-3) 16px; align-items: center; min-height: 38px; margin-bottom: var(--cr-space-5); }
/* #373: without nowrap the row compresses each label instead of wrapping
   whole items, splitting "Interac e-Transfer" over three lines with the
   checkbox stranded top-left. */
.radio label { display: flex; gap: 7px; align-items: center; font-weight: 500; font-size: var(--cr-text-sm); white-space: nowrap; }

/* #226: appointment location link + tenant-titled note callout (booking
   page, confirmation, booking-detail dialog). */
.maplink { color: var(--cr-primary); text-decoration: none; font-weight: 600; font-size: 13.5px; }
.maplink:hover { text-decoration: underline; }
.loc { margin: 2px 0 8px; }
.bring { margin-top: 12px; padding: 12px 14px; background: var(--cr-warning-tint); border-radius: var(--cr-radius-md); font-size: 13.5px; text-align: left; line-height: 1.55; }
/* #327: cancel page's inside-the-refund-window warning box. */
.warnbox { background: var(--cr-warning-tint); border-radius: var(--cr-radius-md); padding: var(--cr-space-3) var(--cr-space-4); font-size: var(--cr-text-sm); margin-bottom: var(--cr-space-4); }
/* On the confirmation page the callout matches the booking-summary card's
   width instead of spanning the whole main column (owner staging catch). */
.confirm .bring { display: block; max-width: 340px; box-sizing: border-box; margin: 12px auto 0; }
.bring strong { display: block; margin-bottom: 2px; }
/* #467: pay-now-or-pay-instructor chooser on the booking details form
   (mixed card-plus-offline types only). Selected card highlights via
   :has(), same accent-color idiom as .consent input. */
.paywrap { text-align: left; }
.paychoice { display: flex; flex-direction: column; gap: var(--cr-space-2); margin-bottom: var(--cr-space-4); }
.paychoice label { width: 100%; box-sizing: border-box; }
.paychoice label { display: flex; gap: var(--cr-space-3); align-items: flex-start; border: 1px solid var(--cr-border); border-radius: var(--cr-radius-md); padding: var(--cr-space-3) var(--cr-space-4); cursor: pointer; font-size: var(--cr-text-sm); }
.paychoice label:has(input:checked) { border-color: var(--cr-primary); background: var(--cr-teal-50); }
.paychoice input { accent-color: var(--cr-primary); margin-top: 3px; }
.paychoice small { display: block; color: var(--cr-ink-600); font-size: var(--cr-text-xs); margin-top: 2px; }
/* #450: offline-payment note on the confirmation page (cash/Interac types
   that skipped Checkout); same footprint as .bring, success-tinted. */
.confirm .payline { max-width: 340px; box-sizing: border-box; margin: var(--cr-space-3) auto 0; padding: var(--cr-space-3) var(--cr-space-4); background: var(--cr-success-tint); color: var(--cr-success-text); border-radius: var(--cr-radius-md); font-size: var(--cr-text-sm); font-weight: 600; text-align: left; }

/* #249: consent control states on the client form, and the revoke confirm
   dialog capped so long copy wraps instead of widening. */
.consent-ro { display: flex; align-items: center; gap: 8px; font-size: 13.5px; color: var(--cr-text-muted); margin-bottom: var(--cr-space-6); }
.consent-ro .dot { width: 8px; height: 8px; border-radius: var(--cr-radius-full); background: var(--cr-slate-500); opacity: .5; flex: none; }
.consent-hint { font-size: 12px; color: var(--cr-text-muted); margin: -14px 0 var(--cr-space-6); line-height: 1.5; }
#revoke-modal, #confirm-modal { max-width: 26rem; }

/* Colour swatches — radio inputs styled as colour dots, token classes only,
   never a hex in a template (colour is stored as a token key). */
.swatches { display: flex; align-items: center; gap: 9px; margin-top: var(--cr-space-3); }
.swatches .lbl { font-size: 12.5px; font-weight: 600; margin-right: 2px; }
.sw { position: relative; width: 22px; height: 22px; border-radius: var(--cr-radius-full); display: inline-block; }
.sw input { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
.sw::before { content: ""; position: absolute; inset: 0; border-radius: var(--cr-radius-full); pointer-events: none; }
.sw:has(input:checked)::before { outline: 2px solid var(--cr-text); outline-offset: 2px; }
.sw:has(input:focus-visible)::before { outline: 2px solid var(--cr-focus); outline-offset: 2px; }
.sw-teal::before { background: var(--cr-teal-600); }
.sw-red::before { background: var(--cr-danger); }
.sw-blue::before { background: var(--cr-info); }
.sw-graphite::before { background: var(--cr-ink-700); }
.sw-amber::before { background: var(--cr-warning); }

/* Off hours inline add/edit row -- one tidy horizontal row at reduced field
   width; :has() (already used by .sw/.chips/.slot above) hides the date
   fields once "Every day" is picked. Comes after .cal-card-head input[type=
   text] on purpose -- same specificity, source order picks the winner. */
/* Off hours -- Acuity-style stacked form: time first, one date with a
   "Block multiple days" reveal, repeat as a checkbox. */
.oh-v { display: flex; flex-direction: column; gap: var(--cr-space-4); max-width: 26rem; }
.oh-v .f { display: flex; flex-direction: column; gap: 6px; }
.oh-v .flabel { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .08em; color: var(--cr-text-muted); }
.oh-v .pair { display: inline-flex; align-items: center; gap: var(--cr-space-2); flex-wrap: nowrap; }
.oh-v .to { color: var(--cr-text-muted); font-size: 13px; }
.oh-v .ck { display: flex; align-items: center; gap: 7px; font-size: var(--cr-text-sm); }
.oh-v input[type="text"] { width: 14rem; }
.oh-v input[type="date"] { width: 10rem; }
.oh-v input[type="time"] { width: 8rem; }
.oh-v .oh-end-wrap { display: none; }
.oh-v:has(input[name="multi"]:checked) .oh-end-wrap { display: inline-flex; }
.oh-v:has(input[name="repeat"]:checked) .oh-date-sec { display: none; }
.oh-v .actions { display: flex; gap: var(--cr-space-2); }

/* Native <dialog> popup (New calendar). */
/* #323 UX028: background and colour pinned to tokens -- without them the
   dialog uses the browser's Canvas/CanvasText pair, which forced/auto dark
   modes flip to a dark surface while the page's literal colours stay light. */
dialog.modal { background: var(--cr-surface); color: var(--cr-text); border: 1px solid var(--cr-border); border-radius: var(--cr-radius-lg); padding: var(--cr-space-6); box-shadow: var(--cr-shadow-lg); min-width: 20rem;
  /* issue 383: a dialog taller than the screen scrolls inside itself --
     without this the bottom actions are unreachable (Manage coupon grew
     past one viewport). .detail keeps overflow: visible for its menus. */
  max-height: calc(100dvh - 2 * var(--cr-space-6)); overflow-y: auto; }
dialog.modal::backdrop { background: rgba(22, 35, 43, .35); }
dialog.modal h2 { margin: 0 0 var(--cr-space-4); font-size: var(--cr-text-lg); }
dialog.modal form { display: flex; flex-direction: column; gap: var(--cr-space-4); }
dialog.modal .btn-row { margin: 0; }
dialog.modal.detail { min-width: 22rem; overflow: visible; }
dialog.modal.detail .when { margin: 0; font-weight: 600; }
dialog.modal.detail .what { margin: 0; color: var(--cr-text-muted); }
dialog.modal.detail .drows { display: flex; flex-direction: column; }
/* Booking-detail Actions dropdown (home.html) -- a <details> menu, no JS
   (CSP forbids inline script; shell.js stays untouched). Reschedule is a
   disabled placeholder until the reschedule flow ships. */
/* Row actions menu -- shared no-JS <details> dropdown (issue 383 option F,
   promoted from the booking-dialog one-off; home.html and coupons.html both
   use it). Trigger is either a labeled summary (booking dialog) or the
   .meatballs icon (table rows). */
.rowmenu, dialog.modal.detail .actions { position: relative; display: inline-block; }
.rowmenu summary, dialog.modal.detail .actions summary { list-style: none; cursor: pointer; }
.rowmenu summary::-webkit-details-marker,
dialog.modal.detail .actions summary::-webkit-details-marker { display: none; }
.rowmenu .menu, dialog.modal.detail .actions .menu { position: absolute; top: calc(100% + 6px); left: 0; min-width: 11rem;
  background: var(--cr-surface); border: 1px solid var(--cr-border); border-radius: var(--cr-radius-md);
  box-shadow: var(--cr-shadow-lg); padding: 6px; margin: 0; display: flex; flex-direction: column; z-index: 5; }
.rowmenu .menu { left: auto; right: 0; min-width: 7rem; } /* row menus hug the table edge, sized to their labels */
/* While open, the summary grows an invisible fixed overlay: a tap anywhere
   outside the menu lands on the summary and closes the <details> — the only
   dismiss path a no-JS dropdown has besides re-tapping the trigger. Sits
   under the menu (z-index 5 above). */
.rowmenu[open] > summary::before, dialog.modal.detail .actions[open] > summary::before {
  content: ""; position: fixed; inset: 0; z-index: 4; cursor: default; }
.rowmenu[open] .meatballs { background: var(--cr-primary-subtle); color: var(--cr-primary-hover); }
.rowmenu .menu a, .rowmenu .menu .menu-item, dialog.modal.detail .actions .menu a { padding: var(--cr-space-2) var(--cr-space-3); border-radius: var(--cr-radius-sm); font-size: 13.5px;
  text-decoration: none; color: var(--cr-text);
  min-height: 44px; display: flex; align-items: center; } /* LAY-5 */
.rowmenu .menu .menu-item { background: none; border: none; width: 100%; text-align: left; cursor: pointer; font: inherit; }
.rowmenu .menu form { display: contents; }
.rowmenu .menu a:hover, .rowmenu .menu .menu-item:hover, dialog.modal.detail .actions .menu a:hover { background: var(--cr-primary-subtle); color: var(--cr-primary-hover); }
.rowmenu .menu .menu-danger { color: var(--cr-danger); }
.rowmenu .menu .menu-danger:hover { background: var(--cr-danger-tint); color: var(--cr-danger-text); }
.meatballs { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; /* LAY-5 */
  border-radius: var(--cr-radius-full); color: var(--cr-text-muted); }
/* hover: hover — on touch the tap-hover sticks after the menu closes, leaving
   the meatballs lit with no menu; pointer devices only. */
@media (hover: hover) {
  .meatballs:hover { background: var(--cr-primary-subtle); color: var(--cr-primary-hover); }
}
.meatballs svg { width: 18px; height: 18px; fill: currentColor; stroke: none; }
/* An absolute .menu inside .table-scroll is clipped by the scroll box
   (overflow-x: auto clips both axes) and can sit entirely off-screen once the
   table is scrolled sideways — the tap seems to do nothing (issue 383
   follow-up). shell.js lifts the open menu to fixed coordinates beside its
   trigger (below when it fits, above otherwise); without JS the absolute
   fallback above still renders. */
dialog.modal.detail .actions .menu span[aria-disabled] { padding: 8px 12px; font-size: 13.5px;
  color: var(--cr-text-muted); cursor: not-allowed; }
dialog.modal.detail .drow { display: flex; justify-content: space-between; gap: var(--cr-space-6); padding: var(--cr-space-2) 0; border-bottom: 1px solid var(--cr-border); font-size: var(--cr-text-sm); }
dialog.modal.detail .drow > span:first-child { color: var(--cr-text-muted); }
dialog.modal.detail .drow:last-child { border-bottom: none; }

/* Quill (documents.html terms editor) -- brand tokens only, no hard-coded
   hexes (brand kit rule). #294 round 2: the toolbar is our own .btn-pill
   buttons (Quill's toolbar module is off, see shell.js), no longer a strip
   glued to the top of the editor box, so it's a plain flex row and the
   editor box gets a full rounded border on its own. #doc-editor is the
   element Quill turns into .ql-container -- targeted by id rather than
   .ql-container.ql-snow because quill.snow.css loads AFTER tokens.css on
   this page and ties on equal specificity go to whichever loaded last;
   an id beats any number of classes regardless of load order. */
.doc-toolbar { display: flex; gap: var(--cr-space-2); margin-bottom: var(--cr-space-2); }
.doc-toolbar .btn-pill[aria-pressed="true"] { background: var(--cr-primary-subtle); border-color: var(--cr-teal-400); color: var(--cr-primary-hover); }
#doc-editor { border: 1px solid var(--cr-border); border-radius: var(--cr-radius-md); background: var(--cr-surface); font-family: var(--cr-font-body); font-size: var(--cr-text-base); }
/* Quill sets outline:none on .ql-editor (snow theme) -- keyboard focus needs
   a visible substitute, same treatment as input:focus above. */
#doc-editor:focus-within { border-color: var(--cr-teal-400); }
/* The scrollable text area is a child div (.ql-editor) Quill creates inside
   #doc-editor -- sizing and content typography apply there, not to the
   container itself. */
#doc-editor .ql-editor { min-height: 14rem; max-height: 24rem; }
#doc-editor .ql-editor h2 { font-size: var(--cr-text-lg); margin: var(--cr-space-4) 0 var(--cr-space-2); }
#doc-editor .ql-editor h3 { font-size: var(--cr-text-base); margin: var(--cr-space-3) 0 var(--cr-space-2); }
#doc-editor .ql-editor p { margin: 0 0 var(--cr-space-3); }
#doc-editor .ql-editor ul, #doc-editor .ql-editor ol { margin: 0 0 var(--cr-space-3); }

/* Tenant image library grid -- Documents screen's Images section and the
   page-settings picker panels that reuse the same tiles for selection
   (#223). No incumbent thumbnail-grid class existed, so this is new. */
.img-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); gap: var(--cr-space-3); margin: var(--cr-space-1) 0 var(--cr-space-2); }
.img-tile { position: relative; border-radius: var(--cr-radius-md); overflow: hidden; aspect-ratio: 4 / 3; background: var(--cr-slate-100); border: 1px solid var(--cr-border); }
/* #458 F2 (owner-decided option B): contain, never crop -- portrait and
   off-ratio photos letterbox against the tile's neutral ground instead of
   losing their top and bottom. */
.img-tile img { width: 100%; height: 100%; object-fit: contain; display: block; }
/* #458 F1: icon button, not a text pill -- a 44px hit area (LAY-5, the
   global button min-height) around a compact visual badge. */
.img-tile .img-remove { position: absolute; top: var(--cr-space-1); right: var(--cr-space-1); width: 44px; background: var(--cr-surface); box-shadow: var(--cr-shadow-sm); border: none; border-radius: var(--cr-radius-full); padding: 0; color: var(--cr-danger); cursor: pointer; display: flex; align-items: center; justify-content: center; }
.img-tile .img-remove svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; }
/* Selectable variant for the page-settings pickers (radio for the single
   profile-picture slot, checkbox for the up-to-4 page images) -- the native
   input stays in the DOM for keyboard/screen readers, same :has() ring
   convention as .sw/.check-chips above. */
.img-pick { cursor: pointer; }
.img-pick input { position: absolute; opacity: 0; pointer-events: none; }
.img-pick:has(input:checked) { outline: 3px solid var(--cr-teal-400); outline-offset: -3px; }
/* #458 F3: an unmissable selected state -- the ring stays, a check badge
   joins it, and unselected tiles step back while any selection exists.
   Text checkmark, not an image: background-image would need img-src, the
   glyph needs nothing. */
.img-pick:has(input:checked)::after { content: "\2713"; position: absolute; top: var(--cr-space-1); left: var(--cr-space-1); width: 24px; height: 24px; background: var(--cr-primary); color: var(--cr-text-inverse); border-radius: var(--cr-radius-full); display: flex; align-items: center; justify-content: center; font-size: var(--cr-text-sm); font-weight: 700; }
.img-grid:has(input:checked) .img-pick:not(:has(input:checked)) { opacity: 0.62; }
.img-pick:has(input:focus-visible) { outline: 2px solid var(--cr-focus); outline-offset: 2px; }

/* #481: the Your plan screen -- current-subscription header and the
   three plan cards (single column at the phone tier). */
.plan-head { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--cr-space-4); flex-wrap: wrap; }
.plan-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--cr-space-4); margin: var(--cr-space-4) 0; }
.plan-grid > * { min-width: 0; }
.plan-card { background: var(--cr-surface); border: 1px solid var(--cr-border); border-radius: var(--cr-radius-md); padding: var(--cr-space-5) var(--cr-space-5); display: flex; flex-direction: column; }
.plan-card.cur { border-color: var(--cr-primary); box-shadow: 0 0 0 1px var(--cr-primary); }
.plan-card h3 { font-family: var(--cr-font-display); font-size: var(--cr-text-md); margin: 0; }
.plan-price { font-family: var(--cr-font-display); font-weight: 600; font-size: var(--cr-text-xl); margin: var(--cr-space-2) 0 0; }
.plan-price small { font-size: var(--cr-text-xs); font-weight: 400; color: var(--cr-text-muted); }
.plan-price.soon { font-size: var(--cr-text-md); color: var(--cr-text-muted); font-weight: 400; }
.plan-card ul { margin: var(--cr-space-3) 0 var(--cr-space-5); padding-left: var(--cr-space-5); font-size: var(--cr-text-sm); line-height: 1.8; }
.plan-cta { margin-top: auto; align-self: flex-start; }
.plan-cta[aria-disabled] { opacity: 0.55; cursor: default; }
@media (max-width: 599px) {
  .plan-grid { grid-template-columns: 1fr; }
}

/* Attached terms document, rendered read-only on the booking page (book_page.html). */
.link-btn { background: none; border: none; padding: 0; color: var(--cr-primary); text-decoration: underline; cursor: pointer; font: inherit; min-height: 0; display: inline; }
/* Destructive modifier for a link-styled button -- same combinable
   shape as .btn-pill.btn-danger above, recolored with the same token. */
.link-btn.btn-danger { color: var(--cr-danger); }
/* issue 27 final review Finding 5: the verify screen's resend control needs
   a real 44px touch target (LAY-5) -- scoped to this one control, not a
   change to the shared .link-btn (used elsewhere at its natural text size). */
.verify-resend { min-height: 2.75rem; display: inline-flex; align-items: center; }
/* A six digit code does not need a full-width field: sized to the content,
   large spaced digits so the code reads back at a glance. */
/* Segmented six-cell code box (issue 27, owner-approved reference): one real
   input so paste and one-time-code autofill keep working, cells drawn in CSS.
   .field input's width:100% outranks a bare class -- match its specificity.
   Cell geometry: each glyph advance is 1ch + letter-spacing = --vc-cell, and
   padding centres the glyph in its cell; the five dashed dividers are painted
   backgrounds at cell boundaries. */
.field input.verify-code {
  --vc-cell: 3rem;
  width: calc(var(--vc-cell) * 6);
  font-size: var(--cr-text-2xl);
  font-variant-numeric: tabular-nums;
  text-align: left;
  letter-spacing: calc(var(--vc-cell) - 1ch);
  padding: 0 0 0 calc((var(--vc-cell) - 1ch) / 2);
  overflow: hidden;
  background-image:
    repeating-linear-gradient(to bottom, var(--cr-border) 0 0.25rem, transparent 0.25rem 0.625rem),
    repeating-linear-gradient(to bottom, var(--cr-border) 0 0.25rem, transparent 0.25rem 0.625rem),
    repeating-linear-gradient(to bottom, var(--cr-border) 0 0.25rem, transparent 0.25rem 0.625rem),
    repeating-linear-gradient(to bottom, var(--cr-border) 0 0.25rem, transparent 0.25rem 0.625rem),
    repeating-linear-gradient(to bottom, var(--cr-border) 0 0.25rem, transparent 0.25rem 0.625rem);
  background-repeat: no-repeat;
  background-size: 0.0625rem 55%;
  background-position:
    calc(var(--vc-cell) * 1) center,
    calc(var(--vc-cell) * 2) center,
    calc(var(--vc-cell) * 3) center,
    calc(var(--vc-cell) * 4) center,
    calc(var(--vc-cell) * 5) center;
}
/* #373: once all six digits are in (:valid = pattern [0-9]{6}), the caret
   blinking at the right edge reads as room for a seventh digit -- hide it.
   Deleting a digit makes the input invalid again and the caret returns. */
.field input.verify-code:valid { caret-color: transparent; }
dialog.doc-modal { max-width: 34rem; max-height: 80vh; overflow-y: auto; }
.doc-body h2 { font-size: var(--cr-text-lg); margin: var(--cr-space-4) 0 var(--cr-space-2); }
.doc-body h3 { font-size: var(--cr-text-base); margin: var(--cr-space-3) 0 var(--cr-space-2); }

.ev { display: block; color: inherit; text-decoration: none; }
a.ev:hover { filter: brightness(0.95); text-decoration: none; }

/* Connect/disconnect state line — per calendar (#431: grid so the action
   column can never wrap below the text -- dot | text | action). */
.syncline { display: grid; grid-template-columns: auto 1fr auto; align-items: center;
  column-gap: var(--cr-space-3); padding-top: var(--cr-space-3); margin-top: var(--cr-space-3);
  border-top: 1px solid var(--cr-border); font-size: 13.5px; }
.syncline .txt { display: flex; flex-direction: column; min-width: 0; }
.syncline .nm { font-weight: 600; }
.syncline .st { color: var(--cr-text-muted); font-size: var(--cr-text-sm); }
.dot { width: 8px; height: 8px; border-radius: var(--cr-radius-full); flex: none; display: inline-block; }
.dot.ok { background: var(--cr-success); }
.dot.off { background: var(--cr-slate-400); }
.dot.warn { background: var(--cr-warning); }

/* Off hours rows. */
.rows { display: flex; flex-direction: column; }
.oh-row { display: flex; align-items: center; gap: var(--cr-space-3); flex-wrap: wrap; padding: var(--cr-space-3) 0; border-bottom: 1px solid var(--cr-border); }
.oh-row:last-child { border-bottom: none; }
.oh-row form { display: flex; align-items: center; gap: var(--cr-space-2); flex-wrap: wrap; }
.oh-row input[type="text"] { width: 10rem; }
.oh-row .sp { flex: 1; }
.oh-row .who { font-weight: 600; }
.oh-row .d { color: var(--cr-text-muted); font-size: 13px; }

/* #252: forms builder rows */
.form-row { display: grid; grid-template-columns: 1fr 120px; align-items: center;
  gap: var(--cr-space-3); padding: var(--cr-space-3) 0; border-bottom: 1px solid var(--cr-border); }
.form-row:last-child { border-bottom: none; }
.form-row .who { font-weight: 600; }
.form-row .always { text-align: right; margin: 0; }
.form-row form { display: flex; justify-content: flex-end; }
.custom-actions { align-items: center; }
.danger-row { justify-content: flex-start; align-items: center; margin-top: var(--cr-space-5);
  border-top: 1px solid var(--cr-border); padding-top: var(--cr-space-4); }

/* Home calendar section (#213) -- view toolbar reuses .btn-pill/.cal-card-head
   as-is; these are the two view-specific layouts it adds: the Day view's
   booking list, and the Month view's day-number-plus-count-badge grid
   (deliberately separate from .days/.day above, which is the small
   fixed-size circular date PICKER on the public booking page -- this grid's
   cells are bigger, whole-cell-clickable, and carry a count badge, which
   the picker's 36px circles have no room for). */
.daylist { display: flex; flex-direction: column; gap: var(--cr-space-2); }
.dayrow {
  display: flex; align-items: center; gap: var(--cr-space-4);
  padding: var(--cr-space-3) var(--cr-space-4); border: 1px solid var(--cr-border);
  border-radius: var(--cr-radius-md); text-decoration: none; color: inherit;
}
.dayrow:hover { border-color: var(--cr-teal-400); text-decoration: none; }
.dayrow .t { font-variant-numeric: tabular-nums; font-weight: 600; width: 112px; flex-shrink: 0; }
.dayrow .n { flex: 1; }
.dayrow .ty { color: var(--cr-text-muted); font-size: var(--cr-text-sm); }
.dayrow .sp { flex: 1; }

.mgrid { display: grid; grid-template-columns: repeat(7, 1fr); gap: var(--cr-space-2); }
.mgrid .dow-h {
  text-align: center; font-size: 11px; font-weight: 600; color: var(--cr-text-muted);
  text-transform: uppercase; letter-spacing: .06em; padding: 0 0 var(--cr-space-2);
}
.mcell {
  border: 1px solid var(--cr-border); border-radius: var(--cr-radius-md); min-height: 68px;
  padding: var(--cr-space-2); display: flex; flex-direction: column; gap: 6px;
  text-decoration: none; color: inherit;
}
.mcell.blank { border-color: transparent; }
a.mcell:hover { border-color: var(--cr-teal-400); }
.mcell .n { font-variant-numeric: tabular-nums; font-weight: 600; font-size: 13.5px; }

/* Pull-to-refresh puck (issue 373). Anatomy is global so the gallery can
   show it at any width (same lesson as the gallery tick: never scope a
   component's anatomy to one context); display and positioning are gated
   to the mobile media block below, .g-ptr-static re-enables it inline. */
.ptr { display: none; width: 40px; height: 40px; align-items: center;
  justify-content: center; border-radius: var(--cr-radius-full);
  background: var(--cr-surface); box-shadow: var(--cr-shadow-md); }
.ptr-arrow { width: 20px; height: 20px; stroke: var(--cr-primary);
  stroke-width: 2.5; fill: none; stroke-linecap: round; stroke-linejoin: round; }
.ptr .spin { width: 20px; height: 20px; border-width: 2.5px; margin: 0; }

/* Mobile drawer — sidebar collapses behind the top bar's burger toggle below 600px
   (LAY-2 phone tier). Tablet (600-879px) keeps the persistent desktop sidebar. */
@media (max-width: 599px) {
  .topbar { display: flex; }
  .shell { flex-direction: column; }
  /* Column-flex stretch floors a child at its content min-content width in
     Chromium; without this cap the widest table sets the page width and
     .table-scroll never gets to scroll (LAY-3/7, #267). */
  .content { max-width: 100%; }
  .topbar { width: 100%; }
  .sidebar { position: fixed; inset: 0 auto 0 0; z-index: 40; overflow-y: auto;
             transform: translateX(-105%); transition: transform .2s; }
  .sidebar.open { transform: none; }
  /* #373: the open drawer covers the burger, so it needs its own way out --
     tap the dimmed page or swipe left (shell.js). Same tint as the modal
     ::backdrop above. */
  .drawer-backdrop:not([hidden]) { position: fixed; inset: 0; z-index: 35;
             background: rgba(22, 35, 43, .35); }
  /* Pull-to-refresh puck (issue 373): shell.js slides it down as the pull
     progresses, rotating the arrow, and swaps in the spinner on release.
     overscroll-behavior keeps Chrome's native pull-to-refresh and iOS
     scroll chaining from fighting the gesture. */
  body { overscroll-behavior-y: none; }
  .ptr { display: flex; position: fixed; top: 0; left: 50%; z-index: 30;
    margin-left: -20px; transform: translateY(-48px); }
}

/* Landing page preview card (page.html) */
.page-preview{width:100%;height:560px;border:1px solid var(--cr-border);border-radius:var(--cr-radius-md);display:block;margin:0 auto}
.mono-url{font-family:var(--cr-font-mono);font-size:12.5px;color:var(--cr-text-muted)}
/* Coupon code column (profile.html Coupons panel, #166 task 9). */
.mono{font-family:var(--cr-font-mono);font-size:var(--cr-text-sm)}
@media (prefers-reduced-motion: reduce) { .sidebar { transition: none; } }

/* #252: client answers -- custom form answers on the client edit page.
   Same definition-row shape as dialog.modal.detail's .drow, but that rule
   is scoped to the booking-detail modal and does not reach this plain
   page, so it gets its own small block instead of being reused as-is. */
.answers { display: flex; flex-direction: column; }
.answer-row { display: flex; justify-content: space-between; gap: var(--cr-space-4);
  padding: var(--cr-space-2) 0; border-bottom: 1px solid var(--cr-border); font-size: var(--cr-text-sm); }
.answer-row:last-child { border-bottom: none; }
.answer-label { color: var(--cr-text-muted); }
.answer-date { color: var(--cr-text-muted); flex: none; }

/* LAY-7: wide content scrolls in its own container, never the page.
   Introduced for the gallery; production tables adopt it in #267. */
.table-scroll { overflow-x: auto; }
/* #569: the wrapper carries tabindex="0" in every template so a keyboard
   user can focus and arrow-scroll an overflowing table (axe
   scrollable-region-focusable, WCAG 2.1.1). The global :focus-visible rule
   draws the ring; nothing extra here. */
/* A table's action row breathes -- controls never sit flush against the
   card above them (issue 383 review; same catch as the #294 class). */
.table-scroll + .btn-row { margin-top: var(--cr-space-5); }
/* Same catch from the other side: a filter form directly above a table (the
   clients list, both the portal page and the React client) never sits flush
   against the table's top edge (#606 review). */
form + .table-scroll { margin-top: var(--cr-space-5); }
/* List toolbar (#606, mockup round 4): Search, Filter and Sort over a table,
   right-aligned on every width. A popover hangs under the toolbar's right
   edge; on a phone it spans the content width. The search field drops to
   its own row on a phone (flex order) and takes the Search button's place
   from the tablet tier up. Applied filters are chips under the toolbar. */
.toolbar { display: flex; align-items: center; justify-content: flex-end; gap: var(--cr-space-1); flex-wrap: wrap; margin-bottom: var(--cr-space-4); position: relative; }
.tb { display: inline-flex; align-items: center; gap: var(--cr-space-2); flex: none; white-space: nowrap; padding: 0 var(--cr-space-3); border: 1px solid transparent; border-radius: var(--cr-radius-full); background: none; color: var(--cr-ink-600); font-family: var(--cr-font-body); font-size: var(--cr-text-sm); font-weight: 600; cursor: pointer; }
.tb.on { background: var(--cr-primary-subtle); color: var(--cr-primary-hover); }
.tb.tb-icon { width: var(--cr-space-12); padding: 0; justify-content: center; }
.tb svg, .search-field > svg, .toolbar-pop .opt svg, .filter-chip svg { width: 1.125rem; height: 1.125rem; flex: none; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.tb-count { display: inline-flex; align-items: center; justify-content: center; min-width: 1.125rem; height: 1.125rem; border-radius: var(--cr-radius-full); background: var(--cr-primary); color: var(--cr-surface); font-size: var(--cr-text-xs); }
.toolbar-pop { position: absolute; top: 100%; right: 0; z-index: 2; box-sizing: border-box; width: 100%; max-width: 21.25rem; margin-top: var(--cr-space-1); padding: var(--cr-space-2); background: var(--cr-surface); border: 1px solid var(--cr-border); border-radius: var(--cr-radius-md); box-shadow: var(--cr-shadow-md); }
.toolbar-pop .pop-title { margin: 0; padding: var(--cr-space-2) var(--cr-space-3) var(--cr-space-1); font-family: var(--cr-font-display); font-size: var(--cr-text-xs); font-weight: 600; text-transform: uppercase; letter-spacing: .08em; color: var(--cr-text-muted); }
.toolbar-pop .opt { display: flex; align-items: center; justify-content: space-between; width: 100%; padding: 0 var(--cr-space-3); border: none; border-radius: var(--cr-radius-sm); background: none; color: var(--cr-text); font-family: var(--cr-font-body); font-size: var(--cr-text-sm); text-align: left; cursor: pointer; }
.toolbar-pop .opt[aria-pressed="true"] { background: var(--cr-primary-subtle); color: var(--cr-primary-hover); font-weight: 600; }
.search-field { display: flex; align-items: center; gap: var(--cr-space-2); flex: 1 1 100%; order: 1; box-sizing: border-box; padding: 0 var(--cr-space-1) 0 var(--cr-space-3); border: 1px solid var(--cr-teal-400); border-radius: var(--cr-radius-full); background: var(--cr-surface); color: var(--cr-text-muted); }
.search-field input { flex: 1; min-width: 0; padding: 0; border: none; background: none; color: var(--cr-text); }
.search-field input:focus { outline: none; }
.search-field input::-webkit-search-cancel-button, .search-field input::-webkit-search-decoration { -webkit-appearance: none; appearance: none; }
.search-field:focus-within { outline: 0.125rem solid var(--cr-focus); outline-offset: 0.125rem; }
.filter-chips { display: flex; flex-wrap: wrap; gap: var(--cr-space-2); margin-bottom: var(--cr-space-4); }
.filter-chip { display: inline-flex; align-items: center; gap: var(--cr-space-1); padding: var(--cr-space-1) var(--cr-space-1) var(--cr-space-1) var(--cr-space-3); border-radius: var(--cr-radius-full); background: var(--cr-primary-subtle); color: var(--cr-primary-hover); font-size: var(--cr-text-xs); font-weight: 600; }
.filter-chip .val { font-weight: 400; }
.filter-chip button { display: inline-flex; align-items: center; justify-content: center; width: 2.75rem; height: 2.75rem; min-height: 0; margin: -0.625rem -0.625rem -0.625rem -0.25rem; padding: 0; border: none; border-radius: var(--cr-radius-full); background: none; color: inherit; cursor: pointer; }
/* hover: hover -- a tap's hover tint otherwise sticks on a touch screen until
   the next touch (same defect class as .meatballs:hover above): the Sort
   button stays lit with no menu open, or the chip's x stays lit after it is
   tapped. Pointer devices only, same idiom as .meatballs:hover. */
@media (hover: hover) {
  .tb:hover { background: var(--cr-primary-subtle); color: var(--cr-primary-hover); }
  .toolbar-pop .opt:hover { background: var(--cr-neutral-tint); }
  .filter-chip button:hover svg { background: var(--cr-surface); border-radius: var(--cr-radius-full); }
}
@media (min-width: 600px) {
  .search-field { flex: 0 1 17.5rem; order: 0; }
  .toolbar.searching .tb-search { display: none; }
}
/* No standard scrollbar-width here: declaring it makes Chromium ignore the
   webkit pseudo styling below and revert to auto-hiding overlay bars. */
/* macOS overlay scrollbars hide until scroll, so a clipped column reads as a
   bug instead of "more to the right" — keep the bar visible (webkit). */
.table-scroll::-webkit-scrollbar { height: 8px; }
.table-scroll::-webkit-scrollbar-thumb { background: var(--cr-slate-300); border-radius: var(--cr-radius-full); }
.table-scroll::-webkit-scrollbar-track { background: var(--cr-slate-100); border-radius: var(--cr-radius-full); }
.table-scroll th, .table-scroll td { white-space: nowrap; }

/* #265: design gallery (dev-only /design-system route). */
#g-logo svg { width: 120px; height: auto; display: block; }
.g-row-left { justify-content: flex-start; flex-wrap: wrap; align-items: center; }
/* In the app .spin centres itself with auto margins (book_paid_wait); in a
   flex row those auto margins absorb the free space and shove the other
   icons apart, so the gallery row resets them (#373, Pedro's catch). */
.g-row-left .spin, .g-row-left .tick { margin: 0; }
dialog.modal.g-dialog-static { position: static; min-width: 0; max-width: 100%; box-sizing: border-box; margin: 0; }
/* Gallery-only: the pull-to-refresh puck is fixed and mobile-only in the app;
   the gallery shows it inline at any width. */
.ptr.g-ptr-static { display: flex; position: static; margin: 0; transform: none; }
.g-menu-static .menu { position: static; margin-top: var(--cr-space-1); min-width: 9rem; }
.g-pop-static .toolbar-pop { position: static; width: auto; max-width: 21.25rem; margin-top: var(--cr-space-2); }
/* The gallery card renders its menu permanently open — no tap-to-close
   overlay, or the invisible fixed backdrop would swallow every click on the
   page. */
.g-menu-static[open] > summary::before { content: none; }
#g-flash .page-toast { position: static; transform: none; display: inline-block; margin-bottom: var(--cr-space-4); }

/* #313: notifications catalog (workflows.html). Section subtitle under each
   h2 -- the heading itself reuses the page's existing h2 rule, no new class
   needed there. Sizes in rem/em throughout (not px) so the catalog's new
   rules add zero raw px declarations against the VER-1 ratchet. */
.sectsub { font-size: 0.8125rem; color: var(--cr-text-muted); margin: 0 0 var(--cr-space-3); }

/* Channel pill -- same colour role as .pill-brand, but never carries the
   leading status dot .pill::before draws, so it gets its own tiny class
   instead of an awkward .pill modifier that suppresses part of the base. */
.chan { display: inline-block; font-size: 0.6875rem; font-weight: 600; padding: 0.125rem 0.5625rem;
  border-radius: var(--cr-radius-full); background: var(--cr-primary-subtle); color: var(--cr-primary-hover); }

/* Status switch -- a form-submit button (no JS) styled as an on/off toggle.
   [data-on] carries the enabled state so CSS alone flips the knob and colour. */
.switch { display: inline-flex; align-items: center; gap: var(--cr-space-2); font: inherit;
  font-size: 0.75rem; font-weight: 600; color: var(--cr-text); background: none; border: 0;
  padding: 0; cursor: pointer; }
.switch .knob { width: 1.875rem; height: 1.0625rem; border-radius: var(--cr-radius-full);
  background: var(--cr-slate-300); position: relative; display: inline-block; flex: none; }
.switch .knob::after { content: ""; position: absolute; top: 0.125rem; left: 0.125rem;
  width: 0.8125rem; height: 0.8125rem; border-radius: var(--cr-radius-full); background: var(--cr-white);
  transition: left .12s; }
.switch[data-on] .knob { background: var(--cr-primary); }
.switch[data-on] .knob::after { left: auto; right: 0.125rem; }
.switch:focus-visible { outline: 0.125rem solid var(--cr-focus); outline-offset: 0.125rem; }
.switch:hover .knob { background: var(--cr-slate-400); }
.switch[data-on]:hover .knob { background: var(--cr-primary-hover); }
.switch[disabled] { opacity: .55; cursor: default; }
.switch[disabled]:hover .knob { background: var(--cr-slate-300); }
.switch[data-on][disabled]:hover .knob { background: var(--cr-primary); }

/* #313 Task 2: one-shot notification edit screen (notification_form.html).
   Rem/em throughout except six raw px declarations below, all matching
   existing idioms elsewhere in this file rather than inventing new ones:
   four 1px hairline borders (scopebox, its filter's border-bottom, selchip,
   the body-chips override) and the 1px/1px visually-hidden-input size on
   .switch-input (same values as the pre-existing .chips input above). The
   VER-1 ratchet baseline in scripts/design_tokens_baseline.json was bumped
   to match. */

/* Enabled toggle: a real checkbox (a saved form field, unlike the catalog's
   [data-on] submit-button toggle above) restyled into the same .switch/.knob
   look via the standard visually-hidden-input pattern (.chips input above
   uses the same absolute/1px/opacity:0/pointer-events:none technique) --
   still focusable and keyboard-toggleable, no JS. */
.switch-input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.switch-input:checked ~ .knob { background: var(--cr-primary); }
.switch-input:checked ~ .knob::after { left: auto; right: 0.125rem; }
.switch-input:focus-visible ~ .knob { outline: 0.125rem solid var(--cr-focus); outline-offset: 0.125rem; }
.switch:has(.switch-input:not(:checked):not(:disabled):hover) .knob { background: var(--cr-slate-400); }
.switch:has(.switch-input:checked:not(:disabled):hover) .knob { background: var(--cr-primary-hover); }
.switch:has(.switch-input:disabled) { opacity: .55; cursor: default; }

/* When row: a number input beside a unit select, one line. */
.inline-fields { display: flex; align-items: center; gap: var(--cr-space-2); }
.inline-fields input { width: 4.5rem; flex: none; }
.inline-fields select { width: auto; flex: none; }

/* Sent-for radios, stacked one per line (mockup .radios) -- distinct from
   the existing side-by-side .radio (appointments.html payment methods),
   whose options are short enough for a row; these run longer. */
.radio-stack { margin: 0 0 var(--cr-space-2); }
.radio-stack label { display: flex; align-items: center; gap: var(--cr-space-2);
  font-size: var(--cr-text-sm); font-weight: 400; margin: var(--cr-space-1) 0; }
/* Round 2 (#313): `.field input { width: 100% }` above also matches these
   radios (they're nested inside a .field), stretching the native control to
   fill the row and shoving the label text to the far edge -- same bug class
   the pre-existing .field-checkbox input rule already guards against. Same
   fix, plus the incumbent accent-color idiom (.consent input, .slot input). */
.radio-stack input { width: auto; accent-color: var(--cr-primary); }

/* #341: location autocomplete dropdown (appointments.html), populated by
   shell.js and sitting directly under the location input. Hidden while
   Meeting instructions is selected -- no JS needed for that part, same
   no-JS :has() idiom as the scope-narrowing rule below. */
.loc-suggest { border: 1px solid var(--cr-border); border-radius: var(--cr-radius-sm);
  background: var(--cr-surface); margin: calc(-1 * var(--cr-space-2)) 0 var(--cr-space-2);
  overflow: hidden; }
.loc-suggest button { display: block; width: 100%; text-align: left; background: none;
  color: var(--cr-text); font-weight: 400; border: 0; border-radius: 0;
  border-bottom: 1px solid var(--cr-border); padding: var(--cr-space-2) var(--cr-space-3);
  font-size: var(--cr-text-sm); cursor: pointer; min-height: 0; }
.loc-suggest button:last-child { border-bottom: 0; }
.loc-suggest button:hover, .loc-suggest button:focus { background: var(--cr-primary-subtle); }
.field:has(input[name="location_kind"][value="instructions"]:checked) .loc-suggest { display: none; }

/* Currently-selected appointment-type chips above the scope checklist. */
.selchips { display: flex; gap: var(--cr-space-2); flex-wrap: wrap; margin: 0 0 var(--cr-space-2); }
.selchip { display: inline-flex; align-items: center; gap: var(--cr-space-2);
  font-size: 0.75rem; font-weight: 600; background: var(--cr-primary-subtle);
  color: var(--cr-primary-hover); border: 1px solid var(--cr-teal-200);
  border-radius: var(--cr-radius-full); padding: 0.1875rem 0.625rem; }
.selchip .x { background: none; border: 0; padding: 0; font: inherit; font-weight: 400;
  color: inherit; opacity: .7; cursor: pointer; min-height: 0; }

/* Scrolling, filterable appointment-type checklist. */
.scopebox { border: 1px solid var(--cr-border); border-radius: var(--cr-radius-sm); overflow: hidden; }
.scopebox .filter { border: 0; border-bottom: 1px solid var(--cr-border); border-radius: 0; }
.scopelist { max-height: 9.375rem; overflow-y: auto; padding: var(--cr-space-1) var(--cr-space-3); }
.scopelist label { display: flex; align-items: center; gap: var(--cr-space-2);
  font-size: var(--cr-text-sm); font-weight: 400; margin: var(--cr-space-2) 0; }
/* Round 2 (#313): same .field-input-stretch bug and same fix as .radio-stack
   input above. */
.scopelist input { width: auto; accent-color: var(--cr-primary); }
/* #153 lesson: the display rule above must not defeat [hidden] (shell.js's
   filter, #313 Task 3 fix round 1) -- same guard idiom as .grid-toast[hidden]. */
.scopelist label[hidden] { display: none; }
.scopecount { font-size: var(--cr-text-xs); color: var(--cr-text-muted); margin: var(--cr-space-2) 0 0; }

/* Scope narrowed to "All appointments" -- the checklist below is moot, so
   CSS alone hides it (no JS ships with this task; :has() is already used
   for the booking-flow .chips selection state above). */
.field:has(input[name="scope"][value="all"]:checked) .selchips,
.field:has(input[name="scope"][value="all"]:checked) .scopebox,
.field:has(input[name="scope"][value="all"]:checked) .scopecount { display: none; }

/* Placeholder-insert chips in the notification body field -- small
   monospace pills, not the global button token or the unrelated
   chip-soon status pill above (both would otherwise apply to a bare
   `.chip` element). Sub-44px on purpose, matching the approved mockup's
   small insert affordance; revisit via checks/ui/responsive_baseline.json
   if VER-3 is ever run against this screen. */
.body-chips { margin-top: var(--cr-space-2); }
.body-chips .chip { display: inline-block; min-height: 0; background: var(--cr-neutral-tint);
  color: var(--cr-text-muted); border: 1px solid var(--cr-border); border-radius: var(--cr-radius-sm);
  padding: 0.125rem 0.4375rem; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.71875rem; font-weight: 400; text-transform: none; letter-spacing: normal; cursor: pointer; }
.body-chips .chip:hover { background: var(--cr-slate-200); }
.body-chips .chip:focus-visible { outline: 0.125rem solid var(--cr-focus); outline-offset: 0.125rem; }

/* Condition sections on the campaign editor (issue 347): divider-split
   groups inside one panel-card, each headed by a display-font title and a
   muted one-line summary. .fixed-rules lists the mandatory conditions the
   tenant cannot turn off. */
.section { border-top: 1px solid var(--cr-border); margin-top: var(--cr-space-6); padding-top: var(--cr-space-5); }
.section-title { font-family: var(--cr-font-display); font-weight: 600; font-size: var(--cr-text-lg); letter-spacing: -0.01em; margin: 0 0 var(--cr-space-1); }
.section-sub { font-size: var(--cr-text-sm); color: var(--cr-text-muted); margin: 0 0 var(--cr-space-4); }
.fixed-rules { background: var(--cr-surface-subtle); border: 1px solid var(--cr-border); border-radius: var(--cr-radius-sm); padding: var(--cr-space-3) var(--cr-space-4); margin: 0 0 var(--cr-space-4); }
.fixed-rules b { display: block; font-size: 11px; font-family: var(--cr-font-display); font-weight: 500; text-transform: uppercase; letter-spacing: .14em; color: var(--cr-text-muted); margin-bottom: var(--cr-space-2); }
.fixed-rules ul { margin: 0; padding-left: 18px; font-size: 13px; color: var(--cr-ink-600); display: flex; flex-direction: column; gap: 4px; }
/* .inline-fields input[type="number"] width already covered by the
   existing `.inline-fields input { width: 4.5rem; }` rule above -- no new
   rule needed (issue 347). */

/* Per-field form errors (issue 355): summary box at the top of a long form
   linking each failing field, plus .optional label marks (FRM-2's
   smaller-set clause). Inline messages reuse .error with a data-error-for
   hook that shell.js clears on correction. Reference implementation:
   campaign_form.html. */
.error-summary { border: 1px solid var(--cr-danger); border-radius: var(--cr-radius-sm); padding: var(--cr-space-3) var(--cr-space-4); margin: 0 0 var(--cr-space-5); }
.error-summary b { display: block; font-size: var(--cr-text-sm); color: var(--cr-danger); margin-bottom: var(--cr-space-2); }
.error-summary ul { margin: 0; padding-left: var(--cr-space-4); font-size: var(--cr-text-sm); display: flex; flex-direction: column; gap: var(--cr-space-1); }
.error-summary a { color: var(--cr-danger); }
.error-summary[hidden] { display: none; }
.optional { font-weight: 400; color: var(--cr-text-muted); }

/* #472: Insights screen -- 6-month revenue forecast bars + campaign
   attribution cards. .big is the headline stat (revenue or a due-client
   count); .fbars/.fcol are the stacked-family bar chart, one .fcol per
   month, each family a coloured <i> sized by a server-picked .fh* class
   (see below -- CSP is style-src 'self', an inline style= is silently
   dropped); .fbars-x/.fmoney are the month-label and per-month-dollar rows
   under it; .legend lists the distinct families with the same colour
   swatches; .assume is the "how this is estimated" note. .chip.sel marks
   the active date-range preset (base .chip styling already covers the pill
   look). .insights-grid lays the two campaign cards side by side, single
   column under 600px. */
.big { font-family: var(--cr-font-display); font-weight: 600; font-size: var(--cr-text-2xl);
  letter-spacing: -0.015em; margin: var(--cr-space-2) 0 0; color: var(--cr-text); }
/* #472 follow-up: the horizon picker allows up to 24 months, and 12+ label
   and dollar columns exceed a phone viewport -- the whole chart scrolls in
   its own container (LAY-3), bars, labels and money moving together. */
.fchart { overflow-x: auto; }
.fbars { display: flex; align-items: flex-end; gap: var(--cr-space-3); height: 8.75rem;
  margin: var(--cr-space-6) 0 var(--cr-space-2); }
.fcol { flex: 1; display: flex; flex-direction: column-reverse; height: 100%; gap: 0;
  min-width: 2.75rem; }
.fcol i { display: block; width: 100%; border-radius: var(--cr-radius-sm) var(--cr-radius-sm) 0 0; }
/* #472: CSP-safe bar heights -- style= attributes are dropped by style-src 'self' */
.fh5 { height: 5%; }
.fh10 { height: 10%; }
.fh15 { height: 15%; }
.fh20 { height: 20%; }
.fh25 { height: 25%; }
.fh30 { height: 30%; }
.fh35 { height: 35%; }
.fh40 { height: 40%; }
.fh45 { height: 45%; }
.fh50 { height: 50%; }
.fh55 { height: 55%; }
.fh60 { height: 60%; }
.fh65 { height: 65%; }
.fh70 { height: 70%; }
.fh75 { height: 75%; }
.fh80 { height: 80%; }
.fh85 { height: 85%; }
.fh90 { height: 90%; }
.fh95 { height: 95%; }
.fh100 { height: 100%; }
.gtest { background: var(--cr-primary); }
.g2r { background: var(--cr-forecast-purple); }
.renew { background: var(--cr-teal-50); }
.fbars-x { display: flex; gap: var(--cr-space-3); font-size: var(--cr-text-xs); color: var(--cr-text-muted); }
.fbars-x span { flex: 1; text-align: center; min-width: 2.75rem; }
.fmoney { display: flex; gap: var(--cr-space-3); font-size: var(--cr-text-xs); color: var(--cr-text-muted);
  margin-top: var(--cr-space-1); }
.fmoney span { flex: 1; text-align: center; min-width: 2.75rem; }
.legend { display: flex; gap: var(--cr-space-4); flex-wrap: wrap; margin-top: var(--cr-space-4);
  font-size: var(--cr-text-xs); color: var(--cr-text-muted); }
.legend-item { display: inline-flex; align-items: center; gap: var(--cr-space-1); }
.legend-item i { display: inline-block; width: 0.625rem; height: 0.625rem; border-radius: var(--cr-radius-sm); }
.assume { font-size: var(--cr-text-sm); color: var(--cr-text-muted); background: var(--cr-surface-subtle);
  border-radius: var(--cr-radius-sm); padding: var(--cr-space-3) var(--cr-space-4);
  margin: var(--cr-space-5) 0 0; }
.chip.sel { background: var(--cr-primary-subtle); color: var(--cr-primary-hover); }
.range-form { display: flex; align-items: center; gap: var(--cr-space-2); margin: var(--cr-space-3) 0 var(--cr-space-6); }
.insights-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--cr-space-4); }
/* Grid items default to min-width auto, so the campaigns table's
   min-content width would stretch its card past a phone viewport; letting
   items shrink hands the overflow to .table-scroll instead. */
.insights-grid > * { min-width: 0; }
@media (max-width: 599px) {
  .insights-grid { grid-template-columns: 1fr; }
  /* .range-form's date inputs otherwise overflow a 390px viewport as a
     non-wrapping row -- drop them full-width below the chips instead. */
  .range-form { flex-wrap: wrap; }
  .range-form input[type="date"] { flex: 1 1 100%; }
  /* LAY-5: base .chip padding (2px 10px) is well under the 44px touch
     target on a link; the status-badge use of .chip is a <span>, never
     tapped, so this only widens the clickable preset chips. */
  a.chip { padding: 0.4375rem 0.875rem; }
}
