/* CLIXHOUSE design system — intrinsic layout primitives
   ─────────────────────────────────────────────────────────────────
   Inspired by Heydon Pickering + Andy Bell's "Every Layout"
   (every-layout.dev, 2nd edition 2021). Twelve composable
   primitives that handle layout intrinsically — no @media break-
   points, no JS, no breakpoint guessing. Each layout reconfigures
   itself based on the content and the container it lives in.

   Not a faithful port: Stack, Sidebar, and Switcher were verified
   against the open Every Layout pages; the remaining nine
   primitives (Box, Center, Cluster, Cover, Grid, Frame, Reel,
   Imposter, Icon) draw on the same publicly described shapes plus
   third-party reference ports (Set Studio CUBE boilerplate,
   SmolCSS). Treat them as Every-Layout-influenced rather than
   line-for-line copies.

   "A primitive is something without its own meaning or purpose as
    such, but which can be used in composition to make something
    meaningful." — Heydon Pickering & Andy Bell

   The primitives:
     Stack       — vertical rhythm via owl selector ( * + * )
     Box         — bordered container with intrinsic padding
     Center      — horizontal-center + max-width + margin auto
     Cluster     — wrapping inline group with consistent gap
     Sidebar     — content + sidebar that wraps below at narrow
     Switcher    — N-column → 1-column flip via min() trick
     Cover       — full-viewport with centered + top + bottom
     Grid        — auto-fit + minmax + clamp() for fluid columns
     Frame       — aspect-ratio'd container for media
     Reel        — horizontal scroller (overflow-x: auto)
     Imposter    — absolute-positioned overlay (modal/badge anchor)
     Icon        — inline SVG sized to font-size, vertically centred

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

   Customize per-instance via CSS custom properties on the element.
   Example:  <div class="stack" style="--stack-space: var(--space-l)">

   References:
     https://every-layout.dev/
     Heydon Pickering — https://heydonworks.com/
     Andy Bell        — https://piccalil.li/
*/

@layer layout {

  /* ── STACK — vertical rhythm via lobotomized owl selector ─────── */
  /* "There's a curious aspect of design and visual organization,
      and that is that grouping things together causes them to be
      perceived as belonging together." — Heydon
     The owl selector `* + *` applies margin only to adjacent siblings
     and nothing else. No specificity inflation, no top-margin orphan. */
  .stack {
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
  }
  /* Defensive: zero block margins on direct children before the owl
     selector reapplies them. Without this, a Stack composed into a
     page that didn't load our reset (third-party shell, partial
     adoption) leaks UA element margins on top of --stack-space. Per
     every-layout.dev/layouts/stack/. */
  .stack > * {
    margin-block: 0;
  }
  .stack > * + * {
    margin-block-start: var(--stack-space, var(--space-m, 1.5rem));
  }
  /* Lets a Stack inside a flex parent fill available block-size so
     that `data-split-after` / `margin-block-end: auto` actually push.
     Per every-layout.dev. */
  .stack:only-child {
    block-size: 100%;
  }
  /* Recursive — every level of descendants gets stack rhythm. */
  .stack--recursive * + * {
    margin-block-start: var(--stack-space, var(--space-m, 1.5rem));
  }
  /* "Split-after" — push a specific child to the bottom of the stack. */
  .stack > [data-split-after] {
    margin-block-end: auto;
  }

  /* ── BOX — padded container with intrinsic border ──────────────── */
  /* Border + padding using a single token. The transparent outline
     trick (Manuel Matuzović) ensures forced-colors mode renders the
     visible boundary correctly. */
  .box {
    padding: var(--box-padding, var(--space-m, 1.5rem));
    border: var(--box-border-thickness, 1px) solid;
    border-color: var(--box-border-color, var(--border-subtle, currentColor));
    outline: 0.125rem solid transparent;
    outline-offset: calc(var(--box-border-thickness, 1px) * -1);
    background: var(--box-bg, var(--surface-1, transparent));
    color: var(--box-fg, inherit);
    border-radius: var(--box-radius, 0);
  }
  .box > * {
    color: inherit;
  }

  /* ── CENTER — horizontal centre with intrinsic max-width ───────── */
  /* "Don't apply margin: 0 auto to the element you want centered
      — apply it to a wrapper."

      DIVISION OF LABOR vs .container (utilities.css): .center caps to the
      READING MEASURE (~65ch) for prose/article width, content-box so the
      measure counts text not padding. .container caps to the PAGE SHELL
      (~72rem) for full-width section scaffolding. Prose → .center; page
      wrapper → .container. They are not interchangeable. */
  .center {
    box-sizing: content-box;
    max-inline-size: var(--center-max, var(--measure, 65ch));
    margin-inline: auto;
    padding-inline: var(--center-gutter, var(--space-s, 1rem));
  }
  .center--text { text-align: center; }
  .center--intrinsic { display: flex; flex-direction: column; align-items: center; }

  /* ── CLUSTER — wrapping inline group with consistent gap ──────── */
  /* For tag lists, button rows, nav links. Wraps on its own; no
      breakpoint needed.

      A11y trap (per alsacreations.com/article/lire/1951): applying
      `display: flex` to `<ul>` or `<ol>` strips the implicit "list"
      role in Safari/VoiceOver. Authors using .cluster (or .grid)
      on a list MUST add `role="list"` to keep the semantic for
      assistive technologies:
        <ul class="cluster" role="list"> ... </ul> */
  .cluster {
    display: flex;
    flex-wrap: wrap;
    gap: var(--cluster-space, var(--space-s, 1rem));
    justify-content: var(--cluster-justify, flex-start);
    align-items: var(--cluster-align, center);
  }

  /* ── SIDEBAR — content + sidebar, flips below at narrow ────────── */
  /* The clever bit: flex-basis on the sidebar combined with a target
      width on the content. When there's room the sidebar holds its
      width; otherwise content wraps below. No media query.

      Mark the sidebar child with `.side` — the `:has(> .side)` form is
      robust to a third element (comment node, <style>, accidental
      sibling), which the old positional :first/:last-child version
      silently broke. :has() is Baseline (~0.005ms/recalc). A legacy
      positional fallback keeps existing two-child markup working when no
      child is marked `.side`. */
  .sidebar {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sidebar-gap, var(--space-m, 1.5rem));
  }
  /* Preferred named-child form — survives extra children. */
  .sidebar > .side {
    flex-basis: var(--sidebar-width, 20rem);
    flex-grow: 1;
  }
  .sidebar:has(> .side) > :not(.side) {
    flex-basis: 0;
    flex-grow: 999;
    min-inline-size: var(--sidebar-content-min, 50%);
  }
  /* Legacy positional fallback — only when no .side is marked. */
  .sidebar:not(:has(> .side)) > :first-child {
    flex-basis: var(--sidebar-width, 20rem);
    flex-grow: 1;
  }
  .sidebar:not(:has(> .side)) > :last-child {
    flex-basis: 0;
    flex-grow: 999;
    min-inline-size: var(--sidebar-content-min, 50%);
  }
  /* Reverse: sidebar on the right (both forms). */
  .sidebar--right > .side { order: 2; }
  .sidebar--right:not(:has(> .side)) > :first-child { order: 2; }
  .sidebar--right:not(:has(> .side)) > :last-child  { order: 1; }

  /* ── SWITCHER — N-column to 1-column flip ──────────────────────── */
  /* Equal-width columns until container narrows past --switcher-
      threshold; then stacks. The math: each child gets
      `min(100%, (threshold - 100%) * 999)` — collapses to 100%
      when below threshold, otherwise distributes. */
  .switcher {
    display: flex;
    flex-wrap: wrap;
    gap: var(--switcher-gap, var(--space-m, 1.5rem));
    align-items: var(--switcher-align, stretch);
  }
  .switcher > * {
    flex-grow: 1;
    flex-basis: calc((var(--switcher-threshold, 30rem) - 100%) * 999);
  }
  /* Cap at 5 children in a row before forcing 100% stack. Per Every
     Layout. CSS custom properties are not valid inside selectors —
     `:nth-last-child(n + var(--...))` silently drops the rule — so
     this cap is hard-coded. Sites that want a different cap fork
     this block. */
  .switcher > :nth-last-child(n + 5),
  .switcher > :nth-last-child(n + 5) ~ * {
    flex-basis: 100%;
  }

  /* ── COVER — full-viewport vertical layout ─────────────────────── */
  /* Hero / landing pattern: top region, centered content, bottom
      region. Uses min-block-size and flex centering. */
  .cover {
    display: flex;
    flex-direction: column;
    min-block-size: var(--cover-min, 100svb);
    padding: var(--cover-padding, var(--space-l, 2rem));
  }
  .cover > * { margin-block: var(--cover-gap, var(--space-m, 1.5rem)); }
  .cover > :first-child:not(.cover__center) { margin-block-start: 0; }
  .cover > :last-child:not(.cover__center)  { margin-block-end: 0; }
  .cover > .cover__center {
    margin-block: auto;
  }
  /* Toolbar-tracking variant: 100svb (the SMALL viewport) is the safe
     default — it never jumps when the mobile address bar collapses.
     .cover--dvb opts into the DYNAMIC viewport for a hero meant to fill
     the screen and re-flow as the toolbar hides. */
  .cover--dvb { --cover-min: 100dvb; }

  /* ── GRID — auto-fit columns with clamped min-width ────────────── */
  /* The single most useful intrinsic-responsive primitive. Columns
      fit as many as can be at least --grid-min wide; everything else
      wraps. No breakpoints.

      Same a11y trap as .cluster: `display: grid` on `<ul>`/`<ol>`
      strips the list role in Safari/VoiceOver. Use role="list" when
      the grid wraps a semantic list. */
  .grid {
    display: grid;
    grid-template-columns: repeat(
      auto-fit,
      minmax(min(var(--grid-min, 16rem), 100%), 1fr)
    );
    gap: var(--grid-gap, var(--space-m, 1.5rem));
    align-items: var(--grid-align, stretch);
  }

  /* ── FRAME — aspect-ratio'd media container ────────────────────── */
  /* Replaces the padding-bottom hack. Sets aspect-ratio and centers
      the child (img/video) using object-fit: cover. */
  .frame {
    aspect-ratio: var(--frame-ratio, 16 / 9);
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .frame > img,
  .frame > video {
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
  }

  /* ── REEL — horizontal scroller ────────────────────────────────── */
  /* For "see all" rows of cards. scroll-snap snaps to each item;
      scrollbar-color sets the bar tint to the accent. */
  .reel {
    display: flex;
    flex-wrap: nowrap;
    block-size: auto;
    overflow-x: auto;
    overflow-y: hidden;
    gap: var(--reel-gap, var(--space-s, 1rem));
    scroll-snap-type: x mandatory;
    scrollbar-color: var(--accent, currentColor) var(--surface-1, transparent);
  }
  .reel > * {
    flex: 0 0 var(--reel-item-width, auto);
    scroll-snap-align: start;
  }

  /* ── IMPOSTER — absolute-positioned overlay ────────────────────── */
  /* For badges, ribbons, modal-aligned-to-parent. The parent must be
      `position: relative` or otherwise be a positioned ancestor. */
  .imposter {
    position: absolute;
    inset-block-start: 50%;
    inset-inline-start: 50%;
    transform: translate(-50%, -50%);
    max-inline-size: 100%;
    max-block-size: 100%;
    overflow: auto;
  }
  .imposter--corner {
    inset-block-start: 0;
    inset-inline-end: 0;
    inset-inline-start: auto;
    transform: none;
  }

  /* ── ICON — inline SVG, sized to text ──────────────────────────── */
  /* Apply to <svg> directly. Sizes to font size, vertically aligns
      with text baseline, takes currentColor for stroke/fill. */
  .icon {
    inline-size: var(--icon-size, 0.95em);
    block-size: var(--icon-size, 0.95em);
    display: inline-block;
    vertical-align: -0.125em;
    fill: currentColor;
    stroke: currentColor;
    flex-shrink: 0;
  }

  /* ── CQ — container-query context + intrinsic fluid type ───────── */
  /* `.cq` makes an element a SIZE container so descendants respond to
      ITS width (cqi units / @container), not the viewport — the fix for
      "the same component looks wrong in a sidebar vs full-width".
      `.cq-fluid` fluid-sizes type against the container with a REM FLOOR
      (never pure cqi/vw — a unitless viewport floor defeats WCAG 1.4.4
      zoom; the rem term keeps it zoomable).

      INFINITE-LOOP INVARIANT: never apply the custom property a
      @container query READS to the same element the query STYLES, and
      never let a size-container be sized by its own contents in the
      queried axis — a container cannot query its own contribution to its
      size. Style a DESCENDANT. */
  .cq { container-type: inline-size; }
  .cq-named { container: var(--cq-name, none) / inline-size; }
  :where(.cq-fluid) {
    font-size: clamp(1rem, 0.85rem + 2cqi, var(--step-2, 1.4rem));
  }

  /* ── CARD-SUB — subgrid row alignment across a card grid ───────── */
  /* Drop .card-sub on a grid item whose internal rows (media / title /
      body / footer) should line up with its siblings'. It adopts the
      parent grid's row tracks via subgrid, so every card's footer sits on
      the same baseline regardless of body length. Requires a grid PARENT
      that spans the same rows. Subgrid is Baseline widely available
      2026-03-15; degrades to a normal grid item where unsupported. */
  .card-sub {
    display: grid;
    grid-template-rows: subgrid;
    grid-row: span var(--card-sub-rows, 3);
    gap: var(--card-sub-gap, var(--space-s, 1rem));
  }

  /* ── DENSITY — style-query-driven compact spacing ──────────────── */
  /* A parent sets `--density: compact` (every element is a STYLE
      container by default — no container-type needed); descendants
      tighten their spacing with no class on each one. Equality style
      queries: Chrome 111 / Safari 18 / Firefox 151. Degrades to normal
      spacing where unsupported. */
  @container style(--density: compact) {
    .stack > * + * { margin-block-start: var(--stack-space, var(--space-s, 1rem)); }
    .cluster { gap: var(--cluster-space, var(--space-2xs, 0.5rem)); }
    .grid { gap: var(--grid-gap, var(--space-s, 1rem)); }
  }
}
