/* CLIXHOUSE design system — component reference patterns
   ─────────────────────────────────────────────────────────────────
   Opt-in component patterns built with @scope — the 2026 alternative
   to BEM, CSS Modules, and class-prefix conventions. Each component
   is scoped to its own root selector via @scope, so its styles do
   not leak and do not need pre-namespaced class names. Roma Komarov
   (kizu) is the depth source for @scope semantics — see
   blog.kizu.dev/scope-selector-nuance/ and
   blog.kizu.dev/css-scope-issues/.

   Imported via:  @import url("/static/design/patterns.css") layer(components);

   Sites can either import wholesale (and override the OKLCH palette
   tokens to brand) or copy individual @scope blocks and adapt. The
   patterns consume the framework's semantic tokens, so changing
   --accent / --ink / --paper recolors them automatically.

   Components in this file:
     .btn          — Button (primary, secondary, ghost variants)
     .card         — Card (image, body, footer slots)
     .prose        — Article/blog content styling (typography rhythm)
     .nav          — Horizontal navigation with focus states
     .tag          — Inline tag/pill
     .alert        — Status banner (info/success/warning/danger)
     .form         — Form (field, label, input, fieldset, submit row)
                     pairs with :has() validation patterns in modern.css
*/

@layer components {

  /* ── BUTTON ────────────────────────────────────────────────────── */
  @scope (.btn) {
    :scope {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      gap: var(--space-2xs, 0.5rem);
      padding-block: var(--space-xs, 0.75rem);
      padding-inline: var(--space-m, 1.5rem);
      border-radius: var(--radius-md, 0.5rem);
      font: inherit;
      font-weight: 600;
      line-height: 1;
      text-decoration: none;
      cursor: pointer;
      transition:
        background-color var(--motion-fast, 150ms) var(--motion-ease),
        color var(--motion-fast, 150ms) var(--motion-ease),
        transform var(--motion-fast, 150ms) var(--motion-ease);
      border: 1px solid transparent;
      /* Forced-colors safe: transparent outline survives the mode.
         2px is the WCAG SC 2.4.13 perimeter floor — Manuel Matuzović's
         canonical recipe (matuzo.at/blog/2026/box-shadow-no-alternative-to-outline)
         uses 2px so the at-rest indicator survives forced-colors with
         a legible thickness. */
      outline: 2px solid transparent;
      outline-offset: -2px;

      /* Default variant: primary (filled with accent) */
      background: var(--accent, currentColor);
      color: var(--text-on-accent, white);
    }

    /* Keyboard focus ring — re-asserted INSIDE @layer components so it
       beats the at-rest `outline: 2px solid transparent` above. Layer
       order is components > base (layers.css), so a11y.css's base-layer
       :focus-visible would otherwise lose to the at-rest rule here and a
       keyboard-focused button would show only a TRANSPARENT outline.
       Two-indicator WCAG 2.4.13: solid ring + offset halo. */
    :scope:focus-visible {
      outline: 3px solid var(--focus-ring, currentColor);
      outline-offset: 2px;
      box-shadow: 0 0 0 6px var(--focus-halo, Canvas);
    }

    :scope:hover {
      background: var(--accent-hover, var(--accent));
    }
    :scope:active {
      background: var(--accent-active, var(--accent-hover, var(--accent)));
      transform: translateY(1px);
    }
    /* `[disabled]` removes the element from the tab order and blocks
       all interaction — UA handles it. `aria-disabled="true"` is the
       *focusable* disabled state: keyboard users can still Tab to it
       (discoverable) but the handler refuses to act. Collapsing them
       to the same `pointer-events: none` defeats the whole reason for
       aria-disabled. Visual treatment is shared; behavior is not. */
    :scope[disabled],
    :scope[aria-disabled="true"] {
      /* Desaturate rather than fade. opacity drags everything (border,
         focus ring, outline) and reads as broken contrast in dark mode.
         A muted surface + faint text says "off" without breaking a11y. */
      background: var(--surface-2);
      color: var(--text-faint);
      border-color: var(--border-subtle);
      cursor: not-allowed;
    }
    :scope[disabled] {
      pointer-events: none;
    }
    /* aria-disabled: stays focusable, stays clickable at the DOM layer
       (handler is responsible for the no-op). Do NOT add
       pointer-events: none here. */

    /* Variant: secondary (outlined) */
    :scope.btn--secondary {
      background: transparent;
      color: var(--text-strong, currentColor);
      border-color: var(--border-strong, currentColor);
    }
    :scope.btn--secondary:hover {
      background: var(--accent-soft, transparent);
      border-color: var(--accent, currentColor);
    }

    /* Variant: ghost (no border, no background until hover) */
    :scope.btn--ghost {
      background: transparent;
      color: var(--text-strong, currentColor);
      border-color: transparent;
    }
    :scope.btn--ghost:hover {
      background: var(--accent-soft, transparent);
    }

    /* Size: small */
    :scope.btn--sm {
      padding-block: var(--space-3xs, 0.25rem);
      padding-inline: var(--space-xs, 0.75rem);
      font-size: var(--step--1, 0.875rem);
    }
    /* Size: large */
    :scope.btn--lg {
      padding-block: var(--space-s, 1rem);
      padding-inline: var(--space-l, 2rem);
      font-size: var(--step-1, 1.125rem);
    }
  }

  /* ── CARD ──────────────────────────────────────────────────────── */
  /* container-type: inline-size makes the card itself the size
     reference for `cqi` units inside, so card-internal sizing scales
     against the card's width, not the viewport. Per the alsacreations
     "Responsive Moderne" essay — `cqi` solves the "same card looks
     wrong in a sidebar" class of bug. */
  @scope (.card) {
    :scope {
      container-type: inline-size;
      display: flex;
      flex-direction: column;
      background: var(--surface-1, transparent);
      color: var(--text-body, inherit);
      border-radius: var(--radius-lg, 1rem);
      overflow: hidden;
      border: 1px solid var(--border-subtle, currentColor);
    }
    /* Motion is opted IN under no-preference, not stripped OUT under
       reduce — per Kilian Valkhof's progressive-enhancement-with-
       nesting pattern (kilianvalkhof.com/2026/css-html/progressive-
       enhancement-as-a-benefit-of-css-nesting). The transition and
       hover transform only exist when the user hasn't asked for less
       motion; no `!important` override needed, no fight with the
       a11y.css blanket. */
    @media (prefers-reduced-motion: no-preference) {
      :scope {
        transition: transform var(--motion-base, 250ms) var(--motion-ease);
      }
      :scope:hover {
        transform: translateY(-2px);
      }
    }

    /* Slots — `--card-media-shape` is an opt-in clip-path override
       (Temani Afif's clip-path: shape() recipe for squircles).
       Default `none` keeps the image rectangular. Sites that want a
       continuous-curvature squircle on card media set it per-instance:
         .card { --card-media-shape: shape(...); }
       NOTE: clip-path destroys decorations on the clipped element.
       This affects the *image* only, not the card itself — the
       card's border + box-shadow stay intact because the clip is on
       the child <img>, not the scope root. */
    img,
    .card__media {
      inline-size: 100%;
      block-size: auto;
      aspect-ratio: var(--card-media-ratio, 16 / 10);
      object-fit: cover;
      clip-path: var(--card-media-shape, none);
    }

    .card__body {
      padding: var(--space-m, 1.5rem);
      display: flex;
      flex-direction: column;
      gap: var(--space-xs, 0.75rem);
      flex-grow: 1;
    }

    /* Card title uses cqi-aware fluid sizing so it scales with the
       card width, not the viewport. clamp() floor keeps text legible
       in tiny cards; cap keeps display cards from looking shouty. */
    h3, .card__title {
      color: var(--text-strong, inherit);
      font-size: clamp(1rem, 4cqi, var(--step-2, 1.4rem));
      line-height: 1.2;
      margin: 0;
    }

    p {
      color: var(--text-muted, inherit);
      margin: 0;
    }

    .card__footer {
      padding: var(--space-s, 1rem) var(--space-m, 1.5rem);
      border-block-start: 1px solid var(--border-subtle, currentColor);
      display: flex;
      gap: var(--space-s, 1rem);
      align-items: center;
      justify-content: space-between;
    }
  }

  /* ── PROSE — article/blog content typography ───────────────────── */
  /* Lower bound `to (...)` prevents .prose's element-selector
     typography (p, a, ul, figure, blockquote, code) from leaking into
     nested components. A .card embedded inside a .prose article keeps
     its own typography; same for .nav, .btn, and any sibling
     escape-hatch via .no-prose. Per kizu (blog.kizu.dev/scope-selector-nuance/). */
  @scope (.prose) to (.card, .btn, .nav, .alert, .form, .no-prose) {
    :scope {
      color: var(--text-body, inherit);
      max-inline-size: var(--measure, 65ch);
      font-size: var(--step-0, 1rem);
      line-height: 1.65;
    }

    h1, h2, h3, h4, h5, h6 {
      color: var(--text-strong, inherit);
      line-height: 1.15;
      margin-block-start: 1.5em;
      margin-block-end: 0.5em;
    }
    h1:first-child, h2:first-child, h3:first-child { margin-block-start: 0; }

    h1 { font-size: var(--step-5, 2.5rem); }
    h2 { font-size: var(--step-4, 2rem); }
    h3 { font-size: var(--step-3, 1.6rem); }
    h4 { font-size: var(--step-2, 1.4rem); }

    p { margin-block: 1em; }
    p:first-child { margin-block-start: 0; }
    p:last-child  { margin-block-end: 0; }

    a {
      color: var(--accent, currentColor);
      text-decoration: underline;
      text-underline-offset: 0.18em;
      text-decoration-thickness: 0.07em;
    }
    a:hover {
      text-decoration-thickness: 0.13em;
    }

    /* Pull quote */
    blockquote {
      margin-block: 1.5em;
      padding-inline-start: var(--space-m, 1.5rem);
      border-inline-start: 3px solid var(--accent, currentColor);
      color: var(--text-strong, inherit);
      font-style: italic;
      font-size: var(--step-1, 1.125rem);
    }

    /* Inline code */
    code {
      font-family: var(--font-mono, monospace);
      background: var(--surface-2, transparent);
      padding: 0.1em 0.35em;
      border-radius: var(--radius-sm, 0.25rem);
      font-size: 0.9em;
    }
    pre {
      background: var(--surface-2, transparent);
      padding: var(--space-s, 1rem);
      border-radius: var(--radius-md, 0.5rem);
      overflow-x: auto;
      font-size: var(--step--1, 0.875rem);
    }
    pre code {
      background: transparent;
      padding: 0;
    }

    /* Lists */
    ul, ol { padding-inline-start: 1.5em; margin-block: 1em; }
    li { margin-block: 0.4em; }

    /* Figures */
    figure { margin-block: 1.5em; }
    figcaption {
      color: var(--text-muted, inherit);
      font-size: var(--step--1, 0.875rem);
      margin-block-start: 0.5em;
      text-align: center;
    }

    /* Horizontal rule */
    hr {
      border: 0;
      border-block-start: 1px solid var(--border-subtle, currentColor);
      margin-block: 2em;
    }
  }

  /* ── NAV ───────────────────────────────────────────────────────── */
  @scope (.nav) {
    :scope {
      display: flex;
      flex-wrap: wrap;
      gap: var(--space-s, 1rem);
      align-items: center;
    }
    ul {
      display: flex;
      flex-wrap: wrap;
      gap: var(--space-s, 1rem);
      list-style: none;
      padding: 0;
      margin: 0;
    }
    a {
      color: var(--text-strong, inherit);
      text-decoration: none;
      padding-block: var(--space-2xs, 0.5rem);
      padding-inline: var(--space-xs, 0.75rem);
      border-radius: var(--radius-sm, 0.25rem);
      transition: background-color var(--motion-fast, 150ms) var(--motion-ease);
    }
    a:hover {
      background: var(--accent-soft, transparent);
    }
    a[aria-current="page"] {
      color: var(--accent, inherit);
      font-weight: 600;
    }
  }

  /* ── TAG / PILL ────────────────────────────────────────────────── */
  @scope (.tag) {
    :scope {
      display: inline-flex;
      align-items: center;
      gap: var(--space-3xs, 0.25rem);
      padding-block: var(--space-3xs, 0.25rem);
      padding-inline: var(--space-2xs, 0.5rem);
      border-radius: var(--radius-full, 9999px);
      background: var(--accent-soft, transparent);
      color: var(--accent-strong, var(--accent, currentColor));
      font-size: var(--step--1, 0.875rem);
      font-weight: 500;
      line-height: 1.2;
    }
  }

  /* ── ALERT ─────────────────────────────────────────────────────── */
  @scope (.alert) {
    :scope {
      padding: var(--space-s, 1rem) var(--space-m, 1.5rem);
      border-radius: var(--radius-md, 0.5rem);
      border-inline-start: 4px solid var(--accent, currentColor);
      background: var(--accent-soft, var(--surface-1, transparent));
      color: var(--text-strong, inherit);
      display: flex;
      gap: var(--space-s, 1rem);
      align-items: flex-start;
    }
    /* Variants — swap the local --accent to a tone primitive. The
       palette derivations (-soft, -strong) re-evaluate automatically,
       so the banner background, border, and any nested badges inherit
       the right ramp without restating literals. */
    :scope.alert--success {
      --accent: var(--success);
      --accent-soft: var(--success-soft);
      --accent-strong: var(--success-strong);
    }
    :scope.alert--warning {
      --accent: var(--warning);
      --accent-soft: var(--warning-soft);
      --accent-strong: var(--warning-strong);
    }
    :scope.alert--danger {
      --accent: var(--danger);
      --accent-soft: var(--danger-soft);
      --accent-strong: var(--danger-strong);
    }
  }

  /* ── FORM ──────────────────────────────────────────────────────── */
  /* Pairs with :has() form validation patterns in modern.css.
     The .field wrapper consumes --field-border which modern.css
     toggles based on :user-valid / :user-invalid state. */
  @scope (.form) {
    :scope {
      display: flex;
      flex-direction: column;
      gap: var(--space-m, 1.5rem);
      max-inline-size: var(--form-max, 32rem);
    }

    /* Field group — label + input + optional helper/error text. */
    .field {
      display: flex;
      flex-direction: column;
      gap: var(--space-2xs, 0.5rem);
    }

    label {
      font-weight: 500;
      color: var(--text-strong, currentColor);
      font-size: var(--step--1, 0.875rem);
    }
    /* Visual required-marker via :has() — purely cosmetic. The
       canonical "required" announcement comes from the input's
       `required` attribute, which the accessible-name pipeline reads
       as "required" in every screen reader. The pseudo-element
       asterisk announcement is inconsistent across NVDA/JAWS/VoiceOver
       (some read "asterisk", some skip silently) and you cannot
       aria-hidden a pseudo-element, so we treat the asterisk as
       *decoration only* — it is not the assistive cue. Uses --error
       (not --accent): a required asterisk in brand color reads like
       a link, not like a "mandatory" cue. */
    label:has(+ :required)::after,
    label:has(+ * :required)::after {
      content: " *";
      color: var(--error, currentColor);
    }

    input,
    select,
    textarea {
      padding: var(--space-xs, 0.75rem) var(--space-s, 1rem);
      border-radius: var(--radius-sm, 0.25rem);
      border: 1px solid var(--field-border, var(--border-subtle, currentColor));
      background: var(--surface-1, transparent);
      color: var(--text-strong, inherit);
      font: inherit;
      transition:
        border-color var(--motion-fast, 150ms) ease,
        background-color var(--motion-fast, 150ms) ease;
      /* Forced-colors safe — transparent outline survives the mode.
         2px meets WCAG SC 2.4.13 perimeter floor (Matuzović). */
      outline: 2px solid transparent;
      outline-offset: -2px;
    }
    /* Keyboard focus ring — re-asserted in @layer components so it beats
       the at-rest transparent outline above (same layer-order reason as
       .btn). Without this, focused inputs show a transparent outline. */
    input:focus-visible,
    select:focus-visible,
    textarea:focus-visible {
      outline: 3px solid var(--focus-ring, currentColor);
      outline-offset: 2px;
      box-shadow: 0 0 0 6px var(--focus-halo, Canvas);
    }
    textarea {
      min-block-size: 6lh;
    }
    /* field-sizing auto-grows the textarea to its content; gated with
       @supports to match capabilities.css. Limited support (no Firefox as
       of 2026) — degrades to the min-block-size floor where unsupported. */
    @supports (field-sizing: content) {
      textarea { field-sizing: content; }
    }

    /* Helper / error text under the field. */
    .field__help,
    .field__error {
      font-size: var(--step--1, 0.875rem);
      color: var(--text-muted, currentColor);
    }
    .field__error {
      color: var(--error);
    }
    /* Show error only when the field is invalid (paired with :has() pattern). */
    .field:not(:has(:user-invalid)) .field__error { display: none; }

    /* Fieldset — for grouped fields (e.g. radio/checkbox group). */
    fieldset {
      border: 1px solid var(--border-subtle, currentColor);
      border-radius: var(--radius-md, 0.5rem);
      padding: var(--space-s, 1rem) var(--space-m, 1.5rem) var(--space-m, 1.5rem);
      display: flex;
      flex-direction: column;
      gap: var(--space-s, 1rem);
    }
    legend {
      padding-inline: var(--space-2xs, 0.5rem);
      font-weight: 500;
      color: var(--text-strong, currentColor);
    }

    /* Checkbox / radio — inline layout with label. */
    .choice {
      display: flex;
      align-items: center;
      gap: var(--space-2xs, 0.5rem);
    }
    .choice input[type="checkbox"],
    .choice input[type="radio"] {
      inline-size: auto;
      padding: 0;
      accent-color: var(--accent, currentColor);
    }
    .choice label {
      font-weight: 400;
      font-size: var(--step-0, 1rem);
    }

    /* Actions row — submit + cancel layout. */
    .form__actions {
      display: flex;
      gap: var(--space-s, 1rem);
      align-items: center;
      flex-wrap: wrap;
      margin-block-start: var(--space-s, 1rem);
    }
  }

  /* ── TABLE — responsive data table ─────────────────────────────── */
  /* Apply .table to a <table>. Desktop: header rule + zebra rows.
     Phone (≤600px): each row becomes a card and each cell shows its
     column label from `data-label` — tables NEVER scroll horizontally
     on phone (framework hard rule). Every <td> must carry data-label.
     This is the one place a viewport media query is correct: the
     no-horizontal-scroll rule is defined against the phone viewport,
     and tables are typically page-level content, not container-scoped. */
  @scope (.table) {
    :scope {
      inline-size: 100%;
      border-collapse: collapse;
      font-size: var(--step--1, 0.875rem);
    }
    caption {
      text-align: start;
      font-weight: 600;
      color: var(--text-strong, inherit);
      margin-block-end: var(--space-2xs, 0.5rem);
    }
    th, td {
      padding: var(--space-2xs, 0.5rem) var(--space-s, 1rem);
      text-align: start;
      border-block-end: 1px solid var(--border-subtle, currentColor);
    }
    thead th {
      color: var(--text-strong, inherit);
      font-weight: 600;
      border-block-end: 2px solid var(--border-strong, currentColor);
    }
    tbody tr:nth-child(even) {
      background: var(--surface-1, transparent);
    }

    @media (max-width: 600px) {
      /* Hide the header visually but keep it for assistive tech. */
      thead {
        position: absolute;
        clip-path: inset(50%);
      }
      tr {
        display: block;
        margin-block-end: var(--space-s, 1rem);
        border: 1px solid var(--border-subtle, currentColor);
        border-radius: var(--radius-md, 0.5rem);
        overflow: hidden;
      }
      td {
        display: flex;
        justify-content: space-between;
        gap: var(--space-s, 1rem);
      }
      td::before {
        content: attr(data-label);
        font-weight: 600;
        color: var(--text-muted, inherit);
      }
      td:last-child { border-block-end: 0; }
    }
  }

  /* ── SKELETON — loading placeholder ────────────────────────────── */
  /* Token-colored placeholder for loading states. The shimmer sweep is
     a ::after band moved with transform (compositor-only, 60fps) and is
     opted IN only under no-preference — reduced-motion users get a
     static block, never a pulsing one. */
  @scope (.skeleton) {
    :scope {
      display: block;
      position: relative;
      overflow: hidden;
      background: var(--surface-2, currentColor);
      border-radius: var(--radius-sm, 0.25rem);
      min-block-size: 1em;
    }
    :scope.skeleton--text   { block-size: 0.8em; margin-block: 0.3em; }
    :scope.skeleton--circle { border-radius: var(--radius-full, 9999px); aspect-ratio: 1; }

    @media (prefers-reduced-motion: no-preference) {
      :scope::after {
        content: "";
        position: absolute;
        inset: 0;
        background: linear-gradient(
          90deg,
          transparent,
          color-mix(in oklch, var(--paper, white) 55%, transparent),
          transparent
        );
        transform: translateX(-100%);
        animation: cx-skeleton-sweep 1.4s ease-in-out infinite;
      }
    }
  }
  @keyframes cx-skeleton-sweep {
    to { transform: translateX(100%); }
  }
}
