/* ==========================================================================
   LAYOUT — containers and the fixed-ratio image system
   ========================================================================== */

/* --- Containers ----------------------------------------------------------
   Width is chosen by role, not by nesting depth. A container never sets
   vertical rhythm; sections do that. */

.container,
.container--wide,
.container--text {
  width: 100%;
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.container       { max-width: var(--w-content); }
.container--wide { max-width: var(--w-wide); }
.container--text { max-width: calc(var(--w-text) + var(--gutter) * 2); }

/* Full-bleed escape for image runs that should touch the viewport edge. */
.bleed {
  width: 100vw;
  margin-inline: calc(50% - 50vw);
  padding-inline: 0;
}

.section {
  position: relative;
  z-index: var(--z-content);
  padding-block: var(--sp-section);
}

.section--deep { background: var(--paper-deep); }


/* --- The frame -----------------------------------------------------------
   CLAUDE.md §6. Every image sits in a fixed-ratio box and is centered
   inside it — never stretched, never cropped to fit. The frame absorbs odd
   source shapes so a viewer never notices the assets came from different
   projects, years and export settings.

   Three buckets only. Ratios live in tokens.css and were measured off the
   real files; see the note there. */

/* FLEX, NOT GRID — and this matters.
   With `display: grid; place-items: center`, a child's `height: 100%` has to
   resolve against a grid area whose block size comes from the frame's own
   `aspect-ratio`. That is a cyclic dependency, so the percentage falls back
   to `auto`, the image sizes to its natural ratio, and a portrait source
   overflows and gets cropped by `overflow: hidden` — the exact thing §6
   forbids. The Global Retail cover (0.757) painted 279x368 inside a 319x213
   frame before this was changed.

   In a flex container the height from aspect-ratio is definite to the item,
   so max-height resolves. Sizing with max-width/max-height and auto
   dimensions also means the element itself is letterboxed rather than
   object-fit painting inside a stretched box — the mat is real layout, so
   the frame background shows through exactly where it should. */

.frame {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  aspect-ratio: var(--frame-ratio, var(--ratio-wide));
  padding: var(--frame-pad, clamp(var(--sp-s), 2.5vw, var(--sp-m)));
  background: var(--frame-bg, transparent);
}

.frame > img,
.frame > video,
.frame > picture > img {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;   /* contain, never cover — cropping is not ours to do */
  object-position: center;
}

.frame--wide   { --frame-ratio: var(--ratio-wide); }
.frame--square { --frame-ratio: var(--ratio-square); }
.frame--tall   { --frame-ratio: var(--ratio-tall); }

/* A TALL FRAME MUST STILL FIT THE SCREEN IT IS READ ON.
   At 9:16 the frame's height is 1.78x the column width, so on a phone it
   comes out around 600-700px — taller than the strip of viewport left once
   the browser chrome and the masthead have taken theirs. The image inside was
   never cropped (object-fit is contain, and it measured 257x556 inside 335x596
   with 20px clear top and bottom), but you could not see it whole at any
   scroll position, which reads as cropped and amounts to the same thing.

   Capping the frame's height lets the aspect-ratio go unsatisfied, and the
   contained image simply gets smaller — the whole mobile screen is legible at
   once, and click-to-zoom is there for the detail. Kept off wide viewports,
   where the frame already fits and the cap would only shrink the work. */
@media (max-width: 46rem) {
  .frame--tall {
    max-height: 68svh;
    /* CENTRED, BECAUSE THE CAP MAKES IT NARROWER THAN ITS FIGURE.
       max-height caps the height, and aspect-ratio then pulls the WIDTH in to
       match — 298px inside a 335px figure at 375. The frame is a block with
       no margins, so it stayed left-aligned while the caption below it centres
       on the figure: a measured 18.3px offset, against 0 on every other figure
       on the page. Auto margins put the frame back on the figure's axis. */
    margin-inline: auto;
  }
}

/* Backing surface for flat and product shots — packaging, POS panels, any
   asset with its own baked-in background. One tone site-wide (§6), applied
   by adding this class, never by inventing a per-image colour. */
.frame--matted { --frame-bg: var(--surface); }

/* Photographs and in-situ shots sit directly on the paper: no mat, no pad,
   so the image itself carries the edge. */
.frame--bare {
  --frame-bg: transparent;
  --frame-pad: 0px;
}

/* Solo hero — exempt from the bucket system.
   Named --solo rather than --native to avoid colliding with .row--native
   below, which means something different (native pixel size, for the
   packaging renders). This one means "size to your own ratio".

   For images with no neighbour to match, a bucket only costs letterbox and
   buys nothing: the booths show-floor hero (0.76) would lose ~26% vertically
   in the tall frame, and the POS production hero (2.41) ~61% in the wide
   one. Both are named in CLAUDE.md §10 as among the strongest images on the
   site, so they size to themselves and are capped by viewport height rather
   than by a ratio. */
.frame--solo {
  aspect-ratio: auto;
  padding: 0;
}

.frame--solo > img {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: var(--solo-max-h, 88vh);
}

/* Contained dark frame (§7, §10). Two jobs: hard-quarantining Studio
   Eikira's gold so it cannot read as this site's palette, and giving the
   edge-to-edge black Flexx assets a ground — dropped straight onto cream
   they read as holes punched in the page. */
.frame--dark,
.contained-dark {
  --frame-bg: var(--ink);
  color: var(--paper);
  background: var(--ink);
}


/* --- Rows and stacks -----------------------------------------------------
   Side-by-side images must share a ratio (§6). .row sets the ratio once on
   the container and every frame inside inherits it, so a mismatched pair is
   awkward to build by accident rather than easy to ship. */

.row {
  display: grid;
  grid-template-columns: repeat(var(--row-count, 2), 1fr);
  gap: var(--row-gap, clamp(var(--sp-s), 2vw, var(--sp-m)));
  align-items: start;
}

/* This deliberately overrides .frame--wide/--square/--tall on children: a
   bucket class inside a .row is ignored, and the row's ratio wins. That is
   the rule from §6 made structural — "never mix wide and tall in the same
   row" cannot be violated by accident. To change a row's ratio, change the
   row (.row--tall), not the frames inside it. */
.row > .frame { --frame-ratio: var(--row-ratio, var(--ratio-wide)); }

.row--wide   { --row-ratio: var(--ratio-wide); }
.row--square { --row-ratio: var(--ratio-square); }
.row--tall   { --row-ratio: var(--ratio-tall); }

.row--3 { --row-count: 3; }
.row--4 { --row-count: 4; }

/* PHONE SCREENS AT ONE SCALE.
   The ratio sits just under the narrowest screen in the row, so every screen
   is bound by WIDTH rather than height. Equal widths mean the phone viewport
   inside each capture renders at the same scale — which is what a before and
   after have to do to be compared. Bound by height instead, a 0.489 capture
   beside a 0.462 one draws its UI about 6% larger.
   Tops aligned rather than centred, so a shorter capture ends early instead
   of floating in its mat, the way two phones laid side by side would.
   Capped in width: two phone screens stretched across the full figure are
   each over a thousand pixels tall. */
.row--screens {
  --row-ratio: 0.46;
  max-width: 44rem;
  margin-inline: auto;
}

.row--screens.row--3 { max-width: 62rem; }

.row--screens > .frame { align-items: flex-start; }

/* Rows collapse to a single column on narrow viewports. A four-up phone row
   at 375px is four unreadable slivers. */
@media (max-width: 46rem) {
  .row { grid-template-columns: 1fr; }
  .row--tall { grid-template-columns: repeat(2, 1fr); }

  /* A three-up tall row goes two-up here, which strands the third on a row
     of its own at the left. Centre it at its siblings' width, so the set
     keeps one scale and the page keeps its one axis. */
  .row--tall.row--3 > .frame:nth-child(3):last-child {
    grid-column: 1 / -1;
    justify-self: center;
    width: calc((100% - var(--row-gap, clamp(var(--sp-s), 2vw, var(--sp-m)))) / 2);
  }
}

/* For before/after pairs whose sources do not share a ratio. Stacking is
   the documented fallback (§16) — it is a correct outcome, not a failure. */
.stack {
  display: grid;
  gap: var(--sp-s);
}

/* Native-size run. The packaging renders are ~460px and cannot be
   re-exported; they run at roughly native size rather than upscaled (§10).
   This is the one place the near-full-width rule is deliberately broken. */
.row--native {
  --row-count: 3;
  max-width: 60rem;
  margin-inline: auto;
}

.row--native > .frame > img {
  width: auto;
  height: auto;
  max-width: 100%;
  image-rendering: auto;
}


/* --- Figures and captions ------------------------------------------------ */

figure { margin: 0; }

figure > figcaption {
  margin-top: var(--sp-xs);
  font-size: var(--type-small);
  line-height: var(--lh-snug);
  color: var(--ink-muted);
  max-width: var(--measure-narrow);
}

/* Caption belonging to a whole row rather than one image. */
.row-caption {
  margin-top: var(--sp-xs);
  font-size: var(--type-small);
  color: var(--ink-muted);
}


/* --- Copy blocks ---------------------------------------------------------
   Copy follows the hero image directly (§15) — it is not stranded in a
   narrow centered block at the bottom of a section. */

.copy > * + * { margin-top: var(--sp-s); }

.copy h3 + p { margin-top: var(--sp-xs); }
