/* =============================================================
   HONESTLI DESIGN SYSTEM
   A self-contained CSS layer to replace Bootstrap 5.3.
   Lives at: wwwroot/honestli.css (alongside showcase.html).
   Reference once in App.razor (or _Host.cshtml) — no JS dependency.

   Layers (top → bottom):
     1. TOKENS       — design primitives (colors, type, space)
     2. BASE         — html/body, focus rings, scrollbars
     3. LAYOUT       — container, grid, stack, row
     4. UTILITIES    — text, spacing, decoration helpers
     5. COMPONENTS   — buttons, cards, badges, chips, inputs, …
     6. ANIMATIONS   — keyframes used across the system

   Convention: every class starts with `hl-`.
   No bare element selectors except in BASE.
   ============================================================= */

/* -------------------------------------------------------------
   1. TOKENS
   ------------------------------------------------------------- */
:root {
  /* Surfaces */
  --hl-bg:              #0D182A;
  --hl-bg-elev:         #12213A;
  --hl-bg-elev-2:       #1a2d4d;
  --hl-bg-overlay:      rgba(13, 24, 42, 0.72);

  /* Borders */
  --hl-border:          rgba(255, 255, 255, 0.06);
  --hl-border-strong:   rgba(255, 255, 255, 0.10);
  --hl-border-focus:    rgba(249, 115, 22, 0.55);

  /* Text */
  --hl-text:            #ffffff;
  --hl-text-2:          #d1d5db;
  --hl-text-muted:      #9ca3af;
  --hl-text-faint:      #6b7280;
  --hl-text-dim:        #3a4866;

  /* Brand & status */
  --hl-accent:          #F97316;  /* Pro */
  --hl-accent-soft:     rgba(249, 115, 22, 0.13);
  --hl-accent-glow:     rgba(249, 115, 22, 0.40);
  /* Orange-300. --hl-accent reads ~5.1:1 on its own tint, which is fine down
     to 11px and not fine at the 9.5px of a badge marker (§5.3); this lightens
     to ~8.4:1 for that one job. Foreground only — never a fill. */
  --hl-accent-bright:   #FDBA74;

  /* The second brand colour, used opposite the orange in headline accents,
     gradients and glows. Distinct from --hl-info (#60A5FA) and --hl-violet
     (#A78BFA), which are *plan* colours (Basic / Max) and carry meaning.
     This one is decorative — and only ~4.2:1 against the page background, so
     it belongs on display type (the hero headline accent), not on 11px labels.
     Where a blue eyebrow or chip is wanted, --hl-info reads at ~7:1.
     NOTE: #546EFA is hard-coded ~430 times across the scoped stylesheets and
     #FF7F27 (a near-duplicate of --hl-accent) ~110 more. This token exists so
     the blocks #391 touched could stop adding to that; the rest is a separate
     sweep — see the follow-up noted on that issue. */
  --hl-accent-blue:      #546EFA;

  --hl-info:            #60A5FA;  /* Basic */
  --hl-info-soft:       rgba(96, 165, 250, 0.13);

  --hl-violet:          #A78BFA;  /* Max */
  --hl-violet-soft:     rgba(167, 139, 250, 0.13);

  --hl-success:         #4ade80;
  --hl-success-soft:    rgba(74, 222, 128, 0.13);
  --hl-warning:         #fbbf24;
  --hl-warning-soft:    rgba(251, 191, 36, 0.13);
  --hl-danger:          #ef4444;
  --hl-danger-soft:     rgba(239, 68, 68, 0.13);

  /* Typography */
  --hl-font-sans:       'Lexend', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --hl-font-mono:       ui-monospace, 'SF Mono', Menlo, 'Cascadia Code', monospace;

  --hl-fs-xs:           11px;
  --hl-fs-sm:           12.5px;
  --hl-fs-md:           14px;
  --hl-fs-lg:           17px;
  --hl-fs-xl:           22px;
  --hl-fs-2xl:          32px;
  --hl-fs-3xl:          clamp(32px, 4.4vw, 52px);

  --hl-lh-tight:        1.1;
  --hl-lh-snug:         1.35;
  --hl-lh-normal:       1.5;
  --hl-lh-relaxed:      1.6;

  /* Spacing scale — 4px base */
  --hl-sp-0:            0;
  --hl-sp-1:            4px;
  --hl-sp-2:            8px;
  --hl-sp-3:            12px;
  --hl-sp-4:            16px;
  --hl-sp-5:            20px;
  --hl-sp-6:            24px;
  --hl-sp-7:            28px;
  --hl-sp-8:            32px;
  --hl-sp-10:           40px;
  --hl-sp-12:           48px;
  --hl-sp-16:           64px;

  /* Radii */
  --hl-r-sm:            6px;
  --hl-r-md:            10px;
  --hl-r-lg:            14px;
  --hl-r-xl:            20px;
  --hl-r-pill:          999px;

  /* Shadows */
  --hl-shadow-card:     0 10px 30px -16px rgba(0, 0, 0, 0.5);
  --hl-shadow-pop:      0 24px 48px -20px rgba(0, 0, 0, 0.6);
  --hl-shadow-inset:    inset 0 1px 0 rgba(255, 255, 255, 0.04);
  --hl-ring:            0 0 0 3px rgba(249, 115, 22, 0.25);

  /* Motion */
  --hl-ease:            cubic-bezier(0.4, 0, 0.2, 1);
  --hl-dur-fast:        150ms;
  --hl-dur-base:        250ms;
  --hl-dur-slow:        450ms;

  /* Layout */
  --hl-container:       1240px;

  /* The single-column width for form pages — /my-profile, /create-team.
     Its own token rather than a reuse of --hl-container so the form column
     can move without dragging the marketing page width with it; they happen
     to start at the same value. */
  --hl-container-form:  1240px;
}

/* -------------------------------------------------------------
   2. BASE
   ------------------------------------------------------------- */
*,
*::before,
*::after { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--hl-bg);
  color: var(--hl-text-2);
  font-family: var(--hl-font-sans);
  line-height: var(--hl-lh-normal);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Base text size lives on <body>, NOT <html>. Setting font-size on the root
   element redefines what 1rem means for the whole document — and because this
   sheet loads before Bootstrap (which never resets html's size), a 14px root
   silently shrank every Bootstrap rem-based size to 87.5% (the "too small /
   inconsistent" report). Keep the root at the 16px browser default so rem-based
   (Bootstrap) typography is unaffected; hl- components use fixed px / tokens and
   don't depend on this. */
body { font-size: var(--hl-fs-md); }

body { min-height: 100vh; }

h1, h2, h3, h4, h5, h6 {
  margin: 0;
  color: var(--hl-text);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: var(--hl-lh-tight);
}

p { margin: 0; }
a { color: var(--hl-info); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 3px; }

button { font-family: inherit; }

::selection {
  background: var(--hl-accent);
  color: #fff;
}

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.08); border-radius: var(--hl-r-pill); }
::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.14); }
::-webkit-scrollbar-track { background: transparent; }

:focus-visible {
  outline: 2px solid var(--hl-accent);
  outline-offset: 2px;
  border-radius: var(--hl-r-sm);
}

img, svg { display: block; max-width: 100%; }

/* -------------------------------------------------------------
   3. LAYOUT
   ------------------------------------------------------------- */
.hl-container {
  width: 100%;
  max-width: var(--hl-container);
  margin: 0 auto;
  padding: 0 var(--hl-sp-6);
}
.hl-container--narrow { max-width: 720px; }
.hl-container--medium { max-width: 920px; }
.hl-container--wide   { max-width: 1080px; }

/* Form pages — a centred, capped single column. Replaces the Bootstrap
   .container / .row + .col-* pairs those pages used to reach for (#408).

   Deliberately shaped unlike a list page, which stays full-bleed inside
   .app-content: a form is read and filled one field at a time, so line length
   matters more than fitting more on screen, and an uncapped column just makes
   every label a longer journey from its input on a wide monitor. #407 tried
   left-aligning a form page to match the list pages and it was worse — the
   consistency worth having is within each kind of page, not across all of them.

   No horizontal padding, unlike .hl-container: every form page lives inside
   .app-content, which already supplies the gutter, and adding another would
   double-inset the page (badly so on mobile, where the shell's is 20px). */
.hl-form-page {
  width: 100%;
  max-width: var(--hl-container-form);
  margin-inline: auto;
}

.hl-stack { display: flex; flex-direction: column; }
.hl-row   { display: flex; flex-direction: row; align-items: center; }
.hl-row--top    { align-items: flex-start; }
.hl-row--baseline { align-items: baseline; }
.hl-row--between { justify-content: space-between; }
.hl-row--center { justify-content: center; }
.hl-row--end    { justify-content: flex-end; }
.hl-row--wrap   { flex-wrap: wrap; }

.hl-grid    { display: grid; }
.hl-grid-2  { grid-template-columns: 1fr 1fr; }
.hl-grid-3  { grid-template-columns: repeat(3, 1fr); }
.hl-grid-4  { grid-template-columns: repeat(4, 1fr); }
.hl-grid-billing {
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
}

@media (max-width: 860px) {
  .hl-grid-2, .hl-grid-3, .hl-grid-4, .hl-grid-billing { grid-template-columns: 1fr; }
}

/* Gap utilities — work on flex AND grid */
.hl-gap-1  { gap: var(--hl-sp-1); }
.hl-gap-2  { gap: var(--hl-sp-2); }
.hl-gap-3  { gap: var(--hl-sp-3); }
.hl-gap-4  { gap: var(--hl-sp-4); }
.hl-gap-5  { gap: var(--hl-sp-5); }
.hl-gap-6  { gap: var(--hl-sp-6); }
.hl-gap-8  { gap: var(--hl-sp-8); }
.hl-gap-10 { gap: var(--hl-sp-10); }

.hl-flex-1   { flex: 1; min-width: 0; }
.hl-flex-0   { flex: 0 0 auto; }

/* -------------------------------------------------------------
   4. UTILITIES
   ------------------------------------------------------------- */
/* Available to assistive tech, invisible on screen. Clipped rather than
   `display: none` or `visibility: hidden`, both of which take the text out
   of the accessibility tree along with the pixels. Used to caption a
   .hl-skeleton region, whose blocks are aria-hidden and would otherwise
   leave a screen reader nothing to hear. */
.hl-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.hl-text-center { text-align: center; }
.hl-text-right  { text-align: right; }
.hl-text-left   { text-align: left; }

.hl-text-primary { color: var(--hl-text); }
.hl-text-2       { color: var(--hl-text-2); }
.hl-text-muted   { color: var(--hl-text-muted); }
.hl-text-faint   { color: var(--hl-text-faint); }

.hl-text-accent  { color: var(--hl-accent); }
.hl-text-info    { color: var(--hl-info); }
.hl-text-violet  { color: var(--hl-violet); }
.hl-text-success { color: var(--hl-success); }
.hl-text-danger  { color: var(--hl-danger); }

.hl-fs-xs  { font-size: var(--hl-fs-xs); }
.hl-fs-sm  { font-size: var(--hl-fs-sm); }
.hl-fs-md  { font-size: var(--hl-fs-md); }
.hl-fs-lg  { font-size: var(--hl-fs-lg); }
.hl-fs-xl  { font-size: var(--hl-fs-xl); }
.hl-fs-2xl { font-size: var(--hl-fs-2xl); }
.hl-fs-3xl { font-size: var(--hl-fs-3xl); }

.hl-fw-400 { font-weight: 400; }
.hl-fw-500 { font-weight: 500; }
.hl-fw-600 { font-weight: 600; }
.hl-fw-700 { font-weight: 700; }

.hl-tabular { font-variant-numeric: tabular-nums; }
.hl-mono    { font-family: var(--hl-font-mono); }

.hl-truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Eyebrow label — small caps section header.
   Block by default so it stacks above whatever follows in centered hero
   blocks. Inside .hl-row / flex containers it just becomes a flex item
   and aligns normally. Use .hl-eyebrow--inline for runs of inline text.

   Tracking is 0.14em, not the 0.10em this rule shipped with. #391 swept the
   nineteen one-off eyebrows and found thirteen of them, across ten files,
   independently at 0.14em. The six dissenters were 0.16 twice in GetStory,
   0.12 in Error and PaymentConfirmation, 0.10 in Upgrade and 0.08 in
   ShareModal — and five of the six are looser than 0.10em, not tighter, so
   the pages were the signal and the token was the outlier. Widening also
   suits the size: 11px uppercase needs more tracking than 0.10em gives it.
   This moves the WizardShell and PageHeader eyebrows too, which is the point
   of them sharing a class.

   Size/weight stayed at 11px/700 (--hl-fs-xs), which is what most of the
   one-offs already used; Home and Stories were the two at 12px/600. */
.hl-eyebrow {
  display: block;
  font-size: var(--hl-fs-xs);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--hl-text-muted);
}
.hl-eyebrow--inline { display: inline-block; }
/* Bare row — no chip. For eyebrows that carry a dot or rule alongside the
   text but shouldn't look like a pill (GetStory's, Upgrade's plan cards). */
.hl-eyebrow--row {
  display: inline-flex;
  align-items: center;
  gap: var(--hl-sp-2);
}
.hl-eyebrow--accent  { color: var(--hl-accent); }
.hl-eyebrow--info    { color: var(--hl-info); }
.hl-eyebrow--violet  { color: var(--hl-violet); }
.hl-eyebrow--success { color: var(--hl-success); }
.hl-eyebrow--danger  { color: var(--hl-danger); }
/* One step quieter than the default muted — the unemphasised state of a
   two-state eyebrow. Upgrade's plan cards use it for the tiers that aren't
   the recommended one, which flips to --accent. */
.hl-eyebrow--faint   { color: var(--hl-text-faint); }

/* Pill — the eyebrow as a rounded chip rather than bare text. Four marketing
   pages had this hand-rolled at identical values (About, Contact, Privacy in
   orange; How it works in blue).

   Background and border derive from currentColor via color-mix, so one rule
   serves every colour modifier above and adding a fifth needs no work here.
   This is the sheet's first use of color-mix — Baseline since 2023, and the
   whole system is dark-theme-only with no IE/legacy target. */
.hl-eyebrow--pill {
  display: inline-flex;
  align-items: center;
  gap: var(--hl-sp-2);
  padding: 5px var(--hl-sp-3);
  border-radius: var(--hl-r-pill);
  background: color-mix(in srgb, currentColor 14%, transparent);
}
/* Adds the hairline outline the two status chips (Error, PaymentConfirmation)
   carried and the plain marketing pills did not. Kept as a separate modifier
   rather than folded into --pill so adopting the pill doesn't silently put a
   border on pages that never had one. */
.hl-eyebrow--outlined {
  border: 1px solid color-mix(in srgb, currentColor 28%, transparent);
}
/* Status dot. currentColor means it always matches its eyebrow's colour
   modifier — that was the only thing separating the orange and green copies
   of this in Error.razor.css and PaymentConfirmation.razor.css. */
.hl-eyebrow__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  flex-shrink: 0;
  background: currentColor;
  box-shadow: 0 0 8px color-mix(in srgb, currentColor 85%, transparent);
}
.hl-eyebrow__dot--pulse {
  animation: hl-dot-pulse 2.2s ease-in-out infinite;
}
/* Thin rule used instead of a dot — GetStory sets its eyebrow off with one. */
.hl-eyebrow__rule {
  width: 22px;
  height: 1px;
  flex-shrink: 0;
  background: currentColor;
  opacity: 0.6;
}

/* Decorative background glow — position with inline style */
.hl-glow {
  position: absolute;
  border-radius: 50%;
  filter: blur(80px);
  pointer-events: none;
  z-index: 0;
}
.hl-glow--orange { background: radial-gradient(circle, rgba(249,115,22,0.18) 0%, transparent 70%); }
.hl-glow--violet { background: radial-gradient(circle, rgba(167,139,250,0.20) 0%, transparent 70%); }
.hl-glow--info   { background: radial-gradient(circle, rgba(96,165,250,0.18) 0%, transparent 70%); }

/* Divider */
.hl-divider {
  height: 1px;
  background: var(--hl-border);
  border: 0;
  margin: 0;
}
.hl-divider--dashed { background: transparent; border-top: 1px dashed var(--hl-border-strong); }

/* Margin helpers — keep these small. Prefer gap. */
.hl-mt-1 { margin-top: var(--hl-sp-1); }
.hl-mt-2 { margin-top: var(--hl-sp-2); }
.hl-mt-3 { margin-top: var(--hl-sp-3); }
.hl-mt-4 { margin-top: var(--hl-sp-4); }
.hl-mt-6 { margin-top: var(--hl-sp-6); }
.hl-mt-8 { margin-top: var(--hl-sp-8); }
.hl-mt-10 { margin-top: var(--hl-sp-10); }

.hl-position-relative { position: relative; }
.hl-overflow-hidden   { overflow: hidden; }

/* -------------------------------------------------------------
   5. COMPONENTS
   ------------------------------------------------------------- */

/* ── 5.1 BUTTON ──────────────────────────────────── */
.hl-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--hl-sp-2);
  padding: 10px var(--hl-sp-4);
  border-radius: var(--hl-r-md);
  border: 1px solid transparent;
  background: transparent;
  color: inherit;
  font-family: var(--hl-font-sans);
  font-size: var(--hl-fs-md);
  font-weight: 500;
  line-height: 1;
  cursor: pointer;
  /* Bootstrap loads after this file and re-adds `a { text-decoration: underline }`,
     so <a class="hl-btn"> renders underlined unless the class declares it here.
     Class (0,1,0) beats the element selector (0,0,1) regardless of order. */
  text-decoration: none;
  transition: background var(--hl-dur-fast) var(--hl-ease),
              border-color var(--hl-dur-fast) var(--hl-ease),
              color var(--hl-dur-fast) var(--hl-ease),
              transform var(--hl-dur-fast) var(--hl-ease),
              filter var(--hl-dur-fast) var(--hl-ease);
  white-space: nowrap;
  user-select: none;
}
.hl-btn:disabled, .hl-btn[aria-disabled="true"] {
  opacity: 0.5; cursor: not-allowed;
}
/* `a:hover` (0,1,1) outranks `.hl-btn` (0,1,0), so the base rule above isn't
   enough on its own — an <a class="hl-btn"> would underline on hover. */
.hl-btn:hover { text-decoration: none; }
.hl-btn:focus-visible { box-shadow: var(--hl-ring); outline: none; }

/* Variants */
.hl-btn--primary {
  background: var(--hl-accent);
  color: #fff;
  box-shadow: 0 6px 18px -8px var(--hl-accent-glow);
}
.hl-btn--primary:hover:not(:disabled) { filter: brightness(1.08); transform: translateY(-1px); }
.hl-btn--primary:active { transform: translateY(0); }

.hl-btn--hero {
  background: linear-gradient(135deg, var(--hl-accent) 0%, #fb923c 100%);
  color: #fff;
  box-shadow: 0 12px 28px -10px var(--hl-accent-glow);
}
.hl-btn--hero:hover:not(:disabled) { filter: brightness(1.06); transform: translateY(-1px); }

.hl-btn--secondary {
  background: var(--hl-bg-elev-2);
  color: var(--hl-text);
  border-color: var(--hl-border-strong);
}
.hl-btn--secondary:hover:not(:disabled) { background: #213657; }

.hl-btn--outline {
  background: transparent;
  color: var(--hl-text-2);
  border-color: var(--hl-border-strong);
}
.hl-btn--outline:hover:not(:disabled) {
  background: rgba(255,255,255,0.04);
  color: var(--hl-text);
  border-color: rgba(255,255,255,0.16);
}

.hl-btn--ghost {
  background: transparent;
  color: var(--hl-text-muted);
}
.hl-btn--ghost:hover:not(:disabled) {
  background: rgba(255,255,255,0.05);
  color: var(--hl-text);
}

.hl-btn--danger {
  background: var(--hl-danger);
  color: #fff;
}
.hl-btn--danger:hover:not(:disabled) { filter: brightness(1.08); }

/* Sizes */
.hl-btn--sm { padding: 7px var(--hl-sp-3); font-size: var(--hl-fs-sm); border-radius: var(--hl-r-sm); }
.hl-btn--md { padding: 10px var(--hl-sp-4); font-size: var(--hl-fs-md); }
.hl-btn--lg { padding: 14px var(--hl-sp-6); font-size: var(--hl-fs-lg); }

.hl-btn--block { width: 100%; }

/* Icon-only — a square button whose whole content is one icon. Use it for the
   trash affordance on a card in a list, where a "Delete" label would crowd the
   row. Combine with a variant as usual: `.hl-btn.hl-btn--danger.hl-btn--icon`.

   The width/height are the text buttons' heights spelled out (font-size +
   padding + border, box-sizing: border-box), so an icon button sits flush
   against a text button of the same size instead of coming out narrow and
   rectangular the way a bare <i> inside a padded .hl-btn does.

   These buttons have no text, so `aria-label` is REQUIRED — `title` alone is
   not an accessible name for every assistive technology. */
.hl-btn--icon {
  gap: 0;
  padding: 10px;
  flex-shrink: 0;
  width: calc(var(--hl-fs-md) + 22px);   /* 14 + 2×10 padding + 2×1 border = 36px */
  height: calc(var(--hl-fs-md) + 22px);
}
.hl-btn--icon.hl-btn--sm {
  padding: 7px;
  width: calc(var(--hl-fs-sm) + 16px);   /* 12.5 + 2×7 padding + 2×1 border = 28.5px */
  height: calc(var(--hl-fs-sm) + 16px);
}
.hl-btn--icon.hl-btn--lg {
  padding: 14px;
  width: calc(var(--hl-fs-lg) + 30px);   /* 17 + 2×14 padding + 2×1 border = 47px */
  height: calc(var(--hl-fs-lg) + 30px);
}

/* Inline link-button — for tiny "Download PDF" actions */
.hl-action {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  background: transparent;
  color: var(--hl-text-muted);
  border: none;
  cursor: pointer;
  padding: 8px var(--hl-sp-3);
  border-radius: var(--hl-r-sm);
  font: 500 13px/1 var(--hl-font-sans);
  transition: background var(--hl-dur-fast) var(--hl-ease),
              color var(--hl-dur-fast) var(--hl-ease);
}
.hl-action:hover {
  background: rgba(255,255,255,0.05);
  color: var(--hl-text);
}

/* ── 5.2 CARD ──────────────────────────────────── */
.hl-card {
  background: var(--hl-bg-elev);
  border: 1px solid var(--hl-border);
  border-radius: var(--hl-r-lg);
  box-shadow: var(--hl-shadow-card);
  overflow: hidden;
  position: relative;
}
.hl-card--flush  { overflow: visible; }
.hl-card--accent { border-color: var(--hl-accent-glow); }
.hl-card--feature {
  background: linear-gradient(135deg, rgba(84,110,250,0.08) 0%, rgba(249,115,22,0.06) 100%);
  border-color: rgba(84,110,250,0.20);
}

.hl-card__header {
  padding: var(--hl-sp-5) var(--hl-sp-6) var(--hl-sp-4);
  border-bottom: 1px solid var(--hl-border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--hl-sp-4);
}
.hl-card__body   { padding: var(--hl-sp-6); }
.hl-card__body--tight { padding: var(--hl-sp-5) var(--hl-sp-6); }
.hl-card__meta {
  border-top: 1px solid var(--hl-border);
  padding: var(--hl-sp-4) var(--hl-sp-6);
  background: rgba(255,255,255,0.015);
}
.hl-card__footer {
  border-top: 1px solid var(--hl-border);
  padding: var(--hl-sp-3) var(--hl-sp-4);
  display: flex;
  flex-wrap: wrap;
  gap: var(--hl-sp-2);
}

/* ── 5.3 BADGE ──────────────────────────────────── */
/* The status primitive. Sentiment, lifecycle state, and platform state all
   render as .hl-badge — six tones, an optional __dot, an optional leading <i>
   in the dot's place. Badge = the *state* of a thing; chip (5.4) = an
   *attribute* of it, and a chip never carries a dot.

   Labels are SENTENCE CASE — no text-transform, no extra letter-spacing.
   Five scoped sheets used to shout theirs, which made a long status
   ("READ BY TOPIC OWNER") physically wider than the 14px card title beside
   it; sentence case is what keeps a long label legible without competing.
   The single exception is --marker below, which is uppercase precisely
   because it is capped at one short word — read its constraints before
   reaching for it.

   Tone maps the *meaning*, not the vocabulary — /dashboard's "Active" is
   --success (a service is up) while /blacklist's "Active" is --danger (a
   block is in force). Same word, opposite states.

     --success  good / live / positive        --danger   negative / flagged
     --info     in progress / owner engaged   --neutral  inert / N/A
     --warning  needs attention               --accent   Pro / promotional

   Where a status ladder has more rungs than there are meaningful colours,
   colour the rungs that carry a decision and leave the rest --neutral; the
   label already states the rung exactly. /my-responses does this: only
   "Acknowledged" and "Read by topic owner" take colour, because whether the
   owner engaged is the only thing the responder acts on. (#423)

   flex-shrink: 0 because a badge is nowrap — as a flex child next to a title
   that truncates, it must not be the thing that gets squashed. */
.hl-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: var(--hl-r-pill);
  font-size: var(--hl-fs-xs);
  font-weight: 600;
  letter-spacing: 0.02em;
  border: 1px solid transparent;
  white-space: nowrap;
  flex-shrink: 0;
}
.hl-badge--neutral { background: rgba(255,255,255,0.05); color: var(--hl-text-2); border-color: var(--hl-border-strong); }
.hl-badge--success { background: var(--hl-success-soft); color: var(--hl-success); border-color: rgba(74,222,128,0.20); }
.hl-badge--warning { background: var(--hl-warning-soft); color: var(--hl-warning); border-color: rgba(251,191,36,0.20); }
.hl-badge--danger  { background: var(--hl-danger-soft);  color: var(--hl-danger);  border-color: rgba(239,68,68,0.20); }
.hl-badge--info    { background: var(--hl-info-soft);    color: var(--hl-info);    border-color: rgba(96,165,250,0.20); }
.hl-badge--accent  { background: var(--hl-accent-soft);  color: var(--hl-accent);  border-color: rgba(249,115,22,0.20); }

.hl-badge__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex-shrink: 0;
}

/* MARKER — the one place uppercase is correct.
   A dense list row can't afford a full badge: at 11px with 4×10 padding it
   out-weighs the 14px row title it sits beside. The marker is the compact
   form — 9.5px, tighter padding, squared 4px corners so it reads as a label
   rather than a pill you could press. Tone comes from the tone class as
   usual; this changes only geometry and case.

   Uppercase is allowed here, and *only* here, because the rule it bends is
   about width. §5.3's ban exists because a shouted label is physically wider,
   so a long one ("READ BY TOPIC OWNER") out-measures the title beside it.
   That argument has no force on a single short word: "MAIN" uppercase at
   9.5px is narrower than "Main" sentence-case at 11px. The constraint is
   what makes the exception safe, so it is a hard constraint:

     ✔ ONE short word, ~6 characters or fewer
     ✔ a dense list row or compact card, next to a title that truncates
     ✔ an intrinsic property of the item — what it *is*, permanently
     ✘ anything that can grow to two words, ever
     ✘ a lifecycle state that changes as the item moves along  →  plain badge
     ✘ a count, which grows a digit at a time                  →  plain badge

   The feed sidebar is the worked example: the "Main" topic takes a marker,
   while the unread count beside it ("3 new") stays a plain badge — two words
   and a number that grows. If a label can't meet all three ✔, it isn't a
   marker, and the answer is a plain badge, not a wider marker. */
.hl-badge--marker {
  padding: 2px 7px;
  border-radius: 4px;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.04);
}
/* 9.5px needs more separation from the tint than 11px does — see
   --hl-accent-bright. Other tones are already light enough at this size. */
.hl-badge--marker.hl-badge--accent { color: var(--hl-accent-bright); }

/* ── 5.4 CHIP (small inline tag) ──────────────────────────────── */
/* The attribute primitive. What a thing *is* — a plan name, a profile type, a
   billing period — as against 5.3's badge, which is what state it's *in*. A
   chip never carries a dot; that's the badge's tell.

   Sentence case, same as a badge, and for the same reason.

   Bare .hl-chip is neutral and that is the common case — reach for a tone only
   where it means something.

   Plan chips are the worked example, and they take their tones from §1, where
   three colours have carried plan meanings since the system landed:

     Free      neutral        (no token — it isn't a tier)
     Basic     --info         Standard    --info
     Pro       --accent       Premium     --accent
     Max       --violet       Enterprise  --violet

   The team ladder borrows the individual one by tier position; the two run
   parallel at three paid tiers each. PlanTone.cs owns this mapping for the
   three surfaces that render plan names — /all-profiles, /my-teams and
   /upgrade — which until #444 each painted *every* plan accent orange
   separately, so Free wore Max's colour and all three tokens meant nothing.

   A chip may lead with an <i> in place of a tone where the icon separates
   values better than colour does at 11.5px — /profiles distinguishes User
   from Team that way.

   flex-shrink: 0 for the same reason as the badge: a chip is nowrap, so as a
   flex child beside a truncating name it must not be what gets squashed. */
.hl-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 9px;
  border-radius: var(--hl-r-pill);
  background: rgba(255,255,255,0.05);
  color: var(--hl-text-2);
  border: 1px solid var(--hl-border-strong);
  font-size: 11.5px;
  font-weight: 500;
  white-space: nowrap;
  flex-shrink: 0;
}
.hl-chip--success { background: var(--hl-success-soft); color: var(--hl-success); border-color: rgba(74,222,128,0.20); }
.hl-chip--info    { background: var(--hl-info-soft);    color: var(--hl-info);    border-color: rgba(96,165,250,0.20); }
.hl-chip--accent  { background: var(--hl-accent-soft);  color: var(--hl-accent);  border-color: rgba(249,115,22,0.20); }
.hl-chip--violet  { background: var(--hl-violet-soft);  color: var(--hl-violet);  border-color: rgba(167,139,250,0.20); }

/* ── 5.5 FORM CONTROLS ───────────────────────────── */
.hl-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.hl-field__label {
  font-size: var(--hl-fs-sm);
  font-weight: 500;
  color: var(--hl-text-2);
}
.hl-field__hint {
  font-size: var(--hl-fs-sm);
  color: var(--hl-text-muted);
}
/* .validation-message is here because Blazor's <ValidationMessage> renders
   class="validation-message" *after* splatting its attributes, so a class you
   pass it is overwritten and never lands. The error style therefore has to be
   reachable by Blazor's name too, or every EditForm page hand-rolls its own —
   which is exactly what six of them were doing. Scoped under .hl-field so it
   only claims messages inside a design-system field. */
.hl-field__error,
.hl-field .validation-message {
  font-size: var(--hl-fs-sm);
  color: var(--hl-danger);
}

.hl-input,
.hl-select,
.hl-textarea {
  display: block;
  width: 100%;
  padding: 11px var(--hl-sp-3);
  background: var(--hl-bg);
  color: var(--hl-text);
  border: 1px solid var(--hl-border-strong);
  border-radius: var(--hl-r-md);
  font: 400 var(--hl-fs-md)/1.4 var(--hl-font-sans);
  transition: border-color var(--hl-dur-fast) var(--hl-ease),
              background var(--hl-dur-fast) var(--hl-ease),
              box-shadow var(--hl-dur-fast) var(--hl-ease);
}
.hl-input:hover,
.hl-select:hover,
.hl-textarea:hover { border-color: rgba(255,255,255,0.16); }

.hl-input:focus,
.hl-select:focus,
.hl-textarea:focus {
  outline: none;
  border-color: var(--hl-border-focus);
  box-shadow: var(--hl-ring);
}

.hl-input::placeholder,
.hl-textarea::placeholder { color: var(--hl-text-faint); }

.hl-textarea { resize: vertical; min-height: 92px; line-height: 1.5; }

.hl-select {
  appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'><path d='M1 1l5 5 5-5' stroke='%239ca3af' stroke-width='1.6' fill='none' stroke-linecap='round'/></svg>");
  background-repeat: no-repeat;
  background-position: right 14px center;
  padding-right: 36px;
}

.hl-input-group {
  display: flex;
  align-items: center;
  background: var(--hl-bg);
  border: 1px solid var(--hl-border-strong);
  border-radius: var(--hl-r-md);
  padding: 0 12px;
  transition: border-color var(--hl-dur-fast) var(--hl-ease),
              box-shadow var(--hl-dur-fast) var(--hl-ease);
}
.hl-input-group:focus-within {
  border-color: var(--hl-border-focus);
  box-shadow: var(--hl-ring);
}
.hl-input-group__addon {
  color: var(--hl-text-muted);
  font-size: var(--hl-fs-sm);
  flex-shrink: 0;
}
.hl-input-group .hl-input {
  border: none;
  background: transparent;
  padding-left: 8px;
  padding-right: 8px;
}
.hl-input-group .hl-input:focus { box-shadow: none; }

/* Checkbox / Radio — minimal */
.hl-check {
  display: inline-flex;
  align-items: center;
  gap: var(--hl-sp-2);
  cursor: pointer;
  user-select: none;
  font-size: var(--hl-fs-md);
  color: var(--hl-text-2);
}
.hl-check input { accent-color: var(--hl-accent); width: 16px; height: 16px; }

/* Switch */
.hl-switch {
  position: relative;
  width: 38px;
  height: 22px;
  background: var(--hl-bg-elev-2);
  border-radius: var(--hl-r-pill);
  cursor: pointer;
  transition: background var(--hl-dur-fast) var(--hl-ease);
  flex-shrink: 0;
}
.hl-switch::after {
  content: '';
  position: absolute;
  top: 3px;
  left: 3px;
  width: 16px;
  height: 16px;
  background: #fff;
  border-radius: 50%;
  transition: transform var(--hl-dur-fast) var(--hl-ease);
}
.hl-switch--on { background: var(--hl-accent); }
.hl-switch--on::after { transform: translateX(16px); }

/* Segmented control (radio group) */
.hl-segmented {
  display: inline-flex;
  background: var(--hl-bg);
  border: 1px solid var(--hl-border-strong);
  border-radius: var(--hl-r-md);
  padding: 3px;
  gap: 2px;
}
.hl-segmented__item {
  padding: 6px 14px;
  border-radius: var(--hl-r-sm);
  font: 500 var(--hl-fs-sm)/1 var(--hl-font-sans);
  background: transparent;
  color: var(--hl-text-muted);
  border: none;
  cursor: pointer;
  transition: background var(--hl-dur-fast) var(--hl-ease),
              color var(--hl-dur-fast) var(--hl-ease);
}
.hl-segmented__item:hover { color: var(--hl-text); }
.hl-segmented__item--active {
  background: var(--hl-bg-elev-2);
  color: var(--hl-text);
}

/* Choice — radio-pill group. Separate selectable pills (unlike the joined
   .hl-segmented bar), each wrapping a native radio for keyboard + form support.
   For single-select questions / surveys where options carry their own labels. */
.hl-choice {
  display: flex;
  flex-wrap: wrap;
  gap: var(--hl-sp-2);
  border: 0;
  padding: 0;
  margin: 0;
  min-width: 0;
}
.hl-choice__opt {
  display: inline-flex;
  align-items: center;
  gap: var(--hl-sp-2);
  padding: 8px 16px;
  border: 1px solid var(--hl-border-strong);
  border-radius: var(--hl-r-pill);
  background: var(--hl-bg);
  color: var(--hl-text-2);
  font: 600 var(--hl-fs-sm)/1 var(--hl-font-sans);
  cursor: pointer;
  transition: border-color var(--hl-dur-fast) var(--hl-ease),
              background var(--hl-dur-fast) var(--hl-ease),
              color var(--hl-dur-fast) var(--hl-ease);
}
.hl-choice__opt:hover { border-color: var(--hl-border-focus); color: var(--hl-text); }
.hl-choice__opt:focus-within {
  border-color: var(--hl-border-focus);
  box-shadow: var(--hl-ring);
}
.hl-choice__opt input { accent-color: var(--hl-accent); width: 15px; height: 15px; margin: 0; cursor: pointer; }
.hl-choice__opt--on {
  border-color: var(--hl-accent);
  background: var(--hl-accent-soft);
  color: var(--hl-text);
}

/* ── 5.6 ALERT ──────────────────────────────────── */
.hl-alert {
  display: flex;
  gap: var(--hl-sp-3);
  padding: var(--hl-sp-4);
  border-radius: var(--hl-r-md);
  background: var(--hl-info-soft);
  border: 1px solid rgba(96,165,250,0.20);
  color: var(--hl-text-2);
  font-size: var(--hl-fs-md);
  line-height: var(--hl-lh-snug);
}
.hl-alert--success { background: var(--hl-success-soft); border-color: rgba(74,222,128,0.20); }
.hl-alert--warning { background: var(--hl-warning-soft); border-color: rgba(251,191,36,0.20); }
.hl-alert--danger  { background: var(--hl-danger-soft);  border-color: rgba(239,68,68,0.20); }
.hl-alert__icon { flex-shrink: 0; color: var(--hl-info); margin-top: 2px; }
.hl-alert--success .hl-alert__icon { color: var(--hl-success); }
.hl-alert--warning .hl-alert__icon { color: var(--hl-warning); }
.hl-alert--danger  .hl-alert__icon { color: var(--hl-danger); }

/* ── 5.7 LIST GROUP (used for unlocked features etc) ─── */
.hl-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 11px; }
.hl-list__item {
  display: flex;
  align-items: center;
  gap: var(--hl-sp-3);
  color: var(--hl-text-2);
  font-size: var(--hl-fs-md);
}
.hl-list__item--locked { color: var(--hl-text-faint); }
.hl-list__item-value {
  color: var(--hl-text-muted);
  font-size: var(--hl-fs-sm);
  font-variant-numeric: tabular-nums;
}

.hl-dot {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  font-size: 11px;
}
.hl-dot--success { background: var(--hl-success-soft); color: var(--hl-success); }
.hl-dot--locked  { background: rgba(255,255,255,0.04); color: var(--hl-text-dim); }
.hl-dot--accent  { background: var(--hl-accent-soft); color: var(--hl-accent); }

/* ── 5.8 SUCCESS MARK (animated, for confirmations) ─── */
.hl-mark {
  width: 84px;
  height: 84px;
  border-radius: 50%;
  background: radial-gradient(circle at 30% 30%,
              rgba(249,115,22,0.28),
              rgba(249,115,22,0.12) 60%,
              transparent 80%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: relative;
  animation: hl-mark-in 0.6s var(--hl-ease) both;
}
.hl-mark__inner {
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background: var(--hl-accent-soft);
  border: 1px solid var(--hl-accent-glow);
  color: var(--hl-accent);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 12px 28px -10px var(--hl-accent-glow);
  font-size: 28px;
}
.hl-mark--info {
  background: radial-gradient(circle at 30% 30%, rgba(96,165,250,0.28), rgba(96,165,250,0.12) 60%, transparent 80%);
}
.hl-mark--info .hl-mark__inner {
  background: var(--hl-info-soft);
  border-color: rgba(96,165,250,0.40);
  color: var(--hl-info);
  box-shadow: 0 12px 28px -10px rgba(96,165,250,0.6);
}
.hl-mark--violet {
  background: radial-gradient(circle at 30% 30%, rgba(167,139,250,0.28), rgba(167,139,250,0.12) 60%, transparent 80%);
}
.hl-mark--violet .hl-mark__inner {
  background: var(--hl-violet-soft);
  border-color: rgba(167,139,250,0.40);
  color: var(--hl-violet);
  box-shadow: 0 12px 28px -10px rgba(167,139,250,0.6);
}

/* ── 5.9 CARD BRAND CHIP (Visa/MC etc) ───────────── */
.hl-cardbrand {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 24px;
  border-radius: 4px;
  color: #fff;
  font: italic 800 9px/1 var(--hl-font-sans);
  letter-spacing: 0.04em;
  box-shadow: var(--hl-shadow-inset);
  flex-shrink: 0;
}
.hl-cardbrand--visa       { background: linear-gradient(135deg, #1a1f71 0%, #2a3084 100%); }
.hl-cardbrand--mastercard { background: linear-gradient(135deg, #000 0%, #1a1a1a 100%); }
.hl-cardbrand--amex       { background: linear-gradient(135deg, #006fcf 0%, #0884db 100%); }

/* ── 5.10 META PAIR (label + value, used in receipts) ─ */
.hl-meta {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.hl-meta__label {
  font-size: var(--hl-fs-xs);
  font-weight: 700;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--hl-text-faint);
}
.hl-meta__value { color: var(--hl-text-2); font-size: 13.5px; line-height: var(--hl-lh-snug); }
.hl-meta__value-sub { color: var(--hl-text-faint); font-size: 12.5px; }

/* ── 5.11 KEY/VALUE ROW (money breakdowns) ─────── */
.hl-kv {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 4px 0;
}
.hl-kv__key   { color: var(--hl-text-muted); font-size: 13.5px; }
.hl-kv__value { color: var(--hl-text-2); font-size: var(--hl-fs-md); font-weight: 500; font-variant-numeric: tabular-nums; }
.hl-kv--total .hl-kv__key   { color: var(--hl-text-2); font-size: var(--hl-fs-md); font-weight: 600; }
.hl-kv--total .hl-kv__value { color: var(--hl-text); font-size: var(--hl-fs-xl); font-weight: 700; letter-spacing: -0.02em; }

/* ── 5.12 ICON BUBBLE (accent square holding an icon) ─── */
.hl-iconbubble {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 12px;
  background: var(--hl-accent-soft);
  color: var(--hl-accent);
  flex-shrink: 0;
}
.hl-iconbubble--sm { width: 32px; height: 32px; border-radius: 8px; }
.hl-iconbubble--xs { width: 24px; height: 24px; border-radius: 6px; }
.hl-iconbubble--info    { background: var(--hl-info-soft);    color: var(--hl-info); }
.hl-iconbubble--violet  { background: var(--hl-violet-soft);  color: var(--hl-violet); }
.hl-iconbubble--success { background: var(--hl-success-soft); color: var(--hl-success); }
.hl-iconbubble--neutral { background: rgba(255,255,255,0.04); color: var(--hl-text-2); }

/* ── 5.13 NEXT-STEP LINK ROW ─────────────────────── */
.hl-nextstep {
  display: flex;
  align-items: center;
  gap: var(--hl-sp-3);
  padding: 11px var(--hl-sp-3);
  border-radius: var(--hl-r-md);
  background: transparent;
  border: 1px solid rgba(255,255,255,0.04);
  cursor: pointer;
  transition: background var(--hl-dur-fast) var(--hl-ease),
              border-color var(--hl-dur-fast) var(--hl-ease);
  text-decoration: none;
  color: inherit;
}
.hl-nextstep:hover {
  background: rgba(255,255,255,0.04);
  border-color: var(--hl-border-strong);
  text-decoration: none;
}
.hl-nextstep__body { flex: 1; min-width: 0; }
.hl-nextstep__title { color: var(--hl-text); font-size: var(--hl-fs-md); font-weight: 500; }
.hl-nextstep__meta  { color: var(--hl-text-muted); font-size: 12.5px; line-height: 1.5; margin-top: 2px; }
.hl-nextstep__chev  { color: var(--hl-text-faint); transition: color var(--hl-dur-fast) var(--hl-ease); }
.hl-nextstep:hover .hl-nextstep__chev { color: var(--hl-text); }

/* ── 5.13a BACK LINK (the backward sibling of the next-step row) ───
   The page-level "return to the parent page" affordance. Always the first
   child of the page shell, on its own row, above the header — never inside a
   header, never inline with a title, and above any context banner. It owns
   its own bottom margin, so callers must not wrap it in a spacing div.
   Markup — but prefer the component, <BackLink Href="…" Text="…" />, which
   renders exactly this and fixes the icon so callers can't drift:
     <nav class="hl-backlink-nav" aria-label="Breadcrumb">
       <a class="hl-backlink" href="/feed">
         <i class="fa-duotone fa-regular fa-arrow-left-long"></i>
         <span>Back to Feed</span>
       </a>
     </nav>
   The indigo is the brand blue shared with .hl-card--feature, which has no
   token of its own. */
.hl-backlink-nav { margin-bottom: var(--hl-sp-5); }
.hl-backlink {
  display: inline-flex;
  align-items: center;
  gap: var(--hl-sp-2);
  padding: 6px var(--hl-sp-3);
  border-radius: var(--hl-r-pill);
  border: 1px solid rgba(84, 110, 250, 0.35);
  background: rgba(84, 110, 250, 0.08);
  color: #93c5fd;
  font-size: var(--hl-fs-sm);
  font-weight: 500;
  text-decoration: none;
  transition: background var(--hl-dur-fast) var(--hl-ease),
              border-color var(--hl-dur-fast) var(--hl-ease),
              color var(--hl-dur-fast) var(--hl-ease),
              transform var(--hl-dur-fast) var(--hl-ease);
}
.hl-backlink:hover {
  background: rgba(84, 110, 250, 0.18);
  border-color: rgba(84, 110, 250, 0.6);
  color: #c7d2fe;
  text-decoration: none;
  transform: translateX(-2px);
}
.hl-backlink i { font-size: 12px; }
@media (prefers-reduced-motion: reduce) {
  .hl-backlink:hover { transform: none; }
}

/* ── 5.14 STEPS (progress stepper — active step pulses) ───
   Markup: alternate .hl-steps__step and .hl-steps__line.
     <div class="hl-steps">
       <div class="hl-steps__step hl-steps__step--complete">
         <span class="hl-steps__node"><i class="fa-solid fa-check"></i></span>
         <span class="hl-steps__label">Draft</span>
       </div>
       <div class="hl-steps__line hl-steps__line--active"></div>   (green→accent, leads into current)
       <div class="hl-steps__step hl-steps__step--current">
         <span class="hl-steps__node">2</span>
         <span class="hl-steps__label">Review</span>
       </div>
       <div class="hl-steps__line"></div>                          (faint, upcoming)
       <div class="hl-steps__step">
         <span class="hl-steps__node">3</span>
         <span class="hl-steps__label">Publish</span>
       </div>
     </div>
   Step states: (default = upcoming), --complete, --current.
   Line states:  (default = upcoming/faint), --complete (solid green), --active (green→accent).
   Semantic <ol>/<li> works too (the list is reset); wrap a step's node + label in
   <a class="hl-steps__link"> to make it navigable. */
.hl-steps {
  display: flex;
  align-items: flex-start;
  width: 100%;
  list-style: none;   /* lets the stepper be a semantic <ol>; neutralizes list defaults */
  margin: 0;
  padding: 0;
}
.hl-steps__step {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--hl-sp-2);
  flex: 0 0 auto;
}
/* Navigable step — an <a> or <button> wrapping the node + label; gentle lift + label
   brighten on hover. Button-reset props keep a <button> visually identical to the <a> form. */
.hl-steps__link {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: var(--hl-sp-2);
  text-decoration: none;
  color: inherit;
  cursor: pointer;
  border-radius: var(--hl-r-sm);
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  transition: transform var(--hl-dur-fast) var(--hl-ease);
}
.hl-steps__link:hover { transform: translateY(-1px); text-decoration: none; }
.hl-steps__link:hover .hl-steps__label { color: var(--hl-text); }
.hl-steps__node {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  font-size: 12px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  background: var(--hl-bg);
  border: 1.5px solid var(--hl-border-strong);
  color: var(--hl-text-faint);
  transition: border-color var(--hl-dur-fast) var(--hl-ease),
              color var(--hl-dur-fast) var(--hl-ease),
              box-shadow var(--hl-dur) var(--hl-ease);
}
.hl-steps__label {
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--hl-text-faint);
  white-space: nowrap;
  transition: color var(--hl-dur-fast) var(--hl-ease);
}
.hl-steps__line {
  flex: 1 1 auto;
  height: 1.5px;
  min-width: 24px;
  margin-top: 13px;            /* centers the line on the 28px node */
  border-radius: 2px;
  background: var(--hl-border-strong);
}

/* Completed step — green */
.hl-steps__step--complete .hl-steps__node {
  background: var(--hl-success-soft);
  border-color: rgba(74, 222, 128, 0.45);
  color: var(--hl-success);
}
.hl-steps__step--complete .hl-steps__label { color: var(--hl-success); }

/* Current step — accent dot that pulses to anchor "you are here" */
.hl-steps__step--current .hl-steps__node {
  background: var(--hl-accent-soft);
  border-color: var(--hl-accent);
  color: #fff;
  box-shadow: 0 0 0 6px rgba(249, 115, 22, 0.10),
              0 0 24px -4px rgba(249, 115, 22, 0.45);
  animation: hl-step-pulse 2.4s ease-in-out infinite;
}
.hl-steps__step--current .hl-steps__label { color: var(--hl-text); }

/* Connector states */
.hl-steps__line--complete { background: var(--hl-success); }
.hl-steps__line--active {
  background: linear-gradient(90deg, var(--hl-success) 0%, var(--hl-success) 55%, var(--hl-accent) 100%);
}

/* ── 5.15 TOPBAR / NAV (Bootstrap navbar replacement) ── */
.hl-topbar {
  position: sticky;
  top: 0;
  z-index: 50;
  height: 64px;
  background: var(--hl-bg-overlay);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--hl-border);
}
.hl-topbar__inner {
  height: 100%;
  max-width: var(--hl-container);
  margin: 0 auto;
  padding: 0 var(--hl-sp-8);
  display: flex;
  align-items: center;
  gap: var(--hl-sp-6);
}
.hl-navlink {
  color: var(--hl-text-muted);
  font-size: var(--hl-fs-md);
  font-weight: 500;
  padding: 8px 12px;
  border-radius: var(--hl-r-sm);
  text-decoration: none;
  transition: color var(--hl-dur-fast) var(--hl-ease),
              background var(--hl-dur-fast) var(--hl-ease);
}
.hl-navlink:hover { color: var(--hl-text); background: rgba(255,255,255,0.04); text-decoration: none; }
.hl-navlink--active { color: var(--hl-text); background: rgba(255,255,255,0.06); }

/* ── 5.15 TABLE ──────────────────────────────────── */
.hl-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--hl-fs-md);
}
.hl-table thead th {
  text-align: left;
  font-size: var(--hl-fs-xs);
  font-weight: 700;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--hl-text-faint);
  padding: 10px var(--hl-sp-4);
  border-bottom: 1px solid var(--hl-border);
}
.hl-table tbody td {
  padding: 14px var(--hl-sp-4);
  color: var(--hl-text-2);
  border-bottom: 1px solid var(--hl-border);
}
.hl-table tbody tr:hover td { background: rgba(255,255,255,0.02); }
.hl-table tbody tr:last-child td { border-bottom: 0; }

/* ── 5.16 TOOLTIP (CSS-only, no JS) ──────────────── */
.hl-tooltip {
  position: relative;
  display: inline-block;
}
.hl-tooltip::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  background: var(--hl-bg-elev-2);
  border: 1px solid var(--hl-border-strong);
  color: var(--hl-text);
  font-size: var(--hl-fs-sm);
  padding: 6px 10px;
  border-radius: var(--hl-r-sm);
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--hl-dur-fast) var(--hl-ease),
              transform var(--hl-dur-fast) var(--hl-ease);
  z-index: 10;
}
.hl-tooltip:hover::after {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* ── 5.17 SPINNER + LOADING ──────────────────────────
   A bare ring spinner, plus a centered loading block (spinner + caption)
   for full-area "fetching…" states. Markup:
     <div class="hl-loading">
       <span class="hl-spinner hl-spinner--lg" role="status" aria-label="Loading"></span>
       <p class="hl-loading__text">Loading your feed…</p>
     </div> */
.hl-spinner {
  display: inline-block;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 3px solid var(--hl-border-strong);
  border-top-color: var(--hl-accent);
  animation: hl-spin 0.7s linear infinite;
  flex-shrink: 0;
  vertical-align: middle;
}
.hl-spinner--sm { width: 18px; height: 18px; border-width: 2px; }
.hl-spinner--lg { width: 44px; height: 44px; border-width: 4px; }
.hl-spinner--info   { border-top-color: var(--hl-info); }
.hl-spinner--violet { border-top-color: var(--hl-violet); }

.hl-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--hl-sp-4);
  padding: var(--hl-sp-12) var(--hl-sp-6);
  text-align: center;
}
.hl-loading__text {
  font-size: var(--hl-fs-md);
  color: var(--hl-text-muted);
}

/* ── 5.17a SKELETON ──────────────────────────────────
   Placeholder blocks that hold a region's shape while its data loads.
   Prefer these over .hl-loading wherever the incoming content has a known
   layout: a centered spinner gives the region no height, so the real
   content shoves everything around when it lands. A spinner is still the
   right call when the shape genuinely isn't knowable ahead of time.

   .hl-skeleton is the block — size it yourself, or use a --line modifier
   for a run of copy. It is decorative: mark the placeholder subtree
   aria-hidden and put aria-busy on the region it stands in for, so a
   screen reader hears "busy" rather than a pile of empty boxes.

     <div class="hl-skeleton hl-skeleton--title" style="width: 60%"></div>
     <div class="hl-skeleton hl-skeleton--line"></div>
     <div class="hl-skeleton" style="height: 96px"></div>          (a card)

   The sheen is one animation shared by every block on screen, so they
   travel together rather than each shimmering on its own clock. */
.hl-skeleton {
  display: block;
  width: 100%;
  border-radius: var(--hl-r-sm);
  background: linear-gradient(
      90deg,
      var(--hl-bg-elev) 0%,
      var(--hl-bg-elev-2) 50%,
      var(--hl-bg-elev) 100%);
  background-size: 200% 100%;
  animation: hl-skeleton-sheen 1.6s var(--hl-ease) infinite;
}
/* Line heights track the type scale they stand in for, so a skeleton laid
   out like the real block measures the same. */
.hl-skeleton--line  { height: 12px; }
.hl-skeleton--label { height: 10px; }
.hl-skeleton--title { height: 22px; border-radius: var(--hl-r-md); }
.hl-skeleton--pill  { height: 30px; border-radius: var(--hl-r-pill); }

/* ── 5.18 STATE (empty / error / success full-area block) ───
   Centered icon-bubble + title + message + optional actions + optional
   hint, for empty collections and graceful failure screens. In Blazor use
   the <EmptyState> component rather than writing this markup by hand — it
   is what keeps the icon tint, heading level and centring consistent
   across screens. Markup:
     <div class="hl-state-wrap">            <!-- optional: vertical centring -->
       <div class="hl-state">
         <span class="hl-state__icon hl-state__icon--danger"><i class="fa-solid fa-cloud-slash"></i></span>
         <h2 class="hl-state__title">Something went wrong</h2>
         <p class="hl-state__message">We couldn't load your feed. Please try again.</p>
         <div class="hl-state__actions">
           <button class="hl-btn hl-btn--primary">Try again</button>
         </div>
         <p class="hl-state__hint">Your data is safe.</p>
       </div>
     </div> */
.hl-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--hl-sp-4);
  padding: var(--hl-sp-12) var(--hl-sp-6);
  text-align: center;
  max-width: 460px;
  margin: 0 auto;
}
.hl-state__icon {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 26px;
  background: rgba(255, 255, 255, 0.04);
  color: var(--hl-text-muted);
  flex-shrink: 0;
}
.hl-state__icon--danger  { background: var(--hl-danger-soft);  color: var(--hl-danger); }
.hl-state__icon--accent  { background: var(--hl-accent-soft);  color: var(--hl-accent); }
.hl-state__icon--info    { background: var(--hl-info-soft);    color: var(--hl-info); }
.hl-state__icon--success { background: var(--hl-success-soft); color: var(--hl-success); }
.hl-state__title {
  font-size: var(--hl-fs-xl);
  font-weight: 600;
  color: var(--hl-text);
  letter-spacing: -0.02em;
}
.hl-state__message {
  font-size: var(--hl-fs-md);
  color: var(--hl-text-muted);
  line-height: var(--hl-lh-relaxed);
  max-width: 40ch;
}
.hl-state__actions {
  display: flex;
  gap: var(--hl-sp-3);
  margin-top: var(--hl-sp-2);
  flex-wrap: wrap;
  justify-content: center;
}
/* Footnote under the actions — quieter than the message, and deliberately
   dimmer than --hl-text-muted so it reads as secondary to it. */
.hl-state__hint {
  font-size: var(--hl-fs-sm);
  color: var(--hl-text-faint);
  margin: 0;
}

/* Vertical centring for a state that replaces the whole page. min-height
   rather than height: the parent (.app-content is flex:1 inside the app
   shell) has a definite height, and viewport math would risk overshooting
   it and triggering the parent's overflow: auto scrollbar. */
.hl-state-wrap {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 100%;
}

/* Compact: a state that sits *inside* a page which still has content around
   it — a master/detail pane waiting for a selection, a "no results" notice
   under a filter bar — rather than one standing in for the whole page. Same
   block, one step down the scale, so the two read as the same family without
   the pane version shouting over the content beside it. */
.hl-state--compact {
  padding: var(--hl-sp-8) var(--hl-sp-4);
  gap: var(--hl-sp-3);
}
.hl-state--compact .hl-state__icon {
  width: 44px;
  height: 44px;
  font-size: 19px;
}
.hl-state--compact .hl-state__title  { font-size: var(--hl-fs-lg); }
.hl-state--compact .hl-state__message { font-size: var(--hl-fs-sm); }

/* ── 5.19 PAGE HEADER (title + optional eyebrow/subtitle/actions) ───
   The standard header every list/overview page opens with. One type
   scale for all pages — deliberately no size modifier. The eyebrow is
   the existing .hl-eyebrow, not a header-specific class. Markup:
     <div class="hl-page-header">
       <div class="hl-page-header__text">
         <div class="hl-eyebrow hl-eyebrow--accent">Moderation</div>
         <h1 class="hl-page-header__title">Moderate feedback</h1>
         <p class="hl-page-header__subtitle">Review incoming responses.</p>
       </div>
       <div class="hl-page-header__actions">
         <a class="hl-btn hl-btn--primary">New topic</a>
       </div>
     </div>
   When something sits inline beside the title — a team badge, a status
   chip — wrap the two in a title row. Only emit the row when there is
   a suffix; a lone title needs no flex container:
     <div class="hl-page-header__title-row">
       <h1 class="hl-page-header__title">Manage topics</h1>
       <span class="hl-badge hl-badge--info">Vectorsoft</span>
     </div>
   Prefer the <PageHeader> Razor component over hand-writing this. */
.hl-page-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--hl-sp-3);
  margin-bottom: var(--hl-sp-6);
}
.hl-page-header__text {
  /* min-width:0 lets a long title wrap instead of forcing the actions
     cluster off the row. */
  min-width: 0;
}
.hl-page-header__title {
  margin: 0;
  color: var(--hl-text);
  font-family: var(--hl-font-sans);
  font-size: var(--hl-fs-xl);
  font-weight: 600;
  line-height: var(--hl-lh-tight);
  letter-spacing: -0.01em;
  /* Several pages put user-supplied text here — a team name, a topic
     name, an invoice heading. Without this a long unbroken string
     overflows the header instead of wrapping. */
  overflow-wrap: anywhere;
}
/* Title plus an inline suffix. align-items:center rather than baseline:
   the suffix is usually a badge or chip, and baseline alignment hangs
   those below the title's baseline instead of centring on it. */
.hl-page-header__title-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--hl-sp-2);
  min-width: 0;
}
.hl-page-header__subtitle {
  margin: var(--hl-sp-1) 0 0;
  color: var(--hl-text-muted);
  font-family: var(--hl-font-sans);
  font-size: var(--hl-fs-sm);
  line-height: var(--hl-lh-snug);
}
/* Eyebrow sits above the title; .hl-eyebrow carries the type, the
   header only owns the gap beneath it. */
.hl-page-header__text > .hl-eyebrow {
  margin-bottom: var(--hl-sp-1);
}
.hl-page-header__actions {
  display: flex;
  align-items: center;
  gap: var(--hl-sp-2);
  flex-wrap: wrap;
  flex-shrink: 0;
}

@media (max-width: 860px) {
  .hl-page-header {
    align-items: flex-start;
    flex-direction: column;
  }
}

/* ── 5.20 MARKETING HERO (eyebrow + display headline + lead) ───────
   The second header shape. .hl-page-header is the app's — one fixed
   22px scale, title beside its actions. This one is editorial: display
   type, a lead paragraph, CTAs beneath.

   It styles the *text column only*. Every page that uses it wraps the
   hero in its own grid or section — a two-column layout with a product
   image, a glow backdrop, a max-width — and that stays page-local,
   because none of it is shared. Markup:
     <div class="hl-hero hl-hero--lg">
       <div class="hl-eyebrow hl-eyebrow--accent">About Honestli</div>
       <h1 class="hl-hero__title">
         Feedback without <span class="hl-hero__accent">the fallout</span>.
       </h1>
       <p class="hl-hero__lead">Anonymous, good-faith feedback.</p>
       <div class="hl-hero__actions"> … </div>
     </div>
   Prefer the <Hero> Razor component over hand-writing this.

   SIZE. Unlike .hl-page-header this does take a size modifier, because
   the nine pages it replaced were not nine sizes — they were four
   near-exact pairs, and collapsing them to one would have redesigned
   eight pages rather than refactoring them:
     --xl  Home, Anonymous feedback          (the two landing heroes)
     --lg  About, How it works               (were byte-identical)
     (default)  Contact, Pricing, Privacy    (Pricing/Privacy identical)
     --sm  Stories, Upgrade                  (both flat 32px)
   Each tier's value is one of the pair's, not an average. The lead
   colour and size track the tier too: the display tiers ran a brighter
   --hl-text-2 lead, the smaller ones --hl-text-muted. */
.hl-hero {
  --hl-hero-fs:         clamp(28px, 3.6vw, 44px);
  --hl-hero-lh:         1.1;
  --hl-hero-ls:         -0.02em;
  /* 15.5px has no token: the scale jumps 14px (--hl-fs-md) to 17px
     (--hl-fs-lg) with nothing between, and the five leads that landed
     here spread 14.5–16px. Promoting a step into the global scale is a
     bigger call than this component; it stays hero-local until then. */
  --hl-hero-lead-fs:    15.5px;
  --hl-hero-lead-color: var(--hl-text-muted);
}
.hl-hero--xl {
  --hl-hero-fs:         clamp(40px, 5vw, 64px);
  --hl-hero-lh:         1.04;
  --hl-hero-ls:         -0.03em;
  --hl-hero-lead-fs:    var(--hl-fs-lg);
  --hl-hero-lead-color: var(--hl-text-2);
}
.hl-hero--lg {
  --hl-hero-fs:         clamp(36px, 4.5vw, 58px);
  --hl-hero-lh:         1.06;
  --hl-hero-ls:         -0.03em;
  --hl-hero-lead-fs:    var(--hl-fs-lg);
  --hl-hero-lead-color: var(--hl-text-2);
}
.hl-hero--sm {
  --hl-hero-fs:         var(--hl-fs-2xl);
  --hl-hero-lh:         1.15;
}

.hl-hero__title {
  margin: 0;
  color: var(--hl-text);
  font-family: var(--hl-font-sans);
  font-size: var(--hl-hero-fs);
  font-weight: 700;
  line-height: var(--hl-hero-lh);
  letter-spacing: var(--hl-hero-ls);
}
/* Mid-sentence colour break in a headline — "Honest answers." set against
   the rest of the line. Two accents so a headline can alternate. */
.hl-hero__accent      { color: var(--hl-accent); }
.hl-hero__accent--alt { color: var(--hl-accent-blue); }

.hl-hero__lead {
  margin: var(--hl-sp-5) 0 0;
  color: var(--hl-hero-lead-color);
  font-size: var(--hl-hero-lead-fs);
  line-height: var(--hl-lh-relaxed);
  max-width: 56ch;
}
/* Emphasis inside a lead lifts to full white — every page did this. */
.hl-hero__lead strong { color: var(--hl-text); }

.hl-hero__actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--hl-sp-3);
  margin-top: var(--hl-sp-7);
}
/* Trailing row beneath the CTAs — trust signals, a last-updated date,
   feature pills. Deliberately loose: pages put different things here. */
.hl-hero__meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--hl-sp-6);
  margin-top: var(--hl-sp-7);
  color: var(--hl-text-muted);
  font-size: var(--hl-fs-md);
}
/* Most meta items are an icon plus a word; this keeps the two aligned
   without every page re-declaring the flex row. A page that puts a real
   container here (a pill group, say) overrides it from its own scoped
   sheet, which outranks this by one class. */
.hl-hero__meta > * {
  display: inline-flex;
  align-items: center;
  gap: var(--hl-sp-2);
}

/* The kicker owns its own type; the hero owns only the gap beneath it.
   .hl-badge is here too because the two landing heroes open with a badge
   rather than an eyebrow — sentence case with a status dot, not small caps. */
.hl-hero > .hl-eyebrow,
.hl-hero > .hl-badge {
  margin-bottom: var(--hl-sp-4);
}

.hl-hero--center {
  margin-inline: auto;
  text-align: center;
}
.hl-hero--center .hl-hero__lead { margin-inline: auto; }
.hl-hero--center .hl-hero__actions,
.hl-hero--center .hl-hero__meta { justify-content: center; }

/* ── 5.21 MODAL (shell for a Bootstrap-driven dialog) ──────────────
   The system owns the *shell*, not the mechanics. Bootstrap's JS still
   opens and closes the dialog (`bootstrap.Modal`) and still owns the
   backdrop, the focus trap and the Escape key; these classes replace only
   the chrome it paints, which is the part that looked foreign. Markup —
   the outer two elements stay Bootstrap's:

     <div class="modal fade" tabindex="-1" @ref="_modalRef" …>
       <div class="modal-dialog modal-lg modal-dialog-centered
                   modal-dialog-scrollable">
         <div class="modal-content hl-modal">
           <div class="hl-modal__header">
             <div class="hl-modal__heading">
               <div class="hl-eyebrow hl-eyebrow--accent">Share topic</div>
               <h2 class="hl-modal__title" id="…">Title</h2>
             </div>
             <button class="hl-modal__close" data-bs-dismiss="modal"
                     aria-label="Close">✕</button>
           </div>
           <div class="hl-modal__body"> … </div>
           <div class="hl-modal__footer"> … </div>
         </div>
       </div>
     </div>

   `.hl-modal` goes ON `.modal-content` rather than replacing it —
   Bootstrap measures that element, and dropping the class breaks the
   scrollable variant's height maths.

   SCOPED-CSS WARNING. A component's own `*.razor.css` reaches inside this
   shell only through `::deep`: Bootstrap can re-parent the dialog to
   <body>, and the `b-<hash>` attribute is stamped on the component's
   markup, not on what wraps it at runtime. Every consumer's sheet
   therefore anchors its rules with `::deep` — see ShareModal and
   DeleteProfileModal.

   The header tint is the accent at 5%, the same wash the page header uses,
   so a dialog reads as part of the app rather than an overlay dropped on
   top of it. Use the eyebrow for the *kind* of dialog ("Share topic",
   "Delete profile") and the title for its subject. */
.hl-modal {
  background: var(--hl-bg-elev);
  border: 1px solid var(--hl-border-strong);
  border-radius: var(--hl-r-lg);
  color: var(--hl-text-2);
  font-family: var(--hl-font-sans);
  box-shadow: var(--hl-shadow-pop);
  overflow: hidden;
}

.hl-modal__header {
  display: flex;
  align-items: flex-start;
  gap: var(--hl-sp-4);
  padding: var(--hl-sp-5) var(--hl-sp-6);
  border-bottom: 1px solid var(--hl-border);
  background: linear-gradient(180deg, var(--hl-accent-soft), transparent);
}

.hl-modal__heading {
  flex: 1;
  min-width: 0;
}

/* A dialog title, never the page's — h2, so a page keeps one h1. The gap
   under the eyebrow lives here, matching .hl-page-header and .hl-hero. */
.hl-modal__title {
  margin: var(--hl-sp-1) 0 0;
  color: var(--hl-text);
  font-size: 18px;
  font-weight: 600;
  line-height: var(--hl-lh-snug);
  letter-spacing: -0.01em;
  overflow: hidden;
  text-overflow: ellipsis;
  word-break: break-word;
}

.hl-modal__close {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid var(--hl-border-strong);
  border-radius: var(--hl-r-sm);
  color: var(--hl-text-2);
  font-size: var(--hl-fs-md);
  cursor: pointer;
  transition: background var(--hl-dur-fast) var(--hl-ease),
              color var(--hl-dur-fast) var(--hl-ease);
}
.hl-modal__close:hover {
  background: rgba(255, 255, 255, 0.06);
  color: var(--hl-text);
}

/* A column, because dialog bodies are stacked sections far more often than
   not; a consumer that wants something else overrides from its own sheet. */
.hl-modal__body {
  display: flex;
  flex-direction: column;
  gap: var(--hl-sp-5);
  padding: 22px var(--hl-sp-6);
}

.hl-modal__footer {
  display: flex;
  justify-content: flex-end;
  gap: var(--hl-sp-2);
  padding: var(--hl-sp-3) 22px;
  border-top: 1px solid var(--hl-border);
}

@media (max-width: 575px) {
  .hl-modal__header { padding: var(--hl-sp-4) 18px; }
  .hl-modal__body   { padding: 18px; }
  .hl-modal__footer { padding: var(--hl-sp-3) var(--hl-sp-4); }
}

/* -------------------------------------------------------------
   6. ANIMATIONS
   ------------------------------------------------------------- */
@keyframes hl-mark-in {
  0%   { opacity: 0; transform: scale(0.7); }
  70%  { opacity: 1; transform: scale(1.06); }
  100% { opacity: 1; transform: scale(1); }
}
@keyframes hl-fade-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}
.hl-anim-fade-in { animation: hl-fade-in 0.4s var(--hl-ease) both; }

/* Spinner ring rotation — used by .hl-spinner */
@keyframes hl-spin { to { transform: rotate(360deg); } }

/* Sheen travelling across a placeholder block — used by .hl-skeleton.
   The reduced-motion reset below stops this at frame one, which leaves the
   block on the flat --hl-bg-elev end of the gradient. That still reads as a
   placeholder, so the state survives without the movement. */
@keyframes hl-skeleton-sheen {
  from { background-position: 200% 0; }
  to   { background-position: -200% 0; }
}

/* Breathing status dot — used by .hl-eyebrow__dot--pulse */
@keyframes hl-dot-pulse {
  0%, 100% { opacity: 1;    transform: scale(1); }
  50%      { opacity: 0.55; transform: scale(0.82); }
}

/* Active-step "you are here" pulse — used by .hl-steps__step--current */
@keyframes hl-step-pulse {
  0%, 100% { box-shadow: 0 0 0 6px rgba(249, 115, 22, 0.10),
                          0 0 24px -4px rgba(249, 115, 22, 0.45); }
  50%      { box-shadow: 0 0 0 10px rgba(249, 115, 22, 0.04),
                          0 0 28px -2px rgba(249, 115, 22, 0.55); }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
