/*
 * elfrique.components.overlay.css — Overlay / motion primitives
 * Mandate: M02 · C2 Component Kit
 *
 * Scope: Drawer, Toast, CmdK, Reveal.
 * Siblings: elfrique.components.layout.css · elfrique.components.data.css
 *
 * All styles in this file MUST consume tokens from elfrique.css via var(--token).
 * No raw hex. No raw ms. No Bootstrap class names.
 *
 * Populated in Mandate 02 Phases 4a, 4b, 6.
 * Caps: ≤ 1,000 lines hard / ≤ 800 lines soft. Aggregate (3 files) ≤ 3,000.
 */


/* ==========================================================================
   DRAWER  (Phase 4a · W2.7)
   Right-side slide-in dialog at three fixed widths (480 / 640 / 880 px).
   Focus-trapped; Esc + backdrop dismiss via cancellable kit:drawer-close-request.
   XHR lazy-load into the body slot. Below 640px the panel takes full width
   regardless of the Size prop (handled via media query at end of block).
   Source: Views/Shared/Kit/_Drawer.cshtml · Model: DrawerModel (DrawerSize enum).
   Script: wwwroot/js/kit/drawer.js (solhigson.kit.drawer).
   ========================================================================== */

.drawer {
  position: fixed;
  inset: 0;
  z-index: var(--z-drawer);
  display: block;
  font-family: var(--font-sans);
  color: var(--brand-ink);
}

.drawer[hidden] { display: none; }

/* Drawer backdrop motion (M13 Phase 2 · W14.2)
   Enter band (`--d-4`) matches the panel slide for "alongside" choreography.
   Exit collapses to `--d-3` so the dismissal reads as quicker than the open —
   matches the M13 motion spec pairing guidance (panel/drawer enter on `--d-4`,
   small reveal/exit on `--d-3`). The faster exit is asymmetric on purpose:
   open invites attention, close releases it. */
.drawer__backdrop {
  position: absolute; inset: 0;
  background-color: color-mix(in srgb, var(--n-900) 55%, transparent);
  opacity: 0;
  transition: opacity var(--d-3) var(--ease-out-swift);
}

.drawer--open .drawer__backdrop {
  opacity: 1;
  transition: opacity var(--d-4) var(--ease-out-swift);
}

/* Drawer panel motion (M13 Phase 2 · W14.2)
   Same enter/exit asymmetry as the backdrop. M13 Phase 0 adjudication D1
   binds the token values; the references in the Workload Plan (320ms / 200ms)
   are superseded — `--d-4` (400ms) for the enter, `--d-3` (300ms) for the
   exit. Token names only; raw ms forbidden by hard rule #3. */
.drawer__panel {
  position: absolute; top: 0; right: 0; bottom: 0;
  display: flex; flex-direction: column;
  width: 100%; max-width: 640px;
  background-color: var(--n-0);
  border-left: 1px solid var(--n-200);
  box-shadow: var(--shadow-4);
  transform: translateX(100%);
  transition: transform var(--d-3) var(--ease-out-swift);
}

.drawer--open .drawer__panel {
  transform: translateX(0);
  transition: transform var(--d-4) var(--ease-out-swift);
}

/* Content stagger (M13 Phase 2 · W14.2)
   M13 motion spec Rule 3 — sibling stagger interval is `calc(var(--d-1) / 2)`.
   When the drawer opens, header → body → footer reveal in sequence after the
   panel begins its slide-in. Each child fades up `--d-2` from a 4px offset and
   its delay is the stagger constant times its position (1, 2, 3). The CSS
   custom property `--drawer-stagger` exposes the constant for any child that
   needs a custom delay. */
.drawer__panel {
  --drawer-stagger: calc(var(--d-1) / 2);
}

.drawer .drawer__header,
.drawer .drawer__body,
.drawer .drawer__footer {
  opacity: 0;
  transform: translateY(4px);
  transition: opacity var(--d-2) var(--ease-out-swift),
              transform var(--d-2) var(--ease-out-swift);
}

.drawer--open .drawer__header  { opacity: 1; transform: none; transition-delay: calc(var(--drawer-stagger) * 1); }
.drawer--open .drawer__body    { opacity: 1; transform: none; transition-delay: calc(var(--drawer-stagger) * 2); }
.drawer--open .drawer__footer  { opacity: 1; transform: none; transition-delay: calc(var(--drawer-stagger) * 3); }

/* Size modifiers — set max-width only; width 100% below 640px handles mobile. */
.drawer.drawer--sm .drawer__panel { max-width: 480px; }
.drawer.drawer--md .drawer__panel { max-width: 640px; }
.drawer.drawer--lg .drawer__panel { max-width: 880px; }

.drawer__header {
  display: flex; align-items: flex-start; gap: var(--sp-3);
  padding: var(--sp-5) var(--sp-5) var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--n-100);
  background-color: var(--n-0);
  flex: 0 0 auto;
}

.drawer__header-text {
  display: flex; flex-direction: column; gap: var(--sp-1);
  flex: 1 1 auto; min-width: 0;
}

.drawer__title {
  margin: 0;
  font-family: var(--font-serif);
  font-size: var(--fs-5);
  font-weight: 600;
  line-height: 1.2;
  color: var(--brand-ink);
  overflow: hidden;
  text-overflow: ellipsis;
}

.drawer__header-accent {
  font-family: var(--font-mono);
  font-size: var(--fs-1);
  color: var(--n-600);
  line-height: 1.4;
}

.drawer__close {
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
  width: 44px; height: 44px;
  min-width: 44px; min-height: 44px;
  padding: 0; margin: calc(-1 * var(--sp-1)) calc(-1 * var(--sp-2)) 0 0;
  background-color: transparent;
  border: 0;
  border-radius: 50%;
  color: var(--n-600);
  cursor: pointer;
  transition: background-color var(--d-1) var(--ease-out-swift),
              color var(--d-1) var(--ease-out-swift);
}

.drawer__close:hover {
  background-color: var(--brand-ink);
  color: var(--n-0);
}

.drawer__close:focus-visible {
  outline: 3px solid var(--state-info);
  outline-offset: 2px;
}

.drawer__close-glyph {
  font-family: var(--font-mono);
  font-size: var(--fs-5);
  font-weight: 600;
  line-height: 1;
}

.drawer__body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: var(--sp-5);
  background-color: var(--n-0);
  color: var(--brand-ink);
  font-size: var(--fs-3);
  line-height: 1.55;
}

.drawer__body[data-drawer-loading="true"] {
  position: relative;
  overflow: hidden;
}

.drawer__body[data-drawer-loading="true"]::before {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--n-100) 70%, transparent) 0%,
    color-mix(in srgb, var(--n-200) 85%, transparent) 50%,
    color-mix(in srgb, var(--n-100) 70%, transparent) 100%);
  background-size: 200% 100%;
  animation: drawer-shimmer var(--d-5) var(--ease-in-out-subtle) infinite;
  pointer-events: none;
}

@keyframes drawer-shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

.drawer__lazy-placeholder {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-5);
  color: var(--n-600);
  font-family: var(--font-mono);
  font-size: var(--fs-1);
}

.drawer__lazy-spinner {
  display: inline-block;
  width: 16px; height: 16px;
  border: 2px solid var(--n-300);
  border-top-color: var(--brand-ink);
  border-radius: 50%;
  animation: drawer-spinner var(--d-5) linear infinite;
}

@keyframes drawer-spinner {
  to { transform: rotate(360deg); }
}

.drawer__lazy-label { font-style: italic; }

.drawer__lazy-error {
  margin: 0;
  padding: var(--sp-3);
  color: var(--state-danger);
  font-family: var(--font-sans);
  font-size: var(--fs-2);
  background-color: color-mix(in srgb, var(--state-danger) 6%, var(--n-0));
  border: 1px solid color-mix(in srgb, var(--state-danger) 25%, var(--n-200));
  border-radius: var(--r-2);
}

.drawer__empty {
  display: flex; flex-direction: column; gap: var(--sp-2);
  padding: var(--sp-6);
  text-align: center;
  color: var(--n-500);
}

.drawer__empty-headline {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-3);
  font-weight: 600;
  color: var(--n-700);
}

.drawer__empty-hint {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-1);
  color: var(--n-500);
}

.drawer__footer {
  flex: 0 0 auto;
  display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end;
  gap: var(--sp-2);
  padding: var(--sp-4) var(--sp-5);
  background-color: var(--n-50);
  border-top: 1px solid var(--n-100);
}

/* Body scroll-lock hook — drawer.js toggles .drawer-scroll-lock on <body>. */
body.drawer-scroll-lock { overflow: hidden; }

/* ── Drawer · sheet drag handle (M10 Phase 3 W6.5) ──
   Hidden at ≥640px (drawer mode). At <640px the panel becomes a bottom sheet
   and this element acts as both a visual drag affordance and a 44×44 hit
   target for swipe-dismiss (Phase 4 W6.v2.2). Hard rule #8 — the inner bar
   may be smaller than 44px; the button itself is 44×44.

   tabindex="-1" keeps the handle out of the focus order; the close button in
   the header is the keyboard-reachable dismissal path (accessibility-auditor
   role binding: gesture without keyboard equivalent is rejected). */
.drawer__handle {
  display: none;
  position: relative;
  flex: 0 0 auto;
  align-self: center;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  margin: 0;
  background-color: transparent;
  border: 0;
  cursor: grab;
  touch-action: none;
}

.drawer__handle-bar {
  display: block;
  width: 32px;
  height: 4px;
  border-radius: 9999px; /* F-1c-1: --r-pill undefined codebase-wide; literal replaces broken ref */
  background-color: var(--n-300);
  transition: background-color var(--d-1) var(--ease-out-swift);
}

.drawer__handle:hover .drawer__handle-bar,
.drawer__handle:focus-visible .drawer__handle-bar {
  background-color: var(--n-500);
}

.drawer__handle:focus-visible {
  outline: 3px solid var(--state-info);
  outline-offset: -2px;
}

/* ── Drawer · mobile sheet variant (M10 Phase 3 W6.5) ──
   Below 640px, every drawer presents as a bottom sheet:
     - panel anchors to the bottom edge (top: auto)
     - max-width is 100% regardless of Size prop
     - max-height caps at 92vh leaving the top 8vh as the dismiss-zone
     - slide-in is from the bottom (translateY) instead of the side (translateX)
     - top corners round; the side border becomes a top border
     - drag handle becomes visible at the top edge of the panel
   The transition itself respects the same motion tokens as drawer mode
   (--d-3 + --ease-out-swift). prefers-reduced-motion fallback below. */
@media (max-width: 640px) {
  .drawer.drawer--sm .drawer__panel,
  .drawer.drawer--md .drawer__panel,
  .drawer.drawer--lg .drawer__panel {
    max-width: 100%;
  }

  .drawer__panel {
    top: auto;
    right: 0;
    left: 0;
    bottom: 0;
    width: 100%;
    max-height: 92vh;
    border-left: 0;
    border-top: 1px solid var(--n-200);
    border-top-left-radius: var(--r-4);
    border-top-right-radius: var(--r-4);
    transform: translateY(100%);
    box-shadow: var(--shadow-4);
  }

  .drawer--open .drawer__panel {
    transform: translateY(0);
  }

  .drawer__handle {
    display: inline-flex;
  }

  /* Header padding tightens on sheet — drag handle owns the top breathing room. */
  .drawer__header {
    padding-top: var(--sp-2);
  }
}

/* ── Drawer · sheet-mode swipe-dismiss state (M10 Phase 4 · W6.v2.2) ──
   Driver: wwwroot/js/kit/swipe-dismiss.js (solhigson.kit.swipeDismiss).

   States:
     .drawer__panel.is-dragging       — pointer is currently down + tracking;
                                        transition is suppressed so the
                                        transform follows the finger 1:1
                                        without lag. JS writes inline
                                        `transform: translateY(Npx)` on each
                                        requestAnimationFrame flush.
     .drawer__panel.is-springing-back — pointer released below threshold;
                                        transition re-enabled with
                                        --ease-spring so the panel oscillates
                                        gently back to translateY(0).
     .drawer__panel.is-snapping-back  — same release path under reduced-motion;
                                        transition is suppressed so the snap
                                        is instant (per Phase 4 success
                                        criterion: spring-back becomes an
                                        instant jump under reduce).

   touch-action: pan-y on the panel — F-0-5 binding. The platform owns
   horizontal/diagonal gestures (rotor swipes are horizontal); we only
   intercept the vertical axis. swipe-dismiss.js never preventDefault()s. */
@media (max-width: 640px) {
  .drawer__panel {
    touch-action:       pan-y;
  }
  .drawer__handle {
    /* Override the default `touch-action: none` on the handle so VoiceOver
       horizontal rotor swipes flow through the platform. Vertical drag is
       still ours. */
    touch-action:       pan-y;
  }
  .drawer__panel.is-dragging {
    transition:         none;
    will-change:        transform;
    cursor:             grabbing;
  }
  .drawer__panel.is-dragging .drawer__handle {
    cursor:             grabbing;
  }
  .drawer__panel.is-springing-back {
    transition:         transform var(--d-2) var(--ease-spring);
  }
  .drawer__panel.is-snapping-back {
    transition:         none;
  }
}

/* ── Drawer · prefers-reduced-motion ──
   Hard rule #10. The panel appears immediately without transform slide;
   the backdrop appears immediately without opacity fade. Body shimmer and
   spinner animations halt — static placeholder replaces them.
   The sheet-mode drag-handle bar colour transition also halts.
   Sheet-mode swipe spring-back also collapses to an instant snap (the
   `is-springing-back` rule above is a sheet-mode @media block; under
   reduced-motion JS routes through `is-snapping-back` instead, which is
   already transition: none). */
@media (prefers-reduced-motion: reduce) {
  .drawer__panel,
  .drawer__backdrop,
  .drawer__handle-bar {
    transition: none;
  }
  /* Content stagger collapses under reduced motion — every child appears
     immediately at its resting position with no offset and no delay. */
  .drawer .drawer__header,
  .drawer .drawer__body,
  .drawer .drawer__footer,
  .drawer--open .drawer__header,
  .drawer--open .drawer__body,
  .drawer--open .drawer__footer {
    opacity: 1;
    transform: none;
    transition: none;
    transition-delay: 0s;
  }
  .drawer__body[data-drawer-loading="true"]::before,
  .drawer__lazy-spinner {
    animation: none;
  }
}


/* ==========================================================================
   TOAST  (Phase 4a · W2.13)
   Top-right transient stack. Variants map 1:1 to state tokens.
   Auto-dismiss 4000 ms for success + info; explicit-only for warn + danger.
   Hard rule #1 — --state-success is TEAL (enforced via variable, not literal).
   Source: Views/Shared/Kit/_Toast.cshtml · Model: ToastModel (ToastVariant enum).
   Script: wwwroot/js/kit/toast.js (solhigson.kit.toast).
   ========================================================================== */

.toast-stack {
  position: fixed;
  top: var(--sp-4); right: var(--sp-4);
  z-index: var(--z-toast);
  display: flex; flex-direction: column;
  gap: var(--sp-2);
  width: min(22rem, calc(100vw - var(--sp-5)));
  pointer-events: none;
}

.toast {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: start;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  background-color: var(--n-0);
  border: 1px solid var(--n-200);
  border-left-width: 4px;
  border-radius: var(--r-3);
  box-shadow: var(--shadow-3);
  color: var(--brand-ink);
  font-family: var(--font-sans);
  font-size: var(--fs-2);
  line-height: 1.4;
  pointer-events: auto;
  transform: translateX(0);
  opacity: 1;
  transition: transform var(--d-2) var(--ease-out-swift),
              opacity var(--d-2) var(--ease-out-swift);
  animation: toast-enter var(--d-3) var(--ease-out-swift);
}

@keyframes toast-enter {
  from { transform: translateX(24px); opacity: 0; }
  to   { transform: translateX(0);    opacity: 1; }
}

.toast--dismissing {
  transform: translateX(24px);
  opacity: 0;
  pointer-events: none;
}

.toast__icon {
  display: inline-flex;
  align-items: center; justify-content: center;
  width: 24px; height: 24px;
  border-radius: 50%;
  font-family: var(--font-mono);
  font-size: var(--fs-2);
  font-weight: 700;
  line-height: 1;
  color: var(--n-0);
}

.toast__body {
  display: flex; flex-direction: column; gap: var(--sp-1);
  min-width: 0;
}

.toast__title {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-2);
  font-weight: 600;
  color: var(--brand-ink);
}

.toast__message {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-2);
  color: var(--n-700);
  overflow-wrap: anywhere;
}

.toast__action {
  align-self: flex-start;
  margin-top: var(--sp-1);
  font-family: var(--font-sans);
  font-size: var(--fs-2);
  font-weight: 500;
  color: var(--brand-ink);
  text-decoration: underline;
  text-underline-offset: 3px;
  min-height: 44px;
  display: inline-flex; align-items: center;
  padding: 0 var(--sp-1);
  border-radius: var(--r-1);
}

/* M50 Phase 17 — when the action slot renders as a <button> (copy-link
   variant), neutralise UA button chrome so it visually matches the
   anchor variant. HR8 ≥44×44 already inherited via .toast__action. */
button.toast__action {
  background: transparent;
  border: none;
  cursor: pointer;
}

.toast__action:hover { color: color-mix(in srgb, var(--brand-ink) 75%, var(--n-600)); }

.toast__action:focus-visible {
  outline: 3px solid var(--state-info);
  outline-offset: 2px;
}

.toast__close {
  align-self: flex-start;
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px;
  min-width: 44px; min-height: 44px;
  padding: 0; margin: calc(-1 * var(--sp-1)) calc(-1 * var(--sp-2)) 0 0;
  background-color: transparent;
  border: 0;
  border-radius: 50%;
  color: var(--n-600);
  cursor: pointer;
  transition: background-color var(--d-1) var(--ease-out-swift),
              color var(--d-1) var(--ease-out-swift);
}

.toast__close:hover {
  background-color: var(--n-100);
  color: var(--brand-ink);
}

.toast__close:focus-visible {
  outline: 3px solid var(--state-info);
  outline-offset: 2px;
}

.toast__close-glyph {
  font-family: var(--font-mono);
  font-size: var(--fs-4);
  font-weight: 600;
  line-height: 1;
}

/* ── Toast · variants ──
   Each variant drives three chrome properties:
     - .toast border-left-color (accent rail)
     - .toast__icon background-color (icon chip)
     - .toast__title color (headline emphasis)
   Per hard rule #1, SUCCESS maps to --state-success which is teal. */

.toast--success {
  border-left-color: var(--state-success);
}
.toast--success .toast__icon { background-color: var(--state-success); }
.toast--success .toast__title { color: var(--state-success); }

.toast--info {
  border-left-color: var(--state-info);
}
.toast--info .toast__icon { background-color: var(--state-info); }
.toast--info .toast__title { color: var(--state-info); }

.toast--warn {
  border-left-color: var(--state-warn);
}
.toast--warn .toast__icon { background-color: var(--state-warn); }
.toast--warn .toast__title { color: var(--state-warn); }

.toast--danger {
  border-left-color: var(--state-danger);
}
.toast--danger .toast__icon { background-color: var(--state-danger); }
.toast--danger .toast__title { color: var(--state-danger); }

/* ── Toast · small screens ──
   On narrow viewports the stack expands to the full width minus a gutter
   and tucks against the top — still top-right aligned by convention but
   visually centered over the content. */
@media (max-width: 640px) {
  .toast-stack {
    top: var(--sp-3); right: var(--sp-3); left: var(--sp-3);
    width: auto;
  }
}

/* ── Toast · prefers-reduced-motion ──
   Hard rule #10. The enter animation resolves to a single-frame opacity step
   and the exit transition halts. */
@media (prefers-reduced-motion: reduce) {
  .toast,
  .toast--dismissing {
    animation: none;
    transition: none;
  }
}


/* ==========================================================================
   CMDK  (Phase 4b · W2.12)
   Global command palette dialog. Ctrl+K / Cmd+K opens; Esc closes; arrow
   keys navigate options; Enter activates the selected option. Sits above
   every other overlay (z-cmdk: 80 > z-toast: 70 > z-drawer: 50). Hard rule
   #6 — no modal-inside-modal — is enforced by the companion script (refuses
   to open while a drawer is active).
   Source: Views/Shared/Kit/_CmdK.cshtml · Model: CmdKModel (CmdKItem record).
   Script: wwwroot/js/kit/cmdk.js (solhigson.kit.cmdk).
   ========================================================================== */

.cmdk {
  position: fixed;
  inset: 0;
  z-index: var(--z-cmdk);
  display: block;
  font-family: var(--font-sans);
  color: var(--brand-ink);
}

.cmdk[hidden] { display: none; }

.cmdk__backdrop {
  position: absolute; inset: 0;
  background-color: color-mix(in srgb, var(--n-900) 55%, transparent);
  opacity: 0;
  transition: opacity var(--d-3) var(--ease-out-swift);
}

.cmdk--open .cmdk__backdrop { opacity: 1; }

.cmdk__panel {
  position: absolute;
  top: 12vh;
  left: 50%;
  transform: translate(-50%, -12px);
  display: flex; flex-direction: column;
  width: min(calc(100% - var(--sp-5) * 2), 640px);
  max-height: 70vh;
  background-color: var(--n-0);
  border: 1px solid var(--n-200);
  border-radius: var(--r-4);
  box-shadow: var(--shadow-4);
  opacity: 0;
  transition: transform var(--d-3) var(--ease-out-swift),
              opacity var(--d-3) var(--ease-out-swift);
  overflow: hidden;
}

.cmdk--open .cmdk__panel {
  transform: translate(-50%, 0);
  opacity: 1;
}

.cmdk__header {
  display: flex; align-items: flex-start; gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5) var(--sp-2) var(--sp-5);
  flex: 0 0 auto;
}

.cmdk__title {
  flex: 1 1 auto;
  margin: 0;
  font-family: var(--font-serif);
  font-size: var(--fs-4);
  font-weight: 600;
  line-height: 1.2;
  color: var(--brand-ink);
}

.cmdk__close {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px;
  min-width: 44px; min-height: 44px;
  padding: 0; margin: calc(-1 * var(--sp-1)) calc(-1 * var(--sp-2)) 0 0;
  background-color: transparent;
  border: 0;
  border-radius: 50%;
  color: var(--n-600);
  cursor: pointer;
  transition: background-color var(--d-1) var(--ease-out-swift),
              color var(--d-1) var(--ease-out-swift);
}

.cmdk__close:hover {
  background-color: var(--brand-ink);
  color: var(--n-0);
}

.cmdk__close:focus-visible {
  outline: 3px solid var(--state-info);
  outline-offset: 2px;
}

.cmdk__close-glyph {
  font-family: var(--font-mono);
  font-size: var(--fs-5);
  font-weight: 600;
  line-height: 1;
}

.cmdk__search {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-5) var(--sp-3) var(--sp-5);
  border-bottom: 1px solid var(--n-100);
  flex: 0 0 auto;
}

.cmdk__search-icon {
  display: inline-flex;
  align-items: center; justify-content: center;
  color: var(--n-500);
  font-size: var(--fs-3);
  line-height: 1;
}

.cmdk__search-label.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}

.cmdk__search-input {
  width: 100%;
  min-height: 44px;
  padding: var(--sp-2) 0;
  background-color: transparent;
  border: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-3);
  color: var(--brand-ink);
  line-height: 1.4;
}

.cmdk__search-input::placeholder {
  color: var(--n-500);
}

.cmdk__results {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: var(--sp-2) 0;
  background-color: var(--n-0);
}

.cmdk__listbox {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.cmdk__group-header {
  padding: var(--sp-3) var(--sp-5) var(--sp-1) var(--sp-5);
  font-family: var(--font-mono);
  font-size: var(--fs-1);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--n-500);
}

.cmdk__group-header[hidden] { display: none; }

.cmdk__group-label {
  display: inline-block;
  line-height: 1;
}

.cmdk__option {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-5);
  min-height: 44px;
  cursor: pointer;
  color: var(--brand-ink);
  background-color: transparent;
  transition: background-color var(--d-1) var(--ease-out-swift);
}

.cmdk__option[hidden] { display: none; }

.cmdk__option:hover,
.cmdk__option--active {
  background-color: var(--n-50);
}

.cmdk__option--active {
  background-color: color-mix(in srgb, var(--brand-ink) 6%, var(--n-0));
  box-shadow: inset 3px 0 0 0 var(--brand-ink);
}

.cmdk__option-body {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  min-width: 0;
}

.cmdk__option-label {
  font-family: var(--font-sans);
  font-size: var(--fs-3);
  font-weight: 500;
  color: var(--brand-ink);
  line-height: 1.3;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cmdk__option-desc {
  font-family: var(--font-mono);
  font-size: var(--fs-1);
  color: var(--n-600);
  line-height: 1.4;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cmdk__option-hint {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: var(--fs-1);
  color: var(--n-500);
  padding: var(--sp-1) var(--sp-2);
  border: 1px solid var(--n-200);
  border-radius: var(--r-1);
  background-color: var(--n-50);
}

.cmdk__empty,
.cmdk__nomatch {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  padding: var(--sp-6) var(--sp-5);
  text-align: center;
  color: var(--n-500);
  margin: 0;
}

.cmdk__empty-headline,
.cmdk__nomatch {
  font-family: var(--font-sans);
  font-size: var(--fs-3);
  font-weight: 500;
  color: var(--n-700);
}

.cmdk__empty-hint {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-1);
  color: var(--n-500);
}

.cmdk__empty-hint code {
  font-family: var(--font-mono);
  font-size: inherit;
  padding: 0 var(--sp-1);
  background-color: var(--n-50);
  border: 1px solid var(--n-200);
  border-radius: var(--r-1);
  color: var(--brand-ink);
}

.cmdk__footer {
  flex: 0 0 auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-3) var(--sp-5);
  background-color: var(--n-50);
  border-top: 1px solid var(--n-100);
  font-family: var(--font-mono);
  font-size: var(--fs-1);
  color: var(--n-600);
}

.cmdk__footer-hint {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
}

.cmdk__footer-text {
  color: var(--n-600);
}

.cmdk__kbd {
  display: inline-flex;
  align-items: center; justify-content: center;
  min-width: 20px; height: 20px;
  padding: 0 var(--sp-1);
  border: 1px solid var(--n-300);
  border-bottom-width: 2px;
  border-radius: var(--r-1);
  background-color: var(--n-0);
  font-family: var(--font-mono);
  font-size: var(--fs-1);
  font-weight: 600;
  color: var(--brand-ink);
  line-height: 1;
}

/* Body scroll-lock hook — cmdk.js toggles .cmdk-scroll-lock on <body>. */
body.cmdk-scroll-lock { overflow: hidden; }

/* ── CmdK · small screens ──
   Below 640px the palette spans the viewport top with small gutters; the
   max-height shrinks to give the browser chrome room. */
@media (max-width: 640px) {
  .cmdk__panel {
    top: var(--sp-4);
    width: calc(100% - var(--sp-4) * 2);
    max-height: calc(100vh - var(--sp-4) * 2);
  }
}

/* ── CmdK · prefers-reduced-motion ──
   Hard rule #10. The panel appears immediately — no transform slide, no
   opacity fade. Option hover background change is still visible but the
   transition is cancelled. */
@media (prefers-reduced-motion: reduce) {
  .cmdk__panel,
  .cmdk__backdrop,
  .cmdk__option,
  .cmdk__close {
    transition: none;
  }
}


/* ==========================================================================
   REVEAL  (Phase 6 · W2.v2.1)
   Container that runs an entry choreography on first viewport intersection.
   Three variants share one [data-reveal-state] attribute contract: hidden
   starts invisible (opacity 0 + transform or filter), revealed transitions
   to the rest state. Companion script wwwroot/js/kit/reveal.js drives the
   attribute swap via a per-document IntersectionObserver.

   prefers-reduced-motion: reduce is handled in two layers:
     1. JS short-circuits — marks every reveal as "revealed" on init,
        never creates an observer.
     2. CSS short-circuits — the @media block at the end of this section
        cancels the transition declarations so even if JS runs in a
        timing-sensitive way the content is visible without animation.

   Source: Views/Shared/Kit/_Reveal.cshtml · Model: RevealModel.
   Script: wwwroot/js/kit/reveal.js (solhigson.kit.reveal).
   ========================================================================== */

.kit-reveal {
  display: block;
}

/* Shared transition timing. Rest state targets the revealed attribute. */
.kit-reveal[data-reveal-state="hidden"] {
  opacity: 0;
  will-change: transform, opacity, filter;
}

.kit-reveal[data-reveal-state="revealed"] {
  opacity: 1;
  transform: none;
  filter: none;
}

/* ── Variant: fade-up ──
   Fade + translateY(12px → 0). The default and most common variant. */
.kit-reveal--fade-up[data-reveal-state="hidden"] {
  transform: translateY(12px);
}

.kit-reveal--fade-up {
  transition:
    opacity var(--d-3) var(--ease-out-swift),
    transform var(--d-3) var(--ease-out-swift);
}

/* ── Variant: blur-in ──
   Fade + blur(8px → 0). Editorial and hero-scale use. Blur is a GPU
   operation on modern browsers and plays well with transform. */
.kit-reveal--blur-in[data-reveal-state="hidden"] {
  filter: blur(8px);
  transform: translateY(8px);
}

.kit-reveal--blur-in {
  transition:
    opacity var(--d-4) var(--ease-out-swift),
    transform var(--d-4) var(--ease-out-swift),
    filter var(--d-4) var(--ease-out-swift);
}

/* ── Variant: stagger-children ──
   Fade + translateY on each direct child. Children take a staggered
   delay based on nth-child index. The root itself fades in without
   transform so the stagger reads crisply on top of the container
   appearing. The stagger step is --d-1; indexes beyond 8 reuse the tail
   delay so long rails don't grow into a noticeable lag. */
.kit-reveal--stagger-children[data-reveal-state="hidden"] > * {
  opacity: 0;
  transform: translateY(10px);
}

.kit-reveal--stagger-children > * {
  transition:
    opacity var(--d-3) var(--ease-out-swift),
    transform var(--d-3) var(--ease-out-swift);
  transition-delay: 0s;
}

.kit-reveal--stagger-children[data-reveal-state="revealed"] > *:nth-child(2) { transition-delay: var(--d-1); }
.kit-reveal--stagger-children[data-reveal-state="revealed"] > *:nth-child(3) { transition-delay: calc(var(--d-1) * 2); }
.kit-reveal--stagger-children[data-reveal-state="revealed"] > *:nth-child(4) { transition-delay: calc(var(--d-1) * 3); }
.kit-reveal--stagger-children[data-reveal-state="revealed"] > *:nth-child(5) { transition-delay: calc(var(--d-1) * 4); }
.kit-reveal--stagger-children[data-reveal-state="revealed"] > *:nth-child(6) { transition-delay: calc(var(--d-1) * 5); }
.kit-reveal--stagger-children[data-reveal-state="revealed"] > *:nth-child(7) { transition-delay: calc(var(--d-1) * 6); }
.kit-reveal--stagger-children[data-reveal-state="revealed"] > *:nth-child(n+8) { transition-delay: calc(var(--d-1) * 7); }

.kit-reveal--stagger-children[data-reveal-state="revealed"] > * {
  opacity: 1;
  transform: none;
}

/* ── Reveal · prefers-reduced-motion ──
   Hard rule #10 belt-and-braces. JS short-circuits first by marking every
   reveal as revealed on init, but if that somehow lands late the CSS
   cancels the entrance transition so the content is visually perceivable
   immediately. Also force the rest state so content never stays hidden
   even if JS does not run at all. */
@media (prefers-reduced-motion: reduce) {
  .kit-reveal,
  .kit-reveal--stagger-children > * {
    transition: none;
  }
  .kit-reveal[data-reveal-state="hidden"],
  .kit-reveal--stagger-children[data-reveal-state="hidden"] > * {
    opacity: 1;
    transform: none;
    filter: none;
  }
}

/* ============================================================
   BUY TICKETS DRAWER (M09 Phase 4 W7.v2.2)
   ------------------------------------------------------------
   Token-only styles for Views/Shared/_BuyTicketsDrawerBody.cshtml.
   Replaces the deleted _BuyTicketsModal.cshtml's 280-line inline
   <style> block. Hard rules #2 (no raw hex), #3 (no raw ms), #4
   (no inline style=), #8 (≥44×44 touch targets), #10 (reduced-
   motion fallback) are all satisfied here — see the matching
   @media (prefers-reduced-motion: reduce) block at the bottom.
   ============================================================ */

.buy-tickets-form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}

.buy-tickets-form__section {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.buy-tickets-form__section-title {
  font-family: var(--font-sans);
  font-size: var(--fs-2);
  font-weight: 600;
  color: var(--n-900);
  margin: 0 0 var(--sp-2);
}

.buy-tickets-form__empty {
  font-family: var(--font-sans);
  font-size: var(--fs-1);
  color: var(--n-600);
  margin: 0;
}

.buy-tickets-form__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.buy-tickets-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3);
  border: 1px solid var(--n-200);
  border-radius: var(--r-3);
  background: var(--n-0);
}

.buy-tickets-row--sold-out {
  opacity: 0.55;
  background: var(--n-50);
}

.buy-tickets-row__info {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}

.buy-tickets-row__name {
  font-family: var(--font-sans);
  font-size: var(--fs-1);
  font-weight: 600;
  color: var(--n-900);
}

.buy-tickets-row__price {
  font-family: var(--font-mono);
  font-size: var(--fs-1);
  font-weight: 600;
  color: var(--brand-ink);
}

.buy-tickets-row__sub {
  font-family: var(--font-sans);
  font-size: var(--fs-0);
  color: var(--n-600);
}

.buy-tickets-row__qty {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-shrink: 0;
}

/* Touch target floor: 44×44 (CLAUDE.md hard rule #8). */
.buy-tickets-qty-btn {
  min-width: 44px;
  min-height: 44px;
  width: 44px;
  height: 44px;
  border: 1px solid var(--n-300);
  border-radius: 9999px; /* F-1c-1: --r-pill undefined codebase-wide; literal replaces broken ref */
  background: var(--n-0);
  color: var(--n-700);
  font-size: var(--fs-2);
  font-weight: 600;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: border-color var(--d-1) var(--ease-out-swift),
              color var(--d-1) var(--ease-out-swift);
}

.buy-tickets-qty-btn:hover:not(:disabled) {
  border-color: var(--brand-ink);
  color: var(--brand-ink);
}

.buy-tickets-qty-btn:focus-visible {
  outline: 2px solid var(--brand-ink);
  outline-offset: 2px;
}

.buy-tickets-qty-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.buy-tickets-qty-value {
  min-width: var(--sp-5);
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--fs-2);
  font-weight: 600;
  color: var(--n-900);
}

.buy-tickets-form__subtotal-section {
  border-top: 1px solid var(--n-200);
  padding-top: var(--sp-3);
}

.buy-tickets-subtotal {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: var(--sp-2) var(--sp-3);
  background: var(--n-50);
  border-radius: var(--r-2);
}

.buy-tickets-subtotal__label {
  font-family: var(--font-sans);
  font-size: var(--fs-1);
  font-weight: 600;
  color: var(--n-700);
}

.buy-tickets-subtotal__amount {
  font-family: var(--font-mono);
  font-size: var(--fs-3);
  font-weight: 600;
  color: var(--brand-ink);
}

.buy-tickets-form__hold-note {
  margin: var(--sp-2) 0 0;
  font-family: var(--font-sans);
  font-size: var(--fs-0);
  color: var(--n-600);
}

.buy-tickets-form__error {
  margin: 0;
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-2);
  background: var(--n-50);
  color: var(--state-danger);
  font-family: var(--font-sans);
  font-size: var(--fs-1);
  border: 1px solid var(--state-danger);
}

.buy-tickets-form__error[hidden] {
  display: none;
}

/* M49 Phase 7 (F4) — dead-end load-failure render boundary. Replaces
   the entire ticket-selector + screen-2 fieldset + payment-method
   radios + Continue/Pay buttons + hold-note. Tokens-only (HR2/HR3);
   no animations introduced. */
.buy-tickets-form__error-state {
  display:        flex;
  flex-direction: column;
  gap:            var(--sp-3);
  padding:        var(--sp-5) var(--sp-3);
  text-align:     center;
}

.buy-tickets-form__error-headline {
  margin:      0;
  font-family: var(--font-sans);
  font-size:   var(--fs-3);
  font-weight: 600;
  color:       var(--n-900);
}

.buy-tickets-form__error-subtext {
  margin:      0;
  font-family: var(--font-sans);
  font-size:   var(--fs-1);
  color:       var(--n-600);
}

.buy-tickets-form__actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--sp-2);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--n-200);
}

/* ── M26 Phase 2: two-screen state machine ──────────────────────────
   Drawer screen 1 (form) and screen 2 (summary) live inside one form;
   buy-tickets.js toggles visibility via the [hidden] attribute on the
   [data-buy-tickets-state="form|summary"] containers. CSS selectors
   below mirror the vote-drawer__state[hidden] shape (HR6 single-overlay
   invariant: same drawer instance, two screens). */
.buy-tickets-form__state[hidden] {
  display: none;
}

.buy-tickets-form__state--form,
.buy-tickets-form__state--summary {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

.buy-tickets-form__guest {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

.buy-tickets-form__summary-title {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-2);
  font-weight: 700;
  color: var(--n-900);
}

.buy-tickets-form__summary-list {
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.buy-tickets-form__summary-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--n-100);
  font-family: var(--font-sans);
  font-size: var(--fs-1);
  color: var(--n-700);
}

.buy-tickets-form__summary-row:last-of-type {
  border-bottom: none;
}

.buy-tickets-form__summary-label {
  font-weight: 600;
  color: var(--n-500);
  margin: 0;
}

.buy-tickets-form__summary-value {
  font-weight: 700;
  color: var(--n-900);
  margin: 0;
  text-align: right;
}

.buy-tickets-form__summary-row--total .buy-tickets-form__summary-value {
  font-family: var(--font-mono);
  font-size: var(--fs-3);
  color: var(--brand-ink);
}

.buy-tickets-form__payment {
  border: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.buy-tickets-form__payment-legend {
  font-family: var(--font-sans);
  font-size: var(--fs-1);
  font-weight: 700;
  color: var(--n-900);
  padding: 0;
}

/* CLAUDE.md hard rule #10 — animations have a reduced-motion fallback. The
   only animation here is the qty-button hover transition; honour the
   user's reduce-motion preference by suppressing the transition. The
   screen-1↔screen-2 swap is JS-driven [hidden] (no CSS transition), so it
   honours reduced-motion by construction. */
@media (prefers-reduced-motion: reduce) {
  .buy-tickets-qty-btn {
    transition: none;
  }
}


/* ==========================================================================
   VOTE DRAWER  (M20 Phase 4 / F5 — slice 1)
   Drawer-body styles for Views/Shared/_VoteDrawerBody.cshtml. Replaces the
   256-line inline <style> block in the legacy Views/Shared/_VoteModal.cshtml
   so the view can comply with hard rule #5 (no <style> in views). All values
   resolve to design tokens (rule #2 + #3); no raw hex; no raw ms.
   Photo gradient is driven by --grad-start / --grad-end CSS custom
   properties set at JS time from data-grad-start / data-grad-end on the
   trigger button (see VotingContest.cshtml's openVoteDrawer).
   ========================================================================== */

.vote-drawer__form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}

.vote-drawer__state[hidden] {
  display: none;
}

/* ── Candidate row (top of form state) ──────────────────────────── */
/* M31 Phase 3 F-3-5 — extra padding-top adds breathing room between the
   contact-details __guest section above and this candidate row (the form
   uses gap: --sp-4 between siblings; padding-top: --sp-2 here lifts the
   total visual gap to ~24px without affecting other section pairings). */
.vote-drawer__candidate {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding-top: var(--sp-2);
  padding-bottom: var(--sp-3);
  border-bottom: 1px solid var(--n-100);
}

.vote-drawer__photo {
  width: 60px;
  height: 60px;
  flex-shrink: 0;
  border-radius: var(--r-3);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-3);
  background: linear-gradient(135deg, var(--grad-start, var(--brand-ink)), var(--grad-end, var(--brand-spark)));
  color: var(--n-0);
  position: relative;
  overflow: hidden;
}

/* M31 Phase 3 F-3-5 — when a real portrait image is injected by
   vote-drawer.js into the photo slot, the .vote-drawer__photo--has-image
   modifier hides the placeholder emoji (rendered server-side as the photo
   div's text content) so the <img> fills the slot cleanly. The gradient
   background continues to render behind the image while it loads — no
   visible flash on slow networks. */
.vote-drawer__photo--has-image {
  color: transparent;
}
.vote-drawer__photo-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--r-3);
  display: block;
}

.vote-drawer__candidate-meta {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  min-width: 0;
}

.vote-drawer__candidate-name {
  font-family: var(--font-sans);
  font-size: var(--fs-2);
  font-weight: 700;
  color: var(--n-900);
}

.vote-drawer__candidate-state {
  font-family: var(--font-sans);
  font-size: var(--fs-0);
  color: var(--n-500);
}

/* ── Quantity section ───────────────────────────────────────────── */
.vote-drawer__qty {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.vote-drawer__qty-label {
  font-family: var(--font-sans);
  font-size: var(--fs-1);
  font-weight: 700;
  color: var(--n-900);
}

.vote-drawer__qty-quick {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--sp-2);
}

/* Touch-target floor 44x44 — hard rule #8. */
.vote-drawer__qty-btn {
  min-height: 44px;
  padding: var(--sp-2) 0;
  border: 2px solid var(--n-200);
  border-radius: var(--r-2);
  background: var(--n-0);
  color: var(--n-700);
  font-family: var(--font-sans);
  font-size: var(--fs-1);
  font-weight: 700;
  text-align: center;
  cursor: pointer;
  transition: border-color var(--d-1) var(--ease-out-swift),
              color       var(--d-1) var(--ease-out-swift),
              background  var(--d-1) var(--ease-out-swift);
}

.vote-drawer__qty-btn[hidden] {
  display: none;
}

.vote-drawer__qty-btn:hover,
.vote-drawer__qty-btn.active {
  border-color: var(--brand-spark);
  color: var(--brand-spark);
  background: var(--n-50);
}

.vote-drawer__qty-btn:focus-visible {
  outline: 2px solid var(--brand-ink);
  outline-offset: 2px;
}

.vote-drawer__qty-custom {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

.vote-drawer__qty-custom-label {
  font-family: var(--font-sans);
  font-size: var(--fs-1);
  font-weight: 600;
  color: var(--n-600);
}

.vote-drawer__qty-input {
  flex: 1;
  min-height: 44px;
  padding: 0 var(--sp-3);
  border: 2px solid var(--n-200);
  border-radius: var(--r-2);
  font-family: var(--font-sans);
  font-size: var(--fs-2);
  font-weight: 700;
  text-align: center;
  outline: none;
  background: var(--n-0);
  color: var(--n-900);
}

.vote-drawer__qty-input:focus {
  border-color: var(--brand-spark);
}

.vote-drawer__qty-error {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-0);
  color: var(--state-danger);
}

.vote-drawer__qty-error[hidden] {
  display: none;
}

/* ── Price calc strip ───────────────────────────────────────────── */
/* M31 Phase 3 F-3-6 — vote-drawer custom-amount → totals-summary spacing.
   The form uses gap: --sp-4 between siblings; the price-calc that summarises
   votes × unit-price needs additional breathing room from the qty section
   that precedes it (custom-amount input lands at the bottom of the qty
   block). margin-top: --sp-2 lifts the visual gap to ~24px without
   affecting other section pairings. Safe under flex `gap`: margin-top on a
   flex item composes ON TOP OF the parent gap (per CSS spec; gap is not a
   replacement for margins but a minimum, additive on item margin). */
.vote-drawer__price-calc {
  margin-top: var(--sp-2);
  padding: var(--sp-3);
  background: var(--n-50);
  border: 1px solid var(--n-200);
  border-radius: var(--r-2);
  text-align: center;
}

.vote-drawer__price-calc-detail {
  font-family: var(--font-sans);
  font-size: var(--fs-1);
  color: var(--n-600);
}

.vote-drawer__price-calc-total {
  margin-top: var(--sp-1);
  font-family: var(--font-mono);
  font-size: var(--fs-3);
  font-weight: 800;
  color: var(--brand-ink);
}

/* ── Guest fields ───────────────────────────────────────────────── */
.vote-drawer__guest {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

.vote-drawer__guest-field {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}

.vote-drawer__guest-label {
  font-family: var(--font-sans);
  font-size: var(--fs-0);
  font-weight: 600;
  color: var(--n-700);
}

.vote-drawer__guest-required {
  color: var(--state-danger);
  margin-left: var(--sp-1);
}

.vote-drawer__guest-input {
  min-height: 44px;
  padding: 0 var(--sp-3);
  border: 2px solid var(--n-200);
  border-radius: var(--r-2);
  background: var(--n-0);
  color: var(--n-900);
  font-family: var(--font-sans);
  font-size: var(--fs-1);
  outline: none;
}

.vote-drawer__guest-input:focus {
  border-color: var(--brand-spark);
}

/* ── Payment section ────────────────────────────────────────────── */
.vote-drawer__payment {
  border: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.vote-drawer__payment-legend {
  font-family: var(--font-sans);
  font-size: var(--fs-1);
  font-weight: 700;
  color: var(--n-900);
  padding: 0;
}

/* ── Action row ─────────────────────────────────────────────────── */
.vote-drawer__actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--sp-2);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--n-200);
}

.vote-drawer__security {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-0);
  color: var(--n-500);
  text-align: center;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-1);
}

/* ── Summary state ─────────────────────────────────────────────── */
.vote-drawer__state--summary {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}

.vote-drawer__summary-title {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-2);
  font-weight: 700;
  color: var(--n-900);
}

.vote-drawer__summary-list {
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.vote-drawer__summary-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--n-100);
  font-family: var(--font-sans);
  font-size: var(--fs-1);
  color: var(--n-700);
}

.vote-drawer__summary-row:last-of-type {
  border-bottom: none;
}

.vote-drawer__summary-label {
  font-weight: 600;
  color: var(--n-500);
  margin: 0;
}

.vote-drawer__summary-value {
  font-weight: 700;
  color: var(--n-900);
  margin: 0;
  text-align: right;
}

.vote-drawer__summary-row--total .vote-drawer__summary-value {
  font-family: var(--font-mono);
  font-size: var(--fs-3);
  color: var(--brand-ink);
}

.vote-drawer__summary-error {
  margin: 0;
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-2);
  background: var(--n-50);
  color: var(--state-danger);
  font-family: var(--font-sans);
  font-size: var(--fs-1);
  border: 1px solid var(--state-danger);
}

.vote-drawer__summary-error[hidden] {
  display: none;
}

/* ── Pay (redirecting) state ────────────────────────────────────── */
.vote-drawer__state--pay {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: var(--sp-5) var(--sp-3);
  gap: var(--sp-2);
}

.vote-drawer__pay-spinner {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  border: 4px solid var(--n-200);
  border-top-color: var(--brand-spark);
  animation: vote-drawer-spin 1s linear infinite;
}

@keyframes vote-drawer-spin {
  to { transform: rotate(360deg); }
}

.vote-drawer__pay-title {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-2);
  font-weight: 800;
  color: var(--n-900);
}

.vote-drawer__pay-msg {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-1);
  color: var(--n-600);
  line-height: 1.5;
}

/* ── Confirmation state ─────────────────────────────────────────── */
.vote-drawer__state--confirmation {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: var(--sp-5) var(--sp-3);
  gap: var(--sp-3);
}

.vote-drawer__confirm-icon {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  background: var(--n-50);
  border: 2px solid var(--state-success);
  color: var(--state-success);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-3);
}

.vote-drawer__confirm-title {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-3);
  font-weight: 800;
  color: var(--n-900);
}

.vote-drawer__confirm-msg {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-1);
  color: var(--n-600);
  line-height: 1.5;
}

.vote-drawer__confirm-number {
  width: 100%;
  padding: var(--sp-3);
  background: var(--n-50);
  border: 2px solid var(--brand-ink);
  border-radius: var(--r-2);
}

.vote-drawer__confirm-number-label {
  font-family: var(--font-sans);
  font-size: var(--fs-0);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--n-500);
  margin-bottom: var(--sp-1);
}

.vote-drawer__confirm-number-value {
  font-family: var(--font-mono);
  font-size: var(--fs-4);
  font-weight: 800;
  color: var(--brand-ink);
  letter-spacing: 2px;
}

.vote-drawer__confirm-details {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-0);
  color: var(--n-600);
  line-height: 1.7;
}

.vote-drawer__confirm-details strong {
  color: var(--n-900);
}

/* ── Reduced-motion fallback (hard rule #10) ────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .vote-drawer__qty-btn {
    transition: none;
  }
  .vote-drawer__pay-spinner {
    animation: none;
  }
}

/* ── Responsive: collapse quick-vote grid on narrow widths ──────── */
@media (max-width: 480px) {
  .vote-drawer__qty-quick {
    grid-template-columns: repeat(2, 1fr);
  }
}


/* ==========================================================================
   UNDO BANNER  (M09 Phase 5 · W7.v2.4)
   Transient banner that pairs with kit/optimistic-style apply-then-rollback
   on low-risk destructive actions (notification mark-read, draft discard).
   Lives inside the existing toast stack so it inherits viewport positioning,
   reduced-motion, focus management, and stack-cap behaviour.
   Source: Views/Shared/Kit/_UndoBanner.cshtml (server template — JS clone).
   Script: wwwroot/js/kit/undo.js (solhigson.kit.undo).
   ========================================================================== */

/* The undo banner is a toast variant; the .toast--undo modifier overrides the
   accent rail to brand-ink (deliberate calm, not alarm) and slots in the
   countdown progress bar at the bottom edge. */
.toast--undo {
  border-left-color: var(--brand-ink);
  /* The progress rail anchors to the border-left as a vertical accent in the
     stacked layout; below we declare a horizontal progress bar at the
     baseline. The rail itself stays brand-ink. */
}

.toast--undo .toast__icon {
  background-color: var(--brand-ink);
  color: var(--n-0);
}

.toast--undo .toast__title {
  color: var(--brand-ink);
}

/* Undo button: kit-aligned shape, 44×44 minimum, brand-ink secondary. */
.toast__undo-btn {
  align-self: flex-start;
  margin-top: var(--sp-1);
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--sp-1);
  min-height: 44px; min-width: 44px;
  padding: 0 var(--sp-3);
  background-color: var(--n-0);
  color: var(--brand-ink);
  border: 1px solid var(--brand-ink);
  border-radius: var(--r-2);
  font-family: var(--font-sans);
  font-size: var(--fs-2);
  font-weight: 600;
  cursor: pointer;
  transition: background-color var(--d-1) var(--ease-out-swift),
              color var(--d-1) var(--ease-out-swift);
}

.toast__undo-btn:hover {
  background-color: var(--brand-ink);
  color: var(--n-0);
}

.toast__undo-btn:focus-visible {
  outline: 3px solid var(--state-info);
  outline-offset: 2px;
}

/* The countdown rail. A short horizontal bar across the bottom of the toast
   visually depleting toward the right edge as the window elapses. CSS
   transition controlled by the JS via inline transform — no per-tick
   transition, so the bar fills in one smooth gesture. Reduced-motion users
   see a static numeric countdown ("Undo (8s)") and the rail is hidden. */
.toast__undo-rail {
  grid-column: 1 / -1;
  height: 3px;
  margin-top: var(--sp-2);
  background-color: var(--n-200);
  border-radius: var(--r-1);
  overflow: hidden;
}

.toast__undo-rail-fill {
  display: block;
  height: 100%;
  width: 100%;
  background-color: var(--brand-ink);
  transform-origin: right center;
  transform: scaleX(1);
  transition: transform var(--d-3) var(--ease-out-swift);
}

/* Reduced-motion: hide the visual rail; the JS surfaces a "(Ns)" suffix on
   the message line so users still know the window length. */
@media (prefers-reduced-motion: reduce) {
  .toast__undo-rail {
    display: none;
  }
  .toast__undo-rail-fill {
    transition: none;
  }
}


/* ==========================================================================
   CHECKOUT · HOLD-EXTEND COUNTDOWN  (M09 Phase 5 · CF-09-4-01)
   The Pay page surfaces a small panel with mm:ss until the buyer's ticket
   reservation expires. Three colour stages: neutral → warn (T-60s) → danger
   (T-15s). At T=0 the page redirects to the originating event detail with a
   respectful expiry toast.
   Source: Views/Checkout/Pay.cshtml.
   Script: wwwroot/js/public/checkout-pay.js.
   ========================================================================== */

.checkout-hold-panel {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  margin-bottom: var(--sp-4);
  background-color: var(--n-0);
  border: 1px solid var(--n-200);
  border-radius: var(--r-3);
  box-shadow: var(--shadow-1);
}

.checkout-hold-panel__icon {
  display: inline-flex;
  align-items: center; justify-content: center;
  width: 40px; height: 40px;
  border-radius: 50%;
  background-color: color-mix(in srgb, var(--brand-ink) 8%, var(--n-0));
  color: var(--brand-ink);
  font-size: var(--fs-3);
}

.checkout-hold-panel__body {
  display: flex; flex-direction: column;
  gap: var(--sp-1);
  min-width: 0;
}

.checkout-hold-panel__label {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-1);
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--n-600);
}

.checkout-hold-panel__clock {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-4);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--brand-ink);
  transition: color var(--d-2) var(--ease-out-swift);
}

/* Stage modifiers — the script sets these as the deadline approaches. */
.checkout-hold-panel__clock--warn {
  color: var(--state-warn);
}

.checkout-hold-panel__clock--danger {
  color: var(--state-danger);
}

/* "Extend hold" CTA — secondary brand-ink outline so it doesn't compete with
   the primary Pay button below. */
.checkout-hold-panel__extend-btn {
  display: inline-flex;
  align-items: center; justify-content: center;
  gap: var(--sp-1);
  min-height: 44px; min-width: 44px;
  padding: 0 var(--sp-3);
  background-color: var(--n-0);
  color: var(--brand-ink);
  border: 1px solid var(--brand-ink);
  border-radius: var(--r-2);
  font-family: var(--font-sans);
  font-size: var(--fs-2);
  font-weight: 600;
  cursor: pointer;
  transition: background-color var(--d-1) var(--ease-out-swift),
              color var(--d-1) var(--ease-out-swift);
}

.checkout-hold-panel__extend-btn:hover {
  background-color: var(--brand-ink);
  color: var(--n-0);
}

.checkout-hold-panel__extend-btn:focus-visible {
  outline: 3px solid var(--state-info);
  outline-offset: 2px;
}

.checkout-hold-panel__extend-btn[disabled] {
  cursor: not-allowed;
  opacity: 0.5;
}

/* Replacement element shown when extend has already been used. Muted, calm,
   not apologetic. */
.checkout-hold-panel__extended-note {
  font-family: var(--font-sans);
  font-size: var(--fs-2);
  font-style: italic;
  color: var(--n-600);
  white-space: nowrap;
}

/* Reduced-motion: suppress colour transitions; stage flips become instant
   step-changes. Animation tokens already wrap the transition declarations
   above, but the explicit guard keeps intent clear and lint-greppable. */
@media (prefers-reduced-motion: reduce) {
  .checkout-hold-panel__clock,
  .checkout-hold-panel__extend-btn {
    transition: none;
  }
}


/* ==========================================================================
   CREATOR · DRAFTS BANNER · DISCARD AFFORDANCE  (M09 Phase 5 · W7.v2.3)
   Augments the existing .creator-event-drafts-banner block (defined in
   elfrique.components.layout.css) with a discard-button shape and relative-
   time meta line. The base list/grid layout stays in layout.css; this block
   only adds the action affordance + .discarding optimistic state.
   ========================================================================== */

.creator-event-drafts-banner__discard-btn {
  display: inline-flex;
  align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px;
  padding: 0 var(--sp-3);
  margin-left: var(--sp-2);
  background-color: var(--n-0);
  color: var(--n-700);
  border: 1px solid var(--n-300);
  border-radius: var(--r-2);
  font-family: var(--font-sans);
  font-size: var(--fs-1);
  font-weight: 500;
  cursor: pointer;
  transition: background-color var(--d-1) var(--ease-out-swift),
              border-color var(--d-1) var(--ease-out-swift),
              color var(--d-1) var(--ease-out-swift);
}

.creator-event-drafts-banner__discard-btn:hover {
  background-color: var(--n-100);
  color: var(--brand-ink);
  border-color: var(--n-400);
}

.creator-event-drafts-banner__discard-btn:focus-visible {
  outline: 3px solid var(--state-info);
  outline-offset: 2px;
}

/* Optimistic-applied state — the row fades to convey the discard intent
   while the 8-second undo window is open. Removed via .undo-rollback class
   if the user clicks Undo, restoring the row. */
.creator-event-drafts-banner__row.is-discarding {
  opacity: 0.45;
  transition: opacity var(--d-2) var(--ease-out-swift);
}

@media (prefers-reduced-motion: reduce) {
  .creator-event-drafts-banner__discard-btn,
  .creator-event-drafts-banner__row.is-discarding {
    transition: none;
  }
}


/* ==========================================================================
   AUTOSAVE STATUS · STATE MODIFIERS  (M09 Phase 5 · W7.v2.3)
   The [data-event-draft-status] element on the event-create form acquires
   semantic colour as the autosave round-trip transitions through Saving →
   Saved → (transient fade) → idle, or fails into Retrying / Failed.
   ========================================================================== */

[data-event-draft-status] {
  font-family: var(--font-sans);
  font-size: var(--fs-1);
  color: var(--n-600);
  transition: opacity var(--d-2) var(--ease-out-swift);
}

[data-event-draft-status][data-status-state="saving"]   { color: var(--n-600); }
[data-event-draft-status][data-status-state="saved"]    { color: var(--state-success); }
[data-event-draft-status][data-status-state="retrying"] { color: var(--state-warn); }
[data-event-draft-status][data-status-state="failed"]   { color: var(--state-danger); }

[data-event-draft-status][data-status-faded="true"] {
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  [data-event-draft-status] {
    transition: none;
  }
}

/* ==========================================================================
   SAFE-AREA DISCIPLINE (M10 Phase 5 · W6.v2.4)

   `viewport-fit=cover` is set in Views/Shared/HeadMeta.cshtml. With cover,
   `env(safe-area-inset-*)` resolves to non-zero on iOS devices with a
   notch / dynamic island / home indicator. This section applies the insets
   to overlay surfaces that anchor against viewport edges.

   Rules:
     - Drawer sheet-mode panel  → padding-bottom inset-bottom so swipe-down
                                  drag handles + bottom CTAs stay above the
                                  iOS home indicator.
     - Toast stack (mobile)     → both top and bottom insets so toasts never
                                  collide with the notch (top-anchored
                                  default) or, when the layout flips on
                                  small viewports, the home indicator.
     - Admin offcanvas (sheet)  → both inset-top + inset-bottom so the menu
                                  header clears the notch and the footer/CTA
                                  clears the home indicator.
     - .main-header public nav  → handled in elfrique.components.layout.css.

   Hard rules:
     #2  No raw hex — env(safe-area-inset-*) is a CSS function, not a hex.
     #3  No raw ms — no transitions added; this is pure layout padding.
   ========================================================================== */

/* Drawer sheet mode (<640px) — padding-bottom inside the panel so the
   bottom CTAs and the swipe handle area stay above the home indicator. */
@media (max-width: 640px) {
  .drawer__panel {
    padding-bottom:    env(safe-area-inset-bottom, 0px);
  }
}

/* Toast stack — top inset on the desktop position; bottom inset is also
   added so a future bottom-anchored toast variant inherits the discipline.
   The default top-anchored stack (top: var(--sp-4)) gets `padding-top` so
   the visible toasts shift below the notch on iOS. */
.toast-stack {
  padding-top:       env(safe-area-inset-top, 0px);
  padding-bottom:    env(safe-area-inset-bottom, 0px);
}

/* Admin offcanvas (Bootstrap .offcanvas-start consumed by _AdminSidebar).
   The whole panel is fixed and slides from the left — the top edge crosses
   under the iOS notch, and the bottom can be occluded by the home
   indicator. Apply both insets to the dash-sidebar--offcanvas container. */
.dash-sidebar--offcanvas {
  padding-top:       env(safe-area-inset-top, 0px);
  padding-bottom:    env(safe-area-inset-bottom, 0px);
}


/* ==========================================================================
   KIT · FAVORITE BUTTON  (M13 Phase 2 · W14.4 — heart promotion + motion)
   Promoted from Views/Shared/_FavoriteButton.cshtml (now Views/Shared/Kit/
   _FavoriteButton.cshtml). The legacy partial shipped a raw-hex inline
   `<style>` block (#ef4444 / #fff) and inline jQuery — both rule violations.
   The new partial is markup-only; this section owns every visual + motion
   concern. The companion script wwwroot/js/kit/favorite.js wires the toggle
   through solhigson.kit.optimistic.apply.

   Filled-state colour decision (Phase 2 adjudication):
     The heart is a state signal, not a brand accent — it tells the user
     "this is favourited / not favourited." `--state-danger` is the closest
     semantic in the token system (red-axis state) and matches user
     expectation for a heart-fill. Using `--brand-spark` would conflate the
     brand accent with a state, which the design discipline forbids
     (semantic-use-only rule). Decision: `--state-danger` for filled.

   Motion (M13 Phase 0 D1-corrected token values):
     - On data-just-favorited="true" — `kit-favorite-bounce` keyframe at
       `--d-3` + `--ease-spring`. 4px translate amplitude exposed as a
       custom property `--kit-favorite-bounce` so designers can tune at
       token level without touching the keyframe.
     - On data-rollback="true" — opacity slide back to empty at
       `--d-3` + `--ease-in-out-subtle` (position correction; not tactile).
     - prefers-reduced-motion: reduce — instant fill / instant rollback.

   Hard rules engaged:
     #2  no raw hex (filled state via --state-danger, surfaces via --n-* /
         --brand-ink)
     #3  no raw ms (--d-* / --ease-* tokens only)
     #8  ≥44×44 (min-width / min-height)
     #10 reduced-motion fallback in same patch
   ========================================================================== */

.kit-favorite {
  --kit-favorite-bounce: 4px;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  min-width: 44px;
  min-height: 44px;
  padding: var(--sp-1) var(--sp-3);
  background-color: color-mix(in srgb, var(--n-0) 15%, transparent);
  border: 1px solid color-mix(in srgb, var(--n-0) 30%, transparent);
  border-radius: 9999px; /* F-1c-1: --r-pill undefined codebase-wide; literal replaces broken ref */
  color: var(--n-0);
  font-family: var(--font-sans);
  font-size: var(--fs-2);
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
  transition: background-color var(--d-1) var(--ease-out-swift),
              border-color var(--d-1) var(--ease-out-swift),
              color var(--d-1) var(--ease-out-swift);
}

.kit-favorite:hover {
  background-color: color-mix(in srgb, var(--n-0) 25%, transparent);
}

.kit-favorite:focus-visible {
  outline: 3px solid var(--state-info);
  outline-offset: 2px;
}

.kit-favorite[disabled] {
  opacity: 0.65;
  cursor: progress;
}

.kit-favorite__icon {
  display: inline-block;
  width: 22px;
  height: 22px;
  /* The icon translates on bounce; opacity is the rollback channel. */
  transition: opacity var(--d-3) var(--ease-in-out-subtle);
}

/* Filled state — the heart fills with --state-danger and the surface tints
   to the same colour at low opacity. The icon's `fill` attribute is set by
   the markup (currentColor when filled, none when empty); CSS controls the
   colour channel via `color`. */
.kit-favorite[data-favorited="true"] {
  color: var(--state-danger);
  background-color: color-mix(in srgb, var(--state-danger) 10%, transparent);
  border-color: color-mix(in srgb, var(--state-danger) 30%, transparent);
}

/* Bounce on confirmation — the icon springs up `--kit-favorite-bounce` and
   settles. The keyframe peaks at 50% (4px lift) and returns to 0 — a single
   tactile pulse. Triggered by JS adding data-just-favorited="true" after
   the optimistic paint succeeds; the attribute self-clears after `--d-3`. */
.kit-favorite[data-just-favorited="true"] .kit-favorite__icon {
  animation: kit-favorite-bounce var(--d-3) var(--ease-spring);
}

@keyframes kit-favorite-bounce {
  0%   { transform: translateY(0); }
  50%  { transform: translateY(calc(var(--kit-favorite-bounce) * -1)); }
  100% { transform: translateY(0); }
}

/* Rollback — server rejected the optimistic toggle. The icon fades from its
   current state to the empty state via opacity (position correction, not
   tactile). JS adds data-rollback="true", which holds for `--d-3`, then
   clears alongside the data-favorited revert. */
.kit-favorite[data-rollback="true"] .kit-favorite__icon {
  opacity: 0.4;
}

/* Light-surface variant — rendered on non-hero backgrounds. Same motion
   contract; only the resting palette changes. The legacy `.favorite-light
   .btn-favorite` selector is replaced by `.kit-favorite--light`. */
.kit-favorite--light {
  background-color: var(--n-100);
  border-color: var(--n-200);
  color: var(--n-600);
}

.kit-favorite--light:hover {
  background-color: var(--n-200);
}

.kit-favorite--light[data-favorited="true"] {
  color: var(--state-danger);
  background-color: color-mix(in srgb, var(--state-danger) 8%, var(--n-0));
  border-color: color-mix(in srgb, var(--state-danger) 20%, var(--n-200));
}

@media (prefers-reduced-motion: reduce) {
  .kit-favorite,
  .kit-favorite__icon {
    transition: none;
  }
  .kit-favorite[data-just-favorited="true"] .kit-favorite__icon {
    animation: none;
  }
  .kit-favorite[data-rollback="true"] .kit-favorite__icon {
    opacity: 1;
  }
}


/* ==========================================================================
   KIT · SKELETON  (M13 Phase 2 · W14.5 — skeleton → content morph)
   New kit primitive. Authored here in overlay.css alongside other transient
   visual states; the legacy `.img-skeleton` rule in site.css L890-922 is a
   M18/C10 carry-forward and is NOT consolidated here in M13.

   Selector convention:
     .kit-skeleton                   — block element occupying the loading
                                       footprint. Sized by the consumer
                                       (width/height utilities or inline-
                                       free measurement on a parent).
     .kit-skeleton--text             — variant tuned for inline text rows
                                       (smaller height, multiple stacked).
     .kit-skeleton--circle           — variant tuned for avatars (square
                                       aspect with full border-radius).
     [data-skeleton-loaded="true"]
       .kit-skeleton                 — loaded state; the skeleton fades out.
     [data-skeleton-loaded="false"]
       .kit-skeleton__content        — pre-load state; content invisible.

   Crossfade contract:
     - opacity-only (CLS=0 — no width/height/top/left animations).
     - duration `--d-3`, easing `--ease-in-out-subtle` (position correction
       semantic, not tactile arrival).
     - reduced-motion: instant swap (no transition).

   The shimmer animation is a slow gradient sweep at `--d-5` infinite. It is
   the loading affordance, not an arrival cue — its visual job is to say
   "still working" while the content network round-trip resolves.
   ========================================================================== */

.kit-skeleton {
  position: relative;
  display: block;
  width: 100%;
  min-height: 1em;
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--n-100) 70%, transparent) 0%,
    color-mix(in srgb, var(--n-200) 85%, transparent) 50%,
    color-mix(in srgb, var(--n-100) 70%, transparent) 100%);
  background-size: 200% 100%;
  border-radius: var(--r-2);
  opacity: 1;
  animation: kit-skeleton-shimmer var(--d-5) var(--ease-in-out-subtle) infinite;
  transition: opacity var(--d-3) var(--ease-in-out-subtle);
}

.kit-skeleton--text {
  height: var(--sp-3);
  border-radius: var(--r-1);
}

.kit-skeleton--circle {
  width: 44px;
  height: 44px;
  border-radius: 50%;
}

@keyframes kit-skeleton-shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* Wrapper utility — opt-in. Any element bearing `[data-skeleton-loaded]`
   becomes a CSS-grid stacking container so the skeleton and the content
   occupy the same grid cell (and therefore the same footprint). This
   guarantees CLS=0 across the swap: neither element pushes the other
   around when its opacity changes.

   The grid is a single cell; both children get `grid-area: 1 / 1`. The
   container's intrinsic size is taken from the larger child (typically the
   real content once loaded; while loading, the skeleton's min-height
   establishes the floor). */
[data-skeleton-loaded] {
  display: grid;
  grid-template-columns: 1fr;
}

[data-skeleton-loaded] > .kit-skeleton,
[data-skeleton-loaded] > .kit-skeleton__content {
  grid-area: 1 / 1;
}

/* The wrapping container toggles data-skeleton-loaded between "false"
   (initial / loading) and "true" (content arrived). Both the skeleton and
   the content transition opacity at the same band so they crossfade rather
   than swap. The container reserves the same footprint in both states —
   layout shift is zero. */
.kit-skeleton__content {
  opacity: 1;
  transition: opacity var(--d-3) var(--ease-in-out-subtle);
}

[data-skeleton-loaded="false"] .kit-skeleton__content {
  opacity: 0;
}

[data-skeleton-loaded="true"] .kit-skeleton {
  opacity: 0;
  /* Stop the shimmer once loaded — the animation no longer signals work in
     progress, and continuing to paint behind the visible content is wasteful. */
  animation: none;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .kit-skeleton,
  .kit-skeleton__content {
    transition: none;
    animation: none;
  }
}

/* ── View Transitions (M13 Phase 3 · W14.3) ─────────────────────────────────
   Driver: wwwroot/js/kit/view-transitions.js (solhigson.kit.viewTransitions).
   Opt-in via <body data-shell="admin"> on the admin shell only — public stays
   on full-reload navigation per the W14.3 anti-instruction.

   Architecture (two paths):
     Supported browsers (Chromium ≥ 111, Safari 18+):
       The driver wraps `window.location.assign(href)` in
       `document.startViewTransition(...)`. The browser snapshots the outgoing
       document, performs the navigation, then composites the
       `::view-transition-old(root)` and `::view-transition-new(root)`
       pseudo-elements over the new render. The rules below define the
       cross-fade timing — opacity-only, no positional motion (M13 Phase 1
       reduced-motion parity contract: position shifts use `--ease-in-out-subtle`,
       fades use `--ease-out-swift`; we deliberately use `--ease-in-out-subtle`
       here because the visual is two co-located documents trading places, not
       an enter or an exit).

     Non-supporting browsers (Firefox, Safari < 18, older Chromium):
       The driver toggles `.view-transition-fallback-active` on `<body>` before
       calling `window.location.assign(href)`. The fallback rule below fades the
       outgoing document over `--d-4`. The new document loads without the class
       attached, so the fade resets to opacity:1 on the new render.

   Hard rules:
     #3  No raw ms — `--d-4` + `--ease-in-out-subtle` only.
     #10 prefers-reduced-motion: reduce — both branches disabled in the
         media query at the foot of this block. Defence-in-depth alongside
         the JS-level shouldEngage() short-circuit.
   ========================================================================== */

/* Supported-browser path. The keyframes consume the same easing on both sides
   so the cross-fade is symmetric — neither outgoing nor incoming reads as
   leading the transition. */
@keyframes view-transition-fade-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}

@keyframes view-transition-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

::view-transition-old(root) {
  animation: view-transition-fade-out var(--d-4) var(--ease-in-out-subtle) both;
}

::view-transition-new(root) {
  animation: view-transition-fade-in var(--d-4) var(--ease-in-out-subtle) both;
}

/* Fallback path. The body fades to opacity 0 over --d-4 the moment the JS
   driver attaches `.view-transition-fallback-active`. The driver delays the
   actual `location.assign(...)` by two requestAnimationFrame ticks so the
   browser commits a paint of the faded state before the navigation request
   tears down the document. */
body.view-transition-fallback-active {
  opacity: 0;
  transition: opacity var(--d-4) var(--ease-in-out-subtle);
}

@media (prefers-reduced-motion: reduce) {
  /* Disable both branches under reduced motion. The JS driver also bypasses
     both branches at the call-site — this rule is defence-in-depth, covering
     the (rare) case where the driver did not run (e.g., script error, blocked
     by CSP) but the body class is somehow attached. */
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation: none;
  }

  body.view-transition-fallback-active {
    opacity: 1;
    transition: none;
  }
}

/* ============================================================
   HOME · HERO PARALLAX (M13 Phase 4 · W14.6)
   ------------------------------------------------------------
   Scroll-driven choreography host for Views/Public/Home.cshtml.

   The Razor wrapper [data-hero-parallax] is a plain block-level
   element with no margin / padding / border / outline / size
   constraints — it is layout-equivalent to the kit Hero <section>
   it contains, and its sole purpose is to host the parallax
   transform without modifying _Hero.cshtml (scope-lock honoured).

   Companion JS (wwwroot/js/home-scroll.js) reads the parallax
   factor from --hero-parallax-factor and applies translate3d on
   scroll, batched through requestAnimationFrame. translate3d is
   GPU-composited; no layout-triggering properties are animated.

   CLS contract: the host's footprint is fixed (the kit Hero
   inside renders at its natural size); only transform mutates,
   so CLS = 0. Lighthouse trace verification in MR phase.

   Per Phase 0 F3 adjudication, parallax CSS lives here in
   elfrique.components.overlay.css; migration to a not-yet-
   authored elfrique.signature.css is a M14/M15 carry-forward.

   Hard rules:
     #2  No raw hex.
     #3  No raw ms — the JS owns the per-frame writes; CSS owns
         only the will-change hint and the reduced-motion bypass,
         neither of which carry timing.
    #10  prefers-reduced-motion: reduce — transform: none +
         will-change: auto. JS bypasses entirely; this rule is
         defence-in-depth.

   Companion JS:  wwwroot/js/home-scroll.js (solhigson.kit.homeScroll).
   Companion view: src/Elfrique.Web.Ui/Views/Public/Home.cshtml.
   ============================================================ */
.home-hero-parallax {
  /* Token-driven parallax factor. 0.4 = hero translates at 40%
     of scroll velocity, classic editorial parallax depth cue.
     The JS reads this via getComputedStyle so the single source
     of truth lives in CSS. Override per-instance by re-declaring
     on the host element if a future page wants a different feel
     (none currently in scope per the Phase 4 anti-instruction
     "HOME ONLY"). */
  --hero-parallax-factor: 0.4;

  /* Layout-equivalent block: no margin / padding. The kit Hero
     section inside takes 100% of this wrapper, which itself takes
     100% of the page body — visually identical to placing _Hero
     directly under <body>. Hint the compositor (will-change)
     because this element transforms every scroll frame. */
  display: block;
  margin: 0;
  padding: 0;
  will-change: transform;
}

/* ── Hero parallax · prefers-reduced-motion ──
   Hard rule #10. The JS module skips the scroll listener entirely
   under reduced motion; this CSS is defence-in-depth so any stale
   inline transform left on the host never produces motion.
   transform: none overrides any JS write that landed before the
   matchMedia check; will-change: auto releases the compositor hint. */
@media (prefers-reduced-motion: reduce) {
  .home-hero-parallax {
    transform: none !important;
    will-change: auto !important;
  }
}


/* ==========================================================================
   DUOTONE  (M14 Phase 2 · W16.1)
   Two-color image treatment: shadows → --brand-ink, highlights → --brand-spark.
   Applied via CSS filter referencing the inline SVG filter rendered by
   _DuotoneFilter.cshtml (included once per page in the three main layouts).

   Usage:   <img class="u-duotone" src="..." alt="...">
            <picture class="u-duotone">...</picture>

   How it works
   ─────────────
   .u-duotone applies filter: url(#duotone-filter). The #duotone-filter element
   is an inline SVG <filter> with <feColorMatrix type="matrix"> whose values
   are read from data/duotone-matrix.json (written by tools/imagepipeline, M14
   Phase 1). The matrix derives from --brand-ink (elfrique.css:35) and
   --brand-spark (elfrique.css:38); no raw hex appears in this file.

   Color-interpolation: sRGB (color-interpolation-filters="sRGB" on the SVG
   <filter>). This matches the browser default for <feColorMatrix> and the
   sharp pipeline's sRGB-encoded output, so both paths produce identical results
   (CF-1-A, M14 Phase 0 adjudication; no linear-light switch).

   Progressive enhancement
   ────────────────────────
   filter: url() referencing a same-document inline SVG filter is Baseline 2019.
   All Elfrique target browsers (Baseline 2024 matrix) support it. No @supports
   gate needed. No CSS-only grayscale fallback — a grey fallback is worse than
   no duotone (plan §113 anti-instruction).

   Contrast contract
   ──────────────────
   Text overlaid on a duotone image MUST meet WCAG AA. Ink-shadow areas are
   dark (--brand-ink channel); spark-highlight areas are warm-mid. Strategy:
   white text in shadow regions; dark text only over a scrim. Full contract in
   docs/imagery.md § "Color-contrast contract".

   PRM (prefers-reduced-motion)
   ─────────────────────────────
   N/A — duotone is a static color transformation, not motion. No
   @media (prefers-reduced-motion: reduce) block is needed or appropriate here.

   .duotone-filter-host
   ─────────────────────
   Host for the _DuotoneFilter.cshtml inline SVG. Zero dimension so it does
   not affect page flow; position: absolute so it is removed from normal flow
   entirely; pointer-events: none so it cannot intercept clicks.
   ========================================================================== */

.duotone-filter-host {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
  pointer-events: none;
}

.u-duotone {
  filter: url(#duotone-filter);
}

/* Kit demo — responsive image block sizing for the Duotone demo row.
   Not a utility; scoped to the /design/kit dev route only.
   Avoids inline style= on the demo <img> elements (hard rule #5 analog
   for the kit demo view). */
.kit-duotone-demo-img {
  display: block;
  width: 100%;
  height: auto;
}

/* ==========================================================================
   M19 PHASE 2 — CUSTOM.CSS RULE MIGRATIONS (overlay target)
   Source: wwwroot/custom/custom.css (deleted at Phase 2 close)
   All legacy token refs replaced with canonical equivalents per M19 mapping.
   No new tokens introduced. Token map:
     --primary-color → --brand-ink
     --white         → --n-0
     --neutral-*     → --n-*
     #e5e5e5         → --n-200
     #111827         → --n-900
     #2e7d32 (non-success border/icon) → --brand-ink (same hex family)
   HC-mode note: --brand-ink not in state palette; no HC override needed.
   ========================================================================== */

/* ── .modal-header (M19 P2 migration)
   Live consumers: Admin modals (AdminContests, ApplicationLog, AppSettings,
   AuditLog, UserDetail, etc.).
   Classification (III) — no canonical equivalent.
   #2e7d32 border/icon → --brand-ink (accepted: canonical brand replaces legacy green).
   #111827 color → --n-900 (identical hex).
   raw 20px → kept as-is (font-size shorthand without em/rem is pre-existing admin
   pattern; extracting to token would be scope-widening per anti-instruction §286).
   ──────────────────────────────────────────────────────────────────────────────── */
.modal-header {
  font-weight: 600;
  font-size: 20px;
  color: var(--n-900);
  border-bottom: 2px solid var(--brand-ink);
  padding-bottom: 10px !important;
}

.modal-header .modal-title {
  font-weight: 600;
  font-size: 18px;
  display: flex;
  align-items: center;
  gap: 6px;
}

.modal-header .modal-title i {
  color: var(--brand-ink);
}

/* ── .modal-footer (M19 P2 migration)
   Live consumers: Admin modals (same set as .modal-header).
   Classification (III) — no canonical equivalent.
   #e5e5e5 → --n-200 (close match; accepted visual delta).
   ──────────────────────────────────────────────────────────────────────────────── */
.modal-footer {
  border-top: 1px solid var(--n-200);
  padding-top: 10px !important;
}

/* ── #messageNotification .alert (M19 P2 migration)
   Live consumers: _Layout.cshtml:173, _LayoutAuth.cshtml:69.
   Classification (III) — no canonical equivalent.
   #e5e5e5 border → --n-200.
   ──────────────────────────────────────────────────────────────────────────────── */
#messageNotification .alert {
  margin-bottom: 5px;
  margin-right: 5px;
  border: 1px solid var(--n-200);
}

/* ── M19 fix-cycle 1.3 — easy-autocomplete container z-index migration
   F-P2-5: pre-Phase-2 custom.css contained .easy-autocomplete-container
   z-index override; Phase 2 deletion missed it. Live consumers:
     Views/Public/Home.cshtml:200
     Views/Public/FlightSearch.cshtml:24
   Migrated verbatim to overlay.css (overlay primitive, semantic fit).
   ──────────────────────────────────────────────────────────────────────────────── */
.easy-autocomplete-container { z-index: 9999 !important; }

/* ==========================================================================
   PAY-NOW BUTTON SPINNER  (M30 Phase 3 · F-3-3)
   Covers data-buy-tickets-confirm-btn (buy-tickets drawer) and
   data-vote-confirm-btn (vote drawer). Applied via JS when a POST is in
   flight; cleared on error (JS re-enables the button) or on success (page
   navigates away).

   HR1: spinner color is --brand-spark (warm accent), not green.
   HR2: no raw hex — tokens only.
   HR3: no raw ms — motion tokens only (--d-2 = 200ms, --d-3 = 300ms).
   HR10: prefers-reduced-motion: reduce — animation replaced with opacity
         pulse at 0 duration so the indicator is still visible as a static
         state change but no movement occurs.
   ========================================================================== */

.btn--pay-in-flight {
  position: relative;
  pointer-events: none;          /* block re-clicks during POST */
  opacity: 0.72;
}

.btn--pay-in-flight .btn-pay-spinner {
  display: inline-block;
  width: 1em;
  height: 1em;
  margin-inline-end: var(--sp-1);
  border: 2px solid color-mix(in srgb, var(--brand-spark) 30%, transparent);
  border-top-color: var(--brand-spark);
  border-radius: 50%;
  animation: pay-spinner-rotate var(--d-3) linear infinite;
  vertical-align: -0.125em;     /* optical alignment with button text */
  flex-shrink: 0;
}

@keyframes pay-spinner-rotate {
  to { transform: rotate(360deg); }
}

/* HR10 — reduced-motion: replace rotation with opacity pulse so the button
   still signals "in-flight" state without any movement. */
@media (prefers-reduced-motion: reduce) {
  .btn--pay-in-flight .btn-pay-spinner {
    animation: pay-spinner-pulse var(--d-4) var(--ease-in-out-subtle) infinite;
    border-top-color: var(--brand-spark);
    border-color: var(--brand-spark);
  }

  @keyframes pay-spinner-pulse {
    0%, 100% { opacity: 1; }
    50%       { opacity: 0.3; }
  }
}


/* ==========================================================================
   SEARCH RESULTS  (M30 Phase 7 · Cluster G — search restoration)
   AJAX typeahead dropdown shared by Home, /events, /voting hero inputs.
   Partial: Views/Shared/Kit/_SearchResults.cshtml · Model: SearchResultsModel
   Script:  wwwroot/js/kit/search-results.js
   ========================================================================== */

.search-results {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  margin-top: var(--sp-2);
  background-color: var(--n-0);
  border: 1px solid var(--n-200);
  border-radius: var(--r-4);
  box-shadow: var(--shadow-3);
  z-index: var(--z-popover);
  max-height: 26rem;
  overflow-y: auto;
  font-family: var(--font-sans);
  color: var(--brand-ink);
  /* Reveal motion — token-driven; reduced-motion fallback below. */
  transition: opacity var(--d-2) var(--ease-out-swift);
}

.search-results[hidden] {
  display: none;
}

.search-results__list {
  display: flex;
  flex-direction: column;
  padding: var(--sp-2) 0;
}

.search-results__group {
  display: flex;
  flex-direction: column;
}

.search-results__group + .search-results__group {
  border-top: 1px solid var(--n-100);
  margin-top: var(--sp-2);
  padding-top: var(--sp-2);
}

.search-results__group-title {
  font-family: var(--font-mono);
  font-size: var(--fs-1);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--n-500);
  padding: var(--sp-1) var(--sp-4);
  margin: 0;
}

.search-results__row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  /* HR8 — touch target ≥ 44×44. 2.75rem = 44px at the default 16px root. */
  min-height: 2.75rem;
  padding: var(--sp-2) var(--sp-4);
  text-decoration: none;
  color: var(--brand-ink);
  background-color: transparent;
  border: 0;
  cursor: pointer;
  transition: background-color var(--d-1) var(--ease-out-swift);
}

.search-results__row:hover,
.search-results__row:focus-visible {
  background-color: var(--n-50);
  outline: none;
}

.search-results__row:focus-visible {
  /* Accessible focus ring without breaking the row's native flow. */
  box-shadow: inset 0 0 0 2px var(--brand-spark);
}

.search-results__icon {
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--r-3);
  background-color: var(--n-100);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  overflow: hidden;
}

.search-results__icon img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.search-results__icon-glyph {
  color: var(--n-400);
  font-size: var(--fs-3);
}

.search-results__content {
  display: flex;
  flex-direction: column;
  gap: var(--sp-0);
  min-width: 0;
  flex: 1;
}

.search-results__name {
  font-size: var(--fs-2);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.search-results__subtitle {
  font-size: var(--fs-1);
  color: var(--n-500);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.search-results__empty,
.search-results__error,
.search-results__loading {
  padding: var(--sp-5) var(--sp-4);
  text-align: center;
  font-size: var(--fs-2);
  color: var(--n-500);
}

.search-results__error {
  /* Use info token (not danger — search failure is recoverable, not destructive). */
  color: var(--state-info);
}

/* HR10 — reduced-motion: collapse the reveal transition. The panel still
   shows/hides via the [hidden] toggle; only the cross-fade is suppressed. */
@media (prefers-reduced-motion: reduce) {
  .search-results,
  .search-results__row {
    transition: none;
  }
}

/* ============================================================
   flatpickr library popup (.flatpickr-calendar)
   Project-side overrides aligning library defaults to design-refresh tokens.
   ============================================================ */

/* Calendar surface — solid card with neutral border + elevation.
   Base flatpickr.css ships background:transparent / border:0; the override
   gives the popup the design-refresh card look.
   z-index: library only sets z-index on .static.open (999); the default
   floating calendar inherits z-index: auto, which lands behind any
   stacking-context overlay at z>0 — including the admin date-range
   .filter-bar__disclosure-panel at --z-popover (60). Pin the calendar to
   --z-modal so it clears the panel and any sticky admin chrome. */
.flatpickr-calendar {
  background-color: var(--n-0);
  border: 1px solid var(--n-200);
  box-shadow: var(--shadow-3);
  border-radius: var(--r-3);
  color: var(--n-900);
  z-index: var(--z-modal);
}

/* Day cell — neutral text + small radius (overrides library 50rem pill). */
.flatpickr-day {
  color: var(--n-900);
  border-radius: var(--r-1);
}

/* Day cell hover/focus — subtle neutral surface, no colored border.
   flatpickr's default hover paints a colored border; clear it. */
.flatpickr-day:hover,
.flatpickr-day:focus {
  background-color: var(--n-100);
  color: var(--n-900);
  border-color: transparent;
}

/* Selected / range endpoints — brand ink (NOT green; HR1).
   Includes :hover and :focus variants so the active state survives
   pointer/keyboard interaction. */
.flatpickr-day.selected,
.flatpickr-day.selected:hover,
.flatpickr-day.selected:focus,
.flatpickr-day.startRange,
.flatpickr-day.startRange:hover,
.flatpickr-day.startRange:focus,
.flatpickr-day.endRange,
.flatpickr-day.endRange:hover,
.flatpickr-day.endRange:focus {
  background-color: var(--brand-ink);
  border-color: var(--brand-ink);
  color: var(--n-0);
}

/* In-range cell (between startRange and endRange) — soft fill, ink text.
   flatpickr emits an inset box-shadow for in-range cells; cancel for cleanliness. */
.flatpickr-day.inRange {
  background-color: var(--n-100);
  color: var(--brand-ink);
  border-color: transparent;
  box-shadow: none;
}

/* Today indicator — transparent fill, ink text, neutral hairline border. */
.flatpickr-day.today {
  background-color: transparent;
  color: var(--brand-ink);
  font-weight: 600;
  border-color: var(--n-300);
}

/* Disabled days — muted text, no surface.
   flatpickr base CSS uses .disabled; theme styles use .flatpickr-disabled.
   Cover both to survive either emission path. */
.flatpickr-day.disabled,
.flatpickr-day.disabled:hover,
.flatpickr-day.flatpickr-disabled,
.flatpickr-day.flatpickr-disabled:hover {
  color: var(--n-400);
  background-color: transparent;
  border-color: transparent;
}

/* Out-of-month days — muted to recede behind the current month. */
.flatpickr-day.prevMonthDay,
.flatpickr-day.nextMonthDay {
  color: var(--n-400);
}

/* Nav arrows — neutral default, ink on hover. fill:currentColor lets the
   library's inline SVG paths inherit the chosen color. */
.flatpickr-months .flatpickr-prev-month,
.flatpickr-months .flatpickr-next-month {
  color: var(--n-700);
  fill: currentColor;
}

.flatpickr-months .flatpickr-prev-month:hover,
.flatpickr-months .flatpickr-next-month:hover {
  color: var(--brand-ink);
}

/* Month/year title in the header. */
.flatpickr-current-month {
  color: var(--n-900);
  font-weight: 600;
}

/* Day-of-week labels (Mon Tue Wed …). */
.flatpickr-weekday {
  color: var(--n-600);
  font-weight: 500;
}

/* ============================================================
   Bootstrap dropdown-menu — design-refresh override
   ============================================================
   The action-menu trigger emitted by DropDownMenuHelper opens a
   Bootstrap-managed `.dropdown-menu`. Bootstrap ships its own
   surface treatment that doesn't read with the design-refresh
   tokens; align the panel + items + divider with the visual
   language already established by `.filter-bar__disclosure-panel`
   and `.filter-bar__disclosure-option` in elfrique.components.data.css
   so all kit menus surface identical chrome.

   Bootstrap JS positioning (Popper) and a11y attributes are
   untouched — this is purely a paint/typography override. */

.dropdown-menu {
  min-width: 12rem;
  padding: var(--sp-2);
  background-color: var(--n-0);
  border: 1px solid var(--n-200);
  border-radius: var(--r-3);
  box-shadow: var(--shadow-3);
  color: var(--brand-ink);
  font-family: var(--font-sans);
  font-size: var(--fs-2);
}

.dropdown-menu .dropdown-item {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  width: 100%;
  min-height: 44px;
  padding: var(--sp-2) var(--sp-3);
  background-color: transparent;
  border: 0;
  border-radius: var(--r-2);
  color: var(--brand-ink);
  font-family: var(--font-sans);
  font-size: var(--fs-2);
  font-weight: 500;
  text-align: start;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--d-1) var(--ease-out-swift),
              color var(--d-1) var(--ease-out-swift);
}

.dropdown-menu .dropdown-item:hover,
.dropdown-menu .dropdown-item:focus,
.dropdown-menu .dropdown-item:focus-visible {
  background-color: var(--n-50);
  color: var(--brand-ink);
  outline: none;
}

.dropdown-menu .dropdown-item:focus-visible {
  outline: 2px solid var(--brand-spark);
  outline-offset: -2px;
}

.dropdown-menu .dropdown-item.active,
.dropdown-menu .dropdown-item:active {
  background-color: var(--brand-ink);
  color: var(--n-0);
}

.dropdown-menu .dropdown-item.disabled,
.dropdown-menu .dropdown-item[aria-disabled="true"] {
  color: var(--n-400);
  cursor: not-allowed;
}

.dropdown-menu .dropdown-divider {
  margin: var(--sp-1) 0;
  border: 0;
  border-top: 1px solid var(--n-200);
}
