/*! @laddtnov/cyberpunk-ui | MIT | github.com/laddtnov/cyberpunk-ui */
/* Generated by scripts/build-bundle.js — edit the parts, not this file. */

/* ── tokens.css ────────────────────────────────────────────── */
/* Everything below is in the `cyberpunk-ui` cascade layer. Any rule you write
   outside a layer beats it regardless of specificity, so overriding the kit
   never needs !important and never needs a longer selector than the one you
   would write anyway. The body is not re-indented: a layer wrapper is not a
   nesting level worth 1,600 lines of churn. */
@layer cyberpunk-ui {

:root {
  /* ── Neon accents ─────────────────────────────────────────── */
  --cy-neon-cyan:   #00f2ff;
  --cy-neon-pink:   #ff00ff;
  --cy-neon-purple: #9d00ff;

  /* Brass gold, the Deus Ex register rather than the neon one: a warmer,
     lower-saturation accent for text that should read as machined rather
     than lit. Deliberately not --cy-warning, which is a pale yellow with a
     meaning attached — this one carries none, and mixing them would make a
     decorative heading look like a caution. */
  --cy-neon-gold:   #d4af37;

  /* ── Surfaces ─────────────────────────────────────────────── */
  --cy-bg:      #0a0a0c;
  --cy-surface: #151518;

  /* ── Text ─────────────────────────────────────────────────── */
  --cy-text:    #d1d1d1;
  --cy-heading: #ffffff;

  /* ── Scrim ───────────────────────────────────────────────────
     The modal backdrop, and deliberately NOT derived from --cy-bg: a
     scrim's job is to push the page behind it away, so it stays dark in
     both themes. Tying it to --cy-bg made the light theme paint a
     near-white wash over a near-white page, which separated nothing.

     It is a finished colour rather than a hue plus an -rgb twin because
     nothing fades it further — the alpha is the point. */
  --cy-backdrop: rgba(10, 10, 12, 0.72);

  /* ── Type ─────────────────────────────────────────────────────
     Load these fonts yourself (Google Fonts / self-hosted); the kit
     degrades to system fonts if they're absent. */
  --cy-font-display: 'Orbitron', system-ui, sans-serif;
  --cy-font-body:    'Rajdhani', system-ui, sans-serif;
  --cy-font-mono:    'Share Tech Mono', ui-monospace, monospace;

  /* The terminal's own stack, and the only place the kit asks for a Nerd
     Font. Those are patched builds of ordinary monospaces carrying an extra
     few thousand glyphs — powerline separators, file-type and branch icons —
     which is exactly what makes a fake terminal look like a real one.

     They are named first and never shipped. The patched TTFs run to tens of
     megabytes against a kit measured in kilobytes, so this token asks for what
     a developer machine very often already has and falls through to
     --cy-font-mono when it does not. Nothing breaks without them; the icons
     simply are not there.

     Kept separate from --cy-font-mono on purpose: Hack is widely installed,
     and putting it first in the shared stack would quietly repaint every mono
     surface in the kit — nav, badges, inputs, breadcrumb — on most developer
     machines, which is not a font choice this kit should make for you. */
  --cy-font-terminal: 'Hack Nerd Font Mono', 'JetBrainsMono Nerd Font',
                      'FiraCode Nerd Font', var(--cy-font-mono);

  /* ── Motion ───────────────────────────────────────────────── */
  --cy-ease: cubic-bezier(0.22, 1, 0.36, 1);

  /* ── Geometry ─────────────────────────────────────────────── */
  --cy-radius-sm: 2px;
  --cy-radius:    4px;
  --cy-radius-lg: 8px;
  --cy-border-width: 1px;

  /* ── Spacing (4px-based scale) ────────────────────────────── */
  --cy-space-xs: 0.25rem;
  --cy-space-sm: 0.5rem;
  --cy-space-md: 0.75rem;
  --cy-space-lg: 1rem;
  --cy-space-xl: 1.5rem;

  /* ── Focus (shared by every interactive element) ──────────── */
  --cy-focus-width:  2px;
  --cy-focus-offset: 2px;
  --cy-focus-color:  var(--cy-neon-cyan);

  /* ── Status ────────────────────────────────────────────────
     --cy-info aliases the cyan on purpose: its channels are already
     published as --cy-cyan-rgb, so there is one source of truth. */
  --cy-success: #52ff9a;
  --cy-warning: #ffc857;
  --cy-danger:  #ff4d4d;
  --cy-info:    var(--cy-neon-cyan);

  --cy-disabled-opacity: 0.45;
}

/* ── Light theme ──────────────────────────────────────────────
   The bright neon hues fail WCAG contrast on light backgrounds
   (cyan is ~1.2:1 on white), so cyan/pink are dimmed here. Purple
   already passes (~5.5:1) and is left untouched. Toggle by setting
   data-theme="light" on the <html> element. */
:root[data-theme="light"] {
  /* #008099 measured 4.07 against --cy-bg — below the 4.5 AA floor for
     normal text. #00707f measures 5.10. */
  --cy-neon-cyan: #00707f;
  --cy-neon-pink: #b3009e;
  /* #d4af37 measures 1.85 on the light background — invisible. This is the
     same hue taken down until it clears the text floor: 5.66. */
  --cy-neon-gold: #7d5800;

  --cy-bg:      #f0f0f5;
  --cy-surface: #ffffff;
  --cy-text:    #3a3a45;
  --cy-heading: #16161d;

  /* Neon status hues fail contrast on a light background. */
  --cy-success: #0a7d43;
  --cy-warning: #8a5a00;
  --cy-danger:  #c2110f;

}

/* ── Light theme, chosen by the operating system ──────────────────
   The block above only fires when something sets data-theme="light" — a
   script, or server-rendered markup. That leaves a visitor whose OS is in
   light mode looking at neon-on-black, which is the one audience the light
   palette exists for.

   Opt in with data-theme="auto" on <html>, which is static markup, not a
   script — the thing this block exists to avoid needing.

   It is opt-in rather than automatic on purpose. `prefers-color-scheme: light`
   matches when a user has expressed no preference at all, not only when they
   have chosen light, so keying this off the absence of data-theme would have
   flipped the kit to light for most visitors and silently restyled every site
   already using it. A neon kit defaults to neon; the light palette is there
   for people who ask, by attribute or by operating system.

   The values are duplicated rather than shared, because a media query cannot
   join a selector list — there is no way to say "this block, and also under
   that condition". check-conventions.js compares the two declaration by
   declaration and fails when they drift, so the cost of duplicating is paid
   once in a checker rather than every time someone edits a colour. */
@media (prefers-color-scheme: light) {
  :root[data-theme="auto"] {

    /* #008099 measured 4.07 against --cy-bg — below the 4.5 AA floor for
       normal text. #00707f measures 5.10. */
    --cy-neon-cyan: #00707f;
    --cy-neon-pink: #b3009e;
    /* #d4af37 measures 1.85 on the light background — invisible. This is the
       same hue taken down until it clears the text floor: 5.66. */
    --cy-neon-gold: #7d5800;

    --cy-bg:      #f0f0f5;
    --cy-surface: #ffffff;
    --cy-text:    #3a3a45;
    --cy-heading: #16161d;

    /* Neon status hues fail contrast on a light background. */
    --cy-success: #0a7d43;
    --cy-warning: #8a5a00;
    --cy-danger:  #c2110f;

  }
}

/* ── Increased contrast ───────────────────────────────────────────
   `prefers-contrast: more` is a request to make everything easier to see,
   and most of the answer belongs here rather than in the components: raising
   a token reaches every rule that consumes it, which is the entire reason
   the substrate exists.

   Body text goes to the extremes of each theme, borders and focus rings get
   thicker, and disabled controls become more legible — "disabled" is still
   communicated by the cursor, the styling and the semantics, so dimming it
   to near-invisible was never carrying the meaning on its own.

   Individual borders in the component files use their own alpha values and
   cannot be lifted from here; each file raises its own alongside this.

   These values are strictly higher-contrast than the base ones they replace,
   so `scripts/check-contrast.js` — which reads the first `:root` block per
   theme — stays authoritative for the floors. */
@media (prefers-contrast: more) {
  :root {
    --cy-text: #ffffff;
    --cy-border-width: 2px;
    --cy-focus-width: 3px;
    --cy-disabled-opacity: 0.7;
  }

  :root[data-theme="light"] {
    --cy-text: #16161d;
  }
}

}

/* ── effects.css ───────────────────────────────────────────── */
/* Everything below is in the `cyberpunk-ui` cascade layer. Any rule you write
   outside a layer beats it regardless of specificity, so overriding the kit
   never needs !important and never needs a longer selector than the one you
   would write anyway. The body is not re-indented: a layer wrapper is not a
   nesting level worth 1,600 lines of churn. */
@layer cyberpunk-ui {

/* ── 1. Box glow ──────────────────────────────────────────────── */
.cy-glow         { box-shadow: 0 0 12px color-mix(in srgb, var(--cy-neon-cyan) 50%, transparent), 0 0 30px color-mix(in srgb, var(--cy-neon-cyan) 25%, transparent); }
.cy-glow--pink   { box-shadow: 0 0 12px color-mix(in srgb, var(--cy-neon-pink) 50%, transparent), 0 0 30px color-mix(in srgb, var(--cy-neon-pink) 25%, transparent); }
.cy-glow--purple { box-shadow: 0 0 12px color-mix(in srgb, var(--cy-neon-purple) 50%, transparent), 0 0 30px color-mix(in srgb, var(--cy-neon-purple) 25%, transparent); }
.cy-glow--gold   { box-shadow: 0 0 12px color-mix(in srgb, var(--cy-neon-gold) 50%, transparent), 0 0 30px color-mix(in srgb, var(--cy-neon-gold) 25%, transparent); }

/* ── 2. Text glow ─────────────────────────────────────────────── */
.cy-text-glow       { text-shadow: 0 0 10px var(--cy-neon-cyan), 0 0 24px color-mix(in srgb, var(--cy-neon-cyan) 50%, transparent); }
.cy-text-glow--pink { text-shadow: 0 0 10px var(--cy-neon-pink), 0 0 24px color-mix(in srgb, var(--cy-neon-pink) 50%, transparent); }

/* Gold reads differently from the neon pair and the numbers say why: it is a
   warm hue at a lower saturation, so the same 10px halo that makes cyan look
   lit makes gold look smeared. The near shadow is tightened and the far one
   dimmed — the effect is a machined edge catching light rather than a tube
   glowing, which is the register this colour belongs to. */
.cy-text-glow--gold { text-shadow: 0 0 6px color-mix(in srgb, var(--cy-neon-gold) 90%, transparent), 0 0 18px color-mix(in srgb, var(--cy-neon-gold) 35%, transparent); }

/* ── 3. Glitch text (RGB split) ───────────────────────────────── */
.cy-glitch {
  color: var(--cy-neon-cyan);
  text-shadow: 0 0 24px var(--cy-neon-cyan), 0 0 60px color-mix(in srgb, var(--cy-neon-cyan) 40%, transparent);
  animation: cy-glitch 2.5s infinite;
}

@keyframes cy-glitch {
  0%, 90%, 100% { text-shadow: 0 0 24px var(--cy-neon-cyan), 0 0 60px color-mix(in srgb, var(--cy-neon-cyan) 40%, transparent); }
  92% { text-shadow: -3px 0 var(--cy-neon-pink), 3px 0 var(--cy-neon-purple); }
  94% { text-shadow: 0 0 24px var(--cy-neon-cyan), 0 0 60px color-mix(in srgb, var(--cy-neon-cyan) 40%, transparent); }
  96% { text-shadow:  3px 0 var(--cy-neon-pink), -3px 0 var(--cy-neon-purple); }
}

/* ── 4. Scanlines overlay ─────────────────────────────────────────
   Apply to any element; a CRT scanline veil is painted on top via
   ::after without intercepting clicks. */
.cy-scanlines { position: relative; }
.cy-scanlines::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;
  background: repeating-linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0)    0,
    rgba(0, 0, 0, 0)    2px,
    rgba(0, 0, 0, 0.18) 3px,
    rgba(0, 0, 0, 0)    4px
  );
}

/* ── 5. Animated grid backdrop ────────────────────────────────────
   Animates via background-position so it can sit on any element
   without hijacking its transform. */
.cy-grid-bg {
  background-image:
    linear-gradient(color-mix(in srgb, var(--cy-neon-cyan) 10%, transparent) 1px, transparent 1px),
    linear-gradient(90deg, color-mix(in srgb, var(--cy-neon-cyan) 10%, transparent) 1px, transparent 1px);
  background-size: 50px 50px;
  animation: cy-grid-move 20s linear infinite;
}

@keyframes cy-grid-move {
  0%   { background-position: 0 0; }
  100% { background-position: 0 50px; }
}

/* ── 6. Blinking terminal cursor ──────────────────────────────────
   Renders a blinking "_" after the element. */
.cy-cursor {
  display: inline-block;
  color: var(--cy-neon-cyan);
  animation: cy-cursor-blink 1s step-end infinite;
}
.cy-cursor::after { content: "_"; }

@keyframes cy-cursor-blink {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0; }
}

/* ── WCAG: honour reduced-motion ──────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .cy-glitch,
  .cy-grid-bg,
  .cy-cursor { animation: none; }
}

/* ── Forced colours ───────────────────────────────────────────────
   Windows High Contrast Mode and friends replace the palette with the
   user's own. This file is where the kit gives up the most, and it should:
   every effect here is decoration, and decoration is exactly what a user
   asking for forced colours is asking to be rid of.

   The removals are explicit rather than left to the UA. `text-shadow` is
   not forced by the feature, so a cyan glow can survive underneath forced
   text and muddy it; `background-image` is not forced either, so the grid
   and the scanline veil would keep painting over content whose colours
   have changed underneath them. The scanline overlay is the worst of these
   — it is a translucent black wash sitting on top of the text, which is a
   direct contrast reduction applied to someone who has asked for more. */
@media (forced-colors: active) {
  .cy-glow,
  .cy-glow--pink,
  .cy-glow--purple,
  .cy-glow--gold { box-shadow: none; }

  .cy-text-glow,
  .cy-text-glow--pink,
  .cy-text-glow--gold,
  .cy-glitch { text-shadow: none; }

  /* The RGB split is drawn entirely in text-shadow, so with the shadow
     gone the animation has nothing left to move. */
  .cy-glitch { animation: none; }

  .cy-scanlines::after { background: none; }

  .cy-grid-bg {
    background-image: none;
    animation: none;
  }
}

}

/* ── components.css ────────────────────────────────────────── */
/* Everything below is in the `cyberpunk-ui` cascade layer. Any rule you write
   outside a layer beats it regardless of specificity, so overriding the kit
   never needs !important and never needs a longer selector than the one you
   would write anyway. The body is not re-indented: a layer wrapper is not a
   nesting level worth 1,600 lines of churn. */
@layer cyberpunk-ui {

/* ── Neon button ──────────────────────────────────────────────── */
.cy-btn {
  display: inline-block;
  font-family: var(--cy-font-mono);
  font-size: 0.95rem;
  letter-spacing: 0.1em;
  padding: var(--cy-space-sm) var(--cy-space-xl);
  color: var(--cy-neon-cyan);
  background: transparent;
  border: var(--cy-border-width) solid var(--cy-neon-cyan);
  border-radius: var(--cy-radius);
  cursor: pointer;
  text-decoration: none;
  transition: color 0.25s, background 0.25s, box-shadow 0.25s, transform 0.2s;
}

.cy-btn:hover,
.cy-btn:focus-visible {
  color: var(--cy-bg);
  background: var(--cy-neon-cyan);
  box-shadow: 0 0 14px var(--cy-neon-cyan), 0 0 30px color-mix(in srgb, var(--cy-neon-cyan) 50%, transparent);
  transform: translateY(-1px);
}

.cy-btn:focus-visible {
  outline: var(--cy-focus-width) solid var(--cy-focus-color);
  outline-offset: var(--cy-focus-offset);
}

.cy-btn--pink {
  color: var(--cy-neon-pink);
  border-color: var(--cy-neon-pink);
}

.cy-btn--pink:hover,
.cy-btn--pink:focus-visible {
  background: var(--cy-neon-pink);
  box-shadow: 0 0 14px var(--cy-neon-pink), 0 0 30px color-mix(in srgb, var(--cy-neon-pink) 50%, transparent);
}

/* ── Holo card (glassmorphism + edge glow) ────────────────────── */
.cy-card {
  background: color-mix(in srgb, var(--cy-neon-cyan) 3%, transparent);
  border: var(--cy-border-width) solid color-mix(in srgb, var(--cy-neon-cyan) 25%, transparent);
  border-radius: var(--cy-radius-lg);
  padding: var(--cy-space-xl);
  color: var(--cy-text);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: 0 0 24px color-mix(in srgb, var(--cy-neon-cyan) 12%, transparent);
  transition: border-color 0.3s, box-shadow 0.3s, transform 0.3s;
}

.cy-card:hover {
  border-color: color-mix(in srgb, var(--cy-neon-cyan) 60%, transparent);
  box-shadow: 0 0 32px color-mix(in srgb, var(--cy-neon-cyan) 30%, transparent);
  transform: translateY(-2px);
}

/* ── WCAG: honour reduced-motion ──────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .cy-btn,
  .cy-card { transition: none; }
  .cy-btn:hover,
  .cy-card:hover { transform: none; }
}

/* ── Button variants ──────────────────────────────────────────── */

/* Secondary: a quieter outline. Deliberately not an alias of --pink,
   which stays a distinct brand accent. */
.cy-btn--secondary {
  color: var(--cy-text);
  border-color: var(--cy-text);
}

.cy-btn--secondary:hover,
.cy-btn--secondary:focus-visible {
  color: var(--cy-bg);
  background: var(--cy-text);
  box-shadow: none;
}

.cy-btn--danger {
  color: var(--cy-danger);
  border-color: var(--cy-danger);
}

.cy-btn--danger:hover,
.cy-btn--danger:focus-visible {
  color: var(--cy-bg);
  background: var(--cy-danger);
  box-shadow: 0 0 14px var(--cy-danger),
              0 0 30px color-mix(in srgb, var(--cy-danger) 50%, transparent);
}

/* ── Button sizes ─────────────────────────────────────────────── */
.cy-btn--sm {
  font-size: 0.8rem;
  padding: var(--cy-space-xs) var(--cy-space-md);
}

.cy-btn--lg {
  font-size: 1.05rem;
  padding: var(--cy-space-md) var(--cy-space-xl);
}

/* ── Disabled ─────────────────────────────────────────────────── */
.cy-btn:disabled,
.cy-btn[aria-disabled="true"] {
  opacity: var(--cy-disabled-opacity);
  cursor: not-allowed;
}

.cy-btn:disabled:hover,
.cy-btn:disabled:focus-visible,
.cy-btn[aria-disabled="true"]:hover,
.cy-btn[aria-disabled="true"]:focus-visible {
  color: inherit;
  background: transparent;
  box-shadow: none;
  transform: none;
}

/* Native :disabled elements cannot receive focus, so removing the outline
   here is safe. [aria-disabled="true"] elements deliberately remain in the
   tab order (see forms.css/README for the pattern), so their outline must
   stay — otherwise a keyboard user can focus a control with no visible
   indicator, violating WCAG 2.4.7. */
.cy-btn:disabled:hover,
.cy-btn:disabled:focus-visible {
  outline: none;
}

/* ── Increased contrast ───────────────────────────────────────── */
@media (prefers-contrast: more) {
  .cy-btn,
  .cy-card { border-color: var(--cy-neon-cyan); }
}

/* ── Forced colours ───────────────────────────────────────────────
   Both components carry a real border, so the UA forces it to the user's
   text colour and the shape survives on its own. What does not survive is
   the glow, and `.cy-btn:disabled` leans on opacity — which forced colours
   does not touch — so the system's own disabled colour is named instead.
   GrayText is the one the platform already uses for this, which is the
   point: in forced colours the user's conventions win, not ours. */
@media (forced-colors: active) {
  .cy-btn,
  .cy-card { box-shadow: none; }

  .cy-btn:disabled {
    color: GrayText;
    border-color: GrayText;
    opacity: 1;
  }
}

}

/* ── containers.css ────────────────────────────────────────── */
/* Everything below is in the `cyberpunk-ui` cascade layer. Any rule you write
   outside a layer beats it regardless of specificity, so overriding the kit
   never needs !important and never needs a longer selector than the one you
   would write anyway. The body is not re-indented: a layer wrapper is not a
   nesting level worth 1,600 lines of churn. */
@layer cyberpunk-ui {

/* Opt-in classes only. Styling bare `details {}` or `dialog {}` would hijack
   every disclosure and modal on a consumer's page the moment they import the
   kit.

   All three components here are built on a native element, so the behaviour
   ships with the browser rather than with the kit: <details> brings its own
   keyboard handling and open state, <dialog> brings focus trapping, Esc and
   the top layer. No JavaScript ships, ever. */

/* ── Accordion ────────────────────────────────────────────────────
   <details class="cy-accordion">
     <summary>Heading</summary>
     <div class="cy-accordion__body">…</div>
   </details>

   The summary is styled through the class rather than given one of its own:
   `.cy-accordion > summary` is still opt-in, since it cannot match without
   the wrapper, and it keeps the consumer's markup contract to one class.
   The body needs its own class because there is no way to select "everything
   that is not the summary" that stays readable. */
.cy-accordion {
  background: var(--cy-surface);
  border: var(--cy-border-width) solid color-mix(in srgb, var(--cy-neon-cyan) 35%, transparent);
  border-radius: var(--cy-radius);
  margin-bottom: var(--cy-space-sm);
  overflow: hidden;
}

.cy-accordion > summary {
  display: flex;
  align-items: center;
  gap: var(--cy-space-sm);
  padding: var(--cy-space-sm) var(--cy-space-md);
  font-family: var(--cy-font-mono);
  font-size: 0.85rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--cy-neon-cyan);
  cursor: pointer;
  transition: background 0.2s, color 0.2s;

  /* Two removals for one marker. `display: flex` already drops the
     ::marker in engines that follow the spec, list-style covers the rest,
     and WebKit needs its own pseudo-element hidden explicitly. */
  list-style: none;
}

.cy-accordion > summary::-webkit-details-marker { display: none; }

.cy-accordion > summary:hover {
  background: color-mix(in srgb, var(--cy-neon-cyan) 6%, transparent);
}

/* The ring is not negotiable — a disclosure is keyboard-operated by
   definition, and the summary is the thing that takes the focus. */
.cy-accordion > summary:focus-visible {
  outline: var(--cy-focus-width) solid var(--cy-focus-color);
  outline-offset: calc(var(--cy-focus-offset) * -1);
}

/* Same rotated-border technique as the checkbox tick in forms.css, so the
   two read as one family. `margin-left: auto` pins it to the trailing edge
   whatever the heading length. */
.cy-accordion > summary::after {
  content: "";
  width: 0.4rem;
  height: 0.4rem;
  flex-shrink: 0;
  margin-inline-start: auto;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  rotate: 45deg;
  transition: rotate 0.2s var(--cy-ease);
}

.cy-accordion[open] > summary::after { rotate: 225deg; }

.cy-accordion[open] > summary {
  border-bottom: var(--cy-border-width) solid color-mix(in srgb, var(--cy-neon-cyan) 20%, transparent);
}

.cy-accordion__body {
  padding: var(--cy-space-md);
  color: var(--cy-text);
}

/* ── Modal ────────────────────────────────────────────────────────
   <dialog class="cy-modal">…</dialog>

   Open it with showModal(), not show(). Only showModal() puts the dialog in
   the top layer, traps focus, closes on Esc and renders ::backdrop at all —
   show() gives a non-modal box with none of that, and no backdrop to style.
   That call is the consumer's; the kit only paints the result. */
.cy-modal {
  padding: var(--cy-space-lg);
  max-width: min(90vw, 32rem);
  background: var(--cy-surface);
  color: var(--cy-text);
  border: var(--cy-border-width) solid color-mix(in srgb, var(--cy-neon-cyan) 50%, transparent);
  border-radius: var(--cy-radius-lg);
  box-shadow: 0 0 30px color-mix(in srgb, var(--cy-neon-cyan) 25%, transparent);
}

/* The fallback is not belt-and-braces: ::backdrop has historically not
   inherited custom properties from the page, so var(--cy-backdrop) can fail
   to resolve here even though it is defined on :root. Without a fallback that
   would leave the backdrop transparent and the page fully legible behind an
   open modal. The literal repeats --cy-backdrop's value; keep them in step. */
.cy-modal::backdrop {
  background: var(--cy-backdrop, rgba(10, 10, 12, 0.72));
  backdrop-filter: blur(6px);
}

/* ── Terminal / code window ───────────────────────────────────────
   <div class="cy-terminal">
     <div class="cy-terminal__bar">~/ops</div>
     <pre>…</pre>
   </div>

   The bar is optional; a terminal with no chrome is a valid code block.
   Combine with .cy-scanlines and .cy-cursor from effects.css for the full
   CRT treatment — they are kept separate so the frame can be used plain. */
.cy-terminal {
  background: var(--cy-surface);
  border: var(--cy-border-width) solid color-mix(in srgb, var(--cy-neon-cyan) 35%, transparent);
  border-radius: var(--cy-radius);
  box-shadow: 0 0 18px color-mix(in srgb, var(--cy-neon-cyan) 12%, transparent);
  overflow: hidden;
}

.cy-terminal__bar {
  display: flex;
  align-items: center;
  gap: var(--cy-space-sm);
  padding: var(--cy-space-xs) var(--cy-space-md);
  background: color-mix(in srgb, var(--cy-neon-cyan) 8%, transparent);
  border-bottom: var(--cy-border-width) solid color-mix(in srgb, var(--cy-neon-cyan) 25%, transparent);
  font-family: var(--cy-font-terminal);
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--cy-neon-cyan);
}

/* Scoped to the frame, so a bare <pre> elsewhere on the page is untouched. */
.cy-terminal > pre {
  margin: 0;
  padding: var(--cy-space-md);
  overflow-x: auto;
  font-family: var(--cy-font-terminal);
  font-size: 0.85rem;
  line-height: 1.6;
  color: var(--cy-text);
}

/* ── Toggle-tip ───────────────────────────────────────────────────
   <button class="cy-btn cy-btn--sm" popovertarget="tip">?</button>
   <div class="cy-popover" popover id="tip">
     Visible to other operatives.
   </div>

   A tooltip you can actually reach. The kit declined tooltips for four
   versions because the CSS-only version is hover-only: keyboard users never
   see it, touch users never see it, and a screen reader is never told it
   exists. None of that is fixable with :hover.

   The Popover API fixes it by making this a *toggle*-tip instead — the
   trigger is a real button, so it is focusable, activatable by Enter and
   Space, and announced with its expanded state. The browser handles
   light-dismiss, Escape, and the top layer. No JavaScript, which is what took
   so long: `popover` only became Baseline in January 2025.

   It is not a hover tooltip and will not become one here. That needs interest
   invokers, which are Chrome-only at the time of writing — and a hover-only
   affordance is the thing this component exists to avoid.

   Positioning: the browser centres a popover in the viewport, which is a fine
   place for a short explanation and works everywhere. Where CSS anchor
   positioning is supported it is pinned above its trigger instead. That is an
   enhancement rather than the design, because anchor positioning is still
   Chrome-only. */
.cy-popover {
  margin: 0;
  max-width: 32ch;
  font-family: var(--cy-font-mono);
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--cy-text);
  background: var(--cy-surface);
  border: var(--cy-border-width) solid color-mix(in srgb, var(--cy-neon-cyan) 45%, transparent);
  border-radius: var(--cy-radius);
  padding: var(--cy-space-md) var(--cy-space-lg);
  box-shadow: 0 0 20px color-mix(in srgb, var(--cy-neon-cyan) 20%, transparent);
}

.cy-popover:popover-open {
  animation: cy-popover-in 0.15s var(--cy-ease);
}

@keyframes cy-popover-in {
  from { opacity: 0; transform: translateY(0.25rem); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Pinned to its trigger where the engine can do it, centred everywhere else.
   position-area needs both halves of the anchor feature, so both are tested. */
@supports (anchor-name: --x) and (position-area: top) {
  /* Scoped to .cy-btn rather than to [popovertarget] alone. A bare attribute
     selector would claim every popover trigger on a consumer's page, which is
     the one thing this kit never does — and the convention checker caught it
     here rather than in review. The trigger is a button either way. */
  .cy-btn[popovertarget] { anchor-name: --cy-popover-anchor; }

  .cy-popover {
    position-anchor: --cy-popover-anchor;
    position-area: top;
    margin-bottom: var(--cy-space-sm);
    position-try-fallbacks: flip-block;
  }
}

/* ── Reduced motion ───────────────────────────────────────────────
   The chevron still turns — it reports the open state, so freezing it would
   remove information rather than decoration. Only the tween goes. */
@media (prefers-reduced-motion: reduce) {
  .cy-accordion > summary,
  .cy-accordion > summary::after { transition: none; }
  .cy-popover:popover-open { animation: none; }
  .cy-modal::backdrop { backdrop-filter: none; }
}

/* ── Increased contrast ───────────────────────────────────────── */
@media (prefers-contrast: more) {
  .cy-accordion,
  .cy-modal,
  .cy-terminal { border-color: var(--cy-neon-cyan); }

  .cy-accordion[open] > summary,
  .cy-terminal__bar { border-bottom-color: var(--cy-neon-cyan); }
}

/* ── Forced colours ───────────────────────────────────────────────
   All three keep their frames, since a forced border-color is still a
   border. The chevron survives too — it is drawn in `currentColor`, so it
   follows the summary's forced text colour without being told to.

   The backdrop is the exception worth spelling out. It is the only thing
   separating an open modal from the page behind it, and it is built from a
   translucent fill plus a blur. Forced colours does not reliably keep
   either, and a modal that cannot be told apart from the page underneath is
   a worse failure than a modal that looks plain. It is pinned to the
   system's own window background so the separation is never in doubt, and
   the blur — decoration, and expensive — goes. */
@media (forced-colors: active) {
  .cy-modal,
  .cy-popover,
  .cy-terminal { box-shadow: none; }

  /* The border is what separates a popover from the page once the glow and
     the surface tint are gone, so it is pinned rather than left translucent. */
  .cy-popover {
    background: Canvas;
    border-color: CanvasText;
  }

  .cy-modal::backdrop {
    background: Canvas;
    backdrop-filter: none;
    opacity: 0.85;
  }
}

}

/* ── navigation.css ────────────────────────────────────────── */
/* Everything below is in the `cyberpunk-ui` cascade layer. Any rule you write
   outside a layer beats it regardless of specificity, so overriding the kit
   never needs !important and never needs a longer selector than the one you
   would write anyway. The body is not re-indented: a layer wrapper is not a
   nesting level worth 1,600 lines of churn. */
@layer cyberpunk-ui {

/* Opt-in classes only. Neither component here gets behaviour from its element
   the way <details> and <dialog> do — a <nav> is a landmark, not a widget, so
   these are styling plus a markup contract rather than paint over a machine.
   What the elements do give is semantics, and the contract exists to keep it:
   a breadcrumb that is not a list announces as a run of loose links.

   There is deliberately no --active modifier on either component. The current
   item is marked with aria-current, which the kit styles directly. A class
   would let you build a nav whose active item looks current and announces as
   ordinary — the exact shape of failure the rest of the kit is written to
   avoid. Using the attribute means the styling cannot drift from the
   announcement, because they are the same declaration. */

/* ── Nav bar ──────────────────────────────────────────────────────
   <nav class="cy-nav" aria-label="Main">
     <a class="cy-nav__brand" href="/">&gt;_ site</a>
     <a href="/docs">Docs</a>
     <a href="/api" aria-current="page">API</a>
   </nav>

   Links are styled through the wrapper rather than given a class each, which
   keeps the markup contract to one class on the <nav> itself. */
.cy-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--cy-space-sm) var(--cy-space-lg);
  padding: var(--cy-space-sm) var(--cy-space-lg);
  background: var(--cy-surface);
  border-bottom: var(--cy-border-width) solid color-mix(in srgb, var(--cy-neon-cyan) 30%, transparent);
  font-family: var(--cy-font-mono);
}

.cy-nav a {
  color: var(--cy-text);
  font-size: 0.85rem;
  letter-spacing: 0.08em;
  text-decoration: none;
  padding: var(--cy-space-xs) 0;
  border-bottom: 2px solid transparent;
  transition: color 0.2s, border-color 0.2s;
}

.cy-nav a:hover {
  color: var(--cy-neon-cyan);
  border-bottom-color: color-mix(in srgb, var(--cy-neon-cyan) 50%, transparent);
}

.cy-nav a:focus-visible {
  outline: var(--cy-focus-width) solid var(--cy-focus-color);
  outline-offset: var(--cy-focus-offset);
}

/* The current page. Styled off the attribute that announces it, never off a
   class — see the note at the top of this file. */
.cy-nav a[aria-current] {
  color: var(--cy-neon-cyan);
  border-bottom-color: var(--cy-neon-cyan);
}

.cy-nav__brand {
  margin-inline-end: auto;
  color: var(--cy-neon-cyan);
  font-size: 0.95rem;
  letter-spacing: 0.1em;
}

/* ── Breadcrumb ───────────────────────────────────────────────────
   <nav aria-label="Breadcrumb">
     <ol class="cy-breadcrumb">
       <li><a href="/">Home</a></li>
       <li><a href="/sector">Sector</a></li>
       <li><a href="/sector/7" aria-current="page">Node 7</a></li>
     </ol>
   </nav>

   The <ol> is not decorative: it is what makes this a trail rather than a row
   of unrelated links, and it is what tells a screen reader how many steps
   there are and which one this is. The wrapping <nav aria-label="Breadcrumb">
   is what lets someone jump to it. Neither can be supplied by CSS. */
.cy-breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--cy-space-xs) var(--cy-space-sm);
  margin: 0;
  padding: 0;
  list-style: none;
  font-family: var(--cy-font-mono);
  font-size: 0.8rem;
}

.cy-breadcrumb li {
  display: flex;
  align-items: center;
  gap: var(--cy-space-sm);
}

/* The separator is decoration and must not be read out. The `/ ""` half of
   this declaration is the alternative text for generated content: it says the
   glyph has no spoken equivalent. Engines that do not support the syntax fall
   back to ignoring generated content or reading the slash — which is why the
   separator is a character with an obvious reading rather than an icon. */
.cy-breadcrumb li + li::before {
  content: "/" / "";
  color: var(--cy-text);
  /* 0.75 is the lowest opacity that clears 4.5:1 against --cy-bg in both
     themes — 7.48:1 dark, 4.92:1 light — which is the same value and the same
     reasoning as .cy-hint in forms.css. The separator is decorative and marked
     as such above, so 1.4.3 does not strictly bind it; it is held to the text
     floor anyway, because a trail whose separators fade out is harder to read
     as a trail, and "technically exempt" is a poor reason to ship something
     dim. At 0.5 it measured 3.88:1 dark and 2.62:1 light. */
  opacity: 0.75;
}

.cy-breadcrumb a {
  color: var(--cy-text);
  text-decoration: none;
  transition: color 0.2s;
}

.cy-breadcrumb a:hover { color: var(--cy-neon-cyan); }

.cy-breadcrumb a:focus-visible {
  outline: var(--cy-focus-width) solid var(--cy-focus-color);
  outline-offset: var(--cy-focus-offset);
}

/* The last crumb is usually the current page. Marked with aria-current so it
   is announced as such; styled from the same attribute so the two cannot
   disagree. It stays a link rather than becoming plain text, so the trail
   remains navigable when the page is reached from elsewhere. */
.cy-breadcrumb a[aria-current] {
  color: var(--cy-neon-cyan);
  cursor: default;
}

/* ── Sidebar ──────────────────────────────────────────────────────
   <nav class="cy-sidebar" aria-labelledby="sectors">
     <h2 class="cy-sidebar__title" id="sectors">Sectors</h2>
     <ul>
       <li><a href="/grid">Grid</a></li>
       <li><a href="/node" aria-current="page">Node</a></li>
     </ul>
   </nav>

   The <ul> is required, and for the same reason the breadcrumb's <ol> is: it
   is what makes a screen reader say "list, 5 items" and count the way through
   them. A stack of bare links in a column looks identical and announces as a
   run of loose links.

   The title should be a heading, not a <span>. It is a real section heading in
   the document, so it belongs in the heading outline that people use to skip
   around a page — and pairing it with aria-labelledby names the landmark
   without repeating the text. A <span> styles the same and announces nothing;
   that is the whole difference, and it is not visible.

   The sidebar sets no width. Where a sidebar sits and how wide it is are
   layout decisions belonging to the page, not to the component — a kit that
   picks 16rem here is a kit you fight in a grid. It fills whatever column it
   is given. Nor is it sticky or scrollable by default: making it a scroll
   container would oblige it to be keyboard-reachable and labelled (the same
   contract .cy-table-scroll carries), and that is a real cost to impose on
   every sidebar for the sake of the tall ones. */
.cy-sidebar {
  display: flex;
  flex-direction: column;
  gap: var(--cy-space-sm);
  padding: var(--cy-space-lg);
  background: var(--cy-surface);
  border-inline-start: 2px solid color-mix(in srgb, var(--cy-neon-cyan) 30%, transparent);
  font-family: var(--cy-font-mono);
}

.cy-sidebar__title {
  margin: 0 0 var(--cy-space-xs);
  color: var(--cy-neon-cyan);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

.cy-sidebar ul {
  display: flex;
  flex-direction: column;
  gap: var(--cy-space-xs);
  margin: 0;
  padding: 0;
  list-style: none;
}

.cy-sidebar a {
  display: block;
  color: var(--cy-text);
  font-size: 0.85rem;
  letter-spacing: 0.06em;
  text-decoration: none;
  padding: var(--cy-space-xs) var(--cy-space-sm);
  border-inline-start: 2px solid transparent;
  border-start-end-radius: var(--cy-radius);
  border-end-end-radius: var(--cy-radius);
  transition: color 0.2s, border-color 0.2s, background 0.2s;
}

.cy-sidebar a:hover {
  color: var(--cy-neon-cyan);
  border-inline-start-color: color-mix(in srgb, var(--cy-neon-cyan) 50%, transparent);
  background: color-mix(in srgb, var(--cy-neon-cyan) 6%, transparent);
}

.cy-sidebar a:focus-visible {
  outline: var(--cy-focus-width) solid var(--cy-focus-color);
  outline-offset: var(--cy-focus-offset);
}

/* Same rule as the nav above: the current item is styled from the attribute
   that announces it. A marker on the left edge rather than an underline,
   because in a vertical stack an underline reads as a separator between items
   rather than as a mark on one. */
.cy-sidebar a[aria-current] {
  color: var(--cy-neon-cyan);
  border-inline-start-color: var(--cy-neon-cyan);
  background: color-mix(in srgb, var(--cy-neon-cyan) 10%, transparent);
}

/* ── Reduced motion ───────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .cy-nav a,
  .cy-sidebar a,
  .cy-breadcrumb a { transition: none; }
}

/* ── Increased contrast ───────────────────────────────────────── */
@media (prefers-contrast: more) {
  .cy-nav { border-bottom-color: var(--cy-neon-cyan); }
  .cy-sidebar { border-inline-start-color: var(--cy-neon-cyan); }
  .cy-breadcrumb li + li::before { opacity: 1; }
}

/* ── Forced colours ───────────────────────────────────────────────
   The current item is the thing to protect. It is distinguished by colour
   plus an underline in the nav, and by colour alone in the breadcrumb — and
   forced colours collapses every link to one value, so the breadcrumb's
   current crumb would become indistinguishable from its ancestors. Both are
   given the system's own link/text pair, and the breadcrumb's current item
   gains weight so it does not rely on hue at all. */
@media (forced-colors: active) {
  .cy-nav a[aria-current] { border-bottom-color: LinkText; }

  /* The sidebar's current item leans on three signals, and forced colours
     takes two of them: the cyan goes to the one link colour, and the faint
     tint behind it goes to the page background. The left marker is all that
     survives, so it is pinned to a system colour and the label is given weight
     — two signals again, neither of them hue. */
  .cy-sidebar a[aria-current] {
    border-inline-start-color: LinkText;
    font-weight: 700;
  }

  .cy-breadcrumb a[aria-current] {
    color: CanvasText;
    font-weight: 700;
  }
}

}

/* ── table.css ─────────────────────────────────────────────── */
/* Everything below is in the `cyberpunk-ui` cascade layer. Any rule you write
   outside a layer beats it regardless of specificity, so overriding the kit
   never needs !important and never needs a longer selector than the one you
   would write anyway. The body is not re-indented: a layer wrapper is not a
   nesting level worth 1,600 lines of churn. */
@layer cyberpunk-ui {

/* Opt-in classes only. The cells are styled through the wrapper — `.cy-table
   th`, `.cy-table td` — so a table elsewhere on the page is untouched and the
   markup contract stays one class.

   A data table is the component where the markup carries the most meaning and
   CSS can supply none of it. `scope` on every header is what lets a screen
   reader say "Sector: Night City" instead of reading a wall of values; a
   <caption> is what names the table; <thead> is what makes a header row a
   header row rather than a bold first line. All three are the consumer's, all
   three are invisible when missing, and the table looks identical either way.
   See docs/components/table.md. */

.cy-table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--cy-font-mono);
  font-size: 0.85rem;
  color: var(--cy-text);
}

.cy-table caption {
  padding-bottom: var(--cy-space-sm);
  color: var(--cy-neon-cyan);
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-align: start;
}

.cy-table th,
.cy-table td {
  padding: var(--cy-space-sm) var(--cy-space-md);
  text-align: start;
  border-bottom: var(--cy-border-width) solid color-mix(in srgb, var(--cy-neon-cyan) 15%, transparent);
}

.cy-table thead th {
  color: var(--cy-neon-cyan);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  border-bottom-color: color-mix(in srgb, var(--cy-neon-cyan) 40%, transparent);
}

/* A row header — <th scope="row"> — is the label for its row, so it reads as
   heading-ish rather than as another value. */
.cy-table tbody th {
  color: var(--cy-heading);
  font-weight: 400;
}

.cy-table tbody tr {
  transition: background 0.2s;
}

.cy-table tbody tr:hover {
  background: color-mix(in srgb, var(--cy-neon-cyan) 6%, transparent);
}

/* ── Variants ─────────────────────────────────────────────────── */

/* Zebra striping is decoration, not information: it helps the eye track a row
   across a wide table and means nothing on its own. */
.cy-table--striped tbody tr:nth-child(even) {
  background: color-mix(in srgb, var(--cy-neon-cyan) 3%, transparent);
}

.cy-table--compact th,
.cy-table--compact td {
  padding: var(--cy-space-xs) var(--cy-space-sm);
}

/* ── Scroll container ─────────────────────────────────────────────
   Put the table in a section with an accessible name and tabindex="0":

     section.cy-table-scroll[tabindex="0"][aria-label="Sector readings"]
       > table.cy-table

   A wide table has to scroll somewhere, and a scrollable box that only
   responds to a mouse fails WCAG 2.1.1 — there is content the keyboard user
   cannot reach. `tabindex="0"` is what fixes that, and a focusable thing needs
   a name, which is what the label supplies.

   Chrome and Firefox now focus overflowing scrollers on their own — measured
   in Chrome 148, which focuses the box once it actually overflows and not
   while it fits. Safari does not, so the attribute is kept rather than left to
   the engine. A linter flagging tabindex on a non-interactive element is
   applying a good rule to its documented exception.

   Use a section rather than a div with role="region": a named section already
   carries that role, and the kit prefers the element over the ARIA attribute
   everywhere else. Note the name is what makes the mapping happen — an
   unnamed section is not a region.

   A plain utility rather than a `__element`, because the table is its child
   rather than the other way round, and because a table which fits needs no
   wrapper at all. */
.cy-table-scroll {
  overflow-x: auto;
  /* The focus ring belongs on the scroll box: when it is focusable, it is the
     thing the user has landed on. */
  border-radius: var(--cy-radius);
}

.cy-table-scroll:focus-visible {
  outline: var(--cy-focus-width) solid var(--cy-focus-color);
  outline-offset: var(--cy-focus-offset);
}

/* ── Reduced motion ───────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .cy-table tbody tr { transition: none; }
}

/* ── Increased contrast ───────────────────────────────────────── */
@media (prefers-contrast: more) {
  .cy-table th,
  .cy-table td { border-bottom-color: color-mix(in srgb, var(--cy-neon-cyan) 50%, transparent); }
  .cy-table thead th { border-bottom-color: var(--cy-neon-cyan); }
  .cy-table--striped tbody tr:nth-child(even) { background: color-mix(in srgb, var(--cy-neon-cyan) 10%, transparent); }
}

/* ── Forced colours ───────────────────────────────────────────────
   Row separation is the whole readability of a table, and it is carried here
   by translucent borders that forced colours flattens to one value — which is
   fine, since a border is geometry and survives. The striping does not: a
   background tint is rewritten to the page background, so striped and
   unstriped rows become identical. It is dropped rather than left to fail
   silently, and the borders do the work instead. */
@media (forced-colors: active) {
  .cy-table--striped tbody tr:nth-child(even) { background: Canvas; }

  .cy-table tbody tr:hover { background: Canvas; }

  .cy-table th,
  .cy-table td { border-bottom-color: CanvasText; }
}

}

/* ── forms.css ─────────────────────────────────────────────── */
/* Everything below is in the `cyberpunk-ui` cascade layer. Any rule you write
   outside a layer beats it regardless of specificity, so overriding the kit
   never needs !important and never needs a longer selector than the one you
   would write anyway. The body is not re-indented: a layer wrapper is not a
   nesting level worth 1,600 lines of churn. */
@layer cyberpunk-ui {

/* Opt-in classes only. Styling bare `input {}` would hijack every input on a
   consumer's page the moment they import the kit. */

/* ── Field wrapper ────────────────────────────────────────────── */
.cy-field {
  display: flex;
  flex-direction: column;
  gap: var(--cy-space-xs);
  margin-bottom: var(--cy-space-lg);
}

.cy-label {
  font-family: var(--cy-font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--cy-neon-cyan);
}

/* ── Text-like controls ───────────────────────────────────────── */
.cy-input {
  font-family: var(--cy-font-mono);
  font-size: 0.95rem;
  color: var(--cy-text);
  background: var(--cy-surface);
  border: var(--cy-border-width) solid color-mix(in srgb, var(--cy-neon-cyan) 35%, transparent);
  border-radius: var(--cy-radius);
  padding: var(--cy-space-sm) var(--cy-space-md);
  width: 100%;
  transition: border-color 0.2s, box-shadow 0.2s;
}

.cy-input::placeholder {
  color: var(--cy-text);
  /* 0.7 is the lowest opacity that clears 4.5:1 in both themes against
     --cy-surface (the input's background): 6.40:1 dark, 4.57:1 light. */
  opacity: 0.7;
}

.cy-input:hover {
  border-color: color-mix(in srgb, var(--cy-neon-cyan) 60%, transparent);
}

.cy-input:focus-visible {
  outline: var(--cy-focus-width) solid var(--cy-focus-color);
  outline-offset: var(--cy-focus-offset);
  border-color: var(--cy-neon-cyan);
  box-shadow: 0 0 12px color-mix(in srgb, var(--cy-neon-cyan) 30%, transparent);
}

/* Native dropdown arrows cannot be styled consistently, so draw our own.
   `mask-image` was tried so the arrow could take `background-color:
   var(--cy-neon-cyan)` directly, but a <select> has no wrapper element to
   confine it to: masking the select clips its ENTIRE painted box (fill,
   border, and text go with it, leaving only the arrow silhouette) because
   mask is applied to the whole element, not to one background layer. With
   no wrapper markup available (a bare `<select class="cy-input">` is the
   only markup contract this kit makes), the arrow has to stay a background
   image with its colour baked in — so it is baked in per theme instead of
   once, keyed off the same [data-theme="light"] switch tokens.css uses, and
   kept in sync with --cy-neon-cyan's value in each theme below. */
select.cy-input {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath fill='%2300f2ff' d='M0 0h12L6 8z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--cy-space-md) center;
  background-size: 10px;
  padding-inline-end: var(--cy-space-xl);
}

/* The one place in the kit that still needs a physical direction. Everything
   else mirrors on its own through logical properties, but `background-position`
   has no inline-axis keyword — there is no `inset-inline-end` for a background
   image — so a right-to-left page needs the arrow moved explicitly. Matching on
   [dir="rtl"] rather than :dir(rtl) because the attribute is what RTL pages
   already set, and it works in every engine rather than Chrome 120 and up. */
[dir="rtl"] select.cy-input {
  background-position: left var(--cy-space-md) center;
  padding-inline-end: var(--cy-space-md);
  padding-inline-start: var(--cy-space-xl);
}

/* Keep in sync with --cy-neon-cyan's light-theme value in tokens.css. */
:root[data-theme="light"] select.cy-input {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath fill='%2300707f' d='M0 0h12L6 8z'/%3E%3C/svg%3E");
}

textarea.cy-input {
  min-height: 6rem;
  resize: vertical;
}

/* ── Sizes ────────────────────────────────────────────────────── */
.cy-input--sm { font-size: 0.8rem;  padding: var(--cy-space-xs) var(--cy-space-sm); }
.cy-input--lg { font-size: 1.05rem; padding: var(--cy-space-md) var(--cy-space-lg); }

/* ── Validation ───────────────────────────────────────────────────
   :user-invalid, not :invalid — :invalid matches empty required fields
   before the user has typed anything, so forms load pre-shouting in red.
   [aria-invalid="true"] lets JS validation drive the state explicitly. */

/* This rule intentionally sits AFTER .cy-input:focus-visible (line 44).
   Both have equal specificity (0,2,0), so source order is what makes the
   invalid state win while focused. The focus outline remains visible, so
   the field is never left without a focus affordance. Do not reorder. */
.cy-input:user-invalid,
.cy-input[aria-invalid="true"] {
  border-color: var(--cy-danger);
  box-shadow: 0 0 10px color-mix(in srgb, var(--cy-danger) 25%, transparent);
}

.cy-error {
  font-family: var(--cy-font-mono);
  font-size: 0.75rem;
  color: var(--cy-danger);
}

.cy-hint {
  font-family: var(--cy-font-mono);
  font-size: 0.75rem;
  color: var(--cy-text);
  /* 0.75 is the lowest opacity that clears 4.5:1 in both themes against
     --cy-bg (hint text sits on the page background): 7.48:1 dark, 4.86:1
     light. */
  opacity: 0.75;
}

/* ── Checkbox and radio ───────────────────────────────────────────
   appearance:none on the real input, NOT the hidden-input + styled-span
   pattern: the input stays the focusable, screen-reader-announced element
   and we simply paint it. */
.cy-checkbox,
.cy-radio {
  appearance: none;
  width: 1.15rem;
  height: 1.15rem;
  flex-shrink: 0;
  margin: 0;
  display: inline-grid;
  place-content: center;
  /* An inline-grid box derives its baseline from its in-flow content rather
     than its border box. The checkbox's ::after tick (a rotated box with a
     negative margin-top) and the radio's ::after dot (a plain circle) are
     different content, so the two controls compute different baselines and
     land at different heights when default `vertical-align: baseline` is
     used. Aligning both to the line box's middle sidesteps the content-driven
     baseline entirely and keeps them level regardless of ::after shape — do
     not remove this as redundant. */
  vertical-align: middle;
  background: var(--cy-surface);
  border: var(--cy-border-width) solid color-mix(in srgb, var(--cy-neon-cyan) 50%, transparent);
  cursor: pointer;
  transition: border-color 0.2s, box-shadow 0.2s;
}

.cy-checkbox { border-radius: var(--cy-radius-sm); }

/* The tick is drawn in the page background colour because a checked checkbox
   fills solid (see :checked below) — the tick is knocked out of the fill.
   It is only ever visible when checked, since it is scale(0) otherwise.

   The tick is also deliberately small relative to its box: any larger and it
   spans nearly the full square, reading heavier than the radio's dot so the
   two controls look mismatched side by side. */
.cy-checkbox::after {
  content: "";
  width: 0.45rem;
  height: 0.22rem;
  border-left: 2px solid var(--cy-bg);
  border-bottom: 2px solid var(--cy-bg);
  rotate: -45deg;
  margin-top: -0.1rem;
  transform: scale(0);
  transition: transform 0.15s var(--cy-ease);
}

/* The radio's dot is a background-image, NOT an ::after like the checkbox's
   tick.

   The original reason given for this was wrong, and is corrected here so it
   does not get "fixed" back: it claimed Gecko cannot generate pseudo-elements
   on replaced elements such as <input>. That rule does not apply to these
   controls — `appearance: none` makes the input non-replaced, and Firefox
   renders the checkbox's ::after tick above without trouble (verified in
   Firefox on macOS, 0.2.1).

   The change is kept anyway. A background-image needs no generated box at
   all, which is one less rendering assumption on a control whose only state
   signal is that dot — the checkbox at least fills solid when checked, so it
   still reads as selected if its tick were ever to vanish. Animating
   background-size keeps the same grow-in feel the ::after had. */
.cy-radio {
  border-radius: 50%;
  background-image: radial-gradient(
    circle at center,
    var(--cy-neon-cyan) 0 49%,
    transparent 50%
  );
  background-repeat: no-repeat;
  background-position: center;
  background-size: 0 0;
  transition: border-color 0.2s, box-shadow 0.2s, background-size 0.15s var(--cy-ease);
}

.cy-checkbox:checked,
.cy-radio:checked {
  border-color: var(--cy-neon-cyan);
  box-shadow: 0 0 10px color-mix(in srgb, var(--cy-neon-cyan) 45%, transparent);
}

/* A checked checkbox fills solid, matching how a checked radio fills with a
   solid dot. Both controls then signal "selected" the same way — a filled
   shape — instead of the checkbox being line-art beside a solid radio, which
   reads as mismatched when the two sit on the same row. The tick is knocked
   out in --cy-bg, so it stays legible in both themes. */
.cy-checkbox:checked {
  background: var(--cy-neon-cyan);
}

.cy-checkbox:checked::after {
  transform: scale(1);
}

.cy-radio:checked {
  background-size: 0.55rem 0.55rem;
}

.cy-checkbox:focus-visible,
.cy-radio:focus-visible {
  outline: var(--cy-focus-width) solid var(--cy-focus-color);
  outline-offset: var(--cy-focus-offset);
}

/* ── Disabled ─────────────────────────────────────────────────── */
.cy-input:disabled,
.cy-checkbox:disabled,
.cy-radio:disabled {
  opacity: var(--cy-disabled-opacity);
  cursor: not-allowed;
}

/* ── Reduced motion ───────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .cy-input,
  .cy-checkbox,
  .cy-radio,
  .cy-checkbox::after { transition: none; }
}

/* ── Increased contrast ───────────────────────────────────────────
   The hint and the placeholder are dimmed to the lowest opacity that still
   clears 4.5:1 (see the comments on each). Clearing the floor is the
   minimum, not the goal, so when more contrast is asked for they go to
   full strength and stop being the quietest text on the page. */
@media (prefers-contrast: more) {
  .cy-input,
  .cy-checkbox,
  .cy-radio { border-color: var(--cy-neon-cyan); }

  .cy-hint,
  .cy-input::placeholder { opacity: 1; }
}

/* ── Forced colours ───────────────────────────────────────────────
   The checked states are the whole problem here. A checked checkbox fills
   with cyan and knocks its tick out in --cy-bg; a checked radio paints its
   dot as a background-image. Forced colours rewrites `background-color`
   but not `background-image`, and it rewrites the tick's border-color to
   the same text colour it gives the fill — so left alone, the checkbox
   becomes a solid block with an invisible tick, and the radio keeps a cyan
   dot the user asked not to see.

   Naming the system's own selected pair fixes both: Highlight is what the
   platform fills a chosen control with, HighlightText is what it draws on
   top. The kit stops choosing, which is the entire contract of this mode. */
@media (forced-colors: active) {
  .cy-input,
  .cy-checkbox,
  .cy-radio { box-shadow: none; }

  .cy-checkbox:checked,
  .cy-radio:checked {
    background: Highlight;
    border-color: Highlight;
  }

  .cy-checkbox:checked::after {
    border-color: HighlightText;
  }

  /* The dot is a background-image, which forced colours leaves alone, so it
     is dropped: a checked radio becomes a solid Highlight circle instead.
     Deliberately not an inset box-shadow standing in for the dot — shadows
     are unreliable in this mode, and a state signal must not depend on one.
     Filled-versus-outlined carries it, and the round shape still tells the
     two controls apart. */
  .cy-radio:checked { background-image: none; }

  .cy-input:disabled,
  .cy-checkbox:disabled,
  .cy-radio:disabled {
    color: GrayText;
    border-color: GrayText;
    opacity: 1;
  }
}

}

/* ── feedback.css ──────────────────────────────────────────── */
/* Everything below is in the `cyberpunk-ui` cascade layer. Any rule you write
   outside a layer beats it regardless of specificity, so overriding the kit
   never needs !important and never needs a longer selector than the one you
   would write anyway. The body is not re-indented: a layer wrapper is not a
   nesting level worth 1,600 lines of churn. */
@layer cyberpunk-ui {

/* ── Visually-hidden utility ──────────────────────────────────────
   Lives here because .cy-spinner requires a text label to be
   accessible, and a spinner without one is the common mistake. */
.cy-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;
}

/* ── Alerts ───────────────────────────────────────────────────────
   Status colour on a 3px inline-start border plus a faint tint of the
   same hue, mixed from the status token itself. */
.cy-alert {
  display: block;
  font-family: var(--cy-font-mono);
  font-size: 0.9rem;
  line-height: 1.6;
  color: var(--cy-text);
  padding: var(--cy-space-md) var(--cy-space-lg);
  border-radius: var(--cy-radius);
  border-inline-start: 3px solid var(--cy-info);
  background: color-mix(in srgb, var(--cy-neon-cyan) 8%, transparent);
}

.cy-alert--info    { border-inline-start-color: var(--cy-info);    background: color-mix(in srgb, var(--cy-neon-cyan) 8%, transparent); }
.cy-alert--success { border-inline-start-color: var(--cy-success); background: color-mix(in srgb, var(--cy-success) 8%, transparent); }
.cy-alert--warning { border-inline-start-color: var(--cy-warning); background: color-mix(in srgb, var(--cy-warning) 8%, transparent); }
.cy-alert--danger  { border-inline-start-color: var(--cy-danger);  background: color-mix(in srgb, var(--cy-danger) 8%, transparent); }

/* ── Toasts ───────────────────────────────────────────────────────
   Styling only — no JavaScript ships. Consumers append and remove
   nodes; see the README snippet. pointer-events on the container is
   none so it never blocks clicks on the page beneath it. */
.cy-toast-container {
  position: fixed;
  top: var(--cy-space-lg);
  inset-inline-end: var(--cy-space-lg);
  z-index: 9999;
  display: flex;
  flex-direction: column;
  gap: var(--cy-space-sm);
  pointer-events: none;
}

.cy-toast-container--bottom {
  top: auto;
  bottom: var(--cy-space-lg);
}

.cy-toast {
  display: block;
  pointer-events: auto;
  min-width: 240px;
  max-width: 360px;
  font-family: var(--cy-font-mono);
  font-size: 0.85rem;
  color: var(--cy-text);
  background: var(--cy-surface);
  border: var(--cy-border-width) solid color-mix(in srgb, var(--cy-neon-cyan) 40%, transparent);
  border-inline-start: 3px solid var(--cy-info);
  border-radius: var(--cy-radius);
  padding: var(--cy-space-md) var(--cy-space-lg);
  box-shadow: 0 0 24px color-mix(in srgb, var(--cy-neon-cyan) 18%, transparent);
  animation: cy-toast-in 0.25s var(--cy-ease);
}

.cy-toast--success { border-inline-start-color: var(--cy-success); }
.cy-toast--warning { border-inline-start-color: var(--cy-warning); }
.cy-toast--danger  { border-inline-start-color: var(--cy-danger); }

@keyframes cy-toast-in {
  from { opacity: 0; transform: translateX(1rem); }
  to   { opacity: 1; transform: translateX(0); }
}

/* ── Badges ───────────────────────────────────────────────────── */
.cy-badge {
  display: inline-block;
  font-family: var(--cy-font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 0.15rem var(--cy-space-sm);
  border-radius: var(--cy-radius-lg);
  color: var(--cy-bg);
  background: var(--cy-info);
}

.cy-badge--success { background: var(--cy-success); }
.cy-badge--warning { background: var(--cy-warning); }
.cy-badge--danger  { background: var(--cy-danger); }

.cy-badge--outline {
  color: var(--cy-info);
  background: transparent;
  border: var(--cy-border-width) solid currentColor;
}

.cy-badge--outline.cy-badge--success { color: var(--cy-success); }
.cy-badge--outline.cy-badge--warning { color: var(--cy-warning); }
.cy-badge--outline.cy-badge--danger  { color: var(--cy-danger); }

/* ── Spinner ──────────────────────────────────────────────────── */
.cy-spinner {
  display: inline-block;
  width: 1.5rem;
  height: 1.5rem;
  border: 2px solid color-mix(in srgb, var(--cy-neon-cyan) 25%, transparent);
  border-top-color: var(--cy-neon-cyan);
  border-radius: 50%;
  animation: cy-spin 0.8s linear infinite,
             cy-spin-glow 1.6s ease-in-out infinite;
}

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

@keyframes cy-spin-glow {
  0%, 100% { box-shadow: 0 0 6px color-mix(in srgb, var(--cy-neon-cyan) 40%, transparent); }
  50%      { box-shadow: 0 0 16px color-mix(in srgb, var(--cy-neon-cyan) 80%, transparent); }
}

/* ── Progress ─────────────────────────────────────────────────── */
.cy-progress {
  width: 100%;
  height: 6px;
  background: color-mix(in srgb, var(--cy-neon-cyan) 15%, transparent);
  border-radius: var(--cy-radius-lg);
  overflow: hidden;
}

/* `.cy-progress__fill` was removed in 0.5.0. It let a plain div act as a
   progress bar, and it cost more than it returned: a bare div announces
   nothing, so correct use required a progressbar role, three aria-value
   attributes and a label — four things the kit cannot enforce and that were
   easy to omit, leaving a bar that looked finished and told a screen reader
   nothing. Along the way it drew a Sonar rule, read as a rendering duplicate
   when shown beside the native bar, and quietly drifted out of visual sync
   with it.

   The native element below does all of it correctly and for free. Migration
   is in CHANGELOG 0.5.0 for anyone who still needs the div. */

/* Native <progress class="cy-progress" value max>. Vendor pseudo-elements
   cannot share a selector list — an invalid one drops the whole rule — so
   each lives on its own. */
progress.cy-progress {
  appearance: none;
  -webkit-appearance: none;
  border: none;
}

progress.cy-progress::-webkit-progress-bar {
  background: color-mix(in srgb, var(--cy-neon-cyan) 15%, transparent);
  border-radius: var(--cy-radius-lg);
}

/* No transition on either pseudo-element below, deliberately. A native
   <progress> takes its bar length from the element's own value attribute,
   and neither engine transitions the resulting box. A transition here does
   nothing except suggest it works. */
progress.cy-progress::-webkit-progress-value {
  background: linear-gradient(90deg, var(--cy-neon-cyan), var(--cy-neon-pink));
  box-shadow: 0 0 12px color-mix(in srgb, var(--cy-neon-cyan) 60%, transparent);
  border-radius: var(--cy-radius-lg);
}

progress.cy-progress::-moz-progress-bar {
  background: linear-gradient(90deg, var(--cy-neon-cyan), var(--cy-neon-pink));
  box-shadow: 0 0 12px color-mix(in srgb, var(--cy-neon-cyan) 60%, transparent);
  border-radius: var(--cy-radius-lg);
}

/* ── Reduced motion ───────────────────────────────────────────────
   The spinner KEEPS rotating: a frozen spinner reads as broken and it
   is essential feedback, not decoration. Slow it and drop the
   decorative glow pulse instead. */
@media (prefers-reduced-motion: reduce) {
  .cy-spinner { animation: cy-spin 2s linear infinite; }
  .cy-toast   { animation: cy-fade-in 0.2s ease; }
}

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

/* ── Increased contrast ───────────────────────────────────────── */
@media (prefers-contrast: more) {
  .cy-toast,
  .cy-badge { border-color: var(--cy-neon-cyan); }

  .cy-spinner { border-color: color-mix(in srgb, var(--cy-neon-cyan) 60%, transparent); border-top-color: var(--cy-neon-cyan); }

  /* The alert tints sit at 0.08 so the hue reads without competing with the
     text on top. Doubling that keeps the variants distinguishable from each
     other, which is the only job the fill has. */
  .cy-alert            { background: color-mix(in srgb, var(--cy-neon-cyan) 16%, transparent); }
  .cy-alert--info      { background: color-mix(in srgb, var(--cy-neon-cyan) 16%, transparent); }
  .cy-alert--success   { background: color-mix(in srgb, var(--cy-success) 16%, transparent); }
  .cy-alert--warning   { background: color-mix(in srgb, var(--cy-warning) 16%, transparent); }
  .cy-alert--danger    { background: color-mix(in srgb, var(--cy-danger) 16%, transparent); }
}

/* ── Forced colours ───────────────────────────────────────────────
   The spinner is the one component here that loses its meaning outright.
   Its whole animation reads because three borders are faint and the fourth
   is bright — and forced colours rewrites every border-color to the same
   value, which turns a spinning arc into a static ring. Nothing is broken,
   nothing looks wrong, and it silently stops saying "working". Naming two
   different system colours restores the contrast the rotation depends on.

   The progress fill has the mirror-image problem: it is a gradient, so
   forced colours leaves it alone entirely and it would keep its cyan-to-pink
   while everything around it changed. Both the native bar and the div path
   are pinned to the system's Highlight instead.

   Badges are filled shapes with no border, so once the fill is forced to the
   page background they would dissolve into it. They get an outline. */
@media (forced-colors: active) {
  .cy-toast,
  .cy-alert,
  .cy-badge { box-shadow: none; }

  .cy-spinner {
    border-color: GrayText;
    border-top-color: CanvasText;
    animation: cy-spin 0.8s linear infinite;
  }

  .cy-badge { border: var(--cy-border-width) solid CanvasText; }

  .cy-progress {
    background: Canvas;
    border: var(--cy-border-width) solid CanvasText;
  }

  progress.cy-progress::-webkit-progress-value,
  progress.cy-progress::-moz-progress-bar {
    background-image: none;
    background-color: Highlight;
  }

  progress.cy-progress::-webkit-progress-bar { background: Canvas; }
}

}
