/* ============================================================================
   layout-primitives.css -- the IBIX premium layout kit.
   Written 2026-08-01.

   WHAT THIS IS
   Six primitives that account for most of the structural difference between a
   site that reads "designed" and one that reads "assembled": one reusable grid
   module, graduated breakout, unequal gaps, varied section rhythm, container-
   query components, and subgrid alignment. Plus the masonry stub for when
   grid-lanes ships.

   SATISFIES (design/CHECKLISTS.md section B -- foundations)
   - One grid module per site, reused everywhere; spans vary, the grid does not.
   - Row-gap and column-gap are separate tokens and are unequal by default.
   - Section padding varies by content weight from a shared scale, rather than
     one value repeated on every section.
   - Prose measure and page-shell max-width are separate decisions.
   - All new layout CSS uses logical properties, so /ar/ mirrors with no
     second stylesheet (F-6, R 11 section 1).
   - Nothing depends on a feature below Baseline widely available; masonry is
     behind @supports and degrades to ordinary grid.
   Assertion IDs back-fill when section B is numbered; the assertions are the contract.

   BROWSER FLOOR (all Baseline widely available, all safe on this stack)
     CSS Grid ~95% (F-13) | clamp() ~94% (F-14) | logical properties ~97% (F-6)
     container size queries, Baseline Aug 2025, ~93% (F-10)
     subgrid, Baseline Mar 2026, ~90% (F-11) | :has() ~93% (F-12)
     dvh/svh/lvh, Baseline Jun 2025, ~93% (F-7)
   NOT safe: container STYLE queries (no Firefox, F-10) and masonry /
   display: grid-lanes (Safari 26.4 only, non-Baseline, F-23). Both appear
   below only as progressive enhancement.

   Corpus: R 01 sections 1/2/3/5/6/7/8/9, R 11 section 1, R 07 section 8,
   D-9, D-20, F-6, F-7, F-10, F-11, F-12, F-13, F-14, F-23, F-79, F-80.
   ========================================================================= */

:root {
  /* -- SPACING SCALE (R 01 section 2) ------------------------------------
     4px sub-step for micro-gaps, 8px multiples above. The 8pt grid has a real
     engineering rationale -- 8 divides cleanly across 1x/1.5x/2x/3x device
     pixel ratios and every common viewport width is a multiple of 8 -- but
     there is NO controlled usability study showing 8px beats 4px or 10px.
     It is practitioner consensus with a sound technical reason. Say exactly
     that if a client asks "why 8px". */
  --space-1: 0.25rem;  /*   4px  icon/label micro-gaps */
  --space-2: 0.5rem;   /*   8px  tight inline spacing */
  --space-3: 1rem;     /*  16px  default component padding */
  --space-4: 1.5rem;   /*  24px  card padding, form field gaps */
  --space-5: 2rem;     /*  32px  between related components */
  --space-6: 3rem;     /*  48px  between unrelated components */
  --space-7: 4rem;     /*  64px  small section padding (mobile) */
  --space-8: 6rem;     /*  96px  section padding (desktop) */
  --space-9: 8rem;     /* 128px  hero / major section separation */

  /* -- THE GRID MODULE ---------------------------------------------------
     ONE module, site-wide, hero to footer. Award-tier sites are not freeform:
     basement.studio runs a single .grid-layout utility across 16 structurally
     different elements (F-79). The discipline is invisible; the variation is
     not. */
  --grid-cols: 12;
  --grid-max: 1400px;   /* [convention]. basement.studio's own sections cap at
                           1920px (F-79) -- the grid module is the width
                           constraint, not a prose cap. */

  /* -- UNEQUAL GAPS ------------------------------------------------------
     Two tokens, deliberately different. Equal `gap` on both axes is the safe
     default, not the premium one.
     EVIDENCE, STATED HONESTLY (n=2): Obys Agency runs ~44px row-gap against
     ~8.9px column-gap, roughly 5:1 (F-80). basement.studio runs a UNIFORM 12px
     gutter (F-79). So one of the two live-inspected award sites uses unequal
     gaps, not both -- R 01's myths section overstates this as "every example
     checked". Treat unequal gaps as [house doctrine] worth defaulting to
     because it costs nothing and visibly breaks the spreadsheet feel, NOT as
     a measured law of award-winning design.
     The template-site contrast in R 01 section 9 is a hypothesis with n=0 --
     no template site was ever inspected (D-20). Do not repeat its specifics. */
  --grid-gap-col: clamp(0.5rem, 1vw, 0.75rem);
  --grid-gap-row: clamp(2rem, 4vw, 3rem);

  /* -- BREAKOUT WIDTHS (R 01 section 8B) --------------------------------- */
  --breakout-gutter: clamp(1rem, 6vw, 3rem);
  --breakout-popout: minmax(0, 2rem);
  --breakout-feature: minmax(0, 5rem);
}

/* ===========================================================================
   1. PAGE SHELL
   Shell max-width and prose measure are SEPARATE decisions. Most of a page is
   not reading-flow text, so capping the whole shell at the prose measure is
   how a site ends up looking like a blog. --measure lives in type-scale.css
   and belongs only on paragraph containers (R 01 section 7).
   ======================================================================== */
.shell {
  max-inline-size: var(--grid-max);
  margin-inline: auto;
  padding-inline: var(--space-4);
}

/* ===========================================================================
   2. THE GRID MODULE
   Column COUNT is the only thing that changes at a breakpoint. Breakpoints are
   for ARRANGEMENT; clamp() is for MAGNITUDE (R 01 section 3).
   Grid is writing-mode aware, so this mirrors under dir="rtl" with no extra
   CSS at all (R 11 section 1).
   ======================================================================== */
.grid-layout {
  display: grid;
  grid-template-columns: repeat(var(--grid-cols), minmax(0, 1fr));
  column-gap: var(--grid-gap-col);
  row-gap: var(--grid-gap-row);
  max-inline-size: var(--grid-max);
  margin-inline: auto;
  padding-inline: var(--space-4);
}

/* minmax(0, 1fr) not 1fr: a plain 1fr track has min-width:auto, so one long
   unbreakable string (a URL, a phone number, a long Arabic compound) pushes
   the track wider than its share and the whole page scrolls sideways. This is
   the single most common cause of horizontal overflow in a grid, and it is
   exactly what qa.py's overflow check catches at 360px. */

@media (max-width: 900px) { .grid-layout { --grid-cols: 6; } }
@media (max-width: 560px) { .grid-layout { --grid-cols: 4; } }

/* Span vocabulary. The site's variation comes from these, never from a
   different layout system per section. */
.col-span-2 { grid-column: span 2; }
.col-span-3 { grid-column: span 3; }
.col-span-4 { grid-column: span 4; }
.col-span-6 { grid-column: span 6; }
.col-span-8 { grid-column: span 8; }
.col-span-full { grid-column: 1 / -1; }

@media (max-width: 560px) {
  /* Below 4 columns everything that spanned more than the grid must collapse,
     or it silently overflows. */
  .col-span-6, .col-span-8 { grid-column: 1 / -1; }
}

/* ===========================================================================
   3. SECTION RHYTHM
   Uniform section padding repeated on every section is the clearest tell that
   nobody made a decision. Vary it by content weight, from the shared scale.
   Set it in the markup so the intent is visible where the section is:
       <div class="section" style="--section-pad: var(--space-9)"> ... </div>
   (Custom properties in a style attribute are fine -- qa.py only objects to
   inline styles that set responsive PROPERTIES, which no media query can
   override.)

   LONGHAND, DELIBERATELY [house rule]: padding-block-start / -end rather than
   the padding-block shorthand, and never the four-value `padding` shorthand.
   A later shorthand silently resets the inline axis a component was relying
   on, and that failure is invisible until a specific breakpoint. Longhands
   cannot do that.
   ======================================================================== */
.section {
  padding-block-start: var(--section-pad, var(--space-8));
  padding-block-end: var(--section-pad, var(--space-8));
}
.section--hero { --section-pad: var(--space-9); }
.section--dense { --section-pad: var(--space-7); }
.section--flush { --section-pad: 0; }

/* ===========================================================================
   4A. BREAKOUT -- SIMPLE (Josh Comeau's 3-column wrapper)
   For trades and local-business sites: a centred column with occasional
   full-bleed sections. No 100vw scrollbar hack, no JS.
   ======================================================================== */
.wrapper {
  --viewport-padding: var(--space-4);
  display: grid;
  grid-template-columns: 1fr min(65ch, 100%) 1fr;
  padding-inline: var(--viewport-padding);
}
.wrapper > * { grid-column: 2; }
.wrapper > .full-bleed {
  grid-column: 1 / -1;
  margin-inline: calc(var(--viewport-padding) * -1);
}

/* ===========================================================================
   4B. BREAKOUT -- GRADUATED (Ryan Mulligan's named-line grid)
   For editorial and portfolio-tier sites. Four widths, so images can break out
   further than pull quotes, which break out further than body text -- which is
   how award-tier editorial layouts actually behave. Named lines mean a child
   just says `grid-column: feature` and never computes a width.
   ======================================================================== */
.content {
  --gap: var(--breakout-gutter);
  --full: minmax(var(--gap), 1fr);
  --content-width: min(65ch, 100% - var(--gap) * 2);
  display: grid;
  grid-template-columns:
    [full-start] var(--full)
    [feature-start] var(--breakout-feature)
    [popout-start] var(--breakout-popout)
    [content-start] var(--content-width) [content-end]
    var(--breakout-popout) [popout-end]
    var(--breakout-feature) [feature-end]
    var(--full) [full-end];
}
.content > * { grid-column: content; }
.content > .popout { grid-column: popout; }
.content > .feature { grid-column: feature; }
.content > .full { grid-column: full; }

/* ===========================================================================
   5. CONTAINER-QUERY CARD
   The card reflows on ITS OWN width, not the viewport's -- so the same
   component works in a 3-up grid and in a narrow sidebar with no variant
   class and no viewport media query. Baseline widely available since August
   2025 (F-10).
   Container SIZE queries only. Container STYLE queries are still partial with
   no Firefox support -- progressive enhancement only, never load-bearing.
   ======================================================================== */
.card-container {
  container-type: inline-size;
  container-name: card;
}
.card {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--border-subtle);
  border-radius: var(--space-2);
  background-color: var(--surface-1);
}
.card__media {
  aspect-ratio: 16 / 9;
  inline-size: 100%;
  border-radius: var(--space-1);
  background-color: var(--surface-2);
}
@container card (min-width: 380px) {
  .card {
    grid-template-columns: 140px 1fr;
    align-items: center;
  }
  .card__media { aspect-ratio: 1 / 1; }
}

/* :has() -- state a parent from its children, no JS class toggling (F-12). */
.field:has(input:invalid) { border-color: var(--danger); }
.grid-layout > *:has(> .full-bleed) { grid-column: 1 / -1; }

/* ===========================================================================
   6. SUBGRID ALIGNMENT
   The real use case: a row of cards whose eyebrow / title / body / CTA line up
   across all of them even though the content lengths differ. Before subgrid
   this needed fixed heights or a JS equal-height script; both were bugs
   waiting to happen. Baseline widely available since March 2026 (F-11).
   ======================================================================== */
.card-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  column-gap: var(--grid-gap-col);
  row-gap: var(--grid-gap-row);
}
.card-row > .card-aligned {
  display: grid;
  grid-row: span 4;                 /* eyebrow / title / body / cta */
  grid-template-rows: subgrid;
  gap: var(--space-2);
}
@supports not (grid-template-rows: subgrid) {
  /* Fallback: cards stay self-contained and simply do not cross-align.
     Content is never lost -- that is the whole test for a fallback. */
  .card-row > .card-aligned { grid-row: auto; grid-template-rows: none; }
}

/* ===========================================================================
   7. DELIBERATE LAYERING
   The cheapest "this was not a template" signal available on a static stack:
   place a grid cell, then absolutely position a caption, a stat or a rule
   against its corner so it overlaps the neighbouring cell. Obys carries ~96
   absolutely-positioned elements on first paint (F-80). No JS, no WebGL.
   Two to four per page is the dose; more reads as chaos.
   ======================================================================== */
.layer-host { position: relative; }
.layer {
  position: absolute;
  z-index: 1;
  inset-block-start: var(--layer-y, auto);
  inset-inline-end: var(--layer-x, auto);
  pointer-events: none;   /* decoration must never eat a tap */
}
@media (max-width: 560px) {
  /* Layered overlaps are a desktop composition device. On a phone they
     collide with the content they were meant to decorate. */
  .layer { position: static; }
}

/* ===========================================================================
   8. MASONRY -- STUB ONLY, DO NOT BUILD ON IT
   The property landed as `display: grid-lanes` (CSSWG, Jan 2025; Grid Lanes
   direction announced Dec 2025), shipped in Safari 26.4 only, with Chrome and
   Firefox still behind flags. Non-Baseline / Experimental (F-23).
   Anything written before 2026 that says `display: masonry` or "Item Flow"
   describes a road not taken.
   Ship the dense-packing fallback; the @supports block upgrades itself the day
   the browsers arrive, with no code change.
   ======================================================================== */
.masonry {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(260px, 100%), 1fr));
  grid-auto-flow: row dense;
  column-gap: var(--grid-gap-col);
  row-gap: var(--space-4);
}
@supports (display: grid-lanes) {
  .masonry {
    display: grid-lanes;
    grid-auto-flow: row;
  }
}

/* ===========================================================================
   9. RTL -- THE FOUR LEFTOVERS
   Grid, flexbox, and every logical property above already mirror themselves.
   Only these four have no logical equivalent and need an explicit override
   (R 11 section 1): transform, box-shadow offsets, background-position, and
   gradients with physical direction keywords.
   ======================================================================== */
[dir="rtl"] .icon-arrow,
[dir="rtl"] .icon-chevron,
[dir="rtl"] .breadcrumb-separator { transform: scaleX(-1); }

/* Do NOT mirror: clocks, media transport controls, camera, checkmarks, chart
   axes, phone numbers, brand names. Mirror what encodes reading order or
   spatial meaning; leave what encodes time, real-world direction, or a
   convention shared across scripts (R 11 section 3). */

[dir="rtl"] .hero-media { background-position: right center; }
[dir="rtl"] .panel-lift { box-shadow: -8px 8px 24px rgb(0 0 0 / .35); }

/* Carousel tracks need LTR geometry with RTL content inside -- already handled
   in ibix-base.css; noted here so nobody re-solves it. */

/* ---------------------------------------------------------------------------
   FORBIDDEN IN THIS LAYER
   1. A second layout system for one section. One grid module, varied spans.
   2. Plain `gap: 24px` on a multi-row grid. Use the two tokens.
   3. The same padding value on every section (R 01 section 6).
   4. `1fr` without minmax(0, 1fr) on a text-bearing grid track -- guaranteed
      horizontal overflow the first time a long string appears.
   5. New physical properties (margin-left, padding-right, top/left) in any new
      CSS. Logical only; convert old rules opportunistically, never in a
      big-bang rewrite (R 11 section 1).
   6. Building anything on masonry / grid-lanes, container style queries, or
      any other non-Baseline feature without an @supports fallback that still
      shows the content.
   7. backdrop-filter (and its -webkit-backdrop-filter twin) on a position:
      fixed or sticky element -- the blurred region repaints every scroll
      frame, a documented iOS Safari jank pattern. Use a near-opaque solid
      background: rgba(10, 10, 10, .92) (D-9, R 07 section 8). Wherever blur IS
      used, both spellings ship together or Safari silently renders nothing.
   8. Quoting R 01 section 9's "template site" column to anyone. n=0 (D-20).
   ------------------------------------------------------------------------ */
