/* =====================================================================
   Whole Mood chassis — structural theme styles v1.0.0
   (Split out of lde-theme 0.11.0, BLO-1657. CSS class names keep the
   .lde- prefix on purpose: renaming them would break edge-cached HTML
   against new CSS during deploys — see CHASSIS_README.md.)
   PILLAR VALUES LIVE IN THE REGISTRY: every per-slug rule the 0.11.0
   file carried (colour triples, tints, gradients, view-transition
   names, lady scale) is emitted as inline CSS from wm_pillars() /
   wm_quick_pages(); this file keys on custom properties and on the
   derived body classes lde-side-left/right, lde-alt-left/right and
   lde-flip instead of slugs.
   (Superseded-round decision history lives in CHANGELOG-lde-theme.md;
   in-file comments document the LIVE rules only.)
   The mockup's design system (lde-home-mockup-v3.html) translated to
   WordPress. Typography, palette, spacing and layout come from
   theme.json presets; this file carries the moods, the glass, the
   Lady's formation machinery and the experience choreography.

   GLASS, honestly: surfaces use real backdrop-filter blur where the
   browser supports it (all evergreens). The @supports fallback FAKES
   the glass with a near-opaque cream/ink fill — legible, just not
   translucent. Documented here so nobody chases a "broken blur".
   ================================================================== */

:root {
  --lde-cream: var(--wp--preset--color--base, #F7F3EC);
  --lde-ink: var(--wp--preset--color--contrast, #1C1916);
  --lde-lip: var(--wp--preset--color--accent, #8C3130);
  --lde-cream-dark: var(--wp--preset--color--cream-on-dark, #F4EFE6);
  --lde-serif: var(--wp--preset--font-family--playfair, Georgia, serif);
  --lde-body: var(--wp--preset--font-family--lora, Georgia, serif);
  --lde-sans: var(--wp--preset--font-family--inter, Arial, sans-serif);
  --lde-fg: var(--lde-ink);
  --lde-glass-bg: linear-gradient(180deg,
      rgba(250, 246, 238, .22) 0%, rgba(250, 246, 238, .34) 100%);
  --lde-glass-border: rgba(255, 255, 255, .62);
  --lde-glass-shadow: 0 18px 40px rgba(28, 25, 22, .15),
      inset 0 1px 0 rgba(255, 255, 255, .5);
}
body.lde-dark {
  --lde-fg: var(--lde-cream-dark);
  --lde-glass-bg: linear-gradient(180deg,
      rgba(26, 18, 22, .3) 0%, rgba(26, 18, 22, .46) 100%);
  --lde-glass-border: rgba(255, 255, 255, .16);
  --lde-glass-shadow: 0 18px 40px rgba(0, 0, 0, .3),
      inset 0 1px 0 rgba(255, 255, 255, .12);
}
body { color: var(--lde-fg); }
/* ROUND 24 — decorative overflow, clipped.
   `.lde-aura` is `inset: -20% -42%` on a stage that already sits at
   right:5%, so it deliberately runs past the viewport; measured at
   1440 it reached x=1516 and the page scrolled sideways 76px. On
   singles `.lde-single-room` already has `contain: strict` and clips
   it, which is why only the rooms showed the bug.
   `overflow-x: clip` rather than `hidden`, on both: clip does NOT
   create a scroll container, so the sticky header, the sticky rail and
   every position:sticky in the theme keep working. `hidden` here would
   silently break all of them. On `body` alone it does not bite — the
   html/body overflow propagation leaves the aura's own containing
   block unclipped — so `.lde-main` carries it too. */
body, .lde-main { overflow-x: clip; }

/* ------------------------------------------------------- mood layers
   One fixed gradient field per mood, painted on body::before so it
   sits under everything and costs one layer. Archives get the full
   mood; singles get the pale tint of their primary pillar. */
body::before {
  content: ""; position: fixed; inset: 0; z-index: -1;
  pointer-events: none;
  background: linear-gradient(162deg, #FAF6EF 0%, #F7F3EC 46%, #EFE7D9 100%);
}
/* The mood FIELDS themselves — the hand-tuned gradients per mood — are
   IDENTITY, not machinery: each child declares its own
   body.lde-mood-{slug}::before backgrounds in its stylesheet. Without a
   child the base field above stands everywhere. */

/* -------------------------------------------------------- the focus ring
   ROUND 23 HOUSEKEEPING. Measured by tabbing the live pages in
   Chromium: every interactive element in the theme except the new
   search button was falling back to the UA default —
   `auto 1px rgb(16, 16, 16)`. Near-black, one pixel, on Salud's navy
   and Belleza's aubergine, which is to say invisible on three of the
   five rooms. WCAG 2.4.11.

   The ring is `--lde-fg`, so it inherits the room's own light/dark
   decision and cannot fail by construction: whatever contrast the body
   text has against that ground, the ring has too. `:where()` keeps the
   specificity at zero, so any component that wants its own ring — the
   search hits, the contents rail — simply states it and wins without
   an !important anywhere.

   `:focus-visible`, not `:focus`: a mouse click on a card should not
   leave a ring behind it. */
:root { --lde-focus: var(--lde-ink); }
body.lde-dark { --lde-focus: var(--lde-cream); }
:where(a, button, summary, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--lde-focus);
  outline-offset: 3px;
}
/* Inside a glass panel the ring sits on the panel, not on the room, and
   the panel is always the light surface — so it takes the ink ring even
   in a dark room. */
body.lde-dark :where(.lde-glass, .lde-glass-soft)
  :where(a, button, summary, input, [tabindex]):focus-visible {
  outline-color: var(--lde-cream-dark);
}

/* ---------------------------------------------------------- skip link */
.lde-skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  padding: 12px 20px; background: var(--lde-ink); color: var(--lde-cream);
  font: 600 13px var(--lde-sans); text-decoration: none;
}
.lde-skip-link:focus { left: 12px; top: 12px; }

/* ------------------------------------------------------ the wordmark */
.lde-homemark {
  position: absolute; top: 22px; left: clamp(20px, 4.5vw, 64px);
  z-index: 12; padding: 4px 0; text-decoration: none;
  font-family: var(--lde-serif); font-size: 14px; letter-spacing: .44em;
  text-transform: uppercase; font-weight: 500; color: var(--lde-fg);
}

/* ------------------------------------- the two quick buttons (glass)
   «Lo último» + «Imprescindibles»: standalone top-right on the home
   threshold; the RIGHT END of the pill header everywhere else — the
   cards fly up to JOIN them. */
.lde-qbtn {
  border-radius: 99px; padding: 9px 18px; text-decoration: none;
  border: 1px solid var(--lde-glass-border);
  background: var(--lde-glass-bg);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  backdrop-filter: blur(14px) saturate(1.2);
  box-shadow: 0 8px 22px rgba(28, 25, 22, .1);
  color: var(--lde-fg); font: 600 10.5px var(--lde-sans);
  letter-spacing: .2em; text-transform: uppercase; white-space: nowrap;
  transition: border-color .25s, background .25s, box-shadow .25s;
}
.lde-qbtn:hover, .lde-qbtn:focus-visible {
  color: var(--lde-fg); border-color: rgba(140, 49, 48, .5);
}
.lde-qbtn.is-active {
  border-color: rgba(140, 49, 48, .5);
  background: rgba(140, 49, 48, .08);
}
body.lde-dark .lde-qbtn.is-active {
  border-color: rgba(221, 164, 155, .55);
  background: rgba(221, 164, 155, .1);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .lde-qbtn { background: rgba(250, 246, 238, .94); }
  body.lde-dark .lde-qbtn { background: rgba(24, 16, 20, .94); }
}
.lde-pill-sep {
  width: 1px; align-self: stretch; margin: 4px 6px;
  background: rgba(28, 25, 22, .14);
}
body.lde-dark .lde-pill-sep { background: rgba(247, 243, 236, .18); }

/* --------------------------------------------------- the glass header */
header.lde-header {
  position: sticky; top: 0; z-index: 30;
  background: rgba(247, 243, 236, .5);
  -webkit-backdrop-filter: blur(22px) saturate(1.3);
  backdrop-filter: blur(22px) saturate(1.3);
  border-bottom: 1px solid rgba(28, 25, 22, .08);
}
body.lde-dark header.lde-header {
  background: rgba(20, 14, 18, .52);
  border-bottom-color: rgba(247, 243, 236, .14);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  /* faked glass: near-opaque fill, no blur */
  header.lde-header { background: rgba(247, 243, 236, .96); }
  body.lde-dark header.lde-header { background: rgba(20, 14, 18, .96); }
}
.lde-header-in {
  max-width: 1560px; margin: 0 auto;
  padding: 9px clamp(16px, 3.5vw, 48px);
}
.lde-brandmark {
  display: inline-flex; align-items: center; gap: 12px;
  text-decoration: none; color: var(--lde-fg);
}
/* ROUND 12 (Nick, 2026-08-06): the Lady mark is OUT of the header on
   every page — the Lady in the background themes it. The wordmark stays
   as the home link, so .lde-logo and the once-per-page yield it
   implemented are both retired; build_theme_svg.py no longer injects
   into header.html. logo_svg() is kept for favicon / social use. */
.lde-wordmark {
  font-family: var(--lde-serif); font-size: 13px; letter-spacing: .34em;
  text-transform: uppercase; font-weight: 500; white-space: nowrap;
}
.lde-pills { display: flex; gap: 8px; }
.lde-pill {
  border-radius: 99px; padding: 9px 18px; text-decoration: none;
  border: 1px solid transparent; color: var(--lde-fg);
  font: 600 10.5px var(--lde-sans); letter-spacing: .2em;
  text-transform: uppercase; opacity: .82; white-space: nowrap;
  transition: opacity .18s, border-color .25s, background .25s;
}
.lde-pill:hover, .lde-pill:focus-visible { opacity: 1; color: var(--lde-fg); }
.lde-pill.is-active {
  opacity: 1;
  border-color: rgba(140, 49, 48, .5);
  background: rgba(140, 49, 48, .08);
}
body.lde-dark .lde-pill.is-active {
  border-color: rgba(221, 164, 155, .55);
  background: rgba(221, 164, 155, .1);
}

/* -------------------------------------------------------- glass cards */
.lde-glass {
  border-radius: 14px; padding: 24px 28px;
  background: var(--lde-glass-bg);
  -webkit-backdrop-filter: blur(16px) saturate(1.2);
  backdrop-filter: blur(16px) saturate(1.2);
  border: 1px solid var(--lde-glass-border);
  box-shadow: var(--lde-glass-shadow);
}
.lde-glass-soft {
  border-radius: 14px; padding: 24px 26px 20px;
  background: rgba(250, 246, 238, .6);
  -webkit-backdrop-filter: blur(12px) saturate(1.15);
  backdrop-filter: blur(12px) saturate(1.15);
  border: 1px solid rgba(255, 255, 255, .55);
  box-shadow: 0 14px 34px rgba(28, 25, 22, .1);
}
body.lde-dark .lde-glass-soft {
  background: rgba(24, 16, 20, .5);
  border-color: rgba(255, 255, 255, .14);
  box-shadow: 0 14px 34px rgba(0, 0, 0, .3);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .lde-glass, .lde-glass-soft { background: rgba(250, 246, 238, .94); }
  body.lde-dark .lde-glass, body.lde-dark .lde-glass-soft {
    background: rgba(24, 16, 20, .94); }
}

/* ------------------------------------------- the Lady: stage + strokes
   Formation machinery (mockup v3 law): each stroke is a filled path
   revealed by a mask whose centerline stroke draws itself
   (stroke-dashoffset). Timings per stroke live as custom props in the
   generated partials (parts/svg/*). No JS => strokes stand revealed. */
.lde-stage { position: absolute; pointer-events: none; z-index: 1; }
.lde-stage svg.lde-pose {
  position: absolute; inset: 0; width: 100%; height: 100%;
  overflow: visible;
}
.lde-aura {
  position: absolute; inset: -20% -42%; pointer-events: none;
  background: radial-gradient(50% 46% at 50% 42%,
    rgba(249, 245, 237, .85) 0%, rgba(249, 245, 237, .6) 32%,
    rgba(249, 245, 237, .32) 55%, rgba(249, 245, 237, .12) 72%,
    rgba(249, 245, 237, 0) 88%);
  animation: lde-breathe 9s ease-in-out infinite alternate;
}
/* ROUND 14: on a deep room the aura was a cream lozenge with a
   discernible edge, reading as a spotlight rather than air. With a
   cream line it has no legibility work left to do, so it drops to a
   whisper of lift. */
body.lde-dark .lde-aura {
  opacity: 1;
  background: radial-gradient(58% 52% at 50% 44%,
    rgba(249, 245, 237, .15) 0%, rgba(249, 245, 237, .085) 40%,
    rgba(249, 245, 237, .03) 66%, rgba(249, 245, 237, 0) 86%);
}
@keyframes lde-breathe { from { transform: scale(1) } to { transform: scale(1.05) } }
/* ROUND 14 — THE INK.
   The pose partials emit fill="currentColor" for every stroke except
   the burgundy accent, so the line colour is set here, once. On the
   three DEEP rooms (Salud, Belleza, Moda) near-black ink on a
   near-black ground was legible only because a hard-edged cream aura
   was spotlighting her — the aura was doing the drawing's job and
   announcing itself as an artefact while it did. Cream line on a deep
   ground is a real Gruau idiom and needs no spotlight, so the aura
   demotes to atmosphere below. */
.lde-pose { color: var(--lde-ink); --lde-accent: #8C3130; }
body.lde-dark .lde-pose { color: var(--lde-cream); }
/* ROUND 22 — Moda's ground IS burgundy, so the burgundy mouth
   disappears into it. Round 14 took that darker, to #33100F, on the
   reasoning that a dark mark between a cream line and a mid-burgundy
   ground would be the sharpest thing present. Rendered at room size it
   is not: it is a smudge, and Nick read it as "almost unreadable
   against the red" without knowing which direction we had gone.
   Deep-on-deep was the wrong half of the rule. The washes already
   settled this in round 16 — on a deep ground you go LIGHTER — and the
   accent obeys the same law. Bright vermilion, one step warmer than the
   ground's own hue so it separates by saturation as well as value. */
/* Per-pose accent overrides (e.g. a lighter accent on a deep ground)
   are art decisions — they live in the child's stylesheet. */

.lde-pose .rv {
  fill: none; stroke: #fff; stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: var(--len); stroke-dashoffset: 0;
}
/* JS present: start hidden, draw on .on (delay/duration per stroke) */
html.lde-js .lde-pose .rv { stroke-dashoffset: var(--len); }
html.lde-js .lde-pose.on .rv {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset calc(var(--ddur) * var(--k, 1))
    cubic-bezier(.55, .06, .35, .96) calc(var(--dd) * var(--k, 1));
}
.lde-first { --k: 1.9; }
/* painterly washes + artefact blooms (hero partial) */
.lde-pose .fwash, .lde-pose .fbloom { opacity: 1; }
html.lde-js .lde-pose .fwash { opacity: 0; transition: opacity .22s ease; }
html.lde-js .lde-pose .fbloom {
  opacity: 0; transform: scale(.55);
  transform-box: fill-box; transform-origin: center;
  transition: opacity .2s ease, transform .2s ease;
}
html.lde-js .lde-pose.on .fwash {
  opacity: 1;
  transition: opacity .7s ease-out calc(var(--wash, 400ms) * var(--k, 1));
}
html.lde-js .lde-pose.on .fbloom {
  opacity: 1; transform: scale(1);
  transition: opacity .34s cubic-bezier(.2, .7, .4, 1.2) calc(var(--bd, 0ms) * var(--k, 1)),
    transform .34s cubic-bezier(.2, .7, .4, 1.2) calc(var(--bd, 0ms) * var(--k, 1));
}
/* artefact depth drift (three planes, generated into the partials) */
.lde-drift-a { animation: lde-driftA 120s ease-in-out infinite alternate; }
.lde-drift-b { animation: lde-driftB 90s ease-in-out infinite alternate; }
.lde-drift-c { animation: lde-driftC 70s ease-in-out infinite alternate; }
@keyframes lde-driftA { from { transform: translate(0, 0) } to { transform: translate(26px, -18px) } }
@keyframes lde-driftB { from { transform: translate(0, 0) } to { transform: translate(-16px, 12px) } }
@keyframes lde-driftC { from { transform: translate(0, 0) } to { transform: translate(8px, -7px) } }

/* --------------------------------------------------------- front hero */
.lde-hero {
  position: relative; min-height: 92vh;
  display: flex; flex-direction: column;
  padding: clamp(64px, 9vh, 110px) clamp(20px, 4.5vw, 64px) 30px;
  max-width: 1560px; margin: 0 auto;
}
.lde-stage-hero {
  left: 2.5%; top: 6vh; height: 58vh; aspect-ratio: 400 / 396;
}
.lde-hero-copy {
  position: relative; z-index: 2; margin-left: auto;
  width: min(600px, 47%);
}
/* ROUND 23 HOUSEKEEPING — .62 measured 3.95:1 on Salud, 4.25 on Moda
   and 4.48 on Cocina against the real rendered ground, all short of the
   4.5 this size needs. Sampled off a screenshot with the text hidden,
   not computed from the CSS: the mood field is painted on body::before,
   so walking the DOM for a background-color finds nothing and quietly
   compares cream against cream. .78 clears every room. */
.lde-eyebrow {
  font: 600 11px var(--lde-sans); letter-spacing: .34em;
  text-transform: uppercase; color: var(--lde-fg); opacity: .78;
  margin: 0 0 18px;
}
.lde-hero-title {
  margin: 0 0 14px; letter-spacing: .005em; line-height: 1.06;
}
.lde-hero-title em { font-style: italic; font-weight: 400; }
.lde-tagline {
  font-family: var(--lde-serif); font-style: italic;
  font-size: clamp(16px, 1.35vw, 19px); margin: 0 0 22px;
  color: var(--lde-fg); opacity: .8;
}
.lde-intro { max-width: 520px; }
.lde-intro p { font-size: 14.5px; line-height: 1.6; margin: 0 0 12px; }
.lde-cta a {
  display: inline-block; font: 600 11.5px var(--lde-sans);
  letter-spacing: .24em; text-transform: uppercase;
  color: var(--lde-lip); text-decoration: none;
  border-bottom: 1px solid rgba(140, 49, 48, .4); padding-bottom: 3px;
  transition: border-color .3s;
}
.lde-cta a:hover { border-color: var(--lde-lip); }
body.lde-dark .lde-cta a { color: #DDA49B; border-bottom-color: rgba(221, 164, 155, .4); }

/* -------------------------------------------------------- portal cards */
.lde-portal-row {
  position: relative; z-index: 3; margin-top: auto;
  display: grid; grid-template-columns: repeat(5, 1fr);
  gap: clamp(10px, 1.4vw, 18px); scroll-margin-top: 110px;
}
.lde-portal {
  position: relative; border-radius: 18px; padding: 22px 22px 16px;
  overflow: hidden; isolation: isolate; text-decoration: none;
  display: flex; flex-direction: column; min-height: 238px;
  border: 1px solid var(--lde-glass-border);
  background: var(--lde-glass-bg);
  -webkit-backdrop-filter: blur(20px) saturate(1.25);
  backdrop-filter: blur(20px) saturate(1.25);
  box-shadow: var(--lde-glass-shadow);
  transition: transform .45s cubic-bezier(.2, .8, .25, 1), box-shadow .45s;
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .lde-portal { background: rgba(250, 246, 238, .94); }
}
.lde-portal:hover, .lde-portal:focus-visible {
  transform: translateY(-6px);
  box-shadow: 0 26px 52px rgba(28, 25, 22, .2),
    inset 0 1px 0 rgba(255, 255, 255, .5);
}
.lde-portal-mood { position: absolute; inset: 0; z-index: -1; }
/* ------------------------------------------- featured-image card body
   The pillar's DEEP colour holds the top of the frame and bleeds down
   over the featured image to ~5% at the foot — colour presence across
   the whole card, image visible through it. A state without an image
   (the current majority) falls back to the pure mood-gradient card:
   its .lde-pimg-none layer is simply transparent. */
/* ROUND 34 — the triples are stated once, for BOTH carriers: the
   .lde-p-* classes (portal cards, room-index tiles) and the data-pillar
   attribute the header pills already wear, which is what lets the
   phone's menu sheet paint each room its own ink without a second copy
   of these numbers. An attribute selector is class-level specificity,
   so nothing in the cascade moves. */
/* The per-pillar triples are emitted from the wm_pillars registry
   (wm_pillar_inline_css, block 1) for both carriers. */
.lde-portal-imgs { position: absolute; inset: 0; z-index: -1; }
.lde-pimg {
  position: absolute; inset: 0; opacity: 0; transition: opacity .95s ease;
}
.lde-pimg.is-on { opacity: 1; }
.lde-pimg img {
  position: absolute; left: 0; right: 0; bottom: 0; top: 38%;
  width: 100%; height: 62%; object-fit: cover;
}
.lde-pimg:not(.lde-pimg-none)::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg,
    rgb(var(--lde-pc)) 0%, rgba(var(--lde-pc), .88) 30%,
    rgba(var(--lde-pc), .38) 62%, rgba(var(--lde-pc), .05) 100%);
}
/* text over the colour-held frame: cream registers (AA on all five
   deep pillar colours), a whisper of shadow where the title crosses
   into the image zone */
.lde-pstate.has-img .lde-plabel { color: rgba(247, 243, 236, .92); }
.lde-pstate.has-img .lde-ptitle {
  color: #F7F3EC; text-shadow: 0 1px 14px rgba(0, 0, 0, .3);
}
.lde-portal-name, .lde-portal-count { transition: color .45s ease; }
.lde-portal.lde-has-img .lde-portal-name { color: #F7F3EC; }
.lde-portal.lde-has-img .lde-portal-count { color: rgba(247, 243, 236, .72); }
/* The portal mood veil per pillar is emitted from the registry
   (portal_mood — wm_pillar_inline_css, block 2). */
.lde-portal-name {
  font-family: var(--lde-serif); font-size: 20px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--lde-ink);
}
.lde-portal-states { position: relative; flex: 1; margin: 14px 0 12px; min-height: 112px; }
.lde-pstate {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  gap: 7px; opacity: 0; transition: opacity .95s ease;
}
.lde-pstate.is-on { opacity: 1; }
.lde-plabel {
  font: 700 9.5px var(--lde-sans); letter-spacing: .26em;
  text-transform: uppercase; color: var(--lde-lip);
}
.lde-ptitle {
  font-family: var(--lde-serif); font-weight: 500; font-size: 16.5px;
  line-height: 1.3; color: var(--lde-ink);
}
.lde-portal-count {
  margin-top: auto; font: 600 10px var(--lde-sans); letter-spacing: .24em;
  text-transform: uppercase; color: rgba(28, 25, 22, .5);
}

/* ------------------------------------------------------- article bands */
.lde-band { padding: clamp(28px, 4vh, 52px) clamp(20px, 4.5vw, 64px); }
.lde-bandsub {
  font-family: var(--lde-serif); font-style: italic; font-size: 15px;
  color: var(--lde-fg); opacity: .6; margin: 0 0 26px;
}
.lde-cards .wp-block-post-template { gap: clamp(14px, 1.8vw, 24px); }
/* ROUND 22 — the card grid has to breathe now there are six of them.
   WP's grid layout with an explicit columnCount emits a flat
   repeat(3, minmax(0,1fr)) and ships no breakpoint with it, so raising
   the archive from 3 posts to 6 turned a phone room into three columns
   about eight characters wide. Two up on tablet, one up on phone. */
@media (max-width: 900px) {
  .lde-cards .wp-block-post-template.is-layout-grid,
  .lde-cards .wp-block-post-template[style*="grid-template-columns"] {
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
  }
}
@media (max-width: 620px) {
  .lde-cards .wp-block-post-template.is-layout-grid,
  .lde-cards .wp-block-post-template[style*="grid-template-columns"] {
    grid-template-columns: minmax(0, 1fr) !important;
  }
}
/* ROUND 23 — the card as a column.
   At three per page the ragged date line did not read as ragged. At six
   it does: two rows, six titles of different lengths, and the dates
   land at six different heights. Making the card a column and pushing
   the meta to the bottom pins them to one baseline per row without
   forcing equal-length excerpts, which would be the wrong fix.
   `:where()` on the meta selectors keeps this at zero specificity so
   the existing colour rules below still win. */
.lde-card { display: flex; flex-direction: column; }
/* 1.1.0 — the date (and the read-time line under it) now travel in one
   `.lde-card-foot` wrapper, emitted for EVERY card so the DOM is
   identical with or without a featured image. The wrapper takes the
   pin; the bare selectors stay for anything still rendering an
   unwrapped date (core's post-date block in a query loop). */
.lde-card > :where(.lde-card-meta, .wp-block-post-date, .lde-card-foot) { margin-top: auto; padding-top: 10px; }
.lde-card-foot .lde-card-meta { margin: 0; padding: 0; }
/* Whole-card affordance. The title is the only link inside, so the lift
   has to come from the card while the accessible name stays on the
   anchor — no nested interactive elements, no card-wide click handler,
   nothing for a screen reader to have to disambiguate. */
.lde-card { transition: transform .22s ease, box-shadow .22s ease; }
@media (prefers-reduced-motion: reduce) {
  .lde-card:hover { transform: none; }
}

.lde-card-tag, .lde-card-tag a {
  font: 700 10.5px var(--lde-sans); letter-spacing: .26em;
  text-transform: uppercase; color: var(--lde-lip); text-decoration: none;
}
body.lde-dark .lde-card-tag, body.lde-dark .lde-card-tag a { color: #DDA49B; }
.lde-card .wp-block-post-title a {
  color: var(--lde-ink); text-decoration: none;
}
body.lde-dark .lde-card .wp-block-post-title a { color: var(--lde-cream-dark); }
.lde-card .wp-block-post-title a:hover { color: var(--lde-lip); }
.lde-card .wp-block-post-excerpt {
  font-size: 13.5px; line-height: 1.6; color: rgba(28, 25, 22, .8);
}
body.lde-dark .lde-card .wp-block-post-excerpt { color: rgba(244, 239, 230, .8); }
/* .5 measured 3.1:1 on a plain card over the cream glass — the kind of
   miss that looks like restraint until you measure it. */
.lde-card-meta, .lde-card .wp-block-post-date {
  font: 600 10.5px var(--lde-sans); letter-spacing: .18em;
  text-transform: uppercase; color: rgba(28, 25, 22, .72);
}
/* `lde/room-cards` emits `.lde-card-meta`, not core's
   `.wp-block-post-date`, so on the three deep rooms the date was still
   getting the light-room ink — near-black on navy. */
body.lde-dark .lde-card .wp-block-post-date,
body.lde-dark .lde-card .lde-card-meta { color: rgba(244, 239, 230, .78); }
/* 1.1.0 — the estimated read time, one quiet line under the date. Same
   family as the meta (small caps-tracked sans) but weight 400, so the
   date stays the anchor of the pair. Same measured-contrast inks as the
   meta — .72/.78 are the values that already passed on both surfaces. */
.lde-card-read {
  font: 400 10.5px var(--lde-sans); letter-spacing: .14em;
  text-transform: uppercase; color: rgba(28, 25, 22, .72);
  margin: 3px 0 0; padding: 0;
}
body.lde-dark .lde-card .lde-card-read { color: rgba(244, 239, 230, .78); }


/* ------------------------------------------------- room pagination
   Six posts per page and previous/next where they apply (Nick,
   2026-08-07). The numbers are deliberately quiet — on a five-pillar
   site nobody deep-links to page 4, they just want to keep going. */
/* `display: flex` stated here, not inherited from core's layout
   classes: `lde/room-cards` renders its own <nav> and carries none of
   them, so the pager was a BLOCK and the page numbers stacked on top of
   SIGUIENTE — which is the "crashing" Nick saw. */
/* 1.1.3 — Nick, 2026-08-08: ANTERIOR sits on the left edge of the
   card grid and SIGUIENTE on the right, with the numerals centred
   between them. A three-column grid rather than space-between,
   because on page 1 there is no ANTERIOR and on the last page no
   SIGUIENTE — with space-between the numerals would jump sides as
   you paged. The 1fr/auto/1fr columns hold them dead centre
   whichever buttons happen to exist. The nav is a sibling of
   .lde-cards inside .lde-band, so full width IS the grid's width. */
.lde-band .wp-block-query-pagination {
  margin: 42px auto 8px; gap: 10px;
  display: grid; grid-template-columns: 1fr auto 1fr;
  align-items: center; width: 100%;
}
.lde-band .wp-block-query-pagination-previous { grid-column: 1; justify-self: start; }
.lde-band .wp-block-query-pagination-numbers  { grid-column: 2; justify-self: center; }
.lde-band .wp-block-query-pagination-next     { grid-column: 3; justify-self: end; }
.lde-band .wp-block-query-pagination-previous,
.lde-band .wp-block-query-pagination-next {
  font: 600 11px/1 var(--lde-sans); letter-spacing: .16em;
  text-transform: uppercase; text-decoration: none;
  color: var(--lde-fg); opacity: .82;
  padding: 11px 20px; border-radius: 999px;
  border: 1px solid currentColor;
  transition: opacity .18s ease, background-color .18s ease;
}
.lde-band .wp-block-query-pagination-previous:hover,
.lde-band .wp-block-query-pagination-next:hover {
  opacity: 1; background: rgba(140, 49, 48, .08);
}
body.lde-dark .lde-band .wp-block-query-pagination-previous:hover,
body.lde-dark .lde-band .wp-block-query-pagination-next:hover {
  background: rgba(250, 246, 238, .1);
}
.lde-band .wp-block-query-pagination-numbers {
  font: 500 12px/1 var(--lde-sans); letter-spacing: .1em;
  /* .5 measured 4.06:1 on Salud and 4.33 on Moda — near enough to look
     fine and short enough to fail. */
  color: var(--lde-fg); opacity: .68;
}
.lde-band .wp-block-query-pagination-numbers .page-numbers {
  padding: 0 7px; text-decoration: none; color: inherit;
}
/* The current page needs to be findable at a glance among three plain
   numerals; weight alone at 12px was not enough of a difference. */
/* 1.1.3 — but the underline-plus-bold made it LOUDER than its
   siblings, which Nick read as the selected page standing prouder
   than the ones you might want to click. A ring instead: same
   weight, same colour, same inherited opacity as every other
   numeral — it marks where you are without advertising it, and it
   rhymes with the pill shape of the two buttons either side.
   (The old `opacity: 1` here was always a no-op: the numbers row
   sets opacity on the group, which a child cannot climb out of.) */
.lde-band .wp-block-query-pagination-numbers .current {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 23px; height: 23px; padding: 0;
  border: 1px solid currentColor; border-radius: 999px;
}
.lde-band .wp-block-query-pagination-numbers a:hover { opacity: 1; }
@media (max-width: 900px) {
  .lde-band .wp-block-query-pagination { margin-top: 30px; }
  .lde-band .wp-block-query-pagination-numbers { display: none; }
}

/* --------------------------------------------------------- the rooms */
/* ROUND 14: the hero was 72vh with the stage occupying 66vh from 8vh
   down, and the copy dead-centred in the leftover — which opened ~300px
   of nothing between the strapline and the card band, the "void
   mid-page" on every room. The hero now belongs to HER: she fills it
   top to bottom and the copy lands at her chest height. */
/* ROUND 20 (Nick): bring the post cards UP. The hero was 78vh, so on a
   900px viewport the first card sat below the fold and a category read as a
   poster with a rumour of content beneath it. At 68vh the top row is in
   view on load and the Lady still gets two thirds of the screen. */
.lde-room-hero {
  position: relative; min-height: 68vh;
  display: flex; flex-direction: column; justify-content: center;
  padding: clamp(48px, 7vh, 90px) clamp(20px, 4.5vw, 64px) 30px;
  max-width: 1560px; margin: 0 auto;
}
/* ROUND 14 — THE PLACEMENT LAW (Nick, 2026-08-06).
     Moda + Cocina      full height, RIGHT
     Salud + Bienestar  full height, LEFT
     Belleza            head-and-shoulders, RIGHT
   The poses are drawn facing screen-left. Standing on the LEFT that
   pointed her off the page, away from her own copy — which is what
   every room did until today. The two left-hand rooms are therefore
   mirrored here in CSS, never in the geometry: an SVG transform would
   move the formation masks out from under their paths.
   The copy always takes the side she is not on. */
/* ROUND 18: every pose is generated from its concept, so the stage
   carries the concept's aspect (400 x 563) rather than the old 2/5
   logo-crop proportion the parametric chassis assumed. */
.lde-stage-room { top: 4vh; height: 62vh; aspect-ratio: 400 / 563; left: 6%; }
.lde-room-copy { position: relative; z-index: 2; width: min(600px, 47%);
  margin-left: auto; }

body.lde-side-left .lde-stage-room { left: 6%; right: auto;
  transform: scaleX(-1); }

body.lde-side-right .lde-stage-room { left: auto; right: 5%;
  transform: none; }

/* A cropped pose that needs its own stage geometry (Belleza on LDE)
   overrides these in the CHILD stylesheet — that is art positioning,
   not machinery. */

body.lde-side-right .lde-room-copy { margin-left: 0; margin-right: auto; }
.lde-room-title, .wp-block-query-title {
  font-family: var(--lde-serif); font-weight: 400;
  font-size: var(--wp--preset--font-size--xx-large); margin: 0 0 14px;
  color: var(--lde-fg);
}
.lde-room-copy .wp-block-term-description {
  font-family: var(--lde-serif); font-style: italic;
  font-size: clamp(16px, 1.35vw, 19px); color: var(--lde-fg); opacity: .85;
}

/* ---------------------------------------------------- single / reading */
.lde-reading .wp-block-post-title { margin: 10px 0 14px; }
.lde-post-meta, .lde-post-meta .wp-block-post-date,
.lde-post-meta .wp-block-post-author-name {
  font: 500 12px var(--lde-sans); letter-spacing: .1em;
  text-transform: uppercase; color: rgba(28, 25, 22, .6);
}
.lde-post-tags { margin-top: 28px; font-size: 13px; opacity: .75; }

/* --------------------------------------------------------------- 404 */
.lde-404 { min-height: 80vh; padding: clamp(80px, 14vh, 140px) 20px 60px; text-align: center; }
.lde-404-lady { margin: 0 auto 8px; max-width: 420px; }
.lde-404-lady svg { width: 100%; height: auto; display: block; }
.lde-404-title { margin: 0 0 10px; }
.lde-404 .lde-search-solo {
  position: relative; max-width: 420px; margin: 28px auto 0;
  display: flex; justify-content: center;
}

/* ------------------------------------------------------------- footer */
footer.lde-footer {
  margin-top: clamp(40px, 6vh, 72px);
  border-top: 1px solid rgba(28, 25, 22, .12);
  background: rgba(247, 243, 236, .5);
}
body.lde-dark footer.lde-footer {
  border-top-color: rgba(247, 243, 236, .2);
  background: rgba(22, 16, 20, .4);
}
.lde-footer-in {
  max-width: 1560px; margin: 0 auto;
  padding: 26px clamp(20px, 4.5vw, 64px) 30px;
}
.lde-foot-tag {
  font-family: var(--lde-serif); font-style: italic; font-size: 13px;
  color: var(--lde-fg); opacity: .6;
}
.lde-foot-legal { display: flex; gap: 18px; }
.lde-foot-legal a {
  font: 500 11px var(--lde-sans); letter-spacing: .06em;
  color: var(--lde-fg); opacity: .6; text-decoration: none;
}
.lde-foot-legal a:hover { opacity: 1; }

/* 1.0.0 — «A Whole Mood Blog»: the corporate brand line every child of
   the chassis carries (wm/brand-line, filter `wm_footer_brand`).
   Absolutely positioned ≥901px so it can NEVER change the footer's
   height — on the framed templates a taller footer would compress the
   one-screen composition above it. Centred in the footer's clear
   middle; children may restyle .wm-foot-brand freely. */
footer.lde-footer { position: relative; }
.wm-foot-brand {
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  white-space: nowrap; pointer-events: none;
  font: 500 10px var(--lde-sans); letter-spacing: .24em;
  /* 1.1.3 — Nick, 2026-08-08: 'a watermark rather than a stamp'.
     .45 read as a signature on the page; .32 leaves it present to
     anyone looking for it and invisible to anyone who is not. */
  text-transform: uppercase; color: var(--lde-fg); opacity: .32;
}
/* 1.1.11 — the .wm-foot-brand hide that stood here (1.1.4) is gone with
   the reason for it: at ≤900px the whole footer is now display:none (see
   THE NARROW FRONT PAGE), so a rule hiding one line inside a hidden
   element was dead weight. Nick spotted it. */
@media (max-width: 900px) {
  footer.lde-footer { display: none; }
}
@media (max-width: 900px) {
  /* Unframed layouts: an ordinary centred row of its own at the foot —
     the footer may grow a line here, nothing above it moves. */
  .wm-foot-brand {
    position: static; transform: none; order: 99;
    width: 100%; text-align: center; margin-top: 14px;
  }
}

/* --------------------------------------------- travelling line (JS) */
#lde-travel {
  position: fixed; inset: 0; width: 100%; height: 100%;
  z-index: 45; pointer-events: none;
}
#lde-tline {
  fill: none; stroke: var(--lde-ink); stroke-width: 4;
  stroke-linecap: round; opacity: 0;
  transition: stroke-dashoffset .52s cubic-bezier(.55, .08, .32, 1);
}
/* ROUND 22 — the FLIP facsimile machinery is DELETED.
   `.lde-flier`, `.lde-flier .fl-name`, `body.lde-leaving main` and
   `.lde-flip-bar` styled elements that lde-experience.js built inside the
   DEPARTING document to stand in for the destination's header. A copy of a
   header is not that header: measured in Chromium at 1920px, every pill
   landed 201px right of its real box (188px from the missing
   max-width:1560px centring, 13px from the missing .lde-pill-sep), and the
   error grew with viewport width — which is why the bug read as
   intermittent. Nothing creates these elements any more.
   `#lde-travel` / `#lde-tline` above are NOT part of that: the travelling
   ink line is parked, not retired. */

/* --------------------------------------- cross-document view transitions
   ROUND 22 — merged verbatim from BOX_OUT/COWORK/BLO-1654_r15/_r15-nav.css.
   Supersedes the three-rule block that used to live here: same
   navigation:auto, same lde-header name, same .45s root crossfade, plus
   the per-pillar names that let the browser pair each front-page CARD
   with its header PILL and animate between the two REAL boxes. Nothing is
   measured by hand any more, so nothing can drift. No JS involved: every
   pill, card and quick button is a real <a>, and browsers without
   cross-document view transitions simply navigate.
   Sections 5 (reduced motion) and 6 (below 900px) are nested inside the
   @supports guard here rather than being distributed into the two media
   blocks further down — same cascade, one self-contained unit to diff
   against the source file. The belt-and-braces
   ::view-transition-*(*) { animation: none } rules in the reduced-motion
   block below are kept: they cover same-document transitions too. */
/* ==== SECTION 1 — opt in ============================================ */
/* Guarded so the whole block is skipped where view transitions are
   unknown. `@view-transition` is itself an unknown at-rule there and
   would be dropped anyway; the guard also keeps the names out of
   browsers that would parse them but cannot use them. */
@supports (view-transition-name: none) {

  @view-transition { navigation: auto; }

  /* The page field itself: a gentle crossfade. Same value as before. */
  ::view-transition-old(root),
  ::view-transition-new(root) { animation-duration: .45s; }


  /* ==== SECTION 2 — the persistent chrome ============================ */
  /* The glass bar. Absent on the front page (front-page.html has no
     header template part), so home → room plays it as an unpaired
     "new" group: it fades in as the pills arrive. Room → room pairs it
     and it simply holds still. */
  header.lde-header { view-transition-name: lde-header; }

  /* The wordmark: `.lde-homemark` on the front page (absolute, hero
     top-left) and `.lde-brandmark` in the header everywhere else. One
     name, so it walks between the two. */
  .lde-homemark,
  .lde-brandmark { view-transition-name: lde-brand; }


  /* ==== SECTION 3 — the five pillars ================================= */
  /* One name per pillar slug, shared by the front-page CARD and the
     header PILL. Both already carry data-pillar (lde_render_portal_cards
     and lde_render_pill_nav), so no markup change was needed.

     Names must be unique per document — they are: the front page has
     the cards and no pills, every other template has the pills and no
     cards. If a pillar's category does not exist yet, PHP renders
     neither, and an unmatched name is simply never captured. */
  /* The per-pillar names (card ↔ pill) are emitted from the registry
     (wm_pillar_inline_css, block 3), together with their travel rules
     and the ≤900px opt-out — one emission, one cascade unit. */

  /* The two quick buttons. They have no data attribute of their own, so
     they are matched on their href — `lde_quick_buttons()` builds them
     with home_url('/lo-ultimo/') and home_url('/imprescindibles/'), and
     front-page.html hard-codes the same two paths, both with the
     trailing slash. If those slugs ever change, change them here too. */
  /* The quick-button names are emitted from the quick-page registry
     (matched on their hrefs, exactly as before). */


  /* ==== SECTION 4 — how they travel ================================= */
  /* Order of the pseudo-groups follows paint order, so root sits under
     the header, which sits under the pills. Stated explicitly rather
     than relied upon. */
  ::view-transition-group(lde-header) { z-index: 1; }
  ::view-transition-group(lde-brand) { z-index: 2; }
  /* (The pillar and quick groups get the same z-index and timing from
     the emitted block.) */

  /* The travel itself. .62s on the same curve the old FLIP used
     (cubic-bezier(.22,.9,.24,1), DEPART_MS was 640) so the motion
     character is unchanged — only its accuracy is. */
  ::view-transition-group(lde-brand) {
    animation-duration: .62s;
    animation-timing-function: cubic-bezier(.22, .9, .24, 1);
  }

  /* A 238px-tall glass card and a 36px pill have very different
     proportions. The UA default stretches each snapshot to the group's
     width and lets the height go where it likes, which shears the type.
     Pinning both snapshots to the group box and cropping (object-fit:
     cover) keeps the letterforms honest; object-position 50% 0% holds
     the TOP of the card in frame, which is where `.lde-portal-name`
     sits — so the word BIENESTAR stays on screen the whole way and
     hands over to the pill of the same name. */
  /* (Snapshot pinning + in/out choreography for the pillar cards and
     quick buttons: emitted from the registry, same values.) */

  /* The wordmark keeps its shape end to end, so it only needs the
     plain crossfade over the whole travel. */
  ::view-transition-old(lde-brand) {
    animation: lde-vt-out .34s ease both;
  }
  ::view-transition-new(lde-brand) {
    animation: lde-vt-in .44s ease .16s both;
  }

  @keyframes lde-vt-out { to { opacity: 0; } }
  @keyframes lde-vt-in { from { opacity: 0; } }


  /* ==== SECTION 5 — reduced motion ================================== */
  /* Turn the navigation transition off entirely rather than running it
     with zero-length animations: no snapshot, no pseudo tree, no
     one-frame flash of the outgoing page. Ordinary navigation. */
  @media (prefers-reduced-motion: reduce) {
    @view-transition { navigation: none; }
  }


  /* ==== SECTION 6 — below 900px ===================================== */
  /* `.lde-pills` and `.lde-portal-row` are both horizontal scrollers on
     phones, so at any given moment some pills and some cards are parked
     outside the visible strip. Pairing them there means snapshots
     flying in from off-screen, which reads as a glitch rather than as
     the cards joining the bar. Drop the per-element names and keep the
     root + header crossfade, which is legible at that size anyway. */
  /* NOTE: these selectors must MATCH the specificity of the ones in
     sections 2–3, or the attribute selectors there win regardless of the
     media query and the names survive on phones. Verified in Chromium at
     390px: with a bare `.lde-portal` here the names were still set. */
  @media (max-width: 900px) {
    .lde-homemark,
    .lde-brandmark { view-transition-name: none; }
    /* (The portal/pill/quick opt-outs ride with their emitted names —
       same specificity, same order, same outcome.) */
  }

}

/* ----------------------------------------------------- reduced motion */
@media (prefers-reduced-motion: reduce) {
  html.lde-js .lde-pose .rv { transition: none !important; stroke-dashoffset: 0 !important; }
  html.lde-js .lde-pose .fwash, html.lde-js .lde-pose .fbloom {
    transition: none !important; opacity: 1; transform: none;
  }
  .lde-aura, .lde-drift-a, .lde-drift-b, .lde-drift-c { animation: none !important; }
  .lde-pstate { transition: none !important; }
  @supports (view-transition-name: none) {
    ::view-transition-group(*), ::view-transition-old(*),
    ::view-transition-new(*) { animation: none !important; }
  }
}

/* ------------------------------------------------------------- mobile */
@media (max-width: 900px) {
  .lde-homemark { top: 16px; font-size: 12px; letter-spacing: .34em; }
  .lde-header-in { padding: 8px 14px; gap: 10px; }
  .lde-wordmark { font-size: 11px; letter-spacing: .24em; }
  .lde-pills { overflow-x: auto; gap: 6px; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
  .lde-pills::-webkit-scrollbar { display: none; }
  .lde-pill { padding: 8px 13px; font-size: 10px; letter-spacing: .14em; }
  /* ROUND 34 — the threshold on a phone is ONE SCREEN now. The portal
     row used to be a 72vw snap scroller here (the theme's only genuine
     horizontal scroll; measured scrollWidth 1538 in a 350px box) and
     the page ran to 1298px tall at 390×844. Mockup B replaces the row
     with the room-index grid (see THE ROOM INDEX at the end of this
     file), and the hero compresses so the whole composition — her
     chin at the lintel, the copy, the six tiles, the bottom bar —
     fits inside 390×844 and 360×780 without clipping. She is CROPPED,
     not shrunk: the stage keeps a large figure and lifts it above the
     top edge, so the lips and jaw hold the doorway the way the mock
     drew them. */
  .lde-hero { padding-top: 56px; min-height: 0;
    /* Enough foot for the footer to start below the first screen: the
       composition ends at the grid, the fixed bar holds the bottom,
       and the foot-tag arrives on the swipe instead of peeking out
       from under the bar. */
    padding-bottom: clamp(70px, 12vh, 110px); }
  .lde-hero-copy { width: 100%; margin-left: 0; margin-top: 4vh; }
  .lde-stage-hero { left: 4%; top: -28vh; height: 38vh; }
  /* The row leaves the phone entirely; the grid is the mobile branch. */
  .lde-portal-row { display: none; }
  /* ROUND 14 — the room on a phone. The left/right placement law is a
     desktop composition; in a 390px column there is no "opposite
     side", and the copy at width:100% ran straight across her — the
     pillar title landed ON the figure on every deep room. So on
     mobile she centres, and the copy is pushed BELOW her by padding
     rather than overlapping. The mirror still applies to the two
     left-hand pillars so the family stays consistent, composed with
     the centring translate. */
  .lde-room-hero { min-height: 0; justify-content: flex-start;
    padding-top: 12px; }
  /* ROUND 35 (Nick, 2026-08-08) — shipped live as "ROUND 33" in the
     lde-theme monolith (0.9.14, commits fe1527c / c0b5342; carried into
     0.10.2). Renumbered here only because this line already spent 33 on
     the heading's left edge and 34 on the room index — the change is the
     same one, unaltered.
     The phone room is RECOMPOSED: the pillar title rises to the top of
     the page, matching the single, and the article cards begin roughly a
     third of the way down instead of below the fold. This reverses round
     14's `padding-top: calc(46vh + 26px)`, which existed expressly to
     push the copy BELOW her. She is less dominant on a phone now, by
     design — the featured images (BLO-1658) gave the cards something
     worth arriving at. Phone only: every rule here sits inside the
     existing max-width:900px block, and ≥901px is untouched.
     The padding is now uniform across all pillars, so a cropped pose no
     longer needs its own phone padding — see the note below. */
  .lde-room-hero .lde-room-copy { margin-top: 6px; }
  .lde-room-hero .lde-band { margin-top: 11vh; }
  .lde-stage-room { left: 50%; right: auto; top: 2vh; height: 44vh;
    transform: translateX(-50%); }
  body.lde-side-left .lde-stage-room {
    left: 50%; right: auto; transform: translateX(-50%) scaleX(-1); }
  body.lde-side-right .lde-stage-room {
    left: 50%; right: auto; transform: translateX(-50%); }
  /* (A cropped pose's own phone STAGE geometry — Belleza on LDE — lives
     in the child stylesheet, loaded after this file. Since round 35 its
     hero padding does not: the 12px above is the whole family's.) */
  .lde-room-copy { width: 100%; margin-left: 0; margin-right: 0; }
  body.lde-side-right .lde-room-copy { margin-left: 0; margin-right: 0; }
}


/* =====================================================================
   ROUND 23 — THE SINGLE POST, merged from _r23-single.css.
   The room carries over from the category and the article arrives on
   top of it as glass; the H1 lives on the background surface, not
   inside the panel. ONE scroll model since 0.10.0: lde-read-panel,
   chosen by Nick 2026-08-07 and selected by the lde_read_model filter
   in functions.php (the retired alternatives are recorded in
   CHANGELOG-lde-theme.md, round 23). Sections 7-9 and the
   forced-colors / print blocks are round 15's work, carried over.
   ===================================================================== */

/* 1 ------------------------------------------------------- pillar accent
   One channel triple per pillar so panels can tint themselves with
   rgba() without five copies of every rule. Kept from round 15. */
body { --lde-pillar: 140, 49, 48; }
/* Per-pillar values: emitted from the registry (block 4). */

/* On the three deep rooms the pillar colour is the room itself, so a
   link tinted with it vanishes. Inside the glass, use the cream-side
   accent instead — the panel is the light surface, not the room. */
body.lde-dark { --lde-pillar-on-glass: 226, 176, 168; }
body { --lde-pillar-on-glass: var(--lde-pillar); }


/* 2 ----------------------------------------------------- the page shell */
.lde-single {
  position: relative; z-index: 2;
  max-width: min(1560px, 100%); margin: 0 auto;
  padding: 0 clamp(16px, 4vw, 56px) clamp(40px, 6vh, 80px);
}


/* 3 ------------------------------------------------------------ the room
   Fixed, full strength, behind everything. `body::before` already paints
   the mood field (lde.css section "mood layers"); this is the Lady on
   top of it, and nothing else. She does not draw herself in on a single:
   the experience script does not run here, so she is simply, quietly,
   already there when the page arrives. */
.lde-single-room {
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  contain: strict;
}
.lde-single-room .lde-stage-single {
  /* ROUND 24 — taller and lower than the category pose. With the rail
     lifted to the top of the body she was 6vh-to-72vh and the rail was
     83px-to-664px: she was almost exactly covered, and a figure you can
     only see in the 8px gaps between three panels is not a figure. Run
     her past the rail's bottom edge instead, so the panels cross her
     mid-body and the rest of her stands in the open. Overlaid, not
     buried.
     0.9.13 — the 84vh is now the BASE of round 30's per-pillar scale
     (`--lde-lady-scale`, defined at the round-30 block): this is the
     rule that wins on the single at (0,2,0), so the multiplication has
     to live here, not on the bare class that loses to it. The variable
     `--lde-single-lady-h` is the one round 30 read and nobody defined. */
  --lde-single-lady-h: 84vh;
  position: absolute; top: 9vh;
  height: calc(var(--lde-single-lady-h) * var(--lde-lady-scale, 1));
  aspect-ratio: 400 / 563;
  opacity: .92;
}
.lde-single-room svg.lde-pose { width: 100%; height: 100%; display: block; }


/* 4 -------------------------------------------------------- the masthead
   The H1 on the background surface. Fixed, so the article scrolls over
   it rather than pushing it away, and `pointer-events: none` on the
   block with it restored on the links, so a fixed layer sitting under
   the article never eats a click meant for the text above it. */
.lde-single-masthead {
  position: fixed; z-index: 1; pointer-events: none;
  left: 0; right: 0; top: clamp(74px, 12vh, 132px);
  padding: 0 clamp(16px, 4vw, 56px);
  max-width: min(1560px, 100%); margin: 0 auto;
}
.lde-single-masthead > * { max-width: min(760px, 64%); }
.lde-single-masthead a { pointer-events: auto; }
/* ROUND 24 — the kicker sits on the ROOM, not on glass, so the pillar
   colour is the one colour it cannot use: measured 3.48:1 on Cocina
   (amber-brown on amber) and 2.87:1 on Salud (navy on navy). Same trap
   as Moda's lips and the search button's focus ring. It takes the
   room's own foreground instead and stays legible by construction. */
.lde-single-masthead .lde-eyebrow,
.lde-single-masthead .lde-card-tag,
.lde-single-masthead .lde-card-tag a { color: var(--lde-fg); opacity: .82; }
body.lde-dark .lde-single-masthead .lde-card-tag,
body.lde-dark .lde-single-masthead .lde-card-tag a { color: var(--lde-cream-dark); }
.lde-single-title {
  font-family: var(--lde-serif); font-weight: 400;
  font-size: clamp(30px, 4.4vw, 58px); line-height: 1.06;
  letter-spacing: -.012em; margin: 8px 0 10px; color: var(--lde-fg);
}
/* The base .lde-post-meta rule paints rgba(28,25,22,.6) — ink, from
   when the meta lived inside the light glass panel. On the masthead it
   is on the ROOM, so on Belleza and Salud that was near-black on
   aubergine and navy: measured 1.19:1 and 1.88:1. It takes the room's
   foreground here, like everything else that sits on the room. */
.lde-single-masthead .lde-post-meta {
  display: flex; flex-wrap: wrap; gap: 6px 18px;
  font: 400 12.5px var(--lde-sans); color: var(--lde-fg);
  letter-spacing: .04em; opacity: .8;
}
.lde-single-masthead .lde-post-meta * { color: inherit; }




/* 5 -------------------------------------------------------- the columns
   The rail is FIRST in the document so its skip-to-heading links are
   reached before the article rather than after it; grid placement moves
   it to the right on desktop. Reading order is contents-then-article
   either way, so nothing is visually reordered away from tab order. */
.lde-single-cols {
  display: grid; align-items: start; min-width: 0;
  gap: clamp(14px, 1.8vw, 28px);
  grid-template-columns: minmax(0, 1fr);
}
.lde-rail { display: grid; gap: clamp(12px, 1.4vw, 18px); min-width: 0; }
@media (min-width: 1100px) {
  .lde-single-cols { grid-template-columns: minmax(0, 1fr) clamp(272px, 23vw, 348px); }
  .lde-reading { grid-column: 1; grid-row: 1; }
  .lde-rail {
    grid-column: 2; grid-row: 1; position: sticky; top: 74px;
    max-height: calc(100vh - 96px); overflow-y: auto; padding-right: 2px;
    scrollbar-width: thin; scrollbar-color: rgba(28, 25, 22, .2) transparent;
  }
}

.lde-reading {
  max-width: none; margin: 0; min-width: 0; position: relative;
  display: flex; flex-direction: column;
  padding: clamp(18px, 2.2vw, 38px);
  border-top: 2px solid rgba(var(--lde-pillar), .5);
}
.lde-reading-scroll { min-height: 0; }
.lde-reading .wp-block-post-content { padding-left: 17px; }
.lde-reading .lde-post-tags {
  flex: 0 0 auto; margin: 14px 0 0; padding: 13px 0 0;
  border-top: 1px solid rgba(128, 118, 108, .3);
}


/* 6 ============================== THE SCROLL MODEL — body.lde-read-panel

   The one reading model (0.10.0: the round-23 alternatives are retired;
   see README, «the read-model filter»).
   The internally scrolling panel: --------------------------------------
   The room and masthead hold still and only the text moves, so the H1
   and the Lady are on screen for the whole read. That is the case for
   it, and it is a real one.
   GUARD RAILS, because this pattern earns its bad reputation when it has
   none. The overflow switches on ONLY at a viewport of at least 1100px
   wide and 660px tall. CSS pixels shrink with browser zoom, so a reader
   at 200% on a 1440px screen is at an effective 720px and gets the plain
   page-scrolling article instead — WCAG 1.4.10 reflow is satisfied by
   falling out of the pattern rather than by fighting it. The region is
   focusable and named server-side so it can be scrolled from the
   keyboard, it traps nothing, and scroll chaining is left ON so the page
   keeps moving when the panel bottoms out. */
@media (min-width: 1100px) and (min-height: 660px) {
  body.lde-read-panel .lde-reading { max-height: calc(100vh - 200px); }
  @supports (height: 100dvh) {
    body.lde-read-panel .lde-reading { max-height: calc(100dvh - 200px); }
  }
  body.lde-read-panel .lde-reading-scroll {
    overflow-y: auto; overflow-x: clip;
    scrollbar-gutter: stable both-edges; scroll-behavior: smooth;
    scrollbar-width: thin;
    scrollbar-color: rgba(var(--lde-pillar), .45) transparent;
  }
  body.lde-read-panel .lde-reading-scroll:focus-visible {
    outline: 2px solid rgb(var(--lde-pillar)); outline-offset: 3px;
    border-radius: 6px;
  }
  body.lde-read-panel .lde-reading .wp-block-post-content
    :is(h1, h2, h3, h4, h5, h6) { scroll-margin-top: 22px; }
}


/* 7 -------------------------------------- article typography (round 15) */
.lde-reading .wp-block-post-content :is(h1, h2, h3, h4, h5, h6) {
  scroll-margin-top: 82px;
  margin-left: -17px; padding-left: 14px;
  border-left: 3px solid transparent;
  transition: border-color .2s ease;
}
/* The no-JavaScript answer to "which section am I in": the heading you
   jumped to marks itself. Works from a Google sitelink, from the panel,
   from a pasted URL, with the script blocked, everywhere. */
.lde-reading .wp-block-post-content :is(h1, h2, h3, h4, h5, h6):target {
  border-left-color: rgb(var(--lde-pillar-on-glass));
}
.lde-reading-scroll a:where(:not(.wp-block-button__link)) {
  color: rgb(var(--lde-pillar-on-glass));
  text-decoration-thickness: 1px; text-underline-offset: 2px;
  text-decoration-color: rgba(var(--lde-pillar-on-glass), .42);
}
.lde-reading-scroll a:where(:not(.wp-block-button__link)):hover {
  text-decoration-color: rgb(var(--lde-pillar-on-glass));
}
.lde-reading-scroll blockquote {
  margin: 22px 0; padding: 2px 0 2px 18px;
  border-left: 2px solid rgba(var(--lde-pillar-on-glass), .45);
  font-family: var(--lde-serif); font-style: italic;
}
.lde-reading-scroll hr {
  border: 0; height: 1px; margin: 30px 0;
  background: linear-gradient(90deg,
    rgba(var(--lde-pillar-on-glass), .32), rgba(128, 118, 108, .18));
}
.lde-reading-scroll table {
  width: 100%; border-collapse: collapse; margin: 20px 0;
  font: 400 13px/1.45 var(--lde-sans);
}
.lde-reading-scroll :is(th, td) {
  padding: 9px 10px; text-align: left; vertical-align: top;
  border-bottom: 1px solid rgba(128, 118, 108, .25);
  overflow-wrap: break-word;
}
.lde-reading-scroll th {
  font: 600 10.5px var(--lde-sans); letter-spacing: .06em;
  text-transform: uppercase; opacity: .7;
  border-bottom-color: rgba(var(--lde-pillar-on-glass), .4);
  overflow-wrap: normal;
}


/* 8 ------------------------------------------------- contents (round 15) */
.lde-toc, .lde-recipe, .lde-next { padding: 16px 16px 14px; border-radius: 14px; }
.lde-toc-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; margin: 0 0 10px;
}
.lde-toc-title, .lde-next-title {
  font: 600 10.5px var(--lde-sans); letter-spacing: .2em;
  text-transform: uppercase; margin: 0; opacity: .78;
}
.lde-readtime { font: 400 11px var(--lde-sans); opacity: .74; white-space: nowrap; }
.lde-toc-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; }
.lde-toc-i { margin: 0; }
.lde-toc-i > a {
  display: block; padding: 5px 4px 5px 14px; text-decoration: none;
  font: 400 13px/1.32 var(--lde-sans); color: var(--lde-fg); opacity: .74;
  border-left: 2px solid transparent; transition: opacity .18s, border-color .18s;
}
.lde-toc-d1 > a { padding-left: 24px; font-size: 12.5px; }
.lde-toc-d2 > a { padding-left: 34px; font-size: 12px; }
.lde-toc-i > a:hover { opacity: 1; border-left-color: rgba(var(--lde-pillar-on-glass), .4); }
.lde-toc-i > a:focus-visible {
  outline: 2px solid rgb(var(--lde-pillar-on-glass)); outline-offset: -2px;
  border-radius: 4px; opacity: 1;
}
.lde-toc-i > a[aria-current="location"] {
  opacity: 1; border-left-color: rgb(var(--lde-pillar-on-glass));
  background: rgba(var(--lde-pillar-on-glass), .07);
}


/* 9 ---------------------------------------------- ingredients (round 15) */
.lde-recipe-meta {
  display: flex; flex-wrap: wrap; gap: 4px 14px; margin: 0 0 6px;
  font: 400 11.5px var(--lde-sans); opacity: .78;
}
.lde-recipe-meta span { position: relative; }
.lde-recipe-meta span + span::before {
  content: "·"; position: absolute; left: -9px; opacity: .5;
}
.lde-recipe-group {
  font: 600 10.5px var(--lde-sans); letter-spacing: .16em;
  text-transform: uppercase; opacity: .76; margin: 14px 0 4px;
}
.lde-recipe-group:first-of-type { margin-top: 4px; }
.lde-ing { list-style: none; margin: 0; padding: 0; }
.lde-ing-i {
  display: grid; grid-template-columns: auto 1fr; gap: 2px 8px;
  padding: 7px 0; border-bottom: 1px solid rgba(128, 118, 108, .22);
  font: 400 13px/1.35 var(--lde-sans);
}
.lde-ing-i:last-child { border-bottom: 0; }
.lde-ing-q { font-variant-numeric: tabular-nums; opacity: .72; white-space: nowrap; }
.lde-ing-n:first-child { grid-column: 1 / -1; }
.lde-ing-note { grid-column: 2; font-size: 11.5px; opacity: .55; }
.lde-ing-n:first-child ~ .lde-ing-note { grid-column: 1 / -1; }


/* 10 --------------------------------------------------- NEXT READ (new)
   One panel, three offers, shown one at a time: the related post, the
   latest in this pillar, and the most read. Nick asked for it to animate
   between them.

   PROGRESSIVE, and this matters more than the animation: the panel is a
   plain <ul> of three real links. With no JavaScript and no CSS
   animation you get all three, stacked, and everything works. The
   rotation is a presentation layer on top of that list, not the thing
   that makes the links exist.

   It also pauses on hover and on focus-within, because a control that
   moves out from under a reader mid-click is a trap — and the pause is
   done in CSS, so it holds even if the script never loads. */
.lde-next { position: relative; }
.lde-next-list { list-style: none; margin: 8px 0 0; padding: 0; }
.lde-next-i { margin: 0; }
.lde-next-kind {
  display: block; font: 600 9.5px var(--lde-sans); letter-spacing: .18em;
  text-transform: uppercase; opacity: .74; margin: 0 0 3px;
}
.lde-next-i > a {
  display: block; text-decoration: none; color: var(--lde-fg);
  font-family: var(--lde-serif); font-size: 15.5px; line-height: 1.26;
}
.lde-next-i > a:hover { color: rgb(var(--lde-pillar-on-glass)); }
.lde-next-i > a:focus-visible {
  outline: 2px solid rgb(var(--lde-pillar-on-glass)); outline-offset: 3px;
  border-radius: 4px;
}
.lde-next-meta { font: 400 11px var(--lde-sans); opacity: .7; margin: 4px 0 0; }
/* The rotation. Only engaged when the panel is marked live — the class
   is added by script — so the no-JS stack above is what ships to a
   reader whose script never arrives. */
.lde-next.is-live .lde-next-list {
  display: grid; grid-template-areas: "slot";
}
.lde-next.is-live .lde-next-i {
  grid-area: slot; opacity: 0;
  animation: lde-next-cycle 15s infinite;
}
.lde-next.is-live .lde-next-i:nth-child(2) { animation-delay: -10s; }
.lde-next.is-live .lde-next-i:nth-child(3) { animation-delay: -5s; }
/* The three offers are 33.3% apart, so each has to HOLD for a little
   over a third or the panel goes blank between them. The first pass
   held for 27% and left three ~0.9s gaps per cycle where nothing was on
   screen — caught it on the live post, where the panel looked half
   faded rather than mid-change. Now they overlap by a couple of
   percent and cross-fade instead. */
@keyframes lde-next-cycle {
  0%       { opacity: 0; transform: translateY(5px); }
  2%, 33%  { opacity: 1; transform: none; }
  36%      { opacity: 0; transform: translateY(-5px); }
  100%     { opacity: 0; transform: translateY(-5px); }
}
/* Hold still while it is being read or used. */
.lde-next.is-live:hover .lde-next-i,
.lde-next.is-live:focus-within .lde-next-i { animation-play-state: paused; }
/* Round 23 hid the non-current links here to keep them out of tab
   order mid-fade. It could not work: nothing updates aria-hidden as a
   CSS animation runs, so items 2 and 3 stayed invisible for ever and
   rotated in as cards with no headline. All three are real links. */
/* Three dots, purely a position indicator; not controls, so not focusable. */
.lde-next-dots {
  display: flex; gap: 5px; margin: 10px 0 0;
}
.lde-next:not(.is-live) .lde-next-dots { display: none; }
.lde-next-dot {
  width: 5px; height: 5px; border-radius: 50%;
  background: rgba(var(--lde-pillar-on-glass), .28);
  animation: lde-next-dot 15s infinite;
}
.lde-next-dot:nth-child(2) { animation-delay: -10s; }
.lde-next-dot:nth-child(3) { animation-delay: -5s; }
@keyframes lde-next-dot {
  0%, 1%   { background: rgba(var(--lde-pillar-on-glass), .28); }
  2%, 33%  { background: rgb(var(--lde-pillar-on-glass)); }
  36%,100% { background: rgba(var(--lde-pillar-on-glass), .28); }
}


/* 11 -------------------------------------------------------- high contrast */
@media (forced-colors: active) {
  .lde-toc-i > a[aria-current="location"] { border-left-color: Highlight; background: none; }
  .lde-reading .wp-block-post-content :is(h1, h2, h3, h4, h5, h6):target {
    border-left-color: Highlight;
  }
  .lde-reading::after { display: none; }
  .lde-next.is-live .lde-next-i { animation: none; opacity: 1; }
  .lde-next.is-live .lde-next-list { display: block; }
  .lde-next-dots { display: none; }
}


/* 12 ------------------------------------------------------ reduced motion
   The Next Read panel stops rotating ENTIRELY and shows all three at
   once. A rotating panel is exactly the "moving content" WCAG 2.2.2 is
   about, and the honest reduced-motion answer to a carousel is not a
   slower carousel — it is a list. */
@media (prefers-reduced-motion: reduce) {
  .lde-reading-scroll, .lde-toc-list { scroll-behavior: auto !important; }
  .lde-reading::after { transition: none !important; }
  .lde-toc-i > a,
  .lde-reading .wp-block-post-content :is(h1, h2, h3, h4, h5, h6) {
    transition: none !important;
  }
  .lde-next.is-live .lde-next-list { display: block; }
  .lde-next.is-live .lde-next-i {
    animation: none !important; opacity: 1 !important; transform: none !important;
    margin-bottom: 12px;
  }
  .lde-next-dots { display: none; }
}


/* 13 -------------------------------------------------------------- mobile
   Nothing scrolls inside anything down here: the panel is a plain block
   and the page scrolls, which is the only sane behaviour on a thumb. The
   room stays — she is the reason the post feels like it belongs to its
   pillar — but she drops behind the glass at a lower strength, and the
   masthead becomes an ordinary block in flow so the title is never
   competing with her for the same 40% of a small screen. */
@media (max-width: 900px) {
  .lde-single-room .lde-stage-single {
    /* 0.9.13 — 34vh is the phone base for round 30's per-pillar scale,
       same construction as the desktop rule (see the single room). */
    --lde-single-lady-h: 34vh;
    top: 2vh;
    height: calc(var(--lde-single-lady-h) * var(--lde-lady-scale, 1));
    opacity: .38;
    left: auto; right: -4%; transform: none;
  }
  body.lde-side-left .lde-single-room .lde-stage-single {
    left: -4%; right: auto; transform: scaleX(-1);
  }
  .lde-single-masthead {
    position: static; padding: clamp(18px, 4vh, 34px) 0 clamp(12px, 2vh, 20px);
    max-width: 100%;
  }
  .lde-single-masthead > * { max-width: 100%; }
  .lde-single-title { font-size: clamp(26px, 7.4vw, 34px); }
  body.lde-read-panel .lde-single { padding-top: 0; }
  body.lde-read-panel .lde-reading { max-height: none; }
  body.lde-read-panel .lde-reading-scroll { overflow: visible; }
  .lde-reading { padding: 18px 16px; }
  .lde-reading .wp-block-post-content { padding-left: 15px; }
  .lde-reading .wp-block-post-content :is(h1, h2, h3, h4, h5, h6) {
    margin-left: -15px; padding-left: 12px; scroll-margin-top: 72px;
  }
  /* One column; the rail is already first in the document, so the map
     arrives before the territory with no reordering at all. */
  .lde-single-cols { display: flex; flex-direction: column; }
}


/* 14 --------------------------------------------------------------- print */
@media print {
  .lde-reading, .lde-reading-scroll {
    max-height: none !important; overflow: visible !important;
    border: 0 !important; box-shadow: none !important; padding: 0 !important;
    background: none !important;
  }
  .lde-single-masthead { position: static !important; }
  .lde-rail, .lde-single-room,
  header.lde-header, footer.lde-footer { display: none !important; }
  body.lde-read-panel .lde-single { padding-top: 0 !important; }
}


/* =====================================================================
   ROUND 23 — SEARCH, merged from _r23-search.css.
   The spyglass at the right end of the pill bar and the panel it opens.
   Built on <details>/<summary> over a real GET form, so it works with
   no JavaScript at all; the instant results are an enhancement.
   Retires Relevanssi and Ivory Search (BLO-1655).
   ===================================================================== */

.lde-search { position: relative; }
.lde-search[open] { z-index: 30; }

/* summary's default marker has to go on three engines, and
   `display: grid` on the summary itself is what removes it in Safari. */
.lde-search-btn {
  list-style: none; cursor: pointer;
  display: grid; place-items: center;
  width: 34px; height: 34px; border-radius: 50%;
  border: 1px solid var(--lde-glass-border);
  background: var(--lde-glass-bg);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
          backdrop-filter: blur(14px) saturate(1.2);
  box-shadow: 0 8px 22px rgba(28, 25, 22, .1);
  color: var(--lde-fg);
  transition: border-color .25s, background .25s, transform .25s;
}
.lde-search-btn::-webkit-details-marker { display: none; }
.lde-search-btn::marker { content: ""; }
.lde-search-btn:hover { border-color: rgba(140, 49, 48, .5); }
/* The button sits on the ROOM, so it takes the room-aware ring from
   lde.css rather than the pillar colour — on Salud the pillar colour IS
   the navy behind it, and a navy ring on navy is not a ring. */
.lde-search-btn:focus-visible {
  outline: 2px solid var(--lde-focus, currentColor); outline-offset: 3px;
}
.lde-search[open] .lde-search-btn {
  border-color: rgba(140, 49, 48, .5);
  background: rgba(140, 49, 48, .1);
}
.lde-search-icon { width: 17px; height: 17px; display: block; }

/* The panel. Anchored to the button's right edge so it never runs off
   the viewport on the side it opens from. */
.lde-search-pop {
  position: absolute; top: calc(100% + 10px); right: 0;
  width: min(420px, calc(100vw - 28px));
  padding: 12px; border-radius: 16px;
  border: 1px solid var(--lde-glass-border);
  background: var(--lde-glass-bg);
  -webkit-backdrop-filter: blur(24px) saturate(1.3);
          backdrop-filter: blur(24px) saturate(1.3);
  box-shadow: var(--lde-glass-shadow);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  /* Same honest fallback as the rest of the glass: near-opaque, legible,
     simply not translucent. A search panel you cannot read the results
     through is worse than one that is not made of glass. */
  .lde-search-pop { background: rgba(250, 246, 238, .97); }
  body.lde-dark .lde-search-pop { background: rgba(26, 18, 22, .97); }
}

.lde-search-form { display: flex; gap: 8px; }
.lde-search-input {
  flex: 1 1 auto; min-width: 0;
  padding: 9px 12px; border-radius: 10px;
  border: 1px solid rgba(128, 118, 108, .35);
  background: rgba(255, 255, 255, .5);
  font: 400 15px var(--lde-body); color: var(--lde-fg);
}
body.lde-dark .lde-search-input {
  background: rgba(255, 255, 255, .07);
  border-color: rgba(255, 255, 255, .2);
}
.lde-search-input:focus-visible {
  outline: 2px solid rgb(var(--lde-pillar-on-glass, 140, 49, 48)); outline-offset: 1px;
  border-color: transparent;
}
.lde-search-input::placeholder { color: var(--lde-fg); opacity: .45; }
/* The submit button is the no-JS path and stays reachable, but it is not
   the point of the panel once the script is running, so it reads as
   secondary rather than as the primary action. */
.lde-search-go {
  flex: 0 0 auto; padding: 9px 14px; border-radius: 10px; cursor: pointer;
  border: 1px solid var(--lde-glass-border);
  background: transparent; color: var(--lde-fg);
  font: 600 10.5px var(--lde-sans); letter-spacing: .16em;
  text-transform: uppercase;
}
.lde-search-go:hover { border-color: rgba(140, 49, 48, .5); }
.lde-search-go:focus-visible {
  outline: 2px solid rgb(var(--lde-pillar-on-glass, 140, 49, 48)); outline-offset: 2px;
}

.lde-search-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; }
.lde-search-hit {
  display: grid; gap: 2px; padding: 8px 10px; border-radius: 9px;
  text-decoration: none; color: var(--lde-fg);
}
.lde-search-hit:hover,
.lde-search-hit[aria-selected="true"] { background: rgba(140, 49, 48, .09); }
.lde-search-hit:focus-visible {
  outline: 2px solid rgb(var(--lde-pillar-on-glass, 140, 49, 48)); outline-offset: -2px;
}
.lde-search-kind {
  font: 700 9.5px var(--lde-sans); letter-spacing: .2em;
  text-transform: uppercase; opacity: .62;
}
.lde-search-hit-title {
  font-family: var(--lde-serif); font-size: 15.5px; line-height: 1.25;
}
/* The matched run. `mark`'s UA yellow is the one colour in the system
   that belongs to no room, so it is restyled rather than inherited —
   but it stays a <mark>, because that is what tells a screen reader
   this run is the reason the result is here. */
.lde-search-hit mark {
  background: none; color: inherit;
  box-shadow: inset 0 -.42em 0 rgba(140, 49, 48, .34);
  border-radius: 2px;
}
body.lde-dark .lde-search-hit mark {
  box-shadow: inset 0 -.42em 0 rgba(226, 176, 168, .26);
}
.lde-search-empty {
  margin: 12px 4px 4px; font: 400 13px var(--lde-sans);
  color: var(--lde-fg); opacity: .6;
}

@media (max-width: 900px) {
  .lde-search-btn { width: 30px; height: 30px; }
  .lde-search-icon { width: 15px; height: 15px; }
}

@media (forced-colors: active) {
  .lde-search-btn, .lde-search-pop { border: 1px solid CanvasText; }
  .lde-search-hit[aria-selected="true"] { background: Highlight; color: HighlightText; }
  .lde-search-hit mark { box-shadow: none; text-decoration: underline; }
}

/* A CLOSED <details> still reports a small box for its content in
   Chromium, and a fixed panel inside it keeps its own intrinsic width:
   measured at 390px, the closed search panel's BUSCAR button sat at
   x=397 and pushed the document's scrollWidth to 439 — a horizontal
   scrollbar on every room page, from a panel nobody could see. Taking
   the content out of layout while closed fixes it and costs nothing:
   the browser was not painting it anyway. */
.lde-search:not([open]) .lde-search-pop,
.lde-menu:not([open]) .lde-menu-sheet { display: none; }

@media print { .lde-search { display: none !important; } }

/* The visually-hidden utility. lde.css does not carry one — the theme
   had no markup that needed it until now — so it is defined here rather
   than assumed. Kept out of `display:none` and `visibility:hidden`,
   both of which remove the text from the accessibility tree, which is
   the one thing it exists to stay in. */
.screen-reader-text {
  position: absolute !important;
  width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}
.screen-reader-text:focus {
  position: static !important; width: auto; height: auto;
  margin: 0; clip-path: none; white-space: normal;
}

/* =====================================================================
   ROUND 24 — merged from _r24-panel-nav.css. Internal scroll becomes the
   model, the rail moves to the top of the body over the Lady, the rail
   panels collapse and sticky-stack on phones, and the navigation moves
   to a thumb bar at the bottom of the frame behind a hamburger.
   ===================================================================== */
/* 1 ------------------------------------------------------ ONE VARIABLE
   How far down the page the ARTICLE starts. The masthead's clearance,
   the reading panel's max-height and the mobile stack all derive from
   it, so the three cannot drift apart the way they did when each
   carried its own clamp(). */
/* WordPress pushes the whole document down by 32px for a logged-in
   admin (`html { margin-top: 32px !important }`), which a
   viewport-height layout has no way of knowing about. Un-accounted, the
   room's pager and strapline sit just below the fold — for the one
   person who looks at the site most. */
body.admin-bar { --lde-adminbar: 32px; }
@media screen and (max-width: 782px) {
  body.admin-bar { --lde-adminbar: 46px; }
}

body.lde-single-page {
  --lde-single-top: clamp(150px, 27vh, 258px);
  /* The header shrinks below 900px; measured at 35px. */
  --lde-header-h-m: 35px;
  /* Measured, not guessed: .lde-header is position:sticky and 73px tall
     at every width above 900. Sticky means it is IN FLOW, so <main>
     already begins below it — padding-top on the shell for "header
     clearance" was double-counting and pushed the rail 73px down from
     the top it is supposed to start at. */
  --lde-header-h: 73px;
}

/* B IS THE MODEL. The shell adds NOTHING at the top — the sticky header
   has already taken its own space — and the article carries the
   masthead clearance itself, which is what frees the rail to start at
   the top of the body. */
body.lde-read-panel .lde-single { padding-top: 0; }
body.lde-read-panel .lde-reading { margin-top: var(--lde-single-top); }

/* 2 ------------------------------------------- the rail, at the top
   It begins level with the header and runs down over her. She is 66vh
   tall starting at 6vh on the right at 5%, and the rail is the right
   ~23vw, so the panels sit ON her rather than beside her — which is the
   same glass-over-mood contract the article has, applied to the column
   that used to politely stay out of her way. */
@media (min-width: 1100px) {
  .lde-rail { margin-top: 0; top: calc(var(--lde-header-h) + 10px); }
  /* The rail is now taller than the article's box, so its own cap has to
     come off the top of the page, not off the article. */
  .lde-rail { max-height: calc(100vh - var(--lde-header-h) - 24px); }
  @supports (height: 100dvh) {
    .lde-rail { max-height: calc(100dvh - var(--lde-header-h) - 24px); }
  }
  /* The reading panel's own cap, derived from the same variable. */
  body.lde-read-panel .lde-reading {
    max-height: calc(100vh - var(--lde-single-top) - 78px);
  }
  @supports (height: 100dvh) {
    body.lde-read-panel .lde-reading {
      max-height: calc(100dvh - var(--lde-single-top) - 78px);
    }
  }
}
/* The masthead keeps to the left 64%, so a rail sitting at the top of
   the page does not land on the title. Stated here rather than left to
   luck, because the rail moving up is exactly the change that would
   break it. */
@media (min-width: 1100px) {
  .lde-single-masthead > * { max-width: min(760px, 62%); }
}


/* 3 ---------------------------------------- the rail panels, as panels
   Each one is a <details> now. Open everywhere by default; the caret and
   the collapsing only appear below 900px, where a three-panel rail
   stacked above a long article is otherwise a lot of scrolling before
   the first paragraph.

   <details> rather than a button and a class: the disclosure state is
   the browser's, it is keyboard-operable and announced correctly with
   no ARIA of ours, and with the script blocked everything is simply
   open, which is the safe failure. */
.lde-panel > summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; margin: 0;
}
.lde-panel > summary::-webkit-details-marker { display: none; }
.lde-panel > summary::marker { content: ""; }
.lde-panel > summary:focus-visible {
  outline: 2px solid var(--lde-focus); outline-offset: 4px; border-radius: 6px;
}
.lde-panel-caret {
  display: none; width: 10px; height: 10px; flex: 0 0 auto;
  border-right: 1.6px solid currentColor; border-bottom: 1.6px solid currentColor;
  transform: rotate(45deg) translate(-2px, -2px); opacity: .5;
  transition: transform .2s ease;
}
/* On desktop nothing about these reads as collapsible: no caret, and the
   summary is styled as the heading it already was. It stays operable,
   which is a feature rather than an oversight — a reader who wants the
   contents out of the way can have that. */
@media (min-width: 901px) {
  .lde-panel > summary { cursor: default; }
}


/* 4 -------------------------------------------- mobile: the rail stacks
   Collapsed, each panel is a bar about 44px tall. They stick in
   sequence, so scrolling the article pushes them up into a stack of
   labelled bars at the top of the frame rather than off the screen —
   the contents and the ingredients stay one tap away for the whole
   read, which is the thing the desktop rail does and the mobile one
   never did. */
@media (max-width: 900px) {
  .lde-panel-caret { display: block; }
  .lde-panel[open] > summary .lde-panel-caret {
    transform: rotate(-135deg) translate(-2px, -2px);
  }
  .lde-single-cols { display: block; }
  /* THE RAIL STICKS, not the panels individually.
     Sticky is bounded by the PARENT's box, and with the rail as its own
     block above the article the panels' parent ends where the article
     begins — measured, they unstuck after about one screen and the
     contents vanished for the rest of the read. Making the rail itself
     sticky moves the containing block up to .lde-single-cols, which
     spans the whole article, so the stack holds for the entire read.
     The panels inside are collapsed bars, so the stack IS the rail. */
  /* THE RAIL IS ONE PLATE, and the panels are rows in it.
     Three separately-floating bars left slivers of moving article text
     visible between them, which reads as a rendering fault rather than
     as depth. One opaque surface holding three rows is calmer, is one
     thing to look at rather than three, and means only the plate needs
     to be opaque — the rows can stay flat. */
  .lde-rail {
    display: block; margin: 0 0 12px;
    position: sticky; top: var(--lde-header-h-m); z-index: 12;
    max-height: calc(100vh - var(--lde-header-h-m) - 96px);
    overflow-y: auto; overscroll-behavior: contain;
    padding: 4px; border-radius: 16px;
    background: #FAF6EF;
    border: 1px solid var(--lde-glass-border);
    box-shadow: 0 10px 26px rgba(28, 25, 22, .14);
  }
  body.lde-dark .lde-rail {
    background: #1E1519; box-shadow: 0 10px 26px rgba(0, 0, 0, .34);
  }
  @supports (height: 100dvh) {
    .lde-rail { max-height: calc(100dvh - var(--lde-header-h-m) - 96px); }
  }
  .lde-panel {
    margin: 0; border-radius: 11px;
    background: none; border: 0; box-shadow: none;
    -webkit-backdrop-filter: none; backdrop-filter: none;
  }
  .lde-panel + .lde-panel { border-top: 1px solid rgba(128, 118, 108, .22); }
  /* A collapsed panel must be opaque, not glass: stacked bars sitting
     over a scrolling article show every line of text through them
     otherwise, and three translucent bars over moving type is unreadable
     in a way one is not. */
  .lde-panel:not([open]) { padding: 11px 13px; }
  /* An OPEN panel on a phone expands over the running article, so it
     has the same problem the menu sheet had: at glass alpha the
     paragraphs underneath read straight through the contents list.
     Opaque while open, glass again on desktop where it floats over a
     mood field rather than over text. */
  .lde-panel[open] { padding: 11px 13px 14px; }
}

@media (prefers-reduced-motion: reduce) {
  .lde-panel-caret { transition: none; }
}


/* 5 =================================================== THE BOTTOM NAV

   Below 900px the pill row leaves the header and becomes a fixed bar at
   the bottom of the frame, where a thumb actually is. The five rooms
   and the two quick buttons condense behind a hamburger; the bar itself
   carries the hamburger, the room you are in, and the spyglass.

   THE MARKUP CONSTRAINT that shapes all of this: the five `.lde-pill`
   elements MUST stay rendered on desktop, because the cross-document
   view transition pairs each front-page card with its pill by name and
   a pill inside a closed <details> is not in the render tree at all —
   the whole nav animation would silently stop working. So the details
   ships OPEN and a two-line inline script closes it below 900px before
   first paint. `display: contents` on the <details> was the obvious
   alternative and it is a trap: measured in Chromium, a details with
   display:contents does not hide its content when closed at all.

   With no JavaScript on a phone the sheet is simply open — a stack of
   seven links above the bar. Longer than it should be, and completely
   usable. */
.lde-menu { display: flex; align-items: center; gap: inherit; }
.lde-menu-btn { display: none; }
.lde-menu-sheet { display: flex; align-items: center; gap: inherit; }
.lde-menu-here { display: none; }

@media (max-width: 900px) {
  /* The header keeps the wordmark and nothing else — and it has to STOP
     BEING GLASS, which is not a style choice.
     `backdrop-filter` on an ancestor makes that ancestor the containing
     block for `position: fixed` descendants. Measured: with the header
     still glass, the bottom bar resolved its `bottom: 0` against the
     73px header instead of the viewport and landed at top:-45px, half
     off the top of the screen. Same trap for the menu sheet and the
     search panel, which are also fixed. Dropping the glass here fixes
     all three at once, and on a phone the header was only ever a
     wordmark on the room anyway. */
  header.lde-header, .lde-header-in {
    border: 0; box-shadow: none;
    /* Only the BLUR goes. A plain background does not create a
       containing block — filter, backdrop-filter, transform and
       will-change do — so the header keeps an opaque fill and the
       fixed bottom bar still resolves against the viewport. It needs
       that fill: with the header fully transparent the article's
       headings ran straight under the wordmark as you scrolled. */
    -webkit-backdrop-filter: none; backdrop-filter: none;
    background: #FAF6EF;
  }
  body.lde-dark header.lde-header { background: #160F13; }
  .lde-header-in { background: none; }
  .lde-header-in { justify-content: flex-start; }

  .lde-pills {
    position: fixed; left: 0; right: 0; bottom: 0; top: auto;
    z-index: 40; overflow: visible;
    display: flex; align-items: center; justify-content: space-between;
    gap: 10px;
    padding: 10px clamp(12px, 4vw, 22px);
    /* Clear of the home indicator on iOS, and of nothing on Android. */
    padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
    border-top: 1px solid var(--lde-glass-border);
    background: var(--lde-glass-bg);
    -webkit-backdrop-filter: blur(20px) saturate(1.25);
            backdrop-filter: blur(20px) saturate(1.25);
  }
  /* Same reasoning as the sheet: this bar sits over running text for
     the whole read, so it is a surface, not a veil. */
  .lde-pills { background: rgba(250, 246, 238, .96); }
  body.lde-dark .lde-pills { background: rgba(26, 18, 22, .96); }
  /* Nothing should end up underneath the bar. */
  body { padding-bottom: calc(66px + env(safe-area-inset-bottom, 0px)); }

  .lde-menu { position: static; }
  .lde-menu-btn {
    display: flex; align-items: center; gap: 10px;
    list-style: none; cursor: pointer;
    padding: 8px 14px 8px 12px; border-radius: 999px;
    border: 1px solid var(--lde-glass-border);
    background: transparent; color: var(--lde-fg);
    /* 44px of thumb, which is the whole point of moving it down here. */
    min-height: 40px;
  }
  .lde-menu-btn::-webkit-details-marker { display: none; }
  .lde-menu-btn::marker { content: ""; }
  .lde-menu-btn:focus-visible { outline: 2px solid var(--lde-focus); outline-offset: 3px; }
  .lde-menu[open] .lde-menu-btn {
    border-color: rgba(140, 49, 48, .5); background: rgba(140, 49, 48, .1);
  }
  /* Three rules, drawn rather than typed: an SVG for this would be three
     more bytes of markup for a shape CSS already has. */
  .lde-burger {
    width: 16px; height: 11px; flex: 0 0 auto; position: relative;
  }
  .lde-burger::before, .lde-burger::after, .lde-burger span {
    content: ""; position: absolute; left: 0; right: 0; height: 1.6px;
    background: currentColor; border-radius: 2px;
    transition: transform .2s ease, opacity .2s ease;
  }
  .lde-burger::before { top: 0; }
  .lde-burger span { top: 4.7px; }
  .lde-burger::after { bottom: 0; }
  /* Open, it becomes a close cross — same element, so there is never a
     moment where the control's meaning is ambiguous. */
  .lde-menu[open] .lde-burger::before { transform: translateY(4.7px) rotate(45deg); }
  .lde-menu[open] .lde-burger span { opacity: 0; }
  .lde-menu[open] .lde-burger::after { transform: translateY(-4.7px) rotate(-45deg); }

  /* The room you are in, on the button, so the bar says where you are
     without spending a row on it. */
  .lde-menu-here {
    display: block; font: 600 10.5px var(--lde-sans);
    letter-spacing: .2em; text-transform: uppercase; white-space: nowrap;
    max-width: 42vw; overflow: hidden; text-overflow: ellipsis;
  }

  /* The sheet rises from the bar. */
  /* The sheet rises from the bar, and it is NOT the same glass as a
     panel that floats over a mood field. It floats over TEXT. At the
     system's usual .22-.34 alpha the article's paragraphs read straight
     through the menu items and both became illegible; a navigation
     sheet has to be opaque enough to be a surface. Blur stays, for the
     family resemblance, over a near-solid fill. */
  .lde-menu-sheet {
    position: fixed; left: 8px; right: 8px;
    bottom: calc(62px + env(safe-area-inset-bottom, 0px));
    display: grid; gap: 2px; padding: 10px; border-radius: 18px;
    border: 1px solid var(--lde-glass-border);
    background: rgba(250, 246, 238, .96);
    -webkit-backdrop-filter: blur(24px) saturate(1.3);
            backdrop-filter: blur(24px) saturate(1.3);
    box-shadow: var(--lde-glass-shadow);
    max-height: 62vh; overflow-y: auto;
  }
  body.lde-dark .lde-menu-sheet { background: rgba(26, 18, 22, .96); }
  .lde-menu-sheet .lde-pill,
  .lde-menu-sheet .lde-qbtn {
    display: block; width: 100%; text-align: left;
    padding: 13px 14px; border-radius: 12px; border: 0; background: none;
    box-shadow: none; font-size: 12px; letter-spacing: .2em;
  }
  .lde-menu-sheet .lde-pill.is-active,
  .lde-menu-sheet .lde-qbtn.is-active { background: rgba(140, 49, 48, .1); }
  .lde-menu-sheet .lde-pill-sep {
    display: block; width: auto; height: 1px; margin: 6px 8px;
    background: var(--lde-glass-border);
  }
}

/* The bar prints as nothing, like the rest of the chrome. */
@media print {
  .lde-pills, .lde-menu-btn { display: none !important; }
}

/* =====================================================================
   ROUND 25 — merged from _r25-rooms.css. The room becomes a two-half
   field: she holds one side with her name beneath her, four equal cards
   hold the other. The single inherits the same law — she keeps her
   pillar's side, the rail lies over her, the article takes the other
   half.
   ===================================================================== */
/* 1 ------------------------------------------------- which side she is on
   One switch, stated once. Everything downstream — the stage, the
   title, the cards, the rail, the article — reads it rather than
   repeating the pillar list. */
body.lde-side-left { --lde-side: left; }
body.lde-side-right { --lde-side: right; }


/* 2 ===================================================== THE ROOM, DESKTOP */
@media (min-width: 1100px) {

  /* The hero IS the page. Two halves: hers, and the cards'. */
  .lde-room .lde-room-hero {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: stretch;
    gap: clamp(20px, 3vw, 56px);
    /* HEIGHT, not min-height: the four cards have to divide what is
       there. With min-height the 1fr rows sized to their content, the
       hero grew past the viewport and the pager and the pillar's own
       strapline fell off the bottom of the screen — on the one page
       whose entire point is that it fits. Guarded by a height query so
       a short window falls back to flowing normally. */
    height: calc(100vh - 73px - var(--lde-adminbar, 0px));
    /* The theme has no global border-box reset, so content-box added
       the 58px of vertical padding ON TOP of the 827px height and the
       hero came out 885 tall — one screen plus a bit, which is the one
       measurement this layout cannot get wrong. Scoped rather than
       reset globally: a box-sizing change this late would move
       everything. */
    box-sizing: border-box;
    padding: clamp(18px, 3vh, 34px) clamp(20px, 3.4vw, 56px) clamp(20px, 3.4vh, 44px);
    max-width: 1560px; margin: 0 auto;
  }
  @supports (height: 100dvh) {
    .lde-room .lde-room-hero {
      height: calc(100dvh - 73px - var(--lde-adminbar, 0px));
    }
  }
  @media (max-height: 660px) {
    .lde-room .lde-room-hero { height: auto; min-height: 0; }
  }

  /* HER HALF. The stage stops being a free-floating absolute box and
     becomes a grid cell, so the cards can have the other one honestly
     rather than by being told to keep out of a region. She is still
     painted behind everything — the copy sits over her. */
  .lde-room .lde-stage-room {
    position: relative; inset: auto; left: auto; right: auto; top: auto;
    grid-row: 1; width: 100%; height: auto; min-height: 0;
    aspect-ratio: auto;
  }
  .lde-room .lde-stage-room svg.lde-pose {
    position: absolute; inset: 0; width: 100%; height: 100%;
    object-fit: contain;
  }

  /* HER NAME, BENEATH HER, ON THE BACKGROUND.
     Below rather than above: above, the title and the pill bar stack
     into two rows of chrome before anything of the room shows, and she
     loses the top of the frame she is composed for. Below, the eye
     lands on her, drops to the word, and turns to the cards. It is also
     the arrangement that survives into a post — the same block, the
     same place, so the title does not jump when you click through. */
  .lde-room .lde-room-copy {
    position: absolute; z-index: 3; pointer-events: none;
    bottom: clamp(26px, 5vh, 62px);
    width: calc(50% - clamp(20px, 3.4vw, 56px) - clamp(10px, 1.5vw, 28px));
    margin: 0; max-width: none;
  }
  .lde-room .lde-room-copy a { pointer-events: auto; }

  /* THE CARDS' HALF. Two by two, filling the column top to bottom, and
     — the part Nick asked for explicitly — EVERY CARD THE SAME SIZE.
     `grid-auto-rows: 1fr` on a stretched container is what does it: the
     rows divide the available height evenly regardless of how much text
     any one card holds, so a two-line title and a five-line one produce
     the same rectangle. */
  .lde-room .lde-band {
    grid-row: 1; align-self: stretch; padding: 0;
    display: flex; flex-direction: column; min-height: 0;
  }

  .lde-room .lde-cards { flex: 1 1 auto; min-height: 0; display: flex; }
  .lde-room .lde-cards .wp-block-post-template.is-layout-grid,
  .lde-room .lde-cards .lde-cards-grid {
    flex: 1 1 auto; min-height: 0;
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    /* minmax(0,1fr), NOT 1fr. `1fr` in grid-auto-rows resolves to
       minmax(AUTO, 1fr), so a card whose text runs long pushes its row
       taller and the grid grows past the cell — which is exactly what
       happened: the pager and the pillar's strapline fell off the
       bottom of the screen, and the two rows were different heights,
       which is the one thing Nick asked for them not to be. */
    grid-auto-rows: minmax(0, 1fr);
    gap: clamp(12px, 1.4vw, 20px);
  }
  /* The pager tucks under the cards rather than starting a new band.
     `align-self: center` rather than `margin: 0 auto`: the band is a
     flex COLUMN, and auto side margins on a flex item make it shrink to
     min-content — seen live on Salud, where the two page numbers ended
     up stacked above SIGUIENTE in a 100px-wide box. nowrap states the
     rest of the intention outright. */
  .lde-room .lde-band .wp-block-query-pagination {
    margin: clamp(12px, 1.8vh, 22px) 0 0;
    /* 1.1.3 — stretch, not center: the nav is now a full-width grid
       whose outer columns put the buttons on the cards' edges, and
       align-self:center would shrink it back to content width. */
    align-self: stretch; flex-wrap: nowrap; white-space: nowrap;
  }
}


/* 3 ------------------------------------------------------- THE POST CARD
   Frosted, like everything else that floats over a room — but with the
   featured image revealed in the lower half under a bleed of the
   pillar's own colour, which is the treatment the homepage portals
   already use. Same idiom, so the cards on a room read as the same
   family of object as the cards on the threshold. */
.lde-card {
  position: relative; overflow: hidden; isolation: isolate;
  border-radius: 18px; min-height: 0; min-width: 0;
}
/* The LAYER covers the whole card; the IMAGE occupies the lower
   two-thirds of it. The first pass sized the layer to the image and
   left the top third as plain glass, so the tag — which is cream
   because the card is a deep field — sat on a cream strip and vanished.
   Same construction as the homepage portals: full-bleed layer, image
   from 34% down, gradient over all of it. */
.lde-card-media { position: absolute; inset: 0; z-index: -1; pointer-events: none; }
.lde-card-media img {
  position: absolute; left: 0; right: 0; bottom: 0; top: 34%;
  width: 100%; height: 66%; object-fit: cover; display: block;
}
/* The colour holds the top of the frame and bleeds down over the image
   to near-nothing at the foot, so the picture is present without the
   card becoming a photo with words on it. --lde-pc comes from the
   pillar map below, the same triples the portals use. */
/* TWO layers, because one could not do both jobs.
   The pillar bleed holds the top of the card so the tag, title and
   pullquote have a field to sit on — measured, the first pass faded to
   42% by the pullquote's line and cream italic came out at 2.6:1. It
   now holds .9 through the text block and only lets go below it.
   The second is a short foot scrim for the date, which sits at the very
   bottom where the bleed is deliberately gone: cream on bare photograph
   measured 1.2:1. 84px of fade costs almost none of the reveal Nick
   asked for and makes the line readable on any image, including the
   pale ones we cannot see from here. */
.lde-card-media::after {
  content: ""; position: absolute; inset: 0;
  background:
    /* A light foot, for seating only — no text lives down here any
       more. The date used to, and holding it legible over an arbitrary
       photograph took a scrim heavy enough to undo the reveal Nick
       asked for. Moving the date to the top row (below) solved it
       properly: up there the pillar colour is solid, so the line is
       legible by construction on any image, including the pale ones we
       cannot see from here. */
    /* 1.1.2 — the comment above is 1.0-era and no longer true: the
       date DID come back down here in 1.1.0, and the read time with
       it. The plate that defended them is gone (Nick, 2026-08-08 —
       'lose the visual subchip vibe'), so this ramp does the
       defending instead. .62 is the arithmetic, not a taste: cream
       at .95 over rgba(20,14,12,a) on a PURE WHITE photograph needs
       a >= .596 to clear 4.5:1. The deep part is held to the bottom
       46px, where the two meta lines actually sit, then eases out by
       132px so the reveal across the rest of the picture survives. */
    linear-gradient(to top,
      rgba(20, 14, 12, .62) 0,
      rgba(20, 14, 12, .44) 46px,
      rgba(20, 14, 12, 0) 132px),
    /* The pillar bleed, holding the text block. */
    linear-gradient(180deg,
      rgb(var(--lde-pc, 140, 49, 48)) 0%,
      rgba(var(--lde-pc, 140, 49, 48), .94) 56%,
      rgba(var(--lde-pc, 140, 49, 48), .8) 72%,
      rgba(var(--lde-pc, 140, 49, 48), .24) 90%,
      rgba(var(--lde-pc, 140, 49, 48), .04) 100%);
}
/* The card's bleed colour, NOT the pillar's display colour. Bienestar's
   mid green and Cocina's amber are the two light pillars, and cream over
   them measured 4.1:1 and 4.4:1 for the pullquote — fine on a deep room,
   short on a light one. Both go a step deeper here; the other three are
   already dark enough. The card is a deep object in every room, which is
   also what stops the four cards changing character from pillar to
   pillar. */
/* Per-pillar card triples (card_pc): emitted from the registry
   (block 5). A light pillar's card goes a step deeper than its display
   colour — that contrast decision now lives in the child's registry
   values, measured per brand. */

/* With an image the card is a deep field, so everything on it goes
   cream — including in a LIGHT room, where the card is now darker than
   its surroundings rather than lighter. */
.lde-card.has-media .lde-card-title,
.lde-card.has-media .lde-card-title a,
.lde-card.has-media .lde-card-pull { color: #F7F3EC; }
.lde-card.has-media .lde-card-tag,
.lde-card.has-media .lde-card-tag a { color: rgba(247, 243, 236, .82); }
/* .6 alpha is the portals' value, and there the date sits on a solid
   colour field. Here it sits at the foot of the card where the bleed is
   deliberately gone, so the alpha itself was the failure — measured
   1.6:1 against the photograph beneath. */
/* Full cream. On Moda's burgundy — the lightest of the five card
   colours — .88 measured 4.26:1, which is the sort of miss that only
   shows up on one pillar and only when you check all five. */
/* The dark-room rule below is (0,4,0) and was quietly winning over the
   has-media one, so this states both. */
.lde-card.has-media .lde-card-meta,
.lde-card.has-media .wp-block-post-date,
body.lde-dark .lde-card.has-media .lde-card-meta,
body.lde-dark .lde-card.has-media .wp-block-post-date { color: rgba(247, 243, 236, .95); }
.lde-card.has-media .lde-card-title { text-shadow: 0 1px 14px rgba(0, 0, 0, .28); }

/* 1.1.0 — THE DATE GOES BACK TO THE FOOT. The interim top-row grid
   (pillar and date sharing the first line) existed only because the
   foot of a photo card could not hold cream legibly without a scrim
   heavy enough to undo the reveal. Nick's direction: the meta sits at
   the bottom exactly as on imageless cards — position invariant to
   image presence. So the card is a single column again on BOTH kinds
   of card (the base .lde-card flex rule), and the legibility problem
   is solved where it lives: the foot wrapper becomes a small dark
   glass backplate (below), the same frosted idiom as everything else
   that floats over a room, sized to the text so the photograph keeps
   its reveal around it. */
.lde-card.has-media :where(.lde-card-meta, .wp-block-post-date) {
  font-variant-numeric: tabular-nums;
}
/* The backplate. Dark glass, not pillar ink at full strength — a chip
   of ink would read as a label; glass reads as the card's own material.
   The .64 backing is the measured floor: cream at .95 over it stays
   over 4.5:1 on a pure-white photograph even with backdrop-filter
   unsupported (the rgba alone does the arithmetic — blur is comfort,
   not load-bearing). Imageless cards keep their bare foot: no photo,
   nothing to defend against, and they were already right. */
/* 1.1.2 — THE PLATE IS GONE. The block comment above records why it
   existed and the measurement that justified it; both stand. What
   changed is the instrument, not the requirement: Nick's direction
   2026-08-08 was to keep the date and read time in one wrapper at
   the foot of every card but give that wrapper no outline and no
   fill of its own. The contrast floor is now met by the card image's
   own foot ramp (see .lde-card-media::after, deepened to .62 in the
   same round) plus the shadow below — shading that belongs to the
   photograph rather than a chip laid on top of it. The wrapper
   itself stays: it is what keeps the meta's position identical with
   and without an image, which is a separate fix and still wanted. */
.lde-card.has-media .lde-card-foot {
  align-self: stretch;
  padding: 0; border: 0; border-radius: 0; background: none;
  -webkit-backdrop-filter: none; backdrop-filter: none;
}
/* Belt to the ramp's braces: a soft shadow buys legibility on the
   pale photographs without reading as a box. Same idiom and the same
   direction as the title's shadow above, one stop heavier because
   these two lines are 10.5px and the title is not. */
.lde-card.has-media :where(.lde-card-meta, .lde-card-read, .wp-block-post-date) {
  text-shadow: 0 1px 10px rgba(0, 0, 0, .55), 0 0 2px rgba(0, 0, 0, .35);
}
/* Both surfaces stated, same reason as the meta pair above: the
   dark-room rule is (0,3,1) and would quietly win otherwise. */
.lde-card.has-media .lde-card-read,
body.lde-dark .lde-card.has-media .lde-card-read { color: rgba(247, 243, 236, .92); }

/* THE PULLQUOTE. A line lifted from the piece, set in the serif so it
   reads as the writer's voice rather than as a summary — which is the
   whole difference between a pullquote and an excerpt. It clamps: the
   cards are a fixed size now, so the text has to yield, not the box. */
.lde-card-pull {
  font-family: var(--lde-serif); font-style: italic;
  font-size: clamp(13.5px, 1.02vw, 15.5px); line-height: 1.42;
  margin: 10px 0 0; position: relative; padding-left: 13px;
  display: -webkit-box; -webkit-box-orient: vertical;
  -webkit-line-clamp: 3; line-clamp: 3; overflow: hidden;
}
.lde-card-pull::before {
  content: ""; position: absolute; left: 0; top: .28em; bottom: .28em;
  width: 2px; border-radius: 2px;
  background: rgba(var(--lde-pc, 140, 49, 48), .55);
}
.lde-card.has-media .lde-card-pull::before { background: rgba(247, 243, 236, .5); }
.lde-card-title a {
  color: inherit; text-decoration: none;
  text-decoration-color: transparent;
}
.lde-card-title a:hover { text-decoration: underline; text-decoration-thickness: 1px;
  text-underline-offset: 3px; text-decoration-color: currentColor; }
/* SIZED TO THE CARD, not to the page.
   Seen on live content at 1231px: the 2x2 sits inside half the window,
   so each card is about 240px wide, and real titles — "Alimentos
   Antiinflamatorios — La Lista Que Sí Funciona" — hit the 3-line clamp
   and truncated mid-word on three cards out of four. The harness had
   short titles and never showed it. `cqi` sizes the type against the
   CARD's own width rather than the viewport's, so the same rule holds
   whether the card is 240px in a 2x2 or full width on a phone. */
.lde-card { container-type: inline-size; }
.lde-card .lde-card-title,
.lde-card .wp-block-post-title {
  font-size: clamp(16px, 7.4cqi, 21px); line-height: 1.22;
  letter-spacing: -.004em; margin: 8px 0 0;
  display: -webkit-box; -webkit-box-orient: vertical;
  -webkit-line-clamp: 4; line-clamp: 4; overflow: hidden;
}
@supports not (font-size: 1cqi) {
  .lde-card .lde-card-title,
  .lde-card .wp-block-post-title { font-size: 18px; }
}
/* A card with no featured image has the whole lower two-thirds standing
   empty — on the live rooms that is most of them, since none of the
   current posts carries one. Give the pullquote the room instead of
   leaving a hole. */
.lde-card:not(.has-media) .lde-card-pull {
  -webkit-line-clamp: 6; line-clamp: 6;
}
/* And a whisper of the pillar rising from the foot, so the space where
   the photograph WOULD be reads as part of the card rather than as a
   hole in it. It is the same gesture as the media bleed at a tenth of
   the strength — enough that the four cards read as one family whether
   or not they have images, and quiet enough that it never competes with
   a card that does. */
.lde-card:not(.has-media)::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  pointer-events: none;
  background: linear-gradient(180deg,
    rgba(var(--lde-pc, 140, 49, 48), 0) 42%,
    rgba(var(--lde-pc, 140, 49, 48), .06) 74%,
    rgba(var(--lde-pc, 140, 49, 48), .13) 100%);
}


/* 4 ================================== THE POST: she keeps her side
   The article goes to the OPPOSITE half of the window from her, and the
   rail lies over her. On a left pillar that means rail-left,
   article-right; on a right pillar, the reverse. Round 24's
   always-on-the-right rule is superseded — it solved Salud landing
   under her own title by moving HER, and moving the title and the
   article is the better answer. */
body.lde-single-page .lde-stage-single {
  left: auto; right: 5%; transform: none;
}
@media (min-width: 1100px) {
  /* Left pillars: rail first in the grid, article second. The rail is
     already FIRST in the document — it was put there in round 15 so the
     skip-to-heading links come before the article — so on these two
     pillars the visual order and the DOM order finally agree, and on
     the other three the existing placement keeps them agreeing the
     other way round. Nothing is reordered away from tab order in
     either case. */
  body.lde-side-left .lde-single-cols {
    grid-template-columns: clamp(272px, 23vw, 348px) minmax(0, 1fr);
  }
  body.lde-side-left .lde-rail { grid-column: 1; }
  body.lde-side-left .lde-reading { grid-column: 2; }

  /* The masthead follows the ARTICLE, not the Lady: it is the article's
     head, and set over her it competes with the rail for the same
     column. */
  body.lde-side-left .lde-single-masthead { text-align: left; }
  body.lde-side-left .lde-single-masthead > * {
    margin-left: auto; margin-right: 0; max-width: min(760px, 62%);
  }
}


/* 5 ------------------------------------------------------------- mobile
   None of the two-column argument applies on a phone: one column, cards
   full width, she stays a watermark behind the glass. The card keeps
   its image and its pullquote — those are content, not layout — but
   drops the fixed height, because a 2x2 grid of equal boxes is a
   desktop composition and a stack of them on a phone is just a lot of
   identical scrolling. */
@media (max-width: 1099px) {
  /* The IMAGE moves down, not the LAYER. Moving the layer is what broke
     it the first time: the top of the card fell back to plain glass and
     the cream tag and title measured 1.15:1 on it. */
  .lde-card-media img { top: 42%; height: 58%; }
  .lde-card-pull { -webkit-line-clamp: 2; line-clamp: 2; }
  /* A stacked card is taller, so the pullquote sits further down the
     bleed than it does in the 2x2 — measured 4.07:1 on Cocina. Hold the
     colour longer here; there is more image below it to give back. */
  .lde-card-media::after {
    background:
      linear-gradient(to top, rgba(20, 14, 12, .62) 0, rgba(20, 14, 12, .44) 46px, rgba(20, 14, 12, 0) 132px),
      linear-gradient(180deg,
        rgb(var(--lde-pc, 140, 49, 48)) 0%,
        rgba(var(--lde-pc, 140, 49, 48), .96) 62%,
        rgba(var(--lde-pc, 140, 49, 48), .84) 78%,
        rgba(var(--lde-pc, 140, 49, 48), .26) 92%,
        rgba(var(--lde-pc, 140, 49, 48), .04) 100%);
  }
}
@media (max-width: 900px) {
  .lde-room .lde-room-copy { position: static; width: auto; }
}

/* =====================================================================
   1.1.6 — THE BAND BETWEEN A PHONE AND THE FRAME (431–900px)

   Round 34 composed the ≤900px front page for 390x844 and wrote it in
   viewport-HEIGHT units: the stage is lifted -28vh and stands 38vh
   tall. In a window that is narrow but TALL, the lift scales with a
   height the crop was never measured against and the Lady leaves the
   frame; the hero keeps its natural height while the viewport does not,
   so the tiles sit high with a dead field beneath them.

   MEASURED IN THE BROWSER at 500x797 — 1.1.5 tried this blind and got
   both halves wrong. What it got wrong is recorded here because both
   mistakes are tempting: seating the tiles to the foot of the screen
   with `margin-top: auto` produced 55.6px of dead space between the
   About pane and the categories (Nick: "unnecessary gap"), and lifting
   her by a fraction of her own height still let her paint up into the
   wordmark (Nick: "Lady crashing the header").

   This block does NOT touch 430px and below. The 390x844 composition
   was approved as it stands and stays byte-identical — everything here
   is gated at min-width: 431px on purpose.
   ===================================================================== */
@media (min-width: 431px) and (max-width: 900px) {
  /* No flex-fill, no min-height: the composition takes its natural
     height and the page scrolls, which at this width it was always
     going to do. */
  /* 1.1.9 — NO MAGIC NUMBER. 1.1.6 and 1.1.8 both tried to hold the
     Lady below the wordmark with a hand-tuned margin, and both were
     wrong for a viewport they had not been measured in — CSS cannot
     read another element's rendered box, so any offset that guesses
     where the wordmark ends is a number waiting to drift. (1.1.8's was
     measured with the WP admin bar showing, which pushes the flow down
     ~46px while an absolutely-positioned wordmark stays put: 21px of air
     for a logged-in editor, −15px for every actual reader.)
     The wordmark JOINS THE FLOW instead. It owns a band of its own and
     the hero begins where that band ends, so `overflow: clip` lands
     under the wordmark by construction at every width and every height,
     with nothing to tune. The only number left is the 20px of air below
     the type, which is a spacing decision rather than a guess about
     someone else's geometry. */
  .lde-homemark {
    position: static; display: block; width: 100%; box-sizing: border-box;
    margin: 0; padding: 16px clamp(16px, 4.5vw, 64px) 20px;
  }
  .lde-main.lde-front { margin-top: 0; }
  /* `clip`, so she can never paint into the wordmark's band whatever
     the lift works out to — the boundary is the hero's own top edge
     rather than an offset that has to be right. The 44px above moves
     that edge down and buys the air between wordmark and hat.
     Measured: wordmark ends at 59, she starts at 90. */
  .lde-front .lde-hero {
    overflow: clip;
    padding-bottom: clamp(24px, 4vh, 56px);
  }
  .lde-front .lde-room-index { margin-top: 26px; }
  /* 1.1.10 — SHE IS ON THE PAGE, not cropped to a lintel. Every version
     up to here inherited round 34's phone idea: lift her mostly above
     the top edge and let a sliver of jaw hold the doorway. That reads at
     390x844, where there is no room for anything else. In this band
     there IS room, and the sliver just looked like a missing image
     (Nick, three times: "the lady has left the building", "still not on
     the page at any height"). So no negative lift at all — she stands in
     frame, centred, sized against the viewport's height, and the copy
     begins under her. What buys the space is the intro pane going: Nick,
     2026-08-08, "Lady + 'El arte...' is enough if we don't have the real
     estate." The pane's content lives on /acerca-de/, which the CTA in
     the copy points at, so nothing is lost but the duplication. */
  /* 1.1.11 — SHE IS THE BACKGROUND (Nick, 2026-08-08). Three moves, one
     idea: the threshold becomes a room you walk into rather than a page
     you scroll. `overflow: visible` lets her wash bleed up over the
     wordmark's band instead of being cropped at it — the clip added in
     1.1.9 solved a problem that only existed while she was a lintel.
     `position: fixed` pins her, so the title and the tiles travel up
     across her as you scroll. And 46px of top offset keeps her INK below
     the wordmark while the wash still bleeds past it: the wash over the
     type is the effect asked for, the hat over it would just lose the
     name. */
  .lde-front .lde-hero { overflow: visible; min-height: 100svh; padding-bottom: 0; }
  .lde-stage-hero {
    position: fixed; top: 150px; left: 50%; right: auto;
    transform: translateX(-50%);
    height: clamp(300px, 46vh, 430px);
    z-index: 0; pointer-events: none;
  }
  /* 1.1.13 — THE WHOLE THRESHOLD IS PINNED (Nick). Wordmark, heading and
     tiles are all fixed; nothing on the front page scrolls, because
     there is nothing left to scroll to. Her INK starts at 150px so the
     heading block sits on clean cream — at 46px the tagline landed on
     her hat and vanished, dark on dark — while her WASH still bleeds up
     behind the type and over the wordmark's band, which is the effect
     asked for. The wordmark carries a short cream gradient so it stays
     legible whatever passes under it. */
  .lde-homemark {
    position: fixed; top: 0; left: 0; right: 0; z-index: 5;
    background: linear-gradient(180deg, var(--lde-room-tint, #F6F1E8) 62%, transparent);
  }
  .lde-front .lde-hero-copy {
    position: fixed; z-index: 2; margin: 0; width: auto;
    top: 56px; left: clamp(16px, 4.5vw, 64px); right: clamp(16px, 4.5vw, 64px);
  }
  /* The intro PANE goes but its link stays: with no About pane on a
     narrow screen the CTA is the only route to /acerca-de/, so it is
     unwrapped from the glass and sits under the tagline as a plain
     line. */
  .lde-front .lde-intro {
    display: block; background: none; border: 0; box-shadow: none;
    -webkit-backdrop-filter: none; backdrop-filter: none;
    padding: 0; margin: 14px 0 0; max-width: none;
  }
  .lde-front .lde-intro > p:not(.lde-cta) { display: none; }
  /* 1.1.13 — THE ROOMS SPEAK THE SAME LANGUAGE (Nick): on a narrow
     screen the pillar title and her pose pin, and the article cards
     scroll up over them — which is what the desktop composition already
     does with a fixed figure and a scrolling band, so this is the phone
     saying the same thing rather than a second idea. The band reserves
     the pinned block's height as top margin so the first card starts
     below the title rather than under it. */
  .lde-room .lde-stage-room { position: fixed; z-index: 0; pointer-events: none; }
  .lde-room .lde-room-copy {
    position: fixed; z-index: 2; margin: 0; width: auto;
    top: 56px; left: clamp(16px, 4.5vw, 64px); right: clamp(16px, 4.5vw, 64px);
  }
  .lde-room .lde-band {
    position: relative; z-index: 3;
    margin-top: clamp(150px, 26vh, 240px);
  }

  /* The tiles hold station just above the fixed bar, at every scroll
     position. `sticky` was tried first and is the wrong tool here: with
     the footer gone the front page barely scrolls, so a sticky element
     never reaches its threshold and simply rides the flow — measured at
     289px adrift of the bar. `fixed` is what "at all times" actually
     asks for. The hero reserves their height as foot padding so the copy
     above can never run underneath them. */
  .lde-front .lde-room-index {
    position: fixed; z-index: 3; margin: 0;
    left: clamp(16px, 4.5vw, 64px); right: clamp(16px, 4.5vw, 64px);
    bottom: calc(66px + 12px);
  }
  /* 66px bar + 12px air + the 2x3 grid's own height. If the grid ever
     gains a row this number moves with it — it is the one measurement
     here that a registry change could invalidate. */
  .lde-front .lde-hero { padding-bottom: calc(66px + 12px + 232px); }
  /* One quick button left in the bar once the tiled one goes (below):
     hold the row and keep the spyglass on the right rather than letting
     it drop to a line of its own. */
  .lde-quicknav { display: flex; align-items: center; gap: 10px; }
  .lde-quicknav > .lde-search { margin-left: auto; flex: 0 0 auto; }
}

/* 1.1.6 — the narrow bar drops any quick button that the room-index grid
   is already showing as a tile: on LDE that is «Lo último», which sat in
   the bottom bar and in the grid at the same time. The class comes from
   the registry (see lde_quick_buttons), so this needs no slug in it. */
@media (max-width: 900px) {
  .lde-quicknav .lde-qbtn.is-tiled,
  .lde-pillnav .lde-qbtn.is-tiled { display: none; }
}

/* =====================================================================
   ROUND 26 — the search that grows out of its own button, the rail back
   on the article's shoulder, and the post as ONE FRAME with the footer
   inside it. (Nick's three directives, 2026-08-07: CHANGELOG r26.)
   ===================================================================== */


/* 1 ------------------------------------------- the front page's cluster
   The threshold has no header template part, so the spyglass — which
   lives in the pill nav — did not exist on the page most people arrive
   at. `lde/quick-nav` now renders the two quick buttons AND the search
   as one block, so there is a single definition of that cluster rather
   than two that drift. */
.lde-quicknav {
  position: absolute; top: 18px; right: clamp(16px, 4.5vw, 64px);
  z-index: 12; display: flex; align-items: center; gap: 10px;
}


/* 2 ============================= THE SEARCH, GROWING OUT OF THE BUTTON

   The circle becomes a pill that opens LEFTWARD along the bar, rather
   than a card dropping out from under it. Two reasons it is built as a
   clip rather than as a width animation: a width transition on a flex
   row reflows the input every frame and the caret jitters, and a clip
   runs on the compositor. `inset(0 0 0 100%)` -> `inset(0)` wipes from
   the button's own edge outwards, which is the gesture Nick described.

   <details> cannot transition its own open state, so this is an
   animation on appearance. Closing is instant — there is nothing to
   watch on the way out, and an exit animation on a disclosure means
   holding the element in the tree after it has logically gone. */
.lde-search-pop {
  top: 50%; right: 0; bottom: auto; left: auto;
  transform: translateY(-50%);
  width: min(440px, calc(100vw - 120px));
  padding: 6px 6px 6px 16px;
  border-radius: 999px;
  transform-origin: right center;
  animation: lde-search-open .3s cubic-bezier(.22, .9, .24, 1) both;
  /* Opaque, because this one floats over OTHER CONTROLS. At the
     system's usual glass alpha the pills read straight through it and
     the placeholder landed on top of the word MODA — two rows of type
     in the same 30px band. The same call as the mobile menu sheet: a
     surface, not a veil. */
  background: rgba(250, 246, 238, .98);
  box-shadow: 0 10px 30px rgba(28, 25, 22, .16);
}
body.lde-dark .lde-search-pop { background: rgba(26, 18, 22, .98); }
@keyframes lde-search-open {
  from { clip-path: inset(0 0 0 100% round 999px); opacity: .4; }
  to   { clip-path: inset(0 0 0 0 round 999px); opacity: 1; }
}
.lde-search-form { align-items: center; gap: 6px; }
.lde-search-input {
  border: 0; background: none; padding: 7px 2px;
  font-size: 15px;
}
body.lde-dark .lde-search-input { background: none; border: 0; }
.lde-search-input:focus-visible { outline: none; }
/* The whole pill lights instead of the input inside it — an outline
   drawn around a borderless field inside a pill reads as two controls. */
/* 1.1.5 — Nick, 2026-08-08: "a blinking cursor is enough, the outline is
   gross." He is right, and it is defensible: this is a TEXT FIELD, and
   for a text field the caret is itself the focus indicator — which is
   why browsers ship a caret and no ring for inputs. So the ring goes and
   the caret is made deliberate rather than incidental: full-strength
   pillar accent, so it reads as a placed mark. NOTE THE LIMIT — this
   reasoning covers text inputs ONLY. Do not carry it to the buttons,
   tiles, cards or links, where there is no caret and the ring is the
   only thing a keyboard user has. Those rings stay. */
.lde-search:has(.lde-search-input:focus-visible) .lde-search-pop {
  outline: none;
}
.lde-search-input { caret-color: rgb(var(--lde-pillar-on-glass, 140, 49, 48)); }
.lde-search-go {
  border-radius: 999px; padding: 8px 15px;
  background: rgba(140, 49, 48, .1);
  border-color: rgba(140, 49, 48, .35);
}

/* The results hang BELOW the pill, right-aligned to it, as their own
   card — the pill is a field, not a container. */
.lde-search-results, .lde-search-empty {
  position: absolute; top: calc(100% + 8px); right: 0;
  width: min(440px, calc(100vw - 120px));
  margin: 0; padding: 8px; border-radius: 16px;
  border: 1px solid var(--lde-glass-border);
  background: rgba(250, 246, 238, .97);
  -webkit-backdrop-filter: blur(24px) saturate(1.3);
          backdrop-filter: blur(24px) saturate(1.3);
  box-shadow: var(--lde-glass-shadow);
}
body.lde-dark .lde-search-results,
body.lde-dark .lde-search-empty { background: rgba(26, 18, 22, .97); }
.lde-search-empty { padding: 14px 16px; }
.lde-search-results[hidden], .lde-search-empty[hidden] { display: none; }

@media (prefers-reduced-motion: reduce) {
  .lde-search-pop { animation: none; }
}
@media (max-width: 900px) {
  /* The pop's own geometry lives in the round-27 sheet block near the
     end of the file — the last word on the phone sheet. */
  .lde-search-results, .lde-search-empty {
    position: static; width: auto; box-shadow: none; border: 0;
    background: none; padding: 6px 0 0;
  }
  body.lde-dark .lde-search-results,
  body.lde-dark .lde-search-empty { background: none; }
  body.lde-dark .lde-search-input {
    background: rgba(255, 255, 255, .07); border-color: rgba(255, 255, 255, .2); }
}


/* 3 -------------------------------- the rail, back on the pane's shoulder
   Round 24 lifted it to the top of the body so the panels would cross
   her. Nick: "I led you astray with the rail: they should be aligned to
   the top of the post pane as you had it." He is right — level with the
   article the two columns read as one object with a margin; lifted, the
   rail floated free of the thing it belongs to. She is still under it;
   the panels still cross her. */
@media (min-width: 1100px) {
  .lde-rail { margin-top: var(--lde-single-top); }
  body.lde-read-panel .lde-rail { top: calc(var(--lde-header-h) + 10px); }
}


/* 4 ================================== ONE FRAME, WITH THE FOOTER IN IT
   The post is a screen, not a page; only the pane scrolls. NOT
   `overflow: hidden` on the body — the frame is achieved by SIZING, so
   an overflow still scrolls and nothing is trapped, and the viewport
   gate means a reader at 200% zoom drops out of the frame into an
   ordinary page rather than into a locked one. */
@media (min-width: 1100px) and (min-height: 660px) {
  body.lde-read-panel {
    --lde-footer-h: 62px;
  }
  /* Height comes from the body's flex column now (round 27), not from
     a sum of its neighbours. */
  body.lde-read-panel .lde-main.lde-single {
    box-sizing: border-box;
    padding-bottom: 0;
    display: flex; flex-direction: column; min-height: 0;
  }
  /* STRETCH, not a percentage height.
     The columns are grid items in a row whose size is `auto`, so a
     percentage height on them resolves against a track that is itself
     sized by their content — circular, and the browser correctly
     ignores it: measured, `height: calc(100% - 243px)` on a 765px frame
     produced an 1184px pane. Giving the grid ONE definite row and
     letting the items stretch into it gets there honestly, and each
     column's own margin-top then takes the masthead's share off the
     top. */
  body.lde-read-panel .lde-single-cols {
    flex: 1 1 auto; min-height: 0;
    grid-template-rows: minmax(0, 1fr);
    align-items: stretch;
  }
  body.lde-read-panel .lde-reading,
  body.lde-read-panel .lde-rail {
    min-height: 0; max-height: none; height: auto;
  }
  /* Sticky has nothing left to do once the rail is exactly as tall as
     its own cell — and a sticky box inside a scroll-free frame is a
     promise the layout can no longer keep. */
  body.lde-read-panel .lde-rail { position: relative; top: auto; }
  /* The footer joins the composition: no top border, no band of its
     own, just the last line in the frame. */
  body.lde-read-panel .lde-footer {
    height: var(--lde-footer-h); display: flex; align-items: center;
    /* The footer carries a 54px top margin from the scrolling layout —
     harmless when it follows a page, fatal to a frame: it was exactly
     the 54px by which the document exceeded the viewport. */
    border-top: 0; margin-top: 0;
  }
  body.lde-read-panel .lde-footer-in { width: 100%; padding-block: 0; }
}

/* =====================================================================
   ROUND 27 — Nick's list, 2026-08-07 evening.
   ===================================================================== */


/* 1 --------------------------------------- the threshold's own wordmark
   "the word mark on the homepage is sitting proud, right at the top of
   frame. It shifts down on other pages."
   The header everywhere else is 73px tall with the wordmark centred in
   it; the front page has no header, so the mark was hanging off an
   absolute 22px with nothing to sit in. Both now share one rhythm, and
   the quick cluster is centred on the same line. */
.lde-homemark { top: 31px; }
.lde-quicknav { top: 22px; }

/* 1b ------------------------------ the threshold obeys the bottom law
   (0.10.1) Below 900px every other template moves its nav to a fixed
   bar at the bottom of the frame — see THE BOTTOM NAV, round 24. The
   front page's quick cluster was the one nav that stayed absolute at
   the top, overlapping the homemark at 390px. Same seat, same surface,
   same safe-area padding as .lde-pills, so the search sheet (round 29)
   resolves against this bar exactly as it does against the pill bar.
   Placed AFTER the `top: 22px` above so `top: auto` wins in source
   order. body's 66px bottom padding at ≤900px already reserved this
   space on every page, the front included. */
@media (max-width: 900px) {
  .lde-quicknav {
    position: fixed; top: auto; left: 0; right: 0; bottom: 0;
    z-index: 40;
    justify-content: flex-start;
    padding: 10px clamp(12px, 4vw, 22px);
    /* Clear of the home indicator on iOS, and of nothing on Android. */
    padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
    border-top: 1px solid var(--lde-glass-border);
    background: rgba(250, 246, 238, .96);
    -webkit-backdrop-filter: blur(20px) saturate(1.25);
            backdrop-filter: blur(20px) saturate(1.25);
  }
  body.lde-dark .lde-quicknav { background: rgba(26, 18, 22, .96); }
  /* The spyglass keeps the right end of the bar, as everywhere else. */
  .lde-quicknav .lde-search { margin-left: auto; }
}


/* 2 ======================================= THE SEARCH, WIDENED AND CLOSABLE

   "can we have it take the colour of whatever the background of the page
    is but maybe a tone or two lighter? We should also extend it to the
    full width of the menu items. The search bar also needs a
    cancel/escape option."

   THE COLOUR. Each room declares the tone its own field settles to, and
   the pill mixes a little white into it. `color-mix` rather than five
   more hex values: the pill is then DERIVED from the room and cannot
   drift from it when a mood is retuned. Where color-mix is unsupported
   the earlier flat cream stands, which is the current behaviour. */
body { --lde-room-tint: #F7F3EC; }
/* Pillar tints: emitted from the registry (room_tint, block 6). The
   home and list-page tints are identity — the child declares them. */

@supports (background: color-mix(in oklab, red, white)) {
  .lde-search-pop {
    /* A light room lifts a little; a deep room lifts more, because the
       same 10% of white reads as almost nothing over navy. */
    background: color-mix(in oklab, var(--lde-room-tint) 88%, white);
  }
  body.lde-dark .lde-search-pop {
    background: color-mix(in oklab, var(--lde-room-tint) 78%, white);
  }
  .lde-search-results, .lde-search-empty {
    background: color-mix(in oklab, var(--lde-room-tint) 92%, white);
  }
  body.lde-dark .lde-search-results,
  body.lde-dark .lde-search-empty {
    background: color-mix(in oklab, var(--lde-room-tint) 84%, white);
  }
}

@media (min-width: 901px) {
  /* THE FULL WIDTH OF THE MENU. The panel is positioned against the NAV
     now, not against the button, so it runs from the first pill to the
     spyglass. That means the nav has to be the containing block and the
     search must not be — otherwise `left: 0` is the button's own left
     edge, 34px away. */
  .lde-pills { position: relative; }
  .lde-search { position: static; }
  .lde-search-pop {
    left: 0; right: 0; width: auto;
  }
  .lde-search-results, .lde-search-empty {
    left: auto; right: 0; width: min(520px, 100%);
  }
}

/* THE CANCEL. Escape already closed it; a visible control is for the
   people who do not know that, which is most of them. It is a real
   button, so it is in tab order and announced, and the script only has
   to flip the <details> — with no script the form's own reset behaviour
   and the summary still work. */
.lde-search-close {
  flex: 0 0 auto; display: grid; place-items: center;
  width: 32px; height: 32px; border-radius: 50%; cursor: pointer;
  border: 1px solid transparent; background: none; color: var(--lde-fg);
  opacity: .6; font: 400 17px/1 var(--lde-sans);
}
.lde-search-close:hover { opacity: 1; border-color: var(--lde-glass-border); }
.lde-search-close:focus-visible {
  opacity: 1; outline: 2px solid var(--lde-focus, currentColor); outline-offset: 2px;
}


/* 3 ------------------------------------------------------ the pose sides
   "let's alternate which side the lady is on per category — BIENESTAR
    left, SALUD right, BELLEZA left, COCINA right, MODA left."
   And: "BIENESTAR — she needs to flip."

   The mirror is not decoration: each figure is drawn facing one way, and
   placing her on the far side means turning her so she still looks INTO
   the page. Bienestar is the exception Nick asked for — she now looks
   out, off her own edge, which on the one room whose whole subject is
   calm is a different and better silence. */
@media (min-width: 1100px) {
  body.lde-alt-left .lde-room .lde-stage-room { grid-column: 1; }
  body.lde-alt-right .lde-room .lde-stage-room { grid-column: 2; }

  body.lde-alt-left .lde-room .lde-band { grid-column: 2; }
  body.lde-alt-right .lde-room .lde-band { grid-column: 1; }

  body.lde-alt-left .lde-room .lde-room-copy {
    left: clamp(20px, 3.4vw, 56px); right: auto;
  }
  body.lde-alt-right .lde-room .lde-room-copy {
    right: clamp(20px, 3.4vw, 56px); left: auto;
  }
}
/* Facing. The registry's `flip` flag mirrors a drawing whose native
   facing points off the page on the side it stands. The un-flipped
   default is stated for BOTH sides first, so the flip rule below wins
   by order at equal specificity — same outcome as 0.11.0's per-slug
   list, and (0,3,1) beats the ≤900px (0,2,1) rules exactly as before. */
body.lde-side-left .lde-room .lde-stage-room,
body.lde-side-right .lde-room .lde-stage-room { transform: none; }
body.lde-flip .lde-room .lde-stage-room { transform: scaleX(-1); }

/* The single follows the same alternation, so she does not jump sides
   when you click through from the room into the post. */
body.lde-alt-left.lde-single-page .lde-stage-single {
  left: 5%; right: auto;
}
body.lde-alt-right.lde-single-page .lde-stage-single {
  left: auto; right: 5%;
}
body.lde-alt-left.lde-single-page .lde-stage-single,
body.lde-alt-right.lde-single-page .lde-stage-single { transform: none; }
body.lde-flip.lde-single-page .lde-stage-single { transform: scaleX(-1); }

@media (min-width: 1100px) {
  /* Rail over her, article opposite — on the new sides. */
  body.lde-alt-left .lde-single-cols {
    grid-template-columns: clamp(272px, 23vw, 348px) minmax(0, 1fr);
  }
  body.lde-alt-left .lde-rail { grid-column: 1; }
  body.lde-alt-left .lde-reading { grid-column: 2; }
  body.lde-alt-left .lde-single-masthead > * {
    margin-left: auto; margin-right: 0; max-width: min(760px, 62%);
  }
  body.lde-alt-right .lde-single-cols {
    grid-template-columns: minmax(0, 1fr) clamp(272px, 23vw, 348px);
  }
  body.lde-alt-right .lde-rail { grid-column: 2; }
  body.lde-alt-right .lde-reading { grid-column: 1; }
  body.lde-alt-right .lde-single-masthead > * {
    margin-left: 0; margin-right: auto;
  }
}


/* 4 --------------------------------------------------------- the pager
   "The SIGUIENTE/ANTERIOR buttons are crashing the number count."
   The three parts had a 10px gap and the numbers row had its own 7px
   padding per numeral, so at the narrow width of half a room the pill
   edges and the numerals met. Real space between the groups, and the
   numerals keep their own. */
.lde-band .wp-block-query-pagination { gap: clamp(14px, 1.6vw, 26px); }
.lde-band .wp-block-query-pagination-numbers {
  display: flex; align-items: center; gap: 4px;
}


/* 5 ================================================= THE POST, TIDIED

   Every item here is one of Nick's, in his order. */
@media (min-width: 1100px) {
  /* "The TOC is still at the top of the page. Have you updated that?"
     I had — and it did not take, because round 24 and round 26 both
     wrote `.lde-rail { margin-top }` at identical specificity ~800
     lines apart and I was trusting source order across the whole file.
     Scoped to the page it belongs to, it cannot lose. */
  body.lde-single-page .lde-rail { margin-top: var(--lde-single-top); }
}

/* "There's also a double line at the top of the post pane."
   The pane had a 2px pillar rule ON TOP of the glass panel's own 1px
   border. One line was the intention; two was arithmetic. */
.lde-reading { border-top: 0; }

/* "There's a faint shadow (?) in the rail between the TOC and Next read
    panels."
   Each panel carried the full glass drop shadow, so stacked 14px apart
   they cast onto each other and the gap read as a smudge rather than as
   air. In the rail they are rows in a column, not cards on a table. */
.lde-rail > .lde-panel,
.lde-rail > .lde-toc,
.lde-rail > .lde-recipe,
.lde-rail > .lde-next { box-shadow: none; }

/* "a little padding between the title text and the body pane" and
   "a gap between the bottom of the panes and the footer bar". */
/* 1.1.0 — Nick, after seeing it live: halve that gap. Measured at
   1440x900: masthead bottom 263, panel top 316 — 53px of air. Half is
   ~26px, which is 3vh at the measuring height, so 3vh/27px comes off
   every stop of the clamp. One variable moves the article, the rail
   AND the comments swap together — that is the whole reason they all
   read --lde-single-top. (Was clamp(168px, 29vh, 282px).) */
body.lde-single-page { --lde-single-top: clamp(141px, 26vh, 255px); }
@media (min-width: 1100px) and (min-height: 660px) {
  body.lde-read-panel .lde-main.lde-single { padding-bottom: 18px; }
}

/* "can we have a gentle fade/transparency to the text at the top and
    bottom of the pane so it's not just a hard crop"
   A mask on the scroll region itself, so it travels with the text
   rather than sitting over it as a painted gradient — which would have
   had to know the panel's colour, and there are five of those. 34px is
   about a line and a half: enough to read as the text passing under an
   edge, short enough that no whole line is ever unreadable. */
.lde-reading-scroll {
  --lde-fade: 34px;
  -webkit-mask-image: linear-gradient(to bottom,
    transparent 0, #000 var(--lde-fade),
    #000 calc(100% - var(--lde-fade)), transparent 100%);
          mask-image: linear-gradient(to bottom,
    transparent 0, #000 var(--lde-fade),
    #000 calc(100% - var(--lde-fade)), transparent 100%);
}
/* At the very top and very bottom of the article there is nothing
   arriving or leaving, so the fade would just be dimming the first and
   last lines for no reason. The script adds these two classes. */
.lde-reading-scroll.at-start {
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - var(--lde-fade)), transparent 100%);
          mask-image: linear-gradient(to bottom, #000 calc(100% - var(--lde-fade)), transparent 100%);
}
.lde-reading-scroll.at-end {
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 var(--lde-fade));
          mask-image: linear-gradient(to bottom, transparent 0, #000 var(--lde-fade));
}
.lde-reading-scroll.at-start.at-end {
  -webkit-mask-image: none; mask-image: none;
}

/* The contents panel marks where you ARE, not just where you jumped —
   see the IntersectionObserver in lde-experience.js. The styling for
   [aria-current="location"] already exists; this is the transition so
   it moves rather than snaps between sections. */
.lde-toc-i > a { transition: opacity .18s, border-color .18s, background-color .18s; }


/* 6 ------------------------------------------------- the footer, seated
   "bring the footer onto the page on every page, but make sure it's
    genuinely at the bottom of the frame (there's a gap at the moment).
    We can lose the 'la dama erudita' text ... Perhaps a subscribe to
    newsletter call to action/button?"

   The ROOM already fills the viewport exactly, so the footer needs the
   room to give back its own height rather than to be pushed. Same
   arithmetic as the post, one variable. */
body { --lde-footer-h: 62px; }
@media (min-width: 1100px) and (min-height: 660px) {
  /* THE BODY IS THE FRAME.
     Two goes at this were arithmetic — subtract the header, subtract
     the footer, subtract the admin bar — and both were beaten by a
     margin somewhere in between: measured live, the footer still sat
     53px past the fold because <main> kept a top margin the sum did not
     know about. A flex column cannot be wrong about it. The footer is
     the last item; main takes what is left; nothing has to be counted.
     Everything else on the page is out of flow (the mood field, the
     room, the masthead, the wordmark), so the column has exactly three
     items whatever the template. */
  body {
    display: flex; flex-direction: column;
    height: calc(100vh - var(--lde-adminbar, 0px));
  }
  @supports (height: 100dvh) {
    body { height: calc(100dvh - var(--lde-adminbar, 0px)); }
  }
  /* `flex: 1 1 0`, not `1 1 auto`: with an auto basis a pane that wants
     more room makes MAIN taller and the footer leaves the screen —
     which is exactly what the first flex attempt did to the post. A
     zero basis means main is the leftover, full stop, and the panes cap
     themselves inside it.
     `height` rather than `min-height` on the body for the same reason.
     Nothing is trapped: overflow stays visible, so if something really
     does not fit, the document scrolls as it always did. */
  .lde-main { flex: 1 1 0; min-height: 0; margin-top: 0;
    /* The pose SVG is `overflow: visible` on purpose so a brush mark can
       run past its box; inside a frame that spilled 27px below the fold
       and gave the room a scrollbar with nothing in it. `clip` again,
       not `hidden` — clip is not a scroll container, so the sticky
       header and the rail are untouched. */
    overflow: clip; }
  .lde-footer {
    flex: 0 0 auto;
    height: var(--lde-footer-h); display: flex; align-items: center;
    margin-top: 0; border-top: 0;
  }
  .lde-room .lde-room-hero { height: 100%; }
  /* The footer's own row wraps by default (it is a flex group from the
     block markup) and at 62px tall a second line spills 27px past the
     frame — a scrollbar on every room, from the footer. One line here,
     and the tagline is the part that yields. */
  .lde-footer-in {
    flex-wrap: nowrap; width: 100%; min-width: 0; padding-block: 0;
  }
  .lde-foot-tag {
    min-width: 0; overflow: hidden; text-overflow: ellipsis;
    white-space: nowrap;
  }
}
.lde-footer-in { gap: 18px; }
.lde-foot-sub {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 16px; border-radius: 999px; text-decoration: none;
  border: 1px solid var(--lde-glass-border);
  background: var(--lde-glass-bg);
  color: var(--lde-fg);
  font: 600 10.5px var(--lde-sans); letter-spacing: .18em;
  text-transform: uppercase;
  transition: border-color .2s, background-color .2s;
}
.lde-foot-sub:hover { border-color: rgba(140, 49, 48, .5); }
.lde-foot-sub:focus-visible {
  outline: 2px solid var(--lde-focus, currentColor); outline-offset: 3px;
}


/* ====================================================================
   ROUND 28 — SHARING, AND THE CONVERSATION
   Sharing leads (true on every post); the comment line exists only when
   there is a thread or an invitation (see lde-single.php). The comments
   "card" is the READING PANE turned over — everything else holds still,
   so it reads as turning a page, not navigating. (Origin: CHANGELOG
   r28.)
   ==================================================================== */

/* 1 ------------------------------------------------------- the pane */
.lde-engage { padding: 16px 16px 14px; border-radius: 14px; }

.lde-share {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: 8px;
}
.lde-share-i { margin: 0; }
.lde-share-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; padding: 0; border-radius: 50%;
  border: 1px solid var(--lde-glass-border);
  background: color-mix(in oklab, rgb(var(--lde-pillar-on-glass)) 7%, transparent);
  color: var(--lde-fg); opacity: .82; cursor: pointer;
  text-decoration: none; -webkit-appearance: none; appearance: none;
  transition: opacity .18s ease, background-color .18s ease,
              border-color .18s ease, transform .18s ease;
}
.lde-share-svg { display: block; width: 20px; height: 20px; }
.lde-share-btn:hover, .lde-share-btn:focus-visible {
  opacity: 1; transform: translateY(-1px);
  background: color-mix(in oklab, rgb(var(--lde-pillar-on-glass)) 20%, transparent);
  border-color: color-mix(in oklab, rgb(var(--lde-pillar-on-glass)) 55%, transparent);
}
.lde-share-btn:focus-visible {
  outline: 2px solid var(--lde-focus); outline-offset: 3px;
}

/* The copy confirmation keeps its line whether it is speaking or not,
   so the panel does not jump 18px every time it is used. */
.lde-share-note {
  margin: 9px 0 0; min-height: 15px;
  font: 400 11.5px var(--lde-sans); opacity: 0;
  transition: opacity .2s ease;
}
.lde-share-note.is-on { opacity: .72; }

/* 2 ---------------------------------------------- the way in */
.lde-engage-comments {
  display: flex; align-items: center; gap: 8px;
  margin-top: 12px; padding-top: 12px;
  border-top: 1px solid color-mix(in oklab, var(--lde-fg) 16%, transparent);
  text-decoration: none; color: var(--lde-fg);
  font: 600 12px var(--lde-sans); opacity: .82;
  transition: opacity .18s ease;
}
.lde-engage-comments:hover { opacity: 1; }
.lde-engage-svg { width: 18px; height: 18px; flex: 0 0 auto; opacity: .7; }
.lde-engage-count {
  font: 700 15px var(--lde-serif); line-height: 1;
  color: rgb(var(--lde-pillar-on-glass));
}
.lde-engage-comments.is-empty .lde-engage-word {
  font-weight: 500; letter-spacing: .01em;
}
.lde-engage-go {
  margin-left: auto; opacity: .45;
  transition: transform .18s ease, opacity .18s ease;
}
.lde-engage-comments:hover .lde-engage-go {
  transform: translateX(3px); opacity: .9;
}

/* 3 --------------------------------------------- the thread's pane
   Placed in the article's cell, on whichever side the article is —
   which alternates by pillar, so both cases are written out rather
   than inherited by accident. */
.lde-comments {
  display: flex; flex-direction: column; min-width: 0; position: relative;
  padding: clamp(18px, 2.2vw, 38px);
  border-top: 2px solid rgba(var(--lde-pillar-on-glass), .5);
  scroll-margin-top: 90px;
}
@media (min-width: 1100px) {
  /* Row 2 by default — below the article, which is where a comments
     section belongs when nothing is going to move it. */
  .lde-comments { grid-column: 1; grid-row: 2; }
  /* The rooms whose single moved to the left in round 27 (alt-left ∩
     side-right) put the pane in column 2 — emitted from the registry
     (block 8). */
  /* With the script present it moves INTO the article's cell and the
     two take turns. */
  html.lde-js .lde-comments { grid-row: 1; }
}

/* It must sit EXACTLY where the article sat, or the swap reads as a
   jump rather than as a turn. Same top margin, same cap, off the same
   variable — not a second set of numbers that would drift the first
   time --lde-single-top is touched. (Caught in the harness: without
   this the thread began at the top of the shell and slid up behind the
   masthead, because the article's clearance is a margin on the article
   rather than padding on the shell — see round 26 §1.) */
body.lde-read-panel .lde-comments { margin-top: var(--lde-single-top); }
/* 1.1.0 — on a phone the masthead is static and already in flow, so
   the variable's masthead-clearance job is gone and the whole margin
   is bare gap: measured 245px of empty room between the rail and the
   article at 390x844. Same directive, same arithmetic — half. The
   comments swap takes the identical value so the turn stays a turn. */
@media (max-width: 900px) {
  body.lde-read-panel .lde-reading,
  body.lde-read-panel .lde-comments { margin-top: calc(var(--lde-single-top) / 2); }
}
@media (min-width: 1100px) {
  body.lde-read-panel .lde-comments {
    max-height: calc(100vh - var(--lde-single-top) - 78px);
  }
  @supports (height: 100dvh) {
    body.lde-read-panel .lde-comments {
      max-height: calc(100dvh - var(--lde-single-top) - 78px);
    }
  }
}

/* The swap itself. Gated on html.lde-js so the pattern only exists
   where the script that manages it does; with no script the thread is
   an ordinary section below the article and the rail's link is an
   ordinary fragment link to it.

   VISIBILITY, NOT DISPLAY, and this is not a preference — it is the
   fix for a bug the harness caught. `.lde-main` carries WP's auto side
   margins, which in the round-27 body flex column means it is sized to
   its CONTENT rather than stretched. display:none on the article took
   the article out of that measurement, the shell shrank 137px and
   re-centred, and the whole page — rail, footer, everything — twitched
   sideways on the way into the comments. Keeping the article in the
   layout and merely invisible holds every column still: the two panes
   share one grid cell and the cell is as wide as the wider of them,
   which is what it already was.
   visibility:hidden is also the correct semantics here — it takes the
   hidden pane out of the tab order and out of the accessibility tree,
   which opacity alone would not. */
html.lde-js .lde-comments,
html.lde-js body.lde-comments-open .lde-reading {
  visibility: hidden; opacity: 0; pointer-events: none;
}
html.lde-js body.lde-comments-open .lde-comments {
  visibility: visible; opacity: 1; pointer-events: auto;
}
html.lde-js .lde-comments, html.lde-js .lde-reading {
  transition: opacity .18s ease;
}

.lde-comments-head {
  flex: 0 0 auto; display: flex; align-items: baseline;
  justify-content: space-between; gap: 14px; flex-wrap: wrap;
  margin: 0 0 14px; padding-bottom: 12px;
  border-bottom: 1px solid color-mix(in oklab, var(--lde-fg) 14%, transparent);
}
.lde-comments-title {
  order: 1; margin: 0; font: 400 clamp(19px, 2vw, 25px) var(--lde-serif);
}
.lde-comments-title:focus-visible {
  outline: 2px solid var(--lde-focus); outline-offset: 5px; border-radius: 4px;
}
.lde-comments-back {
  order: 2; margin-left: auto; cursor: pointer;
  padding: 6px 13px; border-radius: 999px;
  border: 1px solid var(--lde-glass-border);
  background: var(--lde-glass-bg); color: var(--lde-fg);
  font: 600 10.5px var(--lde-sans); letter-spacing: .16em;
  text-transform: uppercase; opacity: .8;
  transition: opacity .18s ease, border-color .18s ease;
}
.lde-comments-back:hover { opacity: 1; }
.lde-comments-back:focus-visible {
  outline: 2px solid var(--lde-focus); outline-offset: 3px;
}
.lde-comments-scroll { min-height: 0; }

/* The thread scrolls where the article scrolled, under exactly the same
   guard rails: only at a viewport wide AND tall enough for the pattern,
   so a reader at 200% zoom falls out of it into a plain page. */
@media (min-width: 1100px) and (min-height: 660px) {
  body.lde-read-panel .lde-comments-scroll {
    overflow-y: auto; overflow-x: clip;
    scrollbar-gutter: stable both-edges; scroll-behavior: smooth;
    scrollbar-width: thin;
    scrollbar-color: rgba(var(--lde-pillar-on-glass), .45) transparent;
  }
  body.lde-read-panel .lde-comments-scroll:focus-visible {
    outline: 2px solid rgb(var(--lde-pillar-on-glass)); outline-offset: 3px;
    border-radius: 6px;
  }
}

/* 4 ------------------------------------------------- the comments */
.lde-comment-list, .lde-comment-list .children {
  list-style: none; margin: 0; padding: 0;
}
.lde-comment-list .children {
  margin-top: 14px; padding-left: clamp(14px, 2vw, 30px);
  border-left: 2px solid color-mix(in oklab, rgb(var(--lde-pillar-on-glass)) 30%, transparent);
}
.lde-comment { margin: 0 0 20px; }
.lde-comment-in { min-width: 0; }
.lde-comment-head { display: flex; align-items: center; gap: 10px; margin-bottom: 7px; }
.lde-comment-avatar { border-radius: 50%; flex: 0 0 auto; }
.lde-comment-who {
  margin: 0; font: 600 13.5px var(--lde-sans); letter-spacing: .01em;
}
.lde-comment-when { margin: 1px 0 0; font: 400 11.5px var(--lde-sans); opacity: .6; }
.lde-comment-when a { color: inherit; text-decoration: none; }
.lde-comment-when a:hover { text-decoration: underline; }
.lde-comment-body p {
  margin: 0 0 9px; font: 400 15px/1.62 var(--lde-serif);
}
.lde-comment-body p:last-child { margin-bottom: 0; }
.lde-comment-pending em { opacity: .65; font-size: 12.5px; }
.lde-comment-foot { margin-top: 6px; }
.lde-comment-reply a, .comment-reply-link {
  font: 600 10.5px var(--lde-sans); letter-spacing: .16em;
  text-transform: uppercase; text-decoration: none;
  /* No resting opacity. At 10.5px this is small text needing 4.5:1, and
     .85 measured 4.03 on Cocina's cream — the cheapest way to fail a
     contrast audit is to fade a link for looks. */
  color: rgb(var(--lde-pillar-on-glass));
}
.lde-comment-reply a:hover { text-decoration: underline; }
.lde-comment-pages {
  display: flex; gap: 14px; margin: 6px 0 22px;
  font: 600 11px var(--lde-sans); letter-spacing: .12em; text-transform: uppercase;
}

/* 5 ------------------------------------------------------ the form */
.lde-comment-form {
  margin-top: 22px; padding-top: 18px;
  border-top: 1px solid color-mix(in oklab, var(--lde-fg) 14%, transparent);
}
.lde-comment-form .comment-reply-title {
  margin: 0 0 12px; font: 400 clamp(17px, 1.7vw, 21px) var(--lde-serif);
}
.lde-comment-form .comment-reply-title small { font-size: 12px; margin-left: 10px; }
.lde-comment-form p { margin: 0 0 12px; }
.lde-comment-form label {
  display: block; margin-bottom: 4px;
  font: 600 10.5px var(--lde-sans); letter-spacing: .16em;
  text-transform: uppercase; opacity: .7;
}
.lde-comment-form :is(input[type="text"], input[type="email"], input[type="url"], textarea) {
  width: 100%; max-width: 100%; box-sizing: border-box;
  padding: 10px 13px; border-radius: 10px;
  border: 1px solid var(--lde-glass-border);
  /* Derived from the foreground rather than from a background variable
     that does not exist — the mood field is painted on body::before,
     so there is nothing to name. A 7% wash of the ink reads as a well
     on cream and on plum alike. */
  background: color-mix(in oklab, var(--lde-fg) 7%, transparent);
  color: var(--lde-fg); font: 400 14.5px/1.5 var(--lde-sans);
  transition: border-color .18s ease;
}
.lde-comment-form textarea { resize: vertical; min-height: 108px; }
.lde-comment-form :is(input, textarea):focus-visible {
  outline: 2px solid var(--lde-focus); outline-offset: 2px;
  border-color: color-mix(in oklab, rgb(var(--lde-pillar-on-glass)) 55%, transparent);
}
/* Name / email / site sit in a row on desktop and stack on a phone,
   without a grid on the container — core decides what is in here. */
@media (min-width: 620px) {
  .lde-comment-form .comment-form-author,
  .lde-comment-form .comment-form-email,
  .lde-comment-form .comment-form-url {
    display: inline-block; width: calc(50% - 7px); vertical-align: top;
  }
  .lde-comment-form .comment-form-email { margin-left: 10px; }
}
.lde-comment-form .comment-form-cookies-consent {
  display: flex; align-items: flex-start; gap: 9px;
}
.lde-comment-form .comment-form-cookies-consent label {
  margin: 0; text-transform: none; letter-spacing: 0;
  font: 400 12.5px/1.45 var(--lde-sans); opacity: .72;
}
.lde-comment-form .comment-form-cookies-consent input { width: auto; margin-top: 2px; }
.lde-comment-submit {
  cursor: pointer; padding: 11px 26px; border-radius: 999px;
  border: 1px solid color-mix(in oklab, rgb(var(--lde-pillar-on-glass)) 55%, transparent);
  background: color-mix(in oklab, rgb(var(--lde-pillar-on-glass)) 16%, transparent);
  color: var(--lde-fg);
  font: 600 11px var(--lde-sans); letter-spacing: .18em; text-transform: uppercase;
  transition: background-color .18s ease, transform .18s ease;
}
.lde-comment-submit:hover {
  background: color-mix(in oklab, rgb(var(--lde-pillar-on-glass)) 30%, transparent);
  transform: translateY(-1px);
}
.lde-comment-submit:focus-visible {
  outline: 2px solid var(--lde-focus); outline-offset: 3px;
}
.lde-comments-closed {
  margin: 18px 0 0; font: 400 13.5px var(--lde-sans); opacity: .65;
}

/* 6 ------------------------------------------------------- on a phone
   No swap: the rail is above the article and the comments are below it,
   in document order, which is the order a phone reads in anyway. */
@media (max-width: 1099px) {
  html.lde-js .lde-comments,
  html.lde-js body.lde-comments-open .lde-reading,
  html.lde-js body.lde-comments-open .lde-comments {
    visibility: visible; opacity: 1; pointer-events: auto;
  }
  .lde-comments-back { display: none; }
  body.lde-read-panel .lde-comments { margin-top: 18px; }
}
@media (prefers-reduced-motion: reduce) {
  .lde-share-btn, .lde-engage-go, .lde-comment-submit,
  .lde-share-note { transition: none !important; }
  .lde-share-btn:hover, .lde-comment-submit:hover,
  .lde-engage-comments:hover .lde-engage-go { transform: none; }
  body.lde-read-panel .lde-comments-scroll { scroll-behavior: auto; }
}

/* 7 ------------------------------------------- the frame, without JS
   Round 27 seats the footer by making <body> exactly one screen tall
   and clipping <main>. That is correct when the article and the thread
   take turns in one cell — and wrong the moment they are stacked, which
   is exactly what happens with the script absent: measured in the
   harness, the article was crushed to 68px so the 602px comments
   section could fit beside it inside a clipped 860px shell.
   So with no script the single stops pretending to be a frame and
   becomes an ordinary scrolling page. Everything is present, in order,
   at its full height. Nothing about that is a degraded experience — it
   is what a blog post has always looked like. */
@media (min-width: 1100px) {
  html:not(.lde-js) body.lde-single-page { height: auto; }
  html:not(.lde-js) body.lde-single-page .lde-main {
    flex: 0 0 auto; overflow: visible;
  }
  html:not(.lde-js) body.lde-read-panel .lde-reading,
  html:not(.lde-js) body.lde-read-panel .lde-comments { max-height: none; }
  html:not(.lde-js) body.lde-read-panel .lde-reading-scroll,
  html:not(.lde-js) body.lde-read-panel .lde-comments-scroll { overflow: visible; }
  html:not(.lde-js) body.lde-single-page .lde-rail {
    position: static; max-height: none; overflow: visible;
  }
}


/* 8 ================================ THE FRAME'S MISSING LINK (round 28)
   LAW: WordPress renders every block-theme template inside
   <div class="wp-site-blocks">, so the body's flex column has exactly
   ONE item and the wrapper must carry the column through — a flex
   declaration on .lde-main addresses nothing unless the wrapper is
   itself a flex column. (Full 0.9.3 measurements: CHANGELOG r28.) */
@media (min-width: 1100px) and (min-height: 660px) {
  body > .wp-site-blocks {
    flex: 1 1 auto; min-height: 0;
    display: flex; flex-direction: column;
  }
}
/* Without a script the single is an ordinary scrolling page (see §7),
   so the wrapper must stop constraining anything. */
@media (min-width: 1100px) {
  html:not(.lde-js) body.lde-single-page > .wp-site-blocks {
    display: block; flex: 0 0 auto;
  }
}


/* ====================================================================
   ROUND 29 — THE FOOTER AT EVERY SIZE, A FLEXIBLE RAIL, AND THE ROOM
               AS A GRID OF BUTTONS
   ==================================================================== */

/* 1 ------------------------------------------- the footer, unconditional

   Nick: "can we pin the footer to the bottom of the window, whatever
   size it is?"

   There are two different things called that, and LDE now does both.

   (a) THE STICKY FOOTER, everywhere, at every width and height and on
       every template. When the page is shorter than the window the
       footer sits on the bottom edge; when it is longer the footer
       follows the content down. It can never float in the middle of
       the screen with cream underneath it. Two declarations, no media
       query, no arithmetic: `min-height`, and `margin-top: auto`.

   (b) THE FRAME, which is stronger — the document is EXACTLY one screen
       and the panes scroll inside it. That one keeps a gate, because a
       reading pane 140px tall is not a favour to anybody. §2 lowers
       that gate as far as it honestly goes.

   The wrapper is in the chain here for the same reason as round 28:
   WordPress puts <div class="wp-site-blocks"> between the body and the
   template. */
body {
  min-height: 100vh;
  display: flex; flex-direction: column;
}
@supports (height: 100dvh) {
  body { min-height: 100dvh; }
}
body > .wp-site-blocks {
  flex: 1 0 auto; min-height: 0;
  display: flex; flex-direction: column;
}
.lde-main { flex: 1 0 auto; }
.lde-footer { flex: 0 0 auto; margin-top: auto; }


/* 2 ------------------------------------------------ the frame's new gate

   Was 1100 x 660 for everything. Now:

     rooms and the front page   900 x 520
     the single post           1100 x 600

   The post keeps the wider gate because below 1100 the rail and the
   article stack into one column, and a frame around a stack is a
   200px-tall article. The height comes down from 660 to 600 because
   the rail flexes now (§4) — it no longer needs a fixed 660 to fit
   three panes.

   Below the gate the sticky footer in §1 still applies, so the footer
   is at the bottom of the window there too. It is only the internal
   scrolling that stops.

   0.9.13 — `body.lde-frame`, not `body:not(.lde-single-page)`. The
   negation framed EVERYTHING that was not a post: search results,
   static pages, lo-ultimo, imprescindibles, the 404 — all taller than
   one screen, all clipped by `overflow: clip` with no scroll container,
   their content unreachable. The frame is opt-in now: functions.php
   emits `lde-frame` on the front page and the five pillar rooms — the
   one-screen compositions — and nowhere else. Same (0,1,1) specificity,
   so nothing in the cascade moves. */
@media (min-width: 901px) and (min-height: 520px) {
  body.lde-frame {
    height: calc(100vh - var(--lde-adminbar, 0px));
    min-height: 0;
  }
  @supports (height: 100dvh) {
    body.lde-frame {
      height: calc(100dvh - var(--lde-adminbar, 0px));
    }
  }
  body.lde-frame > .wp-site-blocks { flex: 1 1 auto; }
  body.lde-frame .lde-main {
    flex: 1 1 0; min-height: 0; margin-top: 0; overflow: clip;
  }
  body.lde-frame .lde-footer {
    height: var(--lde-footer-h); display: flex; align-items: center;
    margin-top: 0; border-top: 0;
  }
  body.lde-frame .lde-footer-in {
    flex-wrap: nowrap; width: 100%; min-width: 0; padding-block: 0;
  }
  body.lde-frame .lde-foot-tag {
    min-width: 0; overflow: hidden; text-overflow: ellipsis;
    white-space: nowrap;
  }
  .lde-room .lde-room-hero { height: 100%; }
}
@media (min-width: 1100px) and (min-height: 600px) {
  body.lde-single-page {
    height: calc(100vh - var(--lde-adminbar, 0px)); min-height: 0;
  }
  @supports (height: 100dvh) {
    body.lde-single-page {
      height: calc(100dvh - var(--lde-adminbar, 0px));
    }
  }
  body.lde-single-page > .wp-site-blocks { flex: 1 1 auto; }
  body.lde-single-page .lde-main {
    flex: 1 1 0; min-height: 0; margin-top: 0; overflow: clip;
  }
  body.lde-single-page .lde-footer {
    height: var(--lde-footer-h); display: flex; align-items: center;
    margin-top: 0; border-top: 0;
  }
  /* The footer's own row wraps by default — it is a flex group from the
     block markup — and at 62px tall a second line spills past the frame.
     Round 27 stated this for the room and forgot the post: measured at
     1100x640, the footer's scrollHeight was 74 in a 62px box and the
     document was 12px longer than the window. One line, tagline yields. */
  body.lde-single-page .lde-footer-in {
    flex-wrap: nowrap; width: 100%; min-width: 0; padding-block: 0;
  }
  body.lde-single-page .lde-foot-tag {
    min-width: 0; overflow: hidden; text-overflow: ellipsis;
    white-space: nowrap;
  }
  /* Everything round 27 hung off `body.lde-read-panel` inside the old
     660 gate, restated at 600 so the 600–659 band is not a layout
     nobody has ever seen. */
  body.lde-read-panel .lde-main.lde-single {
    box-sizing: border-box; padding-bottom: 0;
    display: flex; flex-direction: column; min-height: 0;
  }
  body.lde-read-panel .lde-single-cols {
    flex: 1 1 auto; min-height: 0;
    grid-template-rows: minmax(0, 1fr);
    align-items: stretch;
  }
  body.lde-read-panel .lde-reading,
  body.lde-read-panel .lde-comments,
  body.lde-read-panel .lde-rail {
    min-height: 0; max-height: none; height: auto;
  }
  body.lde-read-panel .lde-rail { position: relative; top: auto; }
  body.lde-read-panel .lde-reading-scroll,
  body.lde-read-panel .lde-comments-scroll {
    overflow-y: auto; overflow-x: clip;
    scrollbar-gutter: stable both-edges; scroll-behavior: smooth;
    scrollbar-width: thin;
    scrollbar-color: rgba(var(--lde-pillar-on-glass), .45) transparent;
  }
}
/* And with no script the single is an ordinary page again (round 28 §7),
   at the new gate as well as the old. */
/* THE SINGLE ONLY. Round 28 §7 unframed the post with no script because
   the article and the comments stack there and a frame would crush the
   article to 68px. Nothing of the kind is true of a room or the front
   page: they are one-screen compositions whether or not a script ever
   arrives, and unframing them was a scope error — caught because the
   room harness has no lde-js flag and stopped framing at every size,
   which took an hour to stop blaming on the media query. */
@media (min-width: 901px) {
  html:not(.lde-js) body.lde-single-page { height: auto; min-height: 100vh; }
  html:not(.lde-js) body.lde-single-page > .wp-site-blocks { flex: 1 0 auto; }
  html:not(.lde-js) body.lde-single-page .lde-main {
    flex: 1 0 auto; overflow: visible;
  }
}


/* 3 ---------------------------------------------- the card IS the button

   Nick: "make each post panel a button taking us to the post."

   A stretched link, not a click handler and not a nested <a>. The title
   anchor grows an ::after that covers the whole card, so the entire
   panel is the hit area while the accessible name, the focus ring and
   the status-bar URL all still belong to one link. The category tag
   keeps its own link by sitting above the overlay — two targets, both
   real, nothing to disambiguate. */
.lde-card { position: relative; cursor: pointer; }
.lde-card-title a::after {
  content: ""; position: absolute; inset: 0; z-index: 2;
  border-radius: inherit;
}
.lde-card-tag, .lde-card-tag a { position: relative; z-index: 3; }
/* 0.9.13 — the `.lde-card-media { z-index: 0 }` that stood here is
   deleted. It out-cascaded the z-index: -1 the media layer is GIVEN at
   its definition, so on any card with a featured image the media and
   its 0.9-alpha pillar bleed painted over the deliberately-unpositioned
   card title. The media goes back behind everything; the stretched-link
   overlay (z-index: 2) still covers the whole card. */
/* .lde-card-title is deliberately NOT in this list. Positioning it would
   make IT the containing block for the anchor's ::after, and `inset: 0`
   would then cover the title line rather than the card — and worse, it
   would open a stacking context in which the overlay's z-index could no
   longer out-rank the pullquote's. Measured before the fix:
   elementFromPoint at the middle of a card returned the <p>. */
.lde-card-pull, .lde-card-meta { position: relative; z-index: 1; }
/* The lift belongs to the card and the ring to the link inside it, so
   the ring traces the panel rather than a line of text. */
.lde-card:hover { transform: translateY(-3px); }
.lde-card:has(a:focus-visible) {
  outline: 2px solid var(--lde-focus); outline-offset: 3px;
}
.lde-card-title a:focus-visible { outline: none; }
.lde-card:active { transform: translateY(-1px); }


/* 4 ------------------------------------------------------- the rail flexes

   Nick: "flex the rail so that all three panes are always on the page.
   That means the TOC itself may have to scroll within its pane."

   The rail becomes a flex column. Compartir and Seguir leyendo are
   small and fixed; Contenido and Ingredientes are the ones that can run
   long, so they take what is left and scroll inside themselves. Two
   scrollers rather than one because a post can have both, and a recipe
   pushed off the bottom by a 30-heading contents list is the same bug
   in the other direction.

   Gaps come in from clamp(12px, 1.4vw, 18px) to clamp(8px, .9vw, 12px):
   with four panes the old spacing was spending 54px of a 700px column on
   air. */
@media (min-width: 1100px) and (min-height: 600px) {
  body.lde-read-panel .lde-rail {
    display: flex; flex-direction: column;
    gap: clamp(8px, .9vw, 12px);
    overflow: visible;
  }
  body.lde-read-panel .lde-rail > .lde-panel { min-height: 0; }
  body.lde-read-panel .lde-engage,
  body.lde-read-panel .lde-next { flex: 0 0 auto; }
  /* The two that can run long. `1 1 0` so they share the leftover in
     proportion rather than by content — a short recipe next to a long
     contents list should not get half the column. */
  body.lde-read-panel .lde-toc,
  body.lde-read-panel .lde-recipe {
    flex: 1 1 0; display: flex; flex-direction: column; min-height: 0;
  }
  body.lde-read-panel .lde-toc > summary,
  body.lde-read-panel .lde-recipe > summary { flex: 0 0 auto; }
  body.lde-read-panel .lde-toc-list,
  body.lde-read-panel .lde-recipe .lde-ing,
  body.lde-read-panel .lde-recipe .lde-recipe-group {
    min-height: 0;
  }
  body.lde-read-panel .lde-toc-list,
  body.lde-read-panel .lde-recipe .lde-ing {
    overflow-y: auto; overscroll-behavior: contain;
    scrollbar-width: thin;
    scrollbar-color: rgba(var(--lde-pillar-on-glass), .35) transparent;
  }
  /* A pane with nothing to scroll should not reserve a gutter. */
  body.lde-read-panel .lde-toc-list { padding-right: 2px; }
}

/* The entry being read: bold, full strength, and its rule at full
   colour. It was already marked with aria-current by the scroll-spy —
   this is what that mark now looks like. */
.lde-toc-i > a[aria-current] {
  font-weight: 700; opacity: 1;
  border-left-color: rgb(var(--lde-pillar-on-glass));
}


/* 5 ------------------------------------------------------- the share pane

   Nick: "remove the 'compartir' heading in the share pane itself and we
   can then make that pane a little shorter to get back some space. Have
   the icons in there vertically and horizontally centred and flex them
   to spread evenly across the pane."

   The heading goes only where the disclosure it belongs to has stopped
   being one: above 900px with the script running, every rail pane is
   held open and the summary is a control nobody can use. Below that, or
   with no script, it is the thing you tap to open the pane and it stays
   exactly where it was. `display: none` rather than a visually-hidden
   class on purpose — a hidden-but-focusable summary is a tab stop that
   does nothing. */
@media (min-width: 901px) {
  html.lde-js .lde-engage > summary { display: none; }
  html.lde-js .lde-engage { padding: 12px 14px; }
}
.lde-share {
  justify-content: space-evenly; align-items: center;
  gap: 4px; min-height: 42px;
}
.lde-share-i { display: flex; }
/* No wrap: five icons across a 272px pane at 38px each is 190px of
   icon, so there is room to spread at every rail width the grid can
   produce. Stated rather than left to chance. */
@media (min-width: 1100px) {
  .lde-share { flex-wrap: nowrap; }
}
.lde-engage-comments { margin-top: 10px; padding-top: 10px; }


/* 6 --------------------------------------------------- the front page
   Inside the frame the hero takes the height it is GIVEN, never a
   viewport unit — a 92vh hero inside a clipped `100vh - footer` main is
   how the portal cards lost their feet. `margin-top: auto` seats the
   chip row the same distance off the footer as a room's pager.
   (The "phantom footer" story: CHANGELOG r29.) */
@media (min-width: 901px) and (min-height: 520px) {
  .lde-main.lde-front {
    display: flex; flex-direction: column; min-height: 0;
  }
  .lde-front .lde-hero {
    flex: 1 1 auto; min-height: 0; height: 100%;
    padding-top: clamp(40px, 7vh, 92px);
    padding-bottom: clamp(14px, 2.2vh, 26px);
  }
  .lde-front .lde-portal-row { margin-top: auto; }
  /* She flexes with the frame instead of with the viewport: the hero is
     the frame now, so a percentage of it is the honest unit. At 92vh
     these two were near enough the same number, which is why nobody
     noticed — at 60vh they are not. */
  .lde-stage-hero { top: 6%; height: 74%; }
  .lde-portal { min-height: 0; }
}


/* 7 ------------------------------------------------- rooms, at the new gate
   Round 25's room grid lived inside the 1100 gate. The room composition
   works from 900 — two columns of cards over a half-width band — so it
   moves down with the frame rather than leaving a 900–1099 band with a
   framed page and an unframed grid inside it. */
@media (min-width: 901px) and (max-width: 1099px) and (min-height: 520px) {
  .lde-room .lde-cards { flex: 1 1 auto; min-height: 0; display: flex; }
  .lde-room .lde-cards .lde-cards-grid {
    flex: 1 1 auto; min-height: 0;
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    grid-auto-rows: minmax(0, 1fr);
    gap: clamp(10px, 1.2vw, 16px);
  }
  .lde-room .lde-band .wp-block-query-pagination {
    margin: clamp(10px, 1.6vh, 18px) 0 0;
    /* 1.1.3 — stretch, not center: the nav is now a full-width grid
       whose outer columns put the buttons on the cards' edges, and
       align-self:center would shrink it back to content width. */
    align-self: stretch; flex-wrap: nowrap; white-space: nowrap;
  }
}
/* On a short window the four cards are the same four cards; what has to
   give is the softest thing in them. The pullquote clamps rather than
   the grid changing shape, so every card stays exactly the size of
   every other card — which was the whole point of round 25. */
.lde-card-pull {
  display: -webkit-box; -webkit-box-orient: vertical;
  -webkit-line-clamp: 4; line-clamp: 4; overflow: hidden;
}
@media (max-height: 800px) {
  .lde-card-pull { -webkit-line-clamp: 3; line-clamp: 3; }
}
@media (max-height: 680px) {
  .lde-card-pull { -webkit-line-clamp: 2; line-clamp: 2; }
}


/* 8 ============================ THE LADY LEADS (round 29)
   Every template renders exactly one stage (`.lde-stage-hero` /
   `.lde-stage-room` / `.lde-stage-single`), so one view-transition
   name across all three lets the browser tween her from where she
   stood to where she is going — room to room, and room into post.
   (Why she was never named before round 29: CHANGELOG r29.)

   `object-fit: contain`, because the three stages have three different
   aspect ratios (400:396 in the hero, 400:563 in a room) and the UA
   default stretches each snapshot to the group box — which on a line
   drawing of a woman is not a subtle failure.

   The whole thing sits behind the same @supports guard and the same
   900px cut-off as everything else in the transition system: below that
   she is a different size on a different side of a scrolling strip, and
   the travel reads as a glitch rather than as a walk. */
@supports (view-transition-name: none) {
  .lde-stage { view-transition-name: lde-lady; }

  ::view-transition-group(lde-lady) {
    z-index: 0;
    animation-duration: .7s;
    animation-timing-function: cubic-bezier(.22, .9, .24, 1);
  }
  ::view-transition-old(lde-lady),
  ::view-transition-new(lde-lady) {
    width: 100%; height: 100%;
    object-fit: contain; object-position: 50% 0%;
  }
  /* She overlaps herself through the middle of the travel rather than
     handing over cleanly like the chips: two line drawings crossing at
     50% read as one figure turning, where a hard cut reads as two. */
  ::view-transition-old(lde-lady) {
    animation: lde-vt-out .5s cubic-bezier(.4, 0, 1, 1) both;
  }
  ::view-transition-new(lde-lady) {
    animation: lde-vt-in .52s cubic-bezier(0, 0, .2, 1) .18s both;
  }

  @media (max-width: 900px) {
    .lde-stage { view-transition-name: none; }
  }
}


/* 9 ================================== SEARCH ON A NARROW SCREEN (round 29)
   THE LAST WORD on the phone search: the pill fills the bottom bar,
   wiping open from the right on the same curve and 300ms as desktop,
   same colour rules, results hanging ABOVE it (below is the bottom of
   the screen). Supersedes round 26's bottom sheet — one behaviour on
   every screen. (History: CHANGELOG r29.) */
@media (max-width: 900px) {
  /* The bar is `position: fixed`, so it is already the containing block
     — nothing needs `position: relative` added to it. */
  .lde-search { position: static; }
  .lde-search-pop {
    position: absolute;
    top: 6px; left: 8px; right: 8px;
    bottom: calc(6px + env(safe-area-inset-bottom, 0px));
    width: auto; transform: none; transform-origin: right center;
    display: flex; align-items: center;
    border-radius: 999px; padding: 0 6px 0 16px;
    animation: lde-search-open .3s cubic-bezier(.22, .9, .24, 1) both;
    box-shadow: 0 8px 24px rgba(28, 25, 22, .18);
  }
  .lde-search-form { width: 100%; height: 100%; align-items: center; gap: 6px; }
  /* Borderless inside the pill, exactly as on a wide screen: the pill
     IS the field, and a bordered box inside it reads as two controls. */
  .lde-search-input {
    border: 0; background: none; padding: 7px 2px; border-radius: 0;
    font-size: 16px; /* 16px or iOS zooms the page on focus. */
  }
  body.lde-dark .lde-search-input { background: none; border: 0; }
  .lde-search-input:focus-visible { outline: none; }
  /* 1.1.5 — the ring goes here too; see the note at the desktop rule. */
  .lde-search:has(.lde-search-input:focus-visible) .lde-search-pop {
    outline: none;
  }
  .lde-search-input { caret-color: rgb(var(--lde-pillar-on-glass, 140, 49, 48)); }
  .lde-search-go {
    flex: 0 0 auto; border-radius: 999px; padding: 8px 14px;
    background: rgba(140, 49, 48, .1);
    border-color: rgba(140, 49, 48, .35);
  }
  /* Up, not down. */
  .lde-search-results, .lde-search-empty {
    position: absolute; top: auto; bottom: calc(100% + 10px);
    left: 0; right: 0; width: auto;
    max-height: 56vh; overflow-y: auto;
    padding: 8px; border-radius: 16px;
    border: 1px solid var(--lde-glass-border);
    box-shadow: var(--lde-glass-shadow);
  }
  /* The panel covers the bar, and the CANCEL lands exactly where the
     spyglass was — which is right, because that is where your thumb
     already is. So the panel stays on top and the spyglass stays under
     it. (First pass here raised the spyglass above the panel to "keep
     the way out visible"; measured at 390px, that put a 34px circle on
     top of the 32px × and elementFromPoint returned the icon. The way
     out was the thing blocking the way out.) */
  .lde-search-btn { position: static; z-index: auto; }
}
@media (max-width: 900px) and (prefers-reduced-motion: reduce) {
  .lde-search-pop { animation: none; }
}

/* 10 ----------------------------------------- the wipe releases its clip
   `backwards`, never `both`: the last keyframe's clip-path would stay
   applied while the panel is open and clip away the results card (a
   descendant positioned outside the pill's box).
   (The round-26..31 story: CHANGELOG r31.) */
.lde-search-pop { animation-fill-mode: backwards; }

/* 11 ------------------------------- the aura was the horizontal scrollbar

   `.lde-aura` is `inset: -20% -42%` on purpose — the glow has to spill
   past the figure or it reads as a spotlight. It has been spilling past
   the DOCUMENT too: measured at 1512x860 on Salud, scrollWidth 1788
   against a 1512 viewport, i.e. a horizontal scrollbar on every room,
   and at 1280x720 it added 23px of vertical scroll to a page that was
   otherwise exactly one screen.

   `overflow: clip` on <main> did not catch it, and the reason is worth
   writing down: `.lde-stage` is `position: absolute` inside a <main>
   that is `position: static`, so its containing block is the initial
   containing block — OUTSIDE the element doing the clipping. An
   absolutely positioned box is only clipped by ancestors between it and
   its containing block. One `position: relative` puts main back in that
   chain and the clip starts applying to her.

   `overflow-x: clip` on the root is the second line of defence, for
   anything else that ever escapes. `clip` rather than `hidden`: hidden
   would make the root a scroll container and break `position: sticky`
   on the header. */
html { overflow-x: clip; }
.lde-main { position: relative; }


/* 12 ------------------------------- the rail's scrollers, done properly

   Round 29 §4 made the two long panes flex children and put
   `overflow-y: auto` on their LISTS. Measured, that does nothing: the
   <details> shrank to its 96px share of the column exactly as asked,
   and the <ul> inside it stayed 1153px tall and simply overflowed. The
   reason is that a <details>'s content is not the element you styled —
   the UA wraps it in its own content box, and the list's `flex: 0 1
   auto` was negotiating with that box rather than with the pane.

   So the SCROLLER IS THE PANE. No dependence on ::details-content, no
   guessing at what the UA wraps things in: overflow on the <details>,
   and the summary pinned to the top of it so "CONTENIDO · 12 min" and
   "INGREDIENTES" do not scroll away from the list they label. */
@media (min-width: 1100px) and (min-height: 600px) {
  body.lde-read-panel .lde-toc,
  body.lde-read-panel .lde-recipe {
    display: block; overflow-y: auto; overscroll-behavior: contain;
    scrollbar-width: thin;
    scrollbar-color: rgba(var(--lde-pillar-on-glass), .35) transparent;
  }
  body.lde-read-panel .lde-toc > summary,
  body.lde-read-panel .lde-recipe > summary {
    position: sticky; top: 0; z-index: 2;
    /* Pulled out to the pane's own edges so the list passes UNDER the
       label rather than beside it, then padded back. */
    margin: -16px -16px 10px; padding: 16px 16px 8px;
    /* 1.1.4 — Nick, 2026-08-08: "EN ESTE ARTÍCULO" should sit at the top
       OF THE PANE, not read as a band across it. It was backed with
       --lde-glass-bg (the full glass gradient) while the pane around it
       is .lde-glass-soft at .6 — two different materials stacked, which
       is exactly what draws a bar. Now it takes the pane's own colour,
       opaque enough that the list still passes cleanly underneath, and
       the second backdrop-filter goes: the panel beneath is already
       blurring, and blurring a blur was half of why it read as a
       separate surface. */
    background: rgba(250, 246, 238, .94);
  }
  body.lde-dark.lde-read-panel .lde-toc > summary,
  body.lde-dark.lde-read-panel .lde-recipe > summary {
    background: rgba(24, 16, 20, .94);
  }
  @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    body.lde-read-panel .lde-toc > summary,
    body.lde-read-panel .lde-recipe > summary {
      background: rgba(250, 246, 238, .96);
    }
    body.lde-dark.lde-read-panel .lde-toc > summary,
    body.lde-dark.lde-read-panel .lde-recipe > summary {
      background: rgba(26, 18, 22, .96);
    }
  }
  body.lde-read-panel .lde-toc-list,
  body.lde-read-panel .lde-recipe .lde-ing { overflow: visible; }

  /* And the two fixed panes give back what they can, because every
     pixel they hold is a line of contents the reader cannot see.
     Compartir was 131px for five icons and one line of text. */
  body.lde-read-panel .lde-engage { padding: 10px 14px 12px; }
  body.lde-read-panel .lde-share { min-height: 38px; }
  body.lde-read-panel .lde-share-btn { width: 34px; height: 34px; }
  body.lde-read-panel .lde-share-svg { width: 18px; height: 18px; }
  body.lde-read-panel .lde-share-note { min-height: 0; margin-top: 6px; }
  body.lde-read-panel .lde-share-note:not(.is-on) { display: none; }
  body.lde-read-panel .lde-engage-comments { margin-top: 8px; padding-top: 8px; }
  body.lde-read-panel .lde-next { padding: 14px 16px 12px; }
}


/* ====================================================================
   ROUND 30 — THE LADY'S SCALE, AND GLASS THAT ANSWERS THE POINTER
   ==================================================================== */

/* 1 ---------------------------------- her hat and the title, on resize

   Nick: "we need to keep the Lady from growing so wide that her hat
   crashes with the title text."

   `.lde-stage-hero` is `height: 74%` of the hero with `aspect-ratio:
   400/396`, so her WIDTH is ~1.01x whatever height she is given. On a
   tall narrow window that is the collision: at 1000x1100 she comes out
   748px wide inside a 1000px hero — 75% of it — and the copy block
   starts at 53%. Nothing was wrong with the number; it was measured
   against the wrong axis.

   `min(74%, 44vw)` caps her by the axis she actually grows into. The
   copy keeps `width: min(600px, 47%)` on the right, so 44 + 47 leaves
   9% of clear air between them at every size.

   0.9.13 — desktop only. Unconditional, this rule sat later in the file
   than the phone rule (`height: 30vh` at ≤900px) and beat it: at 390px
   wide, 44vw = 172px against the ~253px she had — the mobile hero
   silently lost 30%. The collision it solves only exists where she and
   the copy share a row, which is the ≥901px layout. */
@media (min-width: 901px) {
  .lde-stage-hero { height: min(74%, 44vw); }
}


/* 2 ----------------------------------------------- evening up the five

   Nick: "let's scale up her default size slightly. She feels to be
   distinctly the smallest of the five."

   Measured — ink bounding box of the drawn line only, washes excluded,
   as a fraction of the 400x563 stage:

       pose        wide    tall
       bienestar    45%     57%
       salud        48%     62%
       belleza      93%     55%
       cocina       68%     64%
       moda         48%     66%

   They are all in one box, so the box is not the variable — how much of
   it each drawing uses is. Belleza and Bienestar present a third
   smaller than Moda, which is the inconsistency behind the feeling.

   Two things at once: a +6% bump for everyone, and 60% of the
   correction that would equalise them. Only 60%, and Belleza barely
   moves at all, because she is 93% WIDE — normalising her on height
   alone would push her past the edge of her own stage. */
/* Per-pose scale (lady_scale): emitted from the registry (block 7). */
.lde-stage-room   { height: calc(62vh * var(--lde-lady-scale, 1)); }
.lde-stage-single { height: calc(var(--lde-single-lady-h, 62vh) * var(--lde-lady-scale, 1)); }
@media (max-width: 900px) {
  /* On a phone she is already the whole background; leave her alone. */
  .lde-stage-room { height: 44vh; }
}

/* 0.9.13 — MAKING THE TWO RULES ABOVE ACTUALLY WIN. As shipped, the
   scale applied only in the 901–1099px room band: everywhere else a
   (0,2,0) rule from an earlier round took the height instead —
   `.lde-single-room .lde-stage-single { height: 84vh }` on the post,
   the ≥1100px room grid where the stage is a cell with `height: auto`,
   and the mobile single's own 34vh. `--lde-single-lady-h` was read and
   never defined. The winning rules now carry the multiplication
   themselves (see the single at its definition, and the mobile single),
   and on the ≥1100px room — where the stage's height belongs to the
   grid and must not change — the POSE scales inside its cell instead,
   feet planted (transform-origin bottom), same factor. The stage stays
   the view-transition surface; only the drawing inside it grows. */
@media (min-width: 1100px) {
  .lde-room .lde-stage-room svg.lde-pose {
    transform: scale(var(--lde-lady-scale, 1));
    transform-origin: 50% 100%;
  }
}


/* 3 =========================== GLASS THAT ANSWERS THE POINTER (round 30)
   THE CONTRACT: one delegated pointermove, coalesced to one rAF,
   writing three custom properties on the element under the cursor;
   rect measured once on enter and cached. `transform` and
   `background-image` only — never box-shadow, filter, or a new
   backdrop-filter. Coarse pointers, no-hover devices and
   prefers-reduced-motion get NONE of it; without the script the
   surfaces are exactly as before. (Full cost analysis: CHANGELOG
   r30.) */
@media (hover: hover) and (pointer: fine) {
  .lde-portal, .lde-card, .lde-pill, .lde-qbtn, .lde-search-btn,
  .lde-share-btn, .lde-comments-back, .lde-comment-submit,
  .lde-foot-sub, .lde-menu-btn {
    --mx: 50%; --my: 50%; --rx: 0deg; --ry: 0deg; --lift: 0px;
    transform: perspective(900px) rotateX(var(--ry)) rotateY(var(--rx))
               translate3d(0, var(--lift), 0);
    transition: transform .34s cubic-bezier(.2, .8, .25, 1),
                box-shadow .34s ease;
    will-change: auto;
    /* The light: a soft highlight tracking the cursor, painted as a
       background LAYER rather than a pseudo-element, so it cannot
       collide with the ::before/::after these surfaces already use.
       Neutral white at low alpha: sheen on a deep room, almost nothing
       on a light one. LIVE AT REST — the radial paints at 50%/50% on
       every surface before the pointer arrives (verified computed
       style, 0.10.0), so this is not collapsible into the .is-lit
       blocks below. */
    background-image:
      radial-gradient(140% 140% at var(--mx) var(--my),
        rgba(255, 255, 255, .20) 0%,
        rgba(255, 255, 255, .07) 26%,
        rgba(255, 255, 255, 0) 58%);
  }
  body.lde-dark :is(.lde-portal, .lde-card, .lde-pill, .lde-qbtn,
    .lde-search-btn, .lde-share-btn, .lde-comments-back,
    .lde-comment-submit, .lde-foot-sub, .lde-menu-btn) {
    background-image:
      radial-gradient(140% 140% at var(--mx) var(--my),
        rgba(255, 255, 255, .16) 0%,
        rgba(255, 255, 255, .05) 28%,
        rgba(255, 255, 255, 0) 60%);
  }
  /* While the pointer is actually on it, the transform follows without
     the easing — easing on a live pointer is lag, not smoothness. The
     easing comes back for the return to rest. */
  .is-lit { transition: transform .06s linear, box-shadow .2s ease; }
  .lde-card.is-lit, .lde-portal.is-lit { will-change: transform; }

  /* The lift is a variable now, so the tilt composes with it instead of
     replacing it — round 29's `transform: translateY(-3px)` on hover
     would otherwise wipe the rotation out. */
  .lde-card:hover { --lift: -3px; transform: perspective(900px)
    rotateX(var(--ry)) rotateY(var(--rx)) translate3d(0, var(--lift), 0); }
  .lde-portal:hover, .lde-portal:focus-visible { --lift: -6px; }
  .lde-share-btn:hover, .lde-share-btn:focus-visible { --lift: -1px; }
  .lde-comment-submit:hover { --lift: -1px; }
}
@media (prefers-reduced-motion: reduce) {
  .lde-portal, .lde-card, .lde-pill, .lde-qbtn, .lde-search-btn,
  .lde-share-btn, .lde-comments-back, .lde-comment-submit,
  .lde-foot-sub, .lde-menu-btn {
    transform: none !important; background-image: none !important;
    transition: none !important;
  }
}

/* 4 ------------------------------- and the tilt has to WIN the cascade
   The composed transform hangs on `.is-lit` (the class the script adds
   under the pointer): compound, so (0,2,0), level with the older
   :hover transform rules and later in the file — the bare-class tilt
   loses to them everywhere else. (The 0.9.6 measurements that proved
   it: CHANGELOG r30.) */
@media (hover: hover) and (pointer: fine) {
  .lde-portal.is-lit, .lde-card.is-lit, .lde-pill.is-lit,
  .lde-qbtn.is-lit, .lde-search-btn.is-lit, .lde-share-btn.is-lit,
  .lde-comments-back.is-lit, .lde-comment-submit.is-lit,
  .lde-foot-sub.is-lit, .lde-menu-btn.is-lit {
    transform: perspective(900px) rotateX(var(--ry)) rotateY(var(--rx))
               translate3d(0, var(--lift), 0);
  }
  /* The lifts, restated as the variable so they survive the compose. */
  .lde-portal.is-lit { --lift: -6px; }
  .lde-card.is-lit { --lift: -3px; }
  .lde-share-btn.is-lit, .lde-comment-submit.is-lit { --lift: -1px; }
}


/* ====================================================================
   ROUND 31 — THE SMALL CONTROLS, THE POST'S LAST GAP, AND THE LINE
   ==================================================================== */

/* 1 -------------------------------- the small controls' own tilt (r31)
   The card treatment is invisible on a 34px transparent control (no
   surface for the light; two pixels of edge displacement), so the
   small controls get a faint surface only while lit, a brighter and
   tighter highlight, and nearly twice the lean from the script.
   (Why the first pass could not be seen: CHANGELOG r31.) */
@media (hover: hover) and (pointer: fine) {
  .lde-pill, .lde-qbtn, .lde-search-btn, .lde-share-btn,
  .lde-comments-back, .lde-comment-submit, .lde-foot-sub, .lde-menu-btn {
    /* A surface, only while the pointer is on it, so nothing about the
       bar at rest changes. */
    transition: transform .06s linear, background-color .2s ease,
                border-color .25s ease, opacity .18s ease;
  }
  .lde-pill.is-lit, .lde-qbtn.is-lit, .lde-search-btn.is-lit,
  .lde-share-btn.is-lit, .lde-comments-back.is-lit,
  .lde-comment-submit.is-lit, .lde-foot-sub.is-lit, .lde-menu-btn.is-lit {
    background-color: color-mix(in oklab, var(--lde-fg) 7%, transparent);
    border-color: color-mix(in oklab, var(--lde-fg) 22%, transparent);
    background-image:
      radial-gradient(72% 120% at var(--mx) var(--my),
        rgba(255, 255, 255, .55) 0%,
        rgba(255, 255, 255, .18) 32%,
        rgba(255, 255, 255, 0) 72%);
  }
  body.lde-dark :is(.lde-pill, .lde-qbtn, .lde-search-btn, .lde-share-btn,
    .lde-comments-back, .lde-comment-submit, .lde-foot-sub,
    .lde-menu-btn).is-lit {
    background-color: color-mix(in oklab, #fff 10%, transparent);
    background-image:
      radial-gradient(72% 120% at var(--mx) var(--my),
        rgba(255, 255, 255, .40) 0%,
        rgba(255, 255, 255, .13) 34%,
        rgba(255, 255, 255, 0) 74%);
  }
  /* The active pill already has a tint of its own; keep it and lay the
     sheen over the top rather than replacing it. */
  .lde-pill.is-active.is-lit {
    background-color: rgba(140, 49, 48, .14);
  }
  body.lde-dark .lde-pill.is-active.is-lit {
    background-color: rgba(221, 164, 155, .16);
  }
}


/* 2 ------------------------------------- the post's last gap (round 31)

   Nick: "in the post template it looks like we still need to add a
   little padding between the footer and the bottom of the panels."

   The frame gives <main> everything the header and footer do not use,
   and the panes stretch into all of it — so the article's glass and the
   rail's last panel finish exactly on the footer's top edge. Correct
   arithmetic, no air. The gap comes off the bottom of main rather than
   out of the footer, so the footer keeps sitting on the frame's floor. */
@media (min-width: 1100px) and (min-height: 600px) {
  body.lde-read-panel .lde-main.lde-single {
    padding-bottom: clamp(12px, 1.6vh, 22px);
  }
}
/* And the same courtesy on a room, where the pager finished flush too.
   (0.9.13: `lde-frame`, with the frame itself — unframed pages have
   normal flow padding and do not want this.) */
@media (min-width: 901px) and (min-height: 520px) {
  body.lde-frame .lde-main {
    padding-bottom: clamp(10px, 1.2vh, 18px);
  }
}


/* 3 --------------------------------------- the travelling line (round 31)
   `#lde-travel` / `#lde-tline` are declared up at section 757 and have
   been parked since round 22. Two additions, both about the fact that
   the line is now drawn in the ARRIVING document rather than the
   departing one. */
#lde-tline { stroke-width: 3.4; }
@media (prefers-reduced-motion: reduce) { #lde-travel { display: none; } }

/* 4 ------------------------------- the post title, in two lines (round 31)

   Nick: "on the post template we need to widen the header box so that
   the title never wraps beyond 2 lines."

   Two changes, because width alone will not do it. The masthead's
   column was `min(760px, 64%)` — round 26 chose 62-64% so a rail sitting
   at the top of the page could not land on the title, and the rail has
   since come back down to the article's shoulder (round 26 §3), so the
   reason has expired. It goes to `min(1080px, 74%)`.

   And the type comes down at the top end: 58px was sized for a
   760px column, and at 1080px the same measure fits appreciably more
   per line. `clamp(30px, 3.6vw, 50px)` puts a 68-character Spanish
   title — which is about the longest the pillars actually carry — on
   two lines at every desktop width.

   `text-wrap: balance` does the rest: with two lines it evens them
   rather than leaving one word stranded, which is the failure that
   makes a two-line title look like a three-line one that got lucky. */
@media (min-width: 1100px) {
  /* `body.lde-single-page` and not the bare class: round 27's
     side-alternation rules are `body.lde-mood-cocina .lde-single-masthead
     > *` at (0,2,1), so a (0,1,1) selector here loses and the column
     stayed at 760px — measured, an 85-character title still took three
     lines. Same specificity, later in the file, and only `max-width`
     touched so the per-pillar margins that put the masthead on the
     correct side are left exactly alone. */
  body.lde-single-page .lde-single-masthead > * {
    max-width: min(1080px, 74%);
  }
  /* Sized against ITS OWN COLUMN, not the viewport. `vw` was the bug in
     miniature: at 1280 the column is 871px and at 1512 it is 1036, but
     `4.4vw` gave the narrower column the LARGER share of it. Measured,
     an 85-character title took three lines at 1280 and two at 1512 with
     the same declaration. 4.8cqi holds ~43 characters a line at every
     width the frame runs at, which is the longest line any of the five
     pillars currently carries. */
  body.lde-single-page .lde-single-masthead { container-type: inline-size; }
  body.lde-single-page .lde-single-title {
    /* `cqi` alone is wrong here: the CONTAINER is the masthead, but the
       measure is the CHILD's `min(1080px, 74%)` of it. So the size is
       derived from that same expression — the column's real width over
       the ratio that holds ~43 characters on a line in Playfair at this
       weight, measured rather than guessed (1036px / 50px). */
    font-size: clamp(28px, calc(min(1080px, 74cqi) / 21), 50px);
    text-wrap: balance;
  }
  /* The article's column is the other half of the frame, so widening
     the masthead cannot be allowed to walk it into the rail: the
     masthead is out of flow and the grid below is untouched. Stated
     because it is the thing that would break if either moved. */
}


/* ====================================================================
   ROUND 32 — THE CHIP'S TITLE AND ITS COUNT
   ==================================================================== */

/* The chip titles must never land on the count (round 32). The states
   are absolutely positioned cross-fades, so the container cannot size
   itself to a title: the type is sized against the CHIP (cqi), clamped
   to three lines with an ellipsis, and the state box reserves exactly
   label + three lines so the count keeps real clearance. Three lines
   and not four: the chip is a doorway, not a summary.
   (The measured failure case: CHANGELOG r32.) */
/* THE HERO NEEDS A DEFINITE WIDTH FIRST, and this is the round-28
   wp-site-blocks trap in miniature. `.lde-hero` is `max-width: 1560px;
   margin: 0 auto` inside a flex column, and auto side margins on a flex
   item make it CONTENT-SIZED. Its widest content is the chip row — so
   the moment the chips got `container-type: inline-size` their
   intrinsic contribution went to zero and the whole hero collapsed with
   them: measured, the row fell from 952px to 578 and every chip from
   176px to 101. `width: 100%` gives the hero a definite size, the grid
   divides that, and containment then affects only what it was meant to.
   Verified both ways at 1512px before and after. */
.lde-front .lde-hero { width: 100%; }
.lde-portal { container-type: inline-size; }
.lde-ptitle {
  font-size: clamp(13px, 11.4cqi, 16.5px);
  display: -webkit-box; -webkit-box-orient: vertical;
  -webkit-line-clamp: 3; line-clamp: 3; overflow: hidden;
}
.lde-plabel { font-size: clamp(8.5px, 6.6cqi, 9.5px); }
.lde-portal-name { font-size: clamp(15px, 13.6cqi, 20px); }
.lde-portal-states {
  /* label + gap + three lines at 1.3, with the same clamp as the type
     so the reservation and the content cannot disagree. */
  min-height: calc(clamp(8.5px, 6.6cqi, 9.5px) + 7px
                   + 3 * 1.3 * clamp(13px, 11.4cqi, 16.5px));
  margin: 12px 0 14px;
}
.lde-portal-count {
  margin-top: auto; padding-top: 8px;
  font-size: clamp(8.5px, 6.4cqi, 10px); letter-spacing: .2em;
  white-space: nowrap;
}
/* (The old ≤900px line-clamp:4 relaxation for .lde-ptitle is gone with
   the phone scroller itself — round 34 hides the portal row below the
   gate, so the container-query clamp above is the only live sizing.) */

/* And `width: 100%` needs `border-box`, or the hero's own 64px side
   padding is ADDED to it: measured immediately after the fix above, the
   hero came out 1640px inside a 1512px viewport. The theme has no
   global border-box reset (round 27 hit the same thing from the other
   direction), so it is stated here rather than assumed. */
.lde-front .lde-hero { box-sizing: border-box; }


/* The search pill's shadow (round 32): clip-path clips box-shadow too,
   so the wipe's last keyframe opens the clip PAST the border box —
   the shadow grows with the pill instead of snapping on at the end.
   Never animate box-shadow itself; that is the expensive way.
   (Full mechanism: CHANGELOG r32.) */
@keyframes lde-search-open {
  from { clip-path: inset(0 0 0 100% round 999px); opacity: .4; }
  to   { clip-path: inset(-46px round 999px); opacity: 1; }
}


/* ====================================================================
   ROUND 33 — THE HEADING'S LEFT EDGE
   ==================================================================== */

/* The heading always starts where the composition starts (round 33):
   both grid columns begin at the shell's own padding edge, so the
   masthead stops mirroring with the pillar — round 27's reason (a rail
   at the top of the page) expired in round 26 §3.
   (The 1512px measurements: CHANGELOG r33.) */
/* (Round 33's left-edge walk-back for the alt-left ∩ side-right rooms
   is emitted from the registry, block 8 — same rule, same position in
   the cascade: it arrives after everything in this file.) */

/* LAW (third occurrence of the same trap — see also .wp-site-blocks in
   round 28 and .lde-hero in round 32): WordPress's auto side margins on
   a flex item turn a stretch into shrink-to-fit. `width: 100%` gives
   the single's main a definite size, so the fixed masthead and the
   grid content start at the same edge.
   (The 0.9.11 measurements: CHANGELOG r33.) */
@media (min-width: 1100px) {
  body.lde-single-page .lde-main { width: 100%; }
}


/* ====================================================================
   0.10.0 — PRODUCTISATION PASS
   ==================================================================== */

/* 1 ------------------------------------- the 404 carries the REAL search
   templates/404.html used a bare core wp:search — unstyled, no instant
   results, no cancel. It renders lde_search_control() now (the
   lde/search-control block), inside a centred wrapper that gives the
   pop a positioned ancestor of its own; in the header that job is done
   by .lde-pills. */
.lde-search-solo .lde-search-pop { left: 0; right: 0; width: auto; }
/* 2 -------------------------------------------- the results page's name
   templates/index.html serves both the blog index and search results;
   the lde/results-title block renders this header only on a search
   (the H1 the results page never had). The count mirrors what the
   instant layer announces to assistive tech. */
.lde-results-head { margin: 6px 0 2px; }
.lde-results-title {
  margin: 0 0 4px; font-size: clamp(26px, 3.2vw, 40px);
  letter-spacing: -.01em; text-wrap: balance;
}
.lde-results-count {
  margin: 0; font: 500 12px var(--lde-sans); letter-spacing: .12em;
  text-transform: uppercase; color: var(--lde-fg); opacity: .6;
}

/* 3 --------------------------------------------------- print, un-framed
   The frame gates are dimension media queries, and paper has
   dimensions too: printing a framed page clipped it to page one
   (review M7). Print gets the ordinary flowing document — one
   statement here rather than `screen and` scattered across every
   frame gate, so the next frame rule cannot re-break it by
   forgetting the prefix. */
@media print {
  body, body.lde-frame, body.lde-single-page {
    height: auto !important; min-height: 0 !important;
    display: block !important;
  }
  body > .wp-site-blocks {
    display: block !important; flex: none !important;
  }
  .lde-main {
    overflow: visible !important; height: auto !important;
    flex: none !important;
  }
  body.lde-read-panel .lde-reading,
  body.lde-read-panel .lde-comments { max-height: none !important; }
}


/* ====================================================================
   ROUND 34 (0.11.0) — THE ROOM INDEX
   ==================================================================== */

/* Nick approved mockup B (2026-08-08): below the 900px gate the five
   portal cards and the door to «Lo último» become a 2×3 grid of solid
   ink tiles — each room's deep pillar colour with its name in cream —
   and nothing on the theme scrolls sideways any more. Two surfaces
   carry it: the front page (the `.lde-room-index` nav the portal block
   renders next to the row) and the phone's menu sheet, whose existing
   pills are RESTYLED into the identical grid — same six doors on both
   surfaces, no second copy of the markup.

   The tiles carry NAMES ONLY. The one-line latest-title fallback Nick
   accepted (tiles at ~96px) was built and measured against the
   deciding test — the one-screen composition at 390×844 AND 360×780 —
   and lost: 80px past the thumb bar at 360×780, titles ellipsised to
   fragments at 170px of tile. Numbers and evidence in CHANGELOG round
   34. The grid is six server-rendered links; with JS off it is exactly
   what it is with JS on.

   No view-transition-name anywhere here, deliberately: the grid exists
   only below 900px, where section 6 of the r15 nav block drops the
   per-pillar names (snapshots flying in from off-screen read as a
   glitch). Tiles are `.lde-tile`, not `.lde-portal`/`.lde-pill`, so
   the naming selectors cannot reach them by construction. */

/* Desktop: the grid does not exist. (display:none keeps it out of the
   render tree, so it costs no layout and can carry no snapshot.) */
.lde-room-index { display: none; }

@media (max-width: 900px) {
  /* 1 ------------------------------------------------ the front grid */
  .lde-room-index {
    position: relative; z-index: 3;
    /* minmax(0,1fr), not 1fr: 1fr is minmax(auto,1fr), and a tile whose
       min-content ran wider than the column (the rejected nowrap title
       line measured 420px tiles inside a 390px viewport) would stretch
       the whole grid past the frame. Same trap as the round-32 hero
       collapse, from the other side; stated defensively so a future
       tile line cannot re-open it. */
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 9px; margin-top: 12px;
    scroll-margin-top: 110px;
  }
  .lde-tile {
    display: flex; flex-direction: column; justify-content: center;
    /* border-box, stated: the theme has no global reset (round 27's
       lesson), and a content-box min-height plus 24px of padding is a
       120px tile pretending to be 96. */
    box-sizing: border-box; min-width: 0;
    min-height: 68px; padding: 12px 16px;
    border-radius: 14px; text-decoration: none;
    background: rgb(var(--lde-pc));
    box-shadow: 0 10px 24px rgba(28, 25, 22, .16);
    transition: transform .3s cubic-bezier(.2, .8, .25, 1), box-shadow .3s;
  }
  /* Extra doors (quick pages styled as tiles) are emitted from the
     quick-page registry (block 9) — the house's own ink, not a pillar. */
  .lde-tile:hover, .lde-tile:focus-visible {
    transform: translateY(-2px);
    box-shadow: 0 14px 30px rgba(28, 25, 22, .22);
  }
  .lde-tile:active { transform: scale(.985); transition-duration: .12s; }
  .lde-tile-name {
    font-family: var(--lde-serif); font-weight: 500; font-size: 14px;
    letter-spacing: .14em; text-transform: uppercase;
    color: var(--lde-cream-dark);
  }
  /* 2 ---------------------------------- the menu sheet, recoloured
     Round 24 built the sheet as a grid already; this narrows it to two
     columns and paints the SAME six doors. The pills keep their DOM,
     their aria-current and their view-transition law — only the paint
     changes. Names only here (the sheet floats over a page that is
     already about something else; six titles would be noise). */
  .lde-menu-sheet { grid-template-columns: 1fr 1fr; gap: 9px; padding: 12px; }
  .lde-menu-sheet .lde-pill[data-pillar] {
    display: flex; align-items: center; width: auto;
    box-sizing: border-box; min-width: 0;
    min-height: 56px; padding: 10px 15px;
    border-radius: 12px; border: 0; box-shadow: none;
    background: rgb(var(--lde-pc));
    color: var(--lde-cream-dark); opacity: 1;
    font-size: 11px; letter-spacing: .18em;
  }
  .lde-menu-sheet .lde-pill-sep { display: none; }
  /* Quick-page doors in the sheet — an ink 'tile' or a quiet full-width
     'row' — are emitted from the quick-page registry (block 9). */
  /* «You are here»: the current room's tile wears a cream ring inside
     its own ink, which survives every pillar colour by construction. */
  .lde-menu-sheet .lde-pill[data-pillar].is-active {
    box-shadow: inset 0 0 0 2px var(--lde-cream-dark);
    background: rgb(var(--lde-pc));
  }
}

/* Press/hover choreography is motion; reduced motion gets none of it,
   same law as every lit surface in the theme. */
@media (prefers-reduced-motion: reduce) {
  .lde-tile { transition: none; }
  .lde-tile:hover, .lde-tile:focus-visible, .lde-tile:active {
    transform: none;
  }
}
