/* ------------------------------------------------------------
   1. Design tokens
   ------------------------------------------------------------ */
:root {
  /* ------------------------------------------------------------
     The pattern library was prototyped against its own copy of the
     palette. Inside the theme there is already one, published by
     theme.json as --wp--preset--color--*, so these are aliases
     rather than a second set of values: change bronze in
     theme.json and it changes here.

     The same names are re-aliased by the ground() mixin in
     abstracts/_mixins.scss, which is what lets a pattern dropped on
     the matt ink board pick up the inverted palette instead of
     printing paper-coloured text on charcoal.

     Fallbacks are the prototype's values, so the file still stands
     up on its own if it is ever opened outside WordPress.
     ------------------------------------------------------------ */
  --paper:        var(--wp--preset--color--paper, #F3F2EF);
  --paper-raised: var(--wp--preset--color--paper-raised, #FAF9F7);
  --paper-sunken: var(--wp--preset--color--paper-sunken, #E9E7E2);
  --ink:          var(--wp--preset--color--ink, #1B1A18);
  --ink-soft:     var(--wp--preset--color--ink-soft, #5A5750);
  --line:         var(--wp--preset--color--line, #D9D6D0);
  --line-strong:  var(--wp--preset--color--line-strong, #ABA79F);
  --bronze:       var(--wp--preset--color--bronze, #79684C);
  --shadow-gap:   var(--richmond-shadow-gap, rgba(20, 18, 15, 0.42));

  /* Type — mirrors theme.json's font families. */
  --font-ui:   var(--wp--preset--font-family--ui, "Instrument Sans", "Helvetica Neue", Arial, sans-serif);
  --font-mono: var(--wp--preset--font-family--mono, "IBM Plex Mono", "SFMono-Regular", Menlo, monospace);

  /* Spacing — 8px base grid, mirroring theme.json's spacing scale. */
  --s-1: 0.5rem;
  --s-2: 1rem;
  --s-3: 1.5rem;
  --s-4: 2rem;
  --s-5: 3rem;
  --s-6: 4.5rem;
  --s-7: 6.5rem;
  --measure: 38rem;
  --page-max: 68rem;
  --page-pad: clamp(1.25rem, 4vw, 3rem);

  /* Easing and duration are the effect's own, not brand tokens. */
  --ease-open:  cubic-bezier(0.32, 0.00, 0.20, 1.00);
  --ease-close: cubic-bezier(0.30, 0.10, 0.10, 1.00);
  --ease-press: cubic-bezier(0.45, 0.00, 0.55, 1.00);
  --t-press: 90ms;
  --t-fast:  250ms;
  --t-mid:   400ms;
  --t-slow:  600ms;
}

/* ------------------------------------------------------------
   19. Editorial — publication-grade layouts

   The principle: a magazine spread keeps its character because
   nothing sits at the same width as its neighbour. Collapsing
   everything to 100% on a phone throws that away, so the column
   grid survives at every size — 12 columns, then 8, then 6 —
   and figures go on indenting, offsetting and bleeding to one
   edge the whole way down. A phone gets a narrower spread, not
   a different document.

   Three devices carry the character:
     - asymmetric spans, never two neighbours alike
     - single-edge bleeds (a figure runs off one side only)
     - captions held to a narrow measure in their own column

   Layout transition: grid-column cannot be animated, but
   aspect-ratio and margin can. Figures transition both, so
   resizing the window re-proportions the composition smoothly
   rather than snapping between two states.
   ------------------------------------------------------------ */
.edition {
  --ed-cols: 12;
  --ed-gutter: clamp(0.7rem, 1.5vw, 1.4rem);
  --ed-pad: clamp(1.25rem, 5vw, 4rem);
  --rule: 1px solid var(--line);
}
@media (max-width: 64rem) { .edition { --ed-cols: 8; } }
@media (max-width: 40rem) { .edition { --ed-cols: 6; } }

.page { padding-inline: var(--ed-pad); }
.page + .page { padding-top: var(--s-6); }

/* Running head and folio — the furniture that says "printed" */
.runhead, .folio {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.runhead { padding-bottom: var(--s-2); border-bottom: var(--rule); }
.folio   { padding-top: var(--s-2); border-top: var(--rule);
           margin-top: var(--s-5); }
.folio .n { font-variant-numeric: tabular-nums; letter-spacing: 0.1em; }

/* The grid */
.ed {
  display: grid;
  grid-template-columns: repeat(var(--ed-cols), minmax(0, 1fr));
  column-gap: var(--ed-gutter);
  row-gap: var(--s-4);
  padding-block: var(--s-5);
}

/* ---- Figures ---- */
.fig { margin: 0; transition: aspect-ratio var(--t-mid) var(--ease-open),
                              margin var(--t-mid) var(--ease-open); }
.ph {
  position: relative;
  background: #E4E2DD;
  border: 1px solid rgba(0, 0, 0, 0.05);
  aspect-ratio: var(--r, 4 / 3);
  transition: aspect-ratio var(--t-mid) var(--ease-open);
}
/* a quiet crop marker, useful while reviewing */
.ph::after {
  content: attr(data-ratio);
  position: absolute;
  left: 8px; bottom: 6px;
  font-family: var(--font-mono);
  font-size: 0.58rem;
  letter-spacing: 0.12em;
  color: #A8A49C;
}

/* Single-edge bleeds. A figure runs off ONE side, which keeps a
   narrow screen from reading as a stack of equal blocks. */
.bleed-l, .bleed-l\@sm { margin-left: calc(var(--ed-pad) * -1); }
.bleed-r, .bleed-r\@sm { margin-right: calc(var(--ed-pad) * -1); }
/* the @sm variants bleed only where the figure actually reaches a
   page edge — above 1024px they sit back inside their column */
@media (min-width: 64.01rem) {
  .bleed-l\@sm { margin-left: 0; }
  .bleed-r\@sm { margin-right: 0; }
}

/* ---- Captions ---- */
.cap {
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--ink-soft);
  margin-top: var(--s-2);
  max-width: 22rem;
}
.cap b {
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: 0.62rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--bronze);
  margin-right: 0.5em;
}
/* A grouped caption block, as printed pages set them */
.cap-group { display: grid; gap: var(--s-2); }

/* ---- Typographic pieces ---- */
.ed-kicker {
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--bronze);
}
.ed-title {
  font-size: clamp(2.3rem, 7.5vw, 5.5rem);
  line-height: 0.96;
  letter-spacing: -0.035em;
  font-weight: 500;
  text-wrap: balance;
  margin: var(--s-2) 0 0;
}
.ed-stand {
  font-size: clamp(1.05rem, 1.6vw, 1.35rem);
  line-height: 1.45;
  color: var(--ink-soft);
  text-wrap: pretty;
}
.ed-body { font-size: 0.95rem; line-height: 1.65; }
.ed-body p + p { margin-top: var(--s-2); }
/* A true two-column text block, as a magazine would set it */
.ed-body--cols { column-count: 2; column-gap: var(--s-4); }
@media (max-width: 52rem) { .ed-body--cols { column-count: 1; } }

.ed-byline {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-soft);
  padding-top: var(--s-2);
  border-top: var(--rule);
}

/* Drop-in quote */
.ed-quote {
  font-size: clamp(1.3rem, 2.6vw, 2rem);
  line-height: 1.32;
  letter-spacing: -0.02em;
  text-wrap: balance;
  margin: 0;
}
.ed-quote cite {
  display: block;
  margin-top: var(--s-2);
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-style: normal;
  color: var(--ink-soft);
}

/* ---- Numbered editorial (the "practical tips" spread) ---- */
.tip { display: grid; grid-template-columns: 2.6rem 1fr; gap: var(--s-2); }
.tip .num {
  font-size: 2.1rem;
  line-height: 0.9;
  font-weight: 500;
  letter-spacing: -0.04em;
  color: var(--bronze);
  font-variant-numeric: tabular-nums;
}
.tip h3 { font-size: 1rem; margin-bottom: var(--s-1); }
.tip p { font-size: 0.88rem; line-height: 1.6; color: var(--ink-soft); }

/* ============ Placements ============
   Written out per spread rather than generated, because the
   whole point is that no two neighbours share a width. */

/* -- Spread 1 · opener -- */
.op-kick  { grid-column: 1 / span 12; }
.op-title { grid-column: 1 / span 9; }
.op-stand { grid-column: 8 / span 5; align-self: end; }
.op-hero  { grid-column: 1 / span 12; }
.op-by    { grid-column: 1 / span 4; }
@media (max-width: 64rem) {
  .op-kick { grid-column: 1 / span 8; }
  .op-title{ grid-column: 1 / span 8; }
  .op-stand{ grid-column: 3 / span 6; }
  .op-hero { grid-column: 1 / span 8; }
  .op-by   { grid-column: 1 / span 5; }
}
@media (max-width: 40rem) {
  .op-kick { grid-column: 1 / span 6; }
  .op-title{ grid-column: 1 / span 6; }
  .op-stand{ grid-column: 2 / span 5; }   /* indented, not full */
  .op-hero { grid-column: 1 / span 6; }
  .op-by   { grid-column: 2 / span 5; }
}

/* -- Spread 2 · the photo spread -- */
.s2-a    { grid-column: 2 / span 4; }
.s2-caps { grid-column: 2 / span 4; display: grid;
           grid-template-columns: 1fr 1fr; gap: var(--ed-gutter); }
.s2-b    { grid-column: 7 / span 6; grid-row: 1 / span 2; align-self: start; }
.s2-body { grid-column: 8 / span 5; }
.s2-c    { grid-column: 1 / span 6; }
.s2-ccap { grid-column: 8 / span 4; align-self: end; }
@media (max-width: 64rem) {
  .s2-a    { grid-column: 1 / span 5; }
  .s2-caps { grid-column: 1 / span 5; }
  .s2-b    { grid-column: 4 / span 5; grid-row: auto; }
  .s2-body { grid-column: 1 / span 6; }
  .s2-c    { grid-column: 1 / span 7; }  /* starts at the edge, so the bleed lands */
  .s2-ccap { grid-column: 2 / span 5; }
}
@media (max-width: 40rem) {
  /* Character preserved: A bleeds left and stays portrait, its
     captions sit in a narrow indented pair, B bleeds right, C
     runs the full measure. Nothing shares a width with its
     neighbour. */
  .s2-a    { grid-column: 1 / span 5; }
  .s2-caps { grid-column: 1 / span 5; grid-template-columns: 1fr; }
  .s2-b    { grid-column: 2 / span 5; }
  .s2-body { grid-column: 1 / span 5; }   /* offset from the figure above */
  .s2-c    { grid-column: 1 / span 6; }
  .s2-ccap { grid-column: 2 / span 5; }
}

/* -- Spread 3 · numbered tips -- */
.s3-head { grid-column: 1 / span 5; }
.s3-img1 { grid-column: 7 / span 6; grid-row: 1 / span 2; }
.s3-1 { grid-column: 1 / span 5; }
.s3-2 { grid-column: 1 / span 5; }
.s3-3 { grid-column: 7 / span 3; }
.s3-4 { grid-column: 10 / span 3; }
.s3-img2 { grid-column: 1 / span 5; }
.s3-5 { grid-column: 7 / span 6; }
@media (max-width: 64rem) {
  .s3-head { grid-column: 1 / span 6; }
  .s3-img1 { grid-column: 1 / span 8; grid-row: auto; }
  .s3-1, .s3-2 { grid-column: 1 / span 4; }
  .s3-3, .s3-4 { grid-column: 5 / span 4; }
  .s3-img2 { grid-column: 1 / span 6; }  /* ditto */
  .s3-5 { grid-column: 1 / span 5; }
}
@media (max-width: 40rem) {
  /* The numerals keep their gutter, so the list still reads as
     a numbered editorial rather than a stack of paragraphs. */
  .s3-head { grid-column: 1 / span 6; }
  .s3-img1 { grid-column: 2 / span 5; }
  .s3-1, .s3-3 { grid-column: 1 / span 5; }
  .s3-2, .s3-4 { grid-column: 2 / span 5; }
  .s3-img2 { grid-column: 1 / span 5; }
  .s3-5 { grid-column: 2 / span 5; }      /* offset from the figure above */
}

/* -- Spread 4 · article with grouped captions -- */
.s4-body { grid-column: 1 / span 3; }
.s4-a    { grid-column: 5 / span 4; }
.s4-b    { grid-column: 9 / span 4; }
.s4-caps { grid-column: 5 / span 3; }
.s4-c    { grid-column: 9 / span 4; }
@media (max-width: 64rem) {
  .s4-body { grid-column: 1 / span 4; }
  .s4-a    { grid-column: 5 / span 4; }
  .s4-b    { grid-column: 4 / span 5; }  /* ends at col 8, so the right bleed lands */
  .s4-caps { grid-column: 6 / span 3; }
  .s4-c    { grid-column: 2 / span 7; }
}
@media (max-width: 40rem) {
  .s4-body { grid-column: 1 / span 6; }
  .s4-a    { grid-column: 2 / span 5; }
  .s4-b    { grid-column: 3 / span 4; }  /* ends at col 6 */
  .s4-caps { grid-column: 1 / span 5; }
  .s4-c    { grid-column: 1 / span 6; }
}

/* -- Spread 5 · full-bleed quote on the matt ground -- */
.spread-dark {
  margin-inline: calc(var(--ed-pad) * -1);
  padding: var(--s-6) var(--ed-pad);
}
.s5-quote { grid-column: 2 / span 8; }
.s5-meta  { grid-column: 11 / span 2; align-self: end; }
@media (max-width: 64rem) {
  .s5-quote { grid-column: 1 / span 7; }
  .s5-meta  { grid-column: 1 / span 4; }
}
@media (max-width: 40rem) {
  .s5-quote { grid-column: 1 / span 6; }
  .s5-meta  { grid-column: 2 / span 5; }
}

/* ---- Entry transition ----
   Figures and text blocks rise 14px and settle as they enter.
   One movement, damped, no parallax. */
.rise {
  opacity: 0;
  transform: translateY(14px);
  transition:
    opacity var(--t-slow) var(--ease-open),
    transform var(--t-slow) var(--ease-open);
  transition-delay: calc(var(--d, 0) * 70ms);
}
.rise.is-in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .rise { opacity: 1; transform: none; transition: none; }
  .fig, .ph { transition: none; }
}

/* ------------------------------------------------------------
   21. Pattern library — composed layouts that take any copy

   The motion prototype (20) positions absolutely, which means
   authored row coordinates, which means each composition assumes
   one length of copy. That cannot survive a CMS.

   This separates the two concerns:

     LAYOUT     CSS Grid with named areas and auto rows, so a
                slot is sized by whatever is put in it. Text of
                any length is handled by the browser, natively.

     ANIMATION  FLIP — measure, invert, play. The browser lays
                out normally; the script measures before and
                after and animates with transforms, which do not
                participate in layout. Position still animates.

   FLIP normally distorts, because scaling a box scales its
   contents. It does not here, because every figure is locked to
   an aspect-ratio: a width change is therefore a UNIFORM scale.
   The ratio decision and the FLIP decision reinforce each other.

   A pattern is nothing but a name, a set of slots, and three
   grid-template-areas maps. That is the whole unit of reuse, and
   it is small enough to hand to an editor as a block pattern.
   ------------------------------------------------------------ */
.pattern {
  display: grid;
  gap: var(--s-3) clamp(0.7rem, 1.5vw, 1.4rem);
  grid-auto-rows: min-content;      /* rows take their content */
  align-items: start;
}

/* Slots. A block carries one class; where it lands is the
   pattern's business, not the block's.

   Two spellings of each. Hand-written markup uses the short one;
   register_block_style() always prefixes what it applies with
   is-style-, and offers no way to opt out, so a slot chosen from
   the sidebar arrives as is-style-slot-a. Both are accepted here
   rather than rewriting classes at render time, which would fix
   the front end and leave the editor showing something else. */
.slot-a,
.is-style-slot-a { grid-area: a; }
.slot-b,
.is-style-slot-b { grid-area: b; }
.slot-c,
.is-style-slot-c { grid-area: c; }
.slot-capa,
.is-style-slot-capa { grid-area: capa; }
.slot-capb,
.is-style-slot-capb { grid-area: capb; }
.slot-capc,
.is-style-slot-capc { grid-area: capc; }
.slot-body,
.is-style-slot-body { grid-area: body; }
.slot-head,
.is-style-slot-head { grid-area: head; }
.slot-one,
.is-style-slot-one { grid-area: one; }
.slot-two,
.is-style-slot-two { grid-area: two; }
.slot-three,
.is-style-slot-three { grid-area: three; }
.slot-four,
.is-style-slot-four { grid-area: four; }
.slot-kicker,
.is-style-slot-kicker { grid-area: kicker; }
.slot-title,
.is-style-slot-title { grid-area: title; }
.slot-stand,
.is-style-slot-stand { grid-area: stand; }
.slot-by,
.is-style-slot-by { grid-area: by; }
.slot-caps,
.is-style-slot-caps { grid-area: caps; }
.slot-note1,
.is-style-slot-note1 { grid-area: note1; }
.slot-note2,
.is-style-slot-note2 { grid-area: note2; }

/* ---- The maps ----------------------------------------------
   Each composition is declared ONCE, as custom properties on the
   pattern. The switching rules below are generic, so adding a
   pattern means declaring six properties and nothing else.

   Critically, the switch is driven by media queries, not by
   script: the correct composition is applied before any
   JavaScript runs. The forcing classes only OVERRIDE it. The
   first version had this the wrong way round — layout depended
   on JS, so when mode detection misfired a phone got the
   twelve-column map and every slot collapsed to one word wide,
   with no fallback. Layout must never depend on script. */

[data-pattern="duet"],
.pattern--duet {
  --cols-lg: 6;
  --areas-lg:
    "a    a    b    b    b    b"
    "capa capa b    b    b    b"
    "body body body body capb capb";
  --cols-md: 4;
  --areas-md:
    "a    a    a    ."
    "capa capa .    ."
    "b    b    b    b"
    ".    .    capb capb"
    "body body body body";
  --cols-sm: 3;
  --areas-sm:
    "a    a    a"
    ".    capa capa"
    ".    b    b"
    "capb capb ."
    ".    body body";
}

[data-pattern="text-led"],
.pattern--text-led {
  --cols-lg: 6;
  --areas-lg:
    "body body a    a    a    a"
    "body body capa capa .    ."
    "b    b    b    b    b    b"
    ".    .    .    capb capb capb";
  --cols-md: 4;
  --areas-md:
    "body body body body"
    "a    a    a    a"
    ".    capa capa capa"
    "b    b    b    b"
    "capb capb capb .";
  --cols-sm: 3;
  --areas-sm:
    "body body body"
    "a    a    a"
    ".    capa capa"
    ".    b    b"
    "capb capb .";
}

[data-pattern="enumerated"],
.pattern--enumerated {
  --cols-lg: 6;
  --areas-lg:
    "head  head  head  .     a     a"
    "one   one   two   two   a     a"
    "three three four  four  .     .";
  --cols-md: 4;
  --areas-md:
    "head  head  head  head"
    "a     a     a     a"
    "one   one   two   two"
    "three three four  four";
  --cols-sm: 3;
  /* Alternating offsets keep the numbered rhythm on a phone.
     The narrow slots land at ~230px, which carries three or four
     words a line — the earlier map put them at ~78px, which is
     what produced a column one word wide. */
  --areas-sm:
    "head  head  head"
    "a     a     a"
    "one   one   one"
    ".     two   two"
    "three three three"
    ".     four  four";
}

/* ------------------------------------------------------------
   plate / plate-flip — one project, as an index entry

   Written for the projects archive rather than the inserter:
   these two are the repeating unit of a run, so unlike the six
   article compositions they are meant to alternate. plate sets
   the figure left and the text in a narrow right-hand column;
   plate-flip mirrors it, and the template swaps between them
   down the page. Nothing sits at the same width as the entry
   above it, which is the whole character rule.

   Two text rows only, and that is the whole trick. A figure
   spanning the text rows hands its slack out EQUALLY across
   them, so three stacked slots become three evenly spaced
   anchors and the numeral drifts away from the title it names.
   With two rows the slack falls in one place: title group at
   the head, credit at the foot of the plate, exactly where
   print puts them, whatever height the figure turns out to be.

   The numeral therefore travels inside the title slot rather
   than in a kicker of its own. The standfirst is dropped for
   the same reason — the opener carries the prose.

   At md and below the figure takes the full measure and the
   text drops beneath it: a four-column split would put the text
   column at ~225px, which is the width the enumerated map warns
   about. The mirror survives as an indent rather than a swap.
   ------------------------------------------------------------ */
[data-pattern="plate"],
.pattern--plate {
  --cols-lg: 6;
  --areas-lg:
    "a a a a title title"
    "a a a a by    by";
  --cols-md: 4;
  --areas-md:
    "a     a     a     a"
    "title title title ."
    "by    by    by    .";
  --cols-sm: 3;
  --areas-sm:
    "a     a     a"
    "title title title"
    "by    by    by";
}

[data-pattern="plate-flip"],
.pattern--plate-flip {
  --cols-lg: 6;
  --areas-lg:
    "title title a a a a"
    "by    by    a a a a";
  --cols-md: 4;
  --areas-md:
    "a     a     a     a"
    ".     title title title"
    ".     by    by    by";
  --cols-sm: 3;
  --areas-sm:
    "a     a     a"
    "title title title"
    "by    by    by";
}

[data-pattern="opener"],
.pattern--opener {
  --cols-lg: 6;
  --areas-lg:
    "kicker kicker kicker .      .     ."
    "title  title  title  title  .     ."
    ".      .      .      stand  stand stand"
    "a      a      a      a      a     a"
    "by     by     .      .      .     .";
  --cols-md: 4;
  --areas-md:
    "kicker kicker kicker ."
    "title  title  title  title"
    ".      stand  stand  stand"
    "a      a      a      a"
    "by     by     by     .";
  --cols-sm: 3;
  --areas-sm:
    "kicker kicker kicker"
    "title  title  title"
    ".      stand  stand"
    "a      a      a"
    "by     by     by";
}

[data-pattern="triptych"],
.pattern--triptych {
  --cols-lg: 6;
  --areas-lg:
    "a    a    b    b    b    b"
    "a    a    b    b    b    b"
    "c    c    c    caps caps caps";
  --cols-md: 4;
  --areas-md:
    "a    a    b    b"
    "c    c    c    c"
    "caps caps caps .";
  --cols-sm: 3;
  --areas-sm:
    "a    a    a"
    ".    b    b"
    "c    c    c"
    ".    caps caps";
}

[data-pattern="marginalia"],
.pattern--marginalia {
  --cols-lg: 6;
  --areas-lg:
    "a     a     a     body body body"
    "a     a     a     body body body"
    "note1 note1 note1 body body body"
    "note2 note2 note2 .    .    .";
  --cols-md: 4;
  --areas-md:
    "a     a     body body"
    "a     a     body body"
    "note1 note1 body body"
    "note2 note2 .    .";
  --cols-sm: 3;
  --areas-sm:
    "a     a     a"
    "body  body  body"
    ".     note1 note1"
    ".     note2 note2";
}

/* ---- The generic switch ----
   Wide by default; media queries step it down; a forcing class
   wins over both so the review controls still work. */
.pattern {
  --cols-n: var(--cols-lg);
  --areas:  var(--areas-lg);
  grid-template-columns: repeat(var(--cols-n), minmax(0, 1fr));
  grid-template-areas: var(--areas);
}
@media (max-width: 64rem) {
  .pattern:not(.force-lg):not(.force-sm) {
    --cols-n: var(--cols-md); --areas: var(--areas-md);
  }
}
@media (max-width: 40rem) {
  .pattern:not(.force-lg):not(.force-md) {
    --cols-n: var(--cols-sm); --areas: var(--areas-sm);
  }
}
.pattern.force-lg { --cols-n: var(--cols-lg); --areas: var(--areas-lg); }
.pattern.force-md { --cols-n: var(--cols-md); --areas: var(--areas-md); }
.pattern.force-sm { --cols-n: var(--cols-sm); --areas: var(--areas-sm); }

/* A marginal note: quieter than body, on the register line. */
.pattern .note {
  font-size: 0.8rem;
  line-height: 1.5;
  color: var(--ink-soft);
  border-left: 2px solid var(--bronze);
  padding-left: var(--s-2);
}
/* The grouped caption block, as printed pages set them. */
.pattern .slot-caps { display: grid; gap: var(--s-2); }

/* No slot may be starved: a grid child defaults to min-width
   auto, which lets a long word hold a column open — or, in a
   narrow slot, forces one word per line. */
.pattern > * { min-width: 0; }

/* ------------------------------------------------------------
   Core's constrained layout, disarmed inside a pattern

   A core/group with the default layout clamps every child to the
   content width and centres it with auto margins. Inside a grid
   that is not a small visual difference: each slot would be
   pinned to 68rem and centred in its own area, and the map would
   stop meaning anything.

   The pattern IS the layout, so the group's own is switched off
   for its children. Scoped to .pattern so no other group on the
   site is affected.
   ------------------------------------------------------------ */
.pattern > :where(*) {
  margin-left: 0;
  margin-right: 0;
  max-width: none;
}
.pattern p, .pattern .tip p { overflow-wrap: break-word; }
/* The numeral gutter shrinks with the slot rather than eating it. */
.pattern .tip { grid-template-columns: clamp(1.6rem, 8%, 2.6rem) 1fr; }

/* ---- Figures: ratio locked, which is what makes FLIP safe ---- */
.pattern .fig { margin: 0; }
.pattern .fig .ph { width: 100%; aspect-ratio: var(--ar, 4 / 3); }

/* ---- Text: fades through the change, so length is free ---- */
.pattern .is-text {
  opacity: 1;
  transition: opacity 425ms var(--ease-open) 300ms;
}
.pattern.is-swapping .is-text {
  opacity: 0;
  transition: opacity 225ms var(--ease-close);
}

/* Elements the script is animating must not fight it. */
.pattern .fig { will-change: transform; }

/* ---- Library chrome ---- */
.lib-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s-3); flex-wrap: wrap;
  padding-bottom: var(--s-2); border-bottom: 1px solid var(--line);
  margin-bottom: var(--s-4);
}
.lib-head h2 { font-size: 1.1rem; }
.lib-name {
  font-family: var(--font-mono); font-size: 0.66rem; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--bronze);
}
.lib-section + .lib-section { margin-top: var(--s-7);
  padding-top: var(--s-6); border-top: 1px solid var(--line); }

.ctl-bar {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap;
  padding: var(--s-2) 0; margin-bottom: var(--s-5);
  background: color-mix(in srgb, var(--paper) 92%, transparent);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--line);
}
.ctl-bar .sep { width: 1px; height: 1.4rem; background: var(--line); }

@media (prefers-reduced-motion: reduce) {
  .pattern .is-text, .pattern .fig { transition: none !important; }
}


/* ------------------------------------------------------------
   12. Reduced motion
   All transitions collapse to near-instant state changes and the
   JS checks the same media query to disable damped dragging.
   ------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 1ms !important;
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
  }
}

/* ------------------------------------------------------------
   WordPress integration

   Core renders an image block as figure > img, where the
   prototype had a .ph placeholder. The ratio lock has to reach
   both: it is not decoration, it is what makes FLIP scale a
   figure uniformly instead of distorting it (invariant 3.5).
   ------------------------------------------------------------ */
.pattern .fig :is(.ph, img) {
  width: 100%;
  aspect-ratio: var(--ar, 4 / 3);
}

.pattern .fig img {
  display: block;
  /* The box is sized by width and --ar; object-fit crops the image into it.
     A height here would fight the ratio rather than help it. */
  object-fit: cover;
}

/* Ratio block styles — the fallback sanctioned in §4.5, chosen over the
   native aspectRatio control because a class survives a core block's
   save/reload cycle and an authored --ar in a style attribute does not.
   They set the same custom property the layout already reads. */
.ratio-3-4,
.is-style-ratio-3-4 { --ar: 3 / 4; }
.ratio-4-3,
.is-style-ratio-4-3 { --ar: 4 / 3; }
.ratio-1-1,
.is-style-ratio-1-1 { --ar: 1 / 1; }
.ratio-3-1,
.is-style-ratio-3-1 { --ar: 3 / 1; }
.ratio-16-10,
.is-style-ratio-16-10 { --ar: 16 / 10; }
.ratio-21-9,
.is-style-ratio-21-9 { --ar: 21 / 9; }

/* ------------------------------------------------------------
   Numerals, drawn rather than typed

   The prototype set each numeral as a .num element inside the
   tip. In the block editor a tip is a paragraph, and a generated
   child inside editable text is exactly what invariant 3.4 says
   will be destroyed the first time RichText re-renders it —
   permanently, with no error.

   A counter cannot be deleted by an editor, cannot be wiped by a
   re-render, and cannot fall out of step with the number of tips.
   The .num rules above still stand for the prototype's markup.
   ------------------------------------------------------------ */
.pattern--enumerated { counter-reset: richmond-tip; }

.pattern--enumerated .tip { counter-increment: richmond-tip; }

.pattern--enumerated .tip:not(:has(.num))::before {
  content: counter(richmond-tip, decimal-leading-zero);
  /* Two digits in a 2.6rem column is tight enough that "01" can break across
     two lines. It must not: a numeral split in half reads as two numerals. */
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  font-size: 2.1rem;
  font-weight: 500;
  letter-spacing: -0.04em;
  line-height: 0.9;
  color: var(--bronze);
}
