/* CLIXHOUSE design system — opt-in 2026 native capabilities
   ─────────────────────────────────────────────────────────────────
   Browser-native features that ship cross-engine via Interop 2026
   and replace whole categories of JavaScript libraries. Sites
   opt in à la carte; nothing here is required. All patterns degrade
   gracefully in older browsers (the styles are no-ops there).

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

   Inventory (NON-MOTION natives — all motion lives in motion.css):
     Scroll-State Queries   — JS-free stuck/scrollable styling
     ::search-text          — style find-in-page highlight
     Caret Shape            — terminal-feel block caret
     Masonry                — grid-lanes waterfall layout
     Anchor Positioning     — [popover] auto-attaches to its trigger
     Field Sizing           — auto-grow textareas and inputs
     Interpolate Size       — enables animate-to-auto (consumed by motion.css)

   Replaces: Floating UI / Popper.js, auto-grow textarea JS, masonry libs.
   (Reveals, scroll-progress, parallax, sticky-shrink, page transitions,
    and overlay enter/exit moved to motion.css — the one motion home.)
*/

@layer components {

  /* NOTE — cross-document View Transitions (@view-transition + the
     named .vt-* recipes) moved to motion.css (the one motion home).

     NOTE — scroll-driven reveals (.reveal / .reveal-x / .reveal-scale /
     .reveal-blur) and the time-based .enter* family now live in motion.css
     (the one motion home), tokenized and data-motion-aware. The old
     random()-jitter-on-.reveal was removed: scroll-driven animations
     ignore time-based animation-delay, so it never staggered anything —
     stagger is a .enter (time-based) feature now. */

  /* NOTE — the scroll-progress bar and sticky-header shrink moved to
     motion.css (the one motion home), alongside parallax + scroll-fade. */

  /* ── SCROLL-STATE QUERIES — JS-free stuck / scrollable styling ─── */
  /* container-type: scroll-state lets a DESCENDANT react to its
     container being stuck (position: sticky pinned) or scrollable — no
     JS, no IntersectionObserver. Chromium 133+ only, NOT Baseline and
     not on Interop 2026, so it ships strictly @supports-gated and
     degrades to a correct static state (no shadow / hidden affordance).

     INVARIANT: a scroll-state container styles its DESCENDANTS, never
     itself. The element gets BOTH the trigger (position: sticky, or a
     scroll container) AND container-type: scroll-state; you then style
     something INSIDE it via @container scroll-state(...). Querying to
     style :scope itself does not work — the trap every shallow tutorial
     hits, encoded here once. */
  @supports (container-type: scroll-state) {
    /* Pinned sticky header: <header class="cx-stuck-ctx">
         <div class="cx-stuck-bar">…</div></header>  (header is sticky) */
    .cx-stuck-ctx {
      container-type: scroll-state;
      position: sticky;
      inset-block-start: 0;
    }
    @container scroll-state(stuck: top) {
      .cx-stuck-bar {
        box-shadow: var(--shadow-3);
        background: var(--surface-1, Canvas);
      }
    }

    /* "more below" affordance — visible only while the box can still
       scroll down: <div class="cx-scrollbox">…<span class="cx-scrollbox__more">↓</span></div> */
    .cx-scrollbox { container-type: scroll-state; }
    .cx-scrollbox__more {
      opacity: 0;
      transition: opacity var(--motion-base, 250ms) var(--motion-ease);
    }
    @container scroll-state(scrollable: bottom) {
      .cx-scrollbox__more { opacity: 1; }
    }
  }

  /* ── ::search-text — style find-in-page highlight ──────────────── */
  /* Per Igalia mrego's implementer notes. Independent of ::selection;
     style is applied to the browser's find-in-page match highlight.
     Safe to ship without @supports — engines that don't recognize
     the pseudo-element drop the rule silently. */
  ::search-text {
    background: var(--accent-soft, Mark);
    color: var(--text-strong, MarkText);
  }
  ::search-text:current {
    background: var(--accent, Mark);
    color: var(--text-on-accent, MarkText);
  }

  /* ── CARET-SHAPE — terminal-feel inputs ────────────────────────── */
  /* Per Igalia mrego on caret CSS properties (Chromium 139+). Opt-in
     via `.caret-block` on an input/textarea — useful for receipt-
     paper / terminal aesthetics (Fresas POS, cxwebmail compose).
     Falls back to the default bar caret in unsupporting engines. */
  @supports (caret-shape: block) {
    .caret-block {
      caret-shape: block;
      caret-animation: manual;
    }
  }

  /* ── MASONRY — reserved name for grid-lanes ────────────────────── */
  /* CSS Grid Level 3 masonry / waterfall layout. The CSS WG resolved
     on `display: grid-lanes` as the activation keyword (Safari TP 234
     ships it; Chromium tracking via flag). Reserving the `.masonry`
     class name now so that, once cross-engine, the framework can
     wire it without breaking sites that adopted a different keyword
     in the interim.

     Adopt today and the layout falls back to .grid auto-fit; once
     grid-lanes ships everywhere, swap the implementation here and
     every adopter inherits true masonry. */
  .masonry {
    display: grid;
    grid-template-columns: repeat(
      auto-fill,
      minmax(min(var(--masonry-min, 16rem), 100%), 1fr)
    );
    gap: var(--masonry-gap, var(--space-m, 1.5rem));
  }
  /* Activation: the CSS WG resolved on `display: grid-lanes` (Safari
     26.4 ships it; Chromium tracking). Test the RESOLVED keyword — the
     older `grid-template-rows: masonry` proposal activates on no shipping
     engine, so gating on it was a permanent no-op. Adopters get auto-fit
     grid until grid-lanes is present, then true masonry, no site change. */
  @supports (display: grid-lanes) {
    .masonry {
      display: grid-lanes;
    }
  }

  /* ── ANCHOR POSITIONING — popovers attach to trigger ───────────── */
  /* The native HTML popover API plus anchor positioning replaces
     every JS popover/tooltip library. Mark a trigger with
     `popovertarget="my-popover" style="anchor-name: --t-id"` and
     give the popover `position-anchor: --t-id`. The popover floats
     below the trigger by default; flips around if there's no room.

     A11y invariants (per hidde.blog/popover-accessibility):
       - The UA automatically wires aria-expanded on the invoking
         button. Do NOT also set it from JS — you'll fight the UA.
       - Tab order is reordered to put popover content right after
         the invoker regardless of DOM position (Chrome/Edge/Firefox;
         Safari does not yet set aria-details — assume DOM order
         there).
       - A `[popover]` without a role gets a fallback `group` role
         in Chrome/Edge/Firefox; Safari skips this. If the popover
         needs a specific role (menu, listbox, dialog), set it
         explicitly. */
  [popover] {
    margin: 0;
    border: 1px solid var(--border-strong, currentColor);
    border-radius: var(--radius-md, 0.5rem);
    background: var(--surface-1, Canvas);
    color: var(--text-body, CanvasText);
    padding: var(--space-s, 0.75rem);
    box-shadow: var(--shadow-4);
  }
  /* Full 4-position fallback chain — per the Zenn / cybozu_frontend
     anchor-positioning writeup, the canonical popover tries every
     edge before giving up. Order chosen so a tooltip near a viewport
     corner walks around the corner, not into it. */
  [popover]:popover-open {
    position-area: bottom span-right;
    position-try-fallbacks:
      top span-right,
      bottom span-left,
      top span-left;
    inset: auto;
    margin-block-start: 0.5rem;
  }

  /* Dialog invariants (per hidde.blog/dialog-modal-popover-differences):
       - A modal dialog is "the only thing that is not inert" — anchor
         the modal contract on <dialog>.showModal() (which sets
         top-layer + inert siblings) rather than aria-modal patches.
       - aria-modal has gaps in VoiceOver + Narrator; <dialog> is the
         load-bearing element, not the role.
       - alertdialog variants are always modal and require an initial
         focus target inside the dialog.
     Style block remains in modern.css under [popover], dialog — this
     is documentation only. */

  /* ── FIELD SIZING — auto-grow inputs/textareas ─────────────────── */
  /* Native CSS replacement for the auto-grow-textarea JS hack.
     Inputs and textareas size to their content.
     Support (per caniuse, May 2026): Chrome 123+, Safari 26.2+,
     Firefox unsupported (open ticket). Falls back to default
     sizing in unsupported engines — declaration is a no-op there. */
  @supports (field-sizing: content) {
    textarea {
      field-sizing: content;
      min-block-size: 3lh;
    }
  }

  /* ── INTERPOLATE-SIZE — animate to/from auto ───────────────────── */
  /* Chromium-only as of May 2026 (Chrome 129+; Firefox + Safari do
     not ship). Enabled in @layer reset via `:root { interpolate-size:
     allow-keywords }`. Components can `transition: height 250ms`
     between 0 and auto in supporting engines; elsewhere the height
     jump-cuts (the transition has no intermediate frames between
     discrete keywords). Gate visual effects you don't want to
     jump-cut via @supports. */
  @supports not (interpolate-size: allow-keywords) {
    /* Document the cliff — no rule needed; opt-in components should
       feature-detect with @supports (interpolate-size: allow-keywords)
       and provide a graceful fallback. */
  }
}
