/* cards — a template's MINIATURE: a real gallery page at a fraction of its size.
 *
 * ⚠⚠ MOVED OUT OF themes.html, 2026-09-13, UNCHANGED, so ONE stylesheet draws the
 * miniature wherever loupe shows a template: the picker on loupe.pictures, and
 * the app's own chooser, where an artist sees every template drawn with their
 * own photographs (app/static/engine/cards.css is a byte-for-byte copy).
 * TEMPLATES.md rule 7: a card is built from the same numbers as the page.
 *
 * ⚠ The container sets `--mock-scale` (themes.html's `.grid` derives it from
 * `--card-w`); the card element carries the preset's classes (LOUPE_RESOLVE),
 * so every token inside is the preset's own. The markup contract, as the card
 * builders write it:
 *   <div class="card {classes}"><a class="mock"><span class="inner behind-rest?">
 *     .mgrid[.hero] > i*   |   .msheet > i[--fx --fy --fw --fh]*   |   .credit > b + span, .rail > i > img
 */

/* ---- the miniature: a real entry page at one-third scale ---- */

.mock {
  position: relative;
  display: block;
  /* ⚠ 100%, so it fills the card's content box exactly whatever that is —
     `--mock-scale` is derived from the same width, so the page inside is
     scaled to what the mock actually shows and nothing is cropped. */
  width: 100%;
  /* the inner page is 675 tall here; 675 × scale is what that becomes */
  height: calc(675px * var(--mock-scale, 0.32));
  overflow: hidden;
  background: var(--ground);
}

/* ⚠⚠ A GRID CARD IS SQUARE, BECAUSE A GRID PAGE IS NOT A SCREENFUL.
   Measured on the live pages at a 1200x675 viewport: case runs 0.85:1
   (portrait), gallery-wall, darkroom and salon 1.12:1, contact and slate
   1.32:1. Not one of them is the 1.78:1 the card used to be, so a landscape
   miniature was showing between 48% and 74% of the page and cropping the
   rest — hiding the very thing the family is for, which is a body of work
   running DOWN the page rather than filling a screen.
   ⚠ SEQUENCE STAYS 16:9. That family really is one screenful: one work fills
   the viewport and nothing scrolls, so a landscape card is the honest shape.
   ⚠⚠ AND SHEET MUST STAY 16:9 TOO, for a harder reason than taste. A sheet's
   frames are [x, y, w, h] in PERCENT of the stage — `calc(var(--fx) * 1%)`
   below — so the stage's aspect IS the arrangement. Make the card taller and
   every rectangle in every arrangement stretches with it, and the card stops
   previewing the shape it exists to draw. The real stage is viewport-shaped,
   so the card must be too. */
/* ⚠ A grid card's inner page is 1200 tall rather than 675, so its rendered
   height is 1200 × scale — which is the card width. Square, as the note says,
   and now square at any width rather than only at 384. */
.fam-grid .mock { height: calc(1200px * var(--mock-scale, 0.32)); }
.fam-grid .mock .inner { height: 1200px; }

/* A real page, at page size, scaled by one constant: 384 / 1200. Every
   token inside is used at its true value, so the miniature's proportions
   are the gallery's proportions and not an approximation of them. */
.mock .inner {
  position: absolute;
  top: 0;
  left: 0;
  width: 1200px;
  height: 675px;
  /* ⚠ WAS THE LITERAL 0.32, which is 384/1200 typed out. Derived now, so the
     miniature is still a real page scaled whole — just by a number that is
     computed rather than repeated. */
  transform: scale(var(--mock-scale, 0.32));
  transform-origin: top left;
  /* The mock is 675px tall standing in for a page whose vmin is 675, so a
     --plate-inset of 15 is 15% of 675 = 6.75px per unit. That is how a
     matted theme shows its mat here without the viewport being involved. */
  padding: calc(var(--plate-inset, 0) * 6.75px);
  /* ⚠ border-box: the mat is INSIDE the page's 1200×675, never added to it — content-box made every matted
     miniature's page 108px wider and taller than its card, its rail and credit pushed off the foot (2026-09-13) */
  box-sizing: border-box;
  background: var(--ground);
}
/* ⚠ A SHEET HAS NO MAT to show — its frames are placed on the page itself — and the mat's padding made a matted
   sheet's page 1307x783 inside a 1200x675 card, so album and ledger drew off-centre and cut at the foot (the
   owner, 2026-09-13, "centering is off here also"). */
.fam-sheet .mock .inner { padding: 0; }

/* ---- what stands behind the works ----
   ⚠⚠ DRAWN FROM THE PRESET, NOT FROM THE FAMILY, and it is its own layer
   rather than .inner's background because it has to be DIMMED. The settled
   state of a gallery is not a photograph at full strength: a sequence steps
   its last plate back to --rest once the row is released, and a grid with a
   backdrop holds it at .plate.backdrop's 0.34. Painting it on .inner meant
   one opacity for the plate and the works together, which is why it was
   drawn at full strength and therefore wrongly.
   ⚠ Before this the card decided by family — a plate for every sequence,
   none for every grid — so the `splash` dial looked the same at "three
   images" as at "nothing, press a frame", and frieze's backdrop never
   appeared at all. LOUPE_BEHIND now answers for the card and the gallery
   alike. See presets.js.
   ⚠ inset, not background-origin: an absolutely positioned child resolves
   against the PADDING box, which is one mat wider than the content box the
   background used. The calc puts the mat back. */
.mock .inner.behind-rest::before,
.mock .inner.behind-backdrop::before {
  content: '';
  position: absolute;
  inset: calc(var(--plate-inset, 0) * 6.75px);
  background-image: var(--mock-plate);
  background-repeat: no-repeat;
  background-position: center;
  background-size: var(--plate-fit, cover);
}
/* The plate left standing behind the row, once the opening has settled.
 *
 * ⚠⚠ NOT `--rest`, AND THE DIVERGENCE IS DELIBERATE. The gallery rests its
 * plate at --rest (0.26) and this card does not, because TONE DOES NOT SCALE
 * THE WAY GEOMETRY DOES. Everything else in this miniature is a real page at
 * page size multiplied by 0.32, which is exactly right for lengths and
 * exactly wrong for luminance: across 1400px a photograph at 26% still has
 * readable structure and reads as a room, and at 384px the same composite is
 * a smudge. Measured on the three that broke — boardwalk composites to about
 * 20/255 against its near-black ground, and riso to about 185/255 against its
 * paper, so the plate vanished into the ground in both directions at once.
 *
 * ⚠ On riso that showed up in a specific and diagnostic way: with the plate
 * at 26% the GROUND showed through everywhere, so the corner scrim — a light
 * radial that should sit under the title in the lower left — read as though
 * it had spread across the whole frame. It had not; there was simply nothing
 * darker left for it to contrast against.
 *
 * ⚠ THE DIAL STILL WORKS, because the `splash` control is legible from the
 * plate's PRESENCE, not from its dimness: at "nothing — press a frame" there
 * is no plate at all. Dimming was never what made that control visible.
 *
 * ⚠ THIS IS THE NUMBER TO TURN if the cards still read wrong. 1 restores
 * exactly how they looked before the plate layer was dimmed at all;
 * boardwalk is the most sensitive to it, its ground being near-black. */
.mock .inner { --mock-rest: 0.85; --mock-backdrop: 0.15; }
/* ⚠⚠ LIGHT GROUNDS TAKE THE PLATE AT FULL STRENGTH. The 15% a mid or dark
   ground shows through is nearly free — boardwalk's near-black adds 1 of 255
   to the composite — but a museum-white wall showing through the same 15%
   adds 36, which is what made gesso and riso read as washed out even after
   the plate was lifted from 0.26 to 0.85. The ground is not neutral in the
   mix; its luminance is.
   ⚠ So on paper and wall the scrim lobe is the ONLY thing lightening the
   picture, which is what it is for — the rest of a full-bleed frame is the
   photograph at full strength. Owner's call 2026-08-31, on gesso and riso. */
.g-wall  .mock .inner,
.g-cloth .mock .inner,
.g-paper .mock .inner { --mock-rest: 1; }
.mock .inner.behind-rest::before { opacity: var(--mock-rest); }
/* ⚠ A centred rail recedes its plate to a backdrop — see the matching rule on
   .rail-centre in loupe.css.
   ⚠⚠ AND IT IS *NOT* LIFTED FOR SCALE, unlike --mock-rest. That compensation
   exists so a resting plate — which is still meant to be looked at — keeps
   some presence at 0.32. A backdrop is meant to be barely there, so lifting
   it defeats the only thing it is for: lifted to 0.5 the card read at 24/255
   against the gallery's 13, advertising frieze as twice as bright as it is.
   This one matches the page exactly. */
.rail-centre .mock .inner.behind-rest::before { opacity: var(--mock-backdrop); }
/* a work standing behind a grid — the engine's own .plate.backdrop values,
   dimmed hard on purpose so the frames read as the subject and not as
   debris on top of a photograph */
.mock .inner.behind-backdrop::before { opacity: 0.34; filter: saturate(0.85); }

/* the same corner shading the real page uses, so the credit reads */
.mock .inner::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    /* the rail's lobe — scaled by --scrim-rail, which a light ground sets to 0 */
    radial-gradient(ellipse 44% 62% at 92% 90%,
      rgba(var(--scrim-rgb) / calc(var(--scrim) * var(--scrim-rail, 1))), transparent 100%),
    /* the credit's lobe. ⚠ The `* 0.9` is the engine's and was missing here,
       so the card drew this lobe 11% stronger than the page it previews. */
    radial-gradient(ellipse var(--scrim-size, 40% 52%) at var(--scrim-at, 4% 94%),
      rgba(var(--scrim-rgb) / calc(var(--scrim) * 0.9)) 0%,
      rgba(var(--scrim-rgb) / calc(var(--scrim) * 0.9 * var(--scrim-falloff, 0.5))) 50%,
      transparent 100%);
}

/* ---- family-aware miniatures ----
   ⚠ The mock is a REAL page at page size, scaled by one constant, so these
   use the same tokens the gallery does — --cell-size, --cell-gap and the
   column count at their true values. A grid preset previews as a grid
   because it IS one, not because a picture of a grid was drawn. Before
   this, every card previewed as a sequence and the two new families
   misrepresented themselves on the one page whose job is to show them. */

.mock .mgrid {
  position: absolute;
  inset: 0;
  display: grid;
  align-content: center;
  justify-content: center;
  gap: var(--cell-gap, 18px);
  grid-template-columns: repeat(var(--grid-columns, 4), var(--cell-size, 154px));
}
.mock .mgrid i {
  display: block;
  width: var(--cell-size, 154px);
  height: var(--cell-size, 154px);
  background-size: cover;
  background-position: center;
  border: var(--thumb-border);
  border-radius: var(--thumb-radius);
  box-shadow: var(--thumb-shadow);
  filter: var(--thumb-filter);
}
/* the lead, when the preset carries one */
.mock .mgrid.hero i:first-child {
  grid-column: span 2; grid-row: span 2;
  width: calc(var(--cell-size, 154px) * 2 + var(--cell-gap, 18px));
  height: calc(var(--cell-size, 154px) * 2 + var(--cell-gap, 18px));
}
/* sheet: the REAL frames, absolutely placed, from the same numbers the
   gallery uses. ⚠ a finding worth stating, and the reason this matters:
   "THE SHAPES THEMSELVES, NOT THEIR NAMES — 'Relaxed' and 'Masonry' mean
   nothing to somebody who has never seen either, and a dropdown of words is
   a choice nobody can actually make." So the card draws the arrangement and
   the person picks the one that looks like what they want. */
/* ⚠⚠ THE PREVIEW'S BOX IS THE ARRANGEMENT'S SHAPE, 800:550 — found
   2026-09-11 when the frames became real photographic shapes. A frame is a
   percentage of its box; with `inset: 0` the box was the whole card, about
   16:9, so every frame drew ~1.22x too wide — 1:1 as 1.22, 2:1 as 2.44 —
   and the card disagreed with the gallery it previews. Invisible while the
   shapes were arbitrary; plain once they were meant to be exact.
   ⚠ Height-led and centred by transform, NOT inset: 0 + aspect-ratio — with
   both side insets set, an absolute box stretches to them and the ratio is
   ignored. */
.mock .msheet {
  position: absolute; top: 0; bottom: 0; left: 50%;
  height: 100%; aspect-ratio: 800 / 550;
  transform: translateX(-50%);
}
.mock .msheet i {
  position: absolute;
  left:   calc(var(--fx) * 1%);
  top:    calc(var(--fy) * 1%);
  width:  calc(var(--fw) * 1%);
  height: calc(var(--fh) * 1%);
  background-size: cover; background-position: center;
  border: var(--thumb-border); border-radius: var(--thumb-radius);
  box-shadow: var(--thumb-shadow); filter: var(--thumb-filter);
}

.mock .rail {
  position: absolute;
  right: 36px;
  bottom: 26px;
  z-index: 2;
  display: flex;
  gap: var(--thumb-gap);
}

/* a matted sequence's rail, centred under the photograph as the gallery draws it (rail-under, 2026-09-13) */
.rail-under .mock .rail { left: 36px; right: 36px; justify-content: center; }
.mock .rail i {
  position: relative;
  display: block;
  width:  var(--thumb-size);
  height: var(--thumb-size);
  border: var(--thumb-border);
  border-radius: var(--thumb-radius);
  background: var(--thumb-bg);
  box-shadow: var(--thumb-shadow);
  overflow: hidden;
  isolation: isolate;
}

.mock .rail i img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  filter: var(--thumb-filter);
}

/* ⚠⚠ THE CENTRED RAIL, mirroring `.rail-centre body:not(.section) #rail` in
   loupe.css. Without this the miniature drew frieze's row filed in the bottom
   right corner while its own recipe line underneath said `rail-centre` — the
   card contradicting itself about the one thing that preset IS.
   ⚠ The numbers are the engine's, resolved against the MOCK's page rather
   than the browser's: this is a 1200px-wide page at 0.32 scale, so the
   engine's 11.4vw is 136.8px here and its 1.9vw is 22.8px. `vw` cannot be
   used — in this document it means the browser viewport, not the page the
   miniature stands in for, so a card would resize with the window while the
   page it previews would not. */
.rail-centre .mock .rail {
  right: auto;
  bottom: auto;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  gap: clamp(9px, 22.8px, 30px);
}
.rail-centre .mock .rail i {
  width:  clamp(54px, 136.8px, 176px);
  height: clamp(54px, 136.8px, 176px);
}

/* The duotone preview mirrors the engine's rule exactly rather than
   approximating it — same grayscale, same screen over --duo-dark, same
   multiply by --duo-light. An earlier version left this axis unpreviewed,
   which made the switch look broken. */
.duo .mock .rail i { background: var(--duo-dark); }

.duo .mock .rail i img {
  filter: grayscale(1) contrast(var(--duo-contrast, 1.1));
  mix-blend-mode: screen;
}

.duo .mock .rail i::after {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--duo-light);
  mix-blend-mode: multiply;
}

.mock .credit {
  position: absolute;
  left: 36px;
  bottom: 28px;
  z-index: 2;
  line-height: 1.9;
  max-width: 520px;
}

.mock .credit b {
  display: block;
  font-weight: normal;
  font-family: var(--font-display);
  font-size: var(--title-size);
  letter-spacing: var(--title-tracking);
  text-transform: var(--title-case);
  color: var(--ink);
  text-shadow: var(--chrome-shadow);
}

.mock .credit span {
  display: block;
  font-family: var(--font-label);
  font-size: var(--sub-size);
  letter-spacing: var(--label-tracking);
  text-transform: var(--label-case);
  color: var(--ink-faint);
  text-shadow: var(--chrome-shadow);
}

