/* ============================================================================
   RARA · pdp.css · product detail page (Slice-4 beautification).
   Layout: framed bottle + long ficha on the left, a sticky buy rail on the right.
   Ficha tecnica regrouped into three designed chunks (Notas · Caracter · Formato)
   with a stacked notes pyramid and reveal-animated bars (scaleX, never width).
   The ONE ficha bar component (_bar.html / components.css) is reused; only its
   PDP-specific animation + richer fill are layered here (pdp.css loads last).
   Single accent = --burgundy; gold lives only on the Colección badge.
   ============================================================================ */

.crumb{font-size:11px;letter-spacing:.1em;color:var(--warm-grey);padding:16px 0 4px}
.crumb a{color:var(--warm-grey)}
.crumb a:hover{color:var(--burgundy)}

/* ---------- layout: media (left) + right rail (buy panel + acta) ----------
   The right rail spans both left-column rows so the sticky buy panel keeps its
   scroll runway, and the Acta de Verificacion sits beneath it — filling the
   right-column void and placing the certificate next to the buy decision. Source
   order gallery→rail→ficha linearizes cleanly on mobile: bottle, purchase + acta,
   then the deep-dive.
   SECTION-SPREAD FIX (owner 2K screenshots): with both rows auto, grid intrinsic
   sizing distributes the tall spanning rail's excess height across BOTH rows
   equally, inflating the media row past the gallery (measured +511..688px dead
   band before NOTAS at 2560w). grid-template-rows:auto 1fr excludes the
   flex-track-crossing rail from the media row's intrinsic size, so row 1 hugs
   the gallery and ALL rail excess resolves into the ficha row — left sections
   flow immediately (gallery → row-gap → ficha), and the fr row still grows to a
   longer ficha when the LEFT column is the long one (fr floors at content in an
   indefinite-height container). DOM order untouched. */
.pdp{
  display:grid;
  grid-template-columns:1.05fr 1fr;
  grid-template-rows:auto 1fr;
  grid-template-areas:"media rail" "ficha rail";
  column-gap:56px;row-gap:44px;
  padding:20px 0 24px;
  align-items:start;
}
/* min-width:0 lets the grid children shrink below their content's min-content, so a
   nowrap descendant (the acta microprint strip) can't force horizontal overflow —
   the strip's own overflow:hidden then clips it. Required for the 375px no-scroll rule. */
.gallery{grid-area:media;min-width:0}
.pdp-rail{grid-area:rail;min-width:0}
.pdp-ficha{grid-area:ficha;min-width:0}

/* ---------- gallery: a matted frame around the bottle ----------
   The <img> carries .vt-product-image + --vt-name: product-<slug>, the shared
   name the catalog card sets on its image, so the bottle morphs card→detail via
   cross-document View Transitions (contract; must be exactly product-<slug>). */
.gallery{position:relative;margin:0;padding:12px;background:var(--brand-mat);border:1px solid var(--line-2);border-radius:6px}
.gallery .sku{position:absolute;top:24px;left:24px;z-index:2;font-family:var(--mono);font-size:11px;letter-spacing:.12em;color:var(--cream);background:rgba(89,28,42,.92);padding:6px 10px}
.gallery-mat{display:block;position:relative;aspect-ratio:4/5;overflow:hidden;background:#FFFFFF;border:1px solid var(--line);border-radius:3px}
.gallery-mat img{width:100%;height:100%;object-fit:contain;background:#FFFFFF;display:block}

/* ---------- right rail: buy panel + the acta beneath it (desktop) ----------
   Owner ruling 2026-07-12: follow-scroll removed, panel is static in the rail.
   z-index + opaque page-bg kept so the acta still tucks cleanly beneath it.
   The acta's extra top gap clears the wax seal (which overhangs its top-right
   corner) and separates it from the buy panel. */
.pdp-buy{z-index:2;background:var(--bg)}
.pdp-rail .acta{margin-top:34px}

/* ---------- THE PLATE — stamped provenance medallion ----------
   Owner board #3: a small engraved medallion pressed at the head of the acta.
   Double-bezel construction (outer hairline ring -> inner core -> hairline
   inset highlight), zero border-radius throughout — deliberately NOT the acta's
   rounded paper mat, so this reads as pressed metal, not a matted certificate.
   Narrower than the rail and centered so its base overlaps the acta's top edge
   (negative margin below) without reaching the wax seal's top-right corner.
   Static component: no hover motion, only the shared [data-reveal] fade. */
.prov-stamp{
  position:relative;z-index:2;
  width:max-content;max-width:238px;margin:34px auto 0;
  padding:3px;border:1px solid var(--burgundy-deep);border-radius:0;
  background:var(--surface);
}
.pdp-rail .prov-stamp + .acta{margin-top:-16px}
.prov-stamp-core{
  position:relative;border:1px solid var(--burgundy-deep);border-radius:0;
  padding:12px 18px 11px;text-align:center;background:var(--cream);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.65),inset 0 -1px 0 rgba(74,22,34,.14);
}
.prov-stamp-house{font-family:var(--serif);font-variant:small-caps;font-size:15px;
  letter-spacing:.02em;color:var(--ink)}
.prov-stamp-folio{display:block;margin-top:2px;font-size:12px;letter-spacing:.08em;
  color:var(--burgundy)}
.prov-stamp-meta{margin-top:6px;padding-top:6px;border-top:1px solid var(--line);
  font-size:10.5px;letter-spacing:.04em;color:var(--warm-grey)}
.prov-stamp-meta span+span::before{content:" · "}
.prov-stamp-status{margin-top:6px;font-size:10px;letter-spacing:.1em;
  text-transform:uppercase;color:var(--brand-verified)}
.prov-stamp-check{margin-right:3px}
.buy-badges{display:flex;flex-wrap:wrap;gap:8px}
.pdp-buy .house{font-family:var(--mono);font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--warm-grey);margin:16px 0 4px}
.pdp-buy h1{font-family:var(--serif);font-weight:500;font-size:clamp(30px,4vw,46px);line-height:1.02;letter-spacing:-.01em;margin:2px 0 10px}
.buy-conc{font-size:12px;letter-spacing:.04em;color:var(--warm-grey)}
.pdp .price{font-family:var(--serif);font-size:32px;margin:18px 0 2px}
.pdp .msi{font-family:var(--mono);font-size:11px;color:var(--muted);margin-bottom:20px}
/* ---------- compare-at (owner directive 2026-07-07): muted was-price + burgundy
   save chip, shown only when the selected size has an anchor above its live price
   (Alpine x-show="compare > price"). Achromatic canvas: existing tokens only. ---- */
/* FX-7.3: neutral disclosure label ahead of the struck anchor (owner-call wording). */
.pdp .price-was-label{font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--warm-grey);margin-right:6px;vertical-align:4px}
.pdp .price-was{font-family:var(--serif);font-size:20px;color:var(--warm-grey);margin-right:10px;text-decoration:line-through;text-decoration-thickness:1px}
.pdp .price-save{display:inline-block;margin-left:10px;padding:3px 9px;font-size:11px;letter-spacing:.06em;line-height:1.4;color:var(--cream);background:var(--burgundy);vertical-align:3px;white-space:nowrap}

/* size selector */
.section-label{font-family:var(--mono);font-size:11px;letter-spacing:.18em;text-transform:uppercase;margin:8px 0 2px}
.sizes{display:flex;gap:10px;flex-wrap:wrap;margin:14px 0 18px}
.sizes button{border:1px solid var(--line-ui);background:none;padding:11px 18px;font-family:var(--mono);font-size:13px;cursor:pointer;min-height:44px;color:var(--ink);transition:border-color var(--dur-fast,.2s) var(--ease-out,ease),background var(--dur-fast,.2s) var(--ease-out,ease)}
.sizes button:hover{border-color:var(--burgundy)}
.sizes button[aria-pressed=true]{border-color:var(--burgundy);background:var(--burgundy);color:var(--cream)}
/* Non-STANDARD variant kind chip (tester / limited edition), owner point 6. */
.sizes button .opt-kind{display:block;font-size:9px;letter-spacing:.04em;text-transform:uppercase;opacity:.7;margin-top:2px}

/* ---------- per-size availability (honest numbers law: real stock_qty only) ----------
   Sold-out size buttons stay clickable (cart semantics unchanged — display only):
   dashed hairline + greyed label + an "Agotado/Sold out" sub-chip; the chip
   inherits cream when the sold-out size is the selected (burgundy) one. The
   .size-stock line under the selector is Alpine-reactive off the same per-button
   state as price (stock:N) and renders the REAL count — burgundy emphasis only
   when the real number is 0..3, never an invented threshold. */
.sizes button.is-soldout{border-style:dashed;color:var(--warm-grey)}
.sizes button.is-soldout:hover{border-color:var(--burgundy)}
.sizes button .opt-stock{display:block;font-size:9px;letter-spacing:.04em;text-transform:uppercase;margin-top:2px;color:var(--burgundy)}
.sizes button[aria-pressed=true] .opt-stock{color:inherit}
.size-stock{font-family:var(--mono);font-size:11px;letter-spacing:.04em;color:var(--muted);margin:-8px 0 14px}
.size-stock.is-low,.size-stock.is-out{color:var(--burgundy)}

/* add-to-cart with loading / added / error states (Alpine toggles the classes).
   position:relative gives THE SEAL's absolutely-positioned fill/hairline (below,
   scoped to .preorder-block .buy-btn) a containing block on desktop; on mobile
   the ≤1000px rule further down overrides this with position:fixed for the
   sticky CTA bar, which is itself a valid containing block too — same-specificity
   .buy-btn selector + later source order there means it correctly wins, so this
   never fights the sticky bar. */
.buy-btn{position:relative;width:100%;justify-content:space-between;margin:6px 0 12px}
.buy-btn .buy-icon{font-size:16px;line-height:1}
.buy-btn.is-loading{opacity:.9;cursor:progress;animation:buyPulse 1s var(--ease-out) infinite}
.buy-btn.is-added{background:var(--good)}
.buy-btn.is-added:hover{background:var(--good)}
.buy-btn.is-error{animation:buyShake .4s var(--ease-out) 1}
/* Sold-out CTA: greyed + not-allowed, mirroring the size chip's is-soldout state.
   The native :disabled is set alongside this class (Alpine :disabled + server attr),
   so the button can't be clicked; the class carries the muted look. Keyed off the
   class (NOT :disabled) so the transient is-loading disabled state keeps its burgundy. */
.buy-btn.is-soldout,.buy-btn.is-soldout:hover{background:var(--warm-grey);color:var(--cream);cursor:not-allowed;opacity:.65;animation:none}
@keyframes buyPulse{0%,100%{opacity:.72}50%{opacity:1}}
@keyframes buyShake{10%,90%{transform:translateX(-1px)}30%,70%{transform:translateX(2px)}50%{transform:translateX(-2px)}}
.buy-error{display:flex;align-items:baseline;gap:8px;margin:-4px 0 12px;font-size:12px;letter-spacing:.01em;color:var(--burgundy)}
.buy-error::before{content:"!";display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:16px;height:16px;font-family:var(--mono);font-size:11px;color:var(--cream);background:var(--burgundy)}
.buy-ship{font-size:11px;letter-spacing:.02em;color:var(--warm-grey)}

/* ---------- ficha chunks: Notas · Carácter · Formato ---------- */
.pdp-ficha{margin-top:4px}
.chunk{margin:0}
.chunk-h{display:flex;align-items:center;gap:12px;font-family:var(--mono);font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--ink);margin:0 0 18px}
.chunk-h::before{content:"";width:18px;height:2px;background:var(--burgundy)}
.chunk-lead{color:var(--muted);font-size:15px;margin:-4px 0 18px}
.chunk-rule{border:0;border-top:1px solid var(--line);margin:34px 0}

/* ---------- "De la misma casa" (casa8) ----------
   Moved from BELOW the .pdp grid into the ficha column's tail (owner 2026-07-14
   spacing blocker): below the grid it stranded the rail-tail void (measured
   1557..1784px of blank left column at 1366..2560w) and started ~1.6k px after
   the last left-column content. As the ficha's last chunk it fills that band.
   The .grid card idiom stays but is pinned 2-up: the base 4..7-up tracks assume
   wrap width, not a ~50% column — and with `related` capped at 4 same-house
   cards, 2-up renders full rows (2x1 or 2x2), never orphan tracks. ≤1000px the
   pdp is single-column full-width and the same 2-up gives the bigger-card read
   the owner chose for the interesa rail / home showcase. */
.pdp-casa .grid{grid-template-columns:repeat(2,1fr)}

.sub-eyebrow{font-family:var(--mono);font-size:10px;letter-spacing:.16em;text-transform:uppercase;color:var(--warm-grey);margin:24px 0 12px}
/* When-to-wear honesty qualifier: this block is an editorial estimate from the
   accords, not community data (honesty rule) — a quiet mono note under the title. */
.ficha-when-note{font-family:var(--mono);font-size:11px;letter-spacing:.02em;color:var(--muted);margin:-6px 0 14px}

/* ---------- the notes pyramid: 3 stacked tiers, narrowest on top ----------
   Pure CSS: the stepped max-widths (centered) form the pyramid silhouette; a
   faint spine shows only in the gaps between the opaque tier bands. NOT a table. */
.pyr-eyebrow{font-family:var(--mono);font-size:10px;letter-spacing:.16em;text-transform:uppercase;color:var(--warm-grey);margin:2px 0 12px}
.pyramid{position:relative;display:flex;flex-direction:column;align-items:center;gap:10px}
.pyramid::before{content:"";position:absolute;top:8px;bottom:8px;left:50%;width:1px;transform:translateX(-.5px);background:var(--line-2)}
.pyr-tier{position:relative;width:100%;display:flex;align-items:baseline;gap:16px;padding:15px 22px;background:var(--surface);border:1px solid var(--line);border-top:2px solid var(--burgundy)}
.pyr-tier--1{max-width:64%}
.pyr-tier--2{max-width:82%}
.pyr-tier--3{max-width:100%}
.pyr-k{flex:0 0 auto;font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--burgundy)}
.pyr-v{font-family:var(--serif);font-size:16px;line-height:1.3;color:var(--ink)}
/* Herbario grabado (owner 2026-07-24): one glyph per pyramid note, inline
   before its own name (components.css .glifo sets the shared size/color;
   this only nudges vertical alignment against the pyramid's larger 16px
   serif baseline). */
.pyr-note{display:inline-flex;align-items:baseline}
.pyr-note .glifo{vertical-align:-1.5px}

/* ---------- house note (authored longevity / sillage, distinct from the bars) ---------- */
.house-note{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px 10px;margin-top:16px}
.house-note-k{font-family:var(--mono);font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--warm-grey)}
.house-note-v{font-family:var(--serif);font-size:15px;color:var(--ink)}

/* ---------- Formato: a designed definition grid (no hairline row-per-spec table) ---------- */
.formato{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:18px 28px;margin:0}
.fmt{display:flex;flex-direction:column;gap:5px}
.fmt dt{font-family:var(--mono);font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--warm-grey)}
.fmt dd{margin:0;font-family:var(--serif);font-size:16px;color:var(--ink)}
.fmt dd.mono{font-family:var(--mono);font-size:13px;letter-spacing:.04em}

/* ---------- Presentacion: rail format and reference facts ---------- */
.pdp-presenta{margin-top:34px}
.presenta-hero{display:flex;align-items:center;gap:20px;margin:6px 0 22px;padding-bottom:22px;border-bottom:1px solid var(--line)}
.presenta-flacon{flex:0 0 auto;display:block}
.presenta-figure{display:flex;flex-direction:column;gap:4px;min-width:0}
.presenta-size{font-family:var(--serif);font-size:38px;line-height:1;color:var(--ink)}
.presenta-cap{font-family:var(--mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--warm-grey)}

.pdp-desc{color:var(--muted);font-size:15px;line-height:1.65;margin-top:28px}   /* justify via .prose-just (components.css) */

/* ---------- bars: richer fill + reveal-driven scaleX (transform only, never width) ----------
   _bar.html sets the resting length via inline width:<pct>%; the fill starts
   scaled to 0 and sweeps to full when its .fbars group reveals. A per-bar delay
   staggers the sweep. The group's own [data-reveal] is softened to an opacity-only
   fade so it does not also translate while the bars fill. */
.pdp .fbars[data-reveal]{transform:none}
.pdp .fbar{grid-template-columns:104px 1fr auto;column-gap:16px}
.pdp .fbar-track{height:9px;background:var(--cream-2)}
/* Gold retired from meters (design-lead WCAG 1.4.11 ruling): the default fill is the
   brand accent for non-semantic intensity bars (Duración/Estela); accord/season/
   day-night bars override this inline with their semantic family/season hue. */
.pdp .fbar-fill{background:var(--burgundy);transform-origin:left center;transform:scaleX(0);transition:transform var(--dur-slow) var(--ease-out)}
.pdp .fbars.is-revealed .fbar-fill{transform:scaleX(1)}
.pdp .fbars .fbar:nth-child(1) .fbar-fill{transition-delay:0ms}
.pdp .fbars .fbar:nth-child(2) .fbar-fill{transition-delay:70ms}
.pdp .fbars .fbar:nth-child(3) .fbar-fill{transition-delay:140ms}
.pdp .fbars .fbar:nth-child(4) .fbar-fill{transition-delay:210ms}
.pdp .fbars .fbar:nth-child(5) .fbar-fill{transition-delay:280ms}
.pdp .fbars .fbar:nth-child(n+6) .fbar-fill{transition-delay:350ms}
.pdp .fbar-trail{font-family:var(--serif);font-size:15px;color:var(--burgundy)}

/* ---------- scent-temperature legend ----------
   Owner redline 2026-07-18: "not exploiting color palettes to convey what the
   smell is" — before the plates render the per-accord hue, this compact line
   teaches the code: five swatches sampled verbatim from the owner-adopted
   family register (never invented hues), one per temperature/character
   cluster (cálido/fresco/amaderado/floral/oriental — see detail.html comment
   for the exact family→hex each dot borrows). Static generic key, not a
   per-product summary — it is the legend for the whole plate system below. */
.temp-legend{display:flex;flex-wrap:wrap;list-style:none;margin:0 0 16px;padding:0;
  font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--warm-grey)}
.temp-legend li{display:inline-flex;align-items:center;gap:6px;padding:0 12px 0 0}
.temp-legend li:not(:last-child){border-right:1px solid var(--line);margin-right:12px}
.temp-dot{width:7px;height:7px;border-radius:50%;flex:none;
  background:var(--hue,var(--muted));
  box-shadow:inset 0 0 0 1px rgba(26,21,24,.18)}

/* ---------- accord plates: engraved instrument-plate redesign ----------
   Owner redline 2026-07-18 (round 2): flat single-color meters didn't carry
   any scent-temperature meaning — only the 8px medallion dot had the accord's
   hue, the fill/track/marker were plain burgundy regardless of accord. Scoped
   to the PDP accords loop ONLY — seasons/day-night/Duración/Estela keep the
   original .fbar meter via _bar.html untouched, as does catalog/_spotlight.html
   and every other surface. Same data contract (a.name / a.intensity /
   a|accord_hue, same for-loop + intensity>0 guard) — see templates/catalog/
   detail.html. The template now also sets --hue on .plate itself (one read of
   accord_hue, never re-derived), so the medallion, the ground tint and the
   gauge fill/marker all draw from the SAME family color instead of three
   independently-invented ones.
   Color is desaturated toward the Intaglio register on purpose (mineral
   pigment, not neon): the fill is a soft two-stop gradient from a ~55%-white
   tint of --hue up to --hue itself, so the bar reads as "warmed/cooled ink"
   rather than a saturated swatch; the plate ground carries --hue at 3%
   opacity as a whisper, not a wash. Motion hooks into the SAME
   [data-reveal]/.is-revealed contract as .fbars (rara-motion.js
   IntersectionObserver) — transform/opacity only, reduced-motion below. */
.pdp .plates[data-reveal]{transform:none}
.plates{display:flex;flex-direction:column;gap:10px}
.plate{display:grid;grid-template-columns:11px 120px 1fr 32px;align-items:center;
  column-gap:16px;padding:19px 20px;border:1px solid rgba(74,22,34,.16);
  background:color-mix(in srgb,var(--hue,var(--muted)) 3%,var(--surface))}
.plate-medallion{width:11px;height:11px;border-radius:50%;
  background:var(--hue,var(--muted));
  box-shadow:inset 0 1px 1px rgba(0,0,0,.35),
    0 0 0 1px rgba(74,22,34,.25),
    0 0 0 3px color-mix(in srgb,var(--hue,var(--muted)) 22%,transparent)}
.plate-name{font-family:var(--serif);font-variant:small-caps;font-size:15.5px;
  letter-spacing:.03em;color:var(--ink)}
/* Herbario grabado (owner 2026-07-24): the glyph rides inside .plate-name,
   ahead of the accord's own text; --hue keeps painting ONLY the medallion +
   gauge (untouched above) -- the glyph is oxblood ink like every other
   engraved mark on the plate, never re-tinted to the accord's hue. */
.plate-name .glifo{vertical-align:-2px}
.plate-gauge{position:relative;height:16px}
.plate-ticks{position:absolute;left:0;right:0;top:4px;bottom:4px;
  background-image:repeating-linear-gradient(to right,rgba(74,22,34,.32) 0 1px,transparent 1px 10%)}
.plate-gauge::after{content:"";position:absolute;left:0;right:0;top:7px;height:1px;
  background:rgba(74,22,34,.35)}
.plate-fill{position:absolute;left:0;top:6.5px;height:3px;
  background:linear-gradient(to right,
    color-mix(in srgb,var(--hue,var(--burgundy)) 55%,white 45%),
    var(--hue,var(--burgundy)));
  transform-origin:left center;transform:scaleX(0);
  transition:transform var(--dur-slow) var(--ease-out)}
.plate-marker{position:absolute;top:1px;width:1.5px;height:13px;
  background:var(--hue,var(--burgundy));
  transform:translateX(-.75px) scaleY(0);transform-origin:center bottom;
  transition:transform var(--dur-slow) var(--ease-out)}
.plates.is-revealed .plate-fill{transform:scaleX(1)}
.plates.is-revealed .plate-marker{transform:translateX(-.75px) scaleY(1)}
.plate-value{font-family:var(--mono);font-size:12.5px;letter-spacing:.03em;
  color:var(--muted);text-align:right}
.plates .plate:nth-child(1) .plate-fill,.plates .plate:nth-child(1) .plate-marker{transition-delay:0ms}
.plates .plate:nth-child(2) .plate-fill,.plates .plate:nth-child(2) .plate-marker{transition-delay:70ms}
.plates .plate:nth-child(3) .plate-fill,.plates .plate:nth-child(3) .plate-marker{transition-delay:140ms}
.plates .plate:nth-child(4) .plate-fill,.plates .plate:nth-child(4) .plate-marker{transition-delay:210ms}
.plates .plate:nth-child(5) .plate-fill,.plates .plate:nth-child(5) .plate-marker{transition-delay:280ms}
.plates .plate:nth-child(n+6) .plate-fill,.plates .plate:nth-child(n+6) .plate-marker{transition-delay:350ms}

/* ---------- responsive ---------- */
@media(max-width:1000px){
  /* single column: drop the two-row template (rows are the 3 stacked areas) */
  .pdp{grid-template-columns:1fr;grid-template-rows:none;grid-template-areas:"media" "rail" "ficha";column-gap:0;row-gap:32px}
  .pdp-buy{position:static;z-index:auto}
  .pdp-rail .acta{margin-top:24px}
  .prov-stamp{margin:24px auto 0}
  .pdp-rail .prov-stamp + .acta{margin-top:-12px}

  /* ---------- FX-5.1: sticky mobile buy CTA ----------
     Owner ruling 2026-07-12 made the rail static (no follow-scroll), so on a
     long PDP the Add-to-cart button scrolls away with the rest of the panel and
     buying requires a scroll back to the top. Anchor just the button (shared by
     the standard buy panel and the preorder "Pagar por adelantado" CTA — same
     .buy-btn) to the viewport bottom instead of the whole panel, full-bleed,
     so the action stays in the thumb zone for the rest of the page. Motion is
     transform/opacity only (one-time entrance, killed under reduced-motion
     below); env(safe-area-inset-bottom) clears notch/home-indicator devices.
     .pdp-ficha gets matching bottom padding so the fixed bar never sits over
     the last "De la misma casa" cards or the footer. */
  .buy-btn{
    position:fixed;left:0;right:0;bottom:0;z-index:40;width:100%;
    margin:0;border-radius:0;
    padding:14px 24px calc(14px + env(safe-area-inset-bottom));
    box-shadow:0 -6px 18px rgba(0,0,0,.16);
    animation:pdpCtaIn var(--dur-mid) var(--ease-out) both;
  }
  .pdp-ficha{padding-bottom:76px}
}
@keyframes pdpCtaIn{from{opacity:0;transform:translateY(100%)}to{opacity:1;transform:translateY(0)}}
@media(max-width:600px){
  .pyr-tier--1,.pyr-tier--2,.pyr-tier--3{max-width:100%}
  .pyramid::before{display:none}
  .formato{grid-template-columns:1fr;gap:16px}
  .pdp .fbar{grid-template-columns:84px 1fr auto}
  .plate{grid-template-columns:11px 88px 1fr 28px;column-gap:12px;padding:14px 16px}
  .temp-legend li{padding:0 8px 0 0}
  .temp-legend li:not(:last-child){margin-right:8px}
}

/* ---------- wide-screen tiers (Slice · widescreen pass) ----------
   The .pdp two-column ratio (1.05fr 1fr) already scales with --wrap, and the
   gallery/rail legitimately benefit from the extra room (bigger bottle photo,
   more breathing room round the buy panel). The ONE thing that must NOT scale
   unbounded is running prose: pdp-desc / chunk-lead / the scent-story paragraphs
   had no measure cap at all, so left alone they'd run 150+ characters wide at
   3840px. Cap them to a readable ~66ch and let the freed column width become
   intentional whitespace instead. column-gap widens in step with --wrap so the
   two-column composition still reads as considered, not just "wider". Guarded
   to min-width:1920px — the <=1600px PDP is byte-for-byte unchanged. */
@media(min-width:1920px){
  .pdp{column-gap:72px}
  .pdp-desc,.chunk-lead,.pdp-story-body p{max-width:66ch}
}
@media(min-width:2560px){
  .pdp{column-gap:88px}
}

/* ---------- reduced motion: bars shown at full, no button motion ----------
   motion.css resets [data-reveal] globally but not the fill's scaleX or the buy
   states, so neutralize them here (bars would otherwise stay at scaleX(0)). */
@media(prefers-reduced-motion:reduce){
  .pdp .fbar-fill{transform:scaleX(1)!important;transition:none!important}
  .plate-fill{transform:scaleX(1)!important;transition:none!important}
  .plate-marker{transform:translateX(-.75px) scaleY(1)!important;transition:none!important}
  .buy-btn.is-loading,.buy-btn.is-error{animation:none!important}
  .buy-btn{animation:none!important}
}

/* ============================================================================
   Slice 5 · gallery reel (cover + ordered ProductImages) + coming-soon buy rail.
   Only transform/opacity animate; single accent = --burgundy; AA holds on every
   text/bg pair. prefers-reduced-motion neutralizes the slide (see bottom).
   ============================================================================ */

/* ---------- reel carousel ---------- */
.gallery[tabindex]:focus-visible{outline:2px solid var(--burgundy);outline-offset:3px}
.reel-mat{position:relative}
.reel-track{display:flex;height:100%;width:100%;
  transition:transform var(--dur-mid) var(--ease-out);will-change:transform}
.reel-img{flex:0 0 100%;width:100%;height:100%;object-fit:contain;background:#FFFFFF;display:block;user-select:none;
  transform:scale(1);transition:transform var(--dur-mid) var(--ease-out)}

/* nav arrows: over the mat on the single accent. Hidden until hover/focus on
   fine pointers; always shown on touch (no hover) and under reduced motion. */
.reel-nav{position:absolute;top:50%;transform:translateY(-50%);z-index:3;
  display:flex;align-items:center;justify-content:center;width:44px;height:44px;
  border:0;cursor:pointer;font-size:26px;line-height:1;color:var(--cream);
  background:rgba(89,28,42,.86);opacity:0;
  transition:opacity var(--dur-fast) var(--ease-out),background var(--dur-fast) var(--ease-out)}
.reel-nav:hover{background:var(--burgundy)}
.reel-nav:focus-visible{opacity:1;outline:2px solid var(--cream);outline-offset:-4px}
.gallery.has-reel:hover .reel-nav,.gallery.has-reel:focus-within .reel-nav{opacity:1}
.reel-prev{left:14px}
.reel-next{right:14px}
@media(hover:none){.reel-nav{opacity:1}}

.reel-counter{position:absolute;top:24px;right:24px;z-index:2;font-family:var(--mono);
  font-size:11px;letter-spacing:.08em;color:var(--cream);background:rgba(89,28,42,.86);padding:5px 9px}
.reel-dots{position:absolute;left:0;right:0;bottom:16px;z-index:2;
  display:flex;justify-content:center;gap:8px}
.reel-dot{width:9px;height:9px;padding:0;border:1px solid var(--cream);background:transparent;
  border-radius:50%;cursor:pointer;
  transition:background var(--dur-fast) var(--ease-out),transform var(--dur-fast) var(--ease-out)}
.reel-dot.is-on{background:var(--cream)}
.reel-dot:hover{transform:scale(1.2)}
.reel-dot:focus-visible{outline:2px solid var(--cream);outline-offset:2px}

/* ---------- FX-3.5: single-image zoom toggle (no reel chrome to reuse) ----------
   Only rendered by the reel|length==1 branch, so it never sits alongside the
   multi-image nav/dots above. Same over-the-mat single-accent chip as .reel-nav
   (hidden until hover/focus on fine pointers, always shown on touch); the mat's
   own overflow:hidden crops the scaled image so no new surface/lightbox is
   needed. transform:scale only — reduced-motion below snaps straight to
   whichever state is active. */
.zoom-btn{position:absolute;top:14px;right:14px;z-index:3;
  display:flex;align-items:center;justify-content:center;width:36px;height:36px;
  border:0;cursor:pointer;font-family:var(--mono);font-size:20px;line-height:1;
  color:var(--cream);background:rgba(89,28,42,.86);opacity:0;
  transition:opacity var(--dur-fast) var(--ease-out),background var(--dur-fast) var(--ease-out)}
.zoom-btn:hover{background:var(--burgundy)}
.zoom-btn:focus-visible{opacity:1;outline:2px solid var(--cream);outline-offset:-4px}
.gallery:hover .zoom-btn,.gallery:focus-within .zoom-btn{opacity:1}
@media(hover:none){.zoom-btn{opacity:1}}
.reel-img.is-zoomed{transform:scale(1.6)}

/* ---------- tagline (render-when-present, under the title) ---------- */
.pdp-tagline{font-family:var(--serif);font-style:italic;font-size:18px;line-height:1.35;
  color:var(--muted);margin:0 0 10px}

/* ---------- coming-soon rail: availability block + no-payment interest form ---------- */
.badge-soon{border-color:var(--burgundy);background:var(--burgundy);color:var(--cream)}
.preorder-block{margin:18px 0 8px;padding:16px 18px;background:var(--surface);
  border:1px solid var(--line-2);border-left:2px solid var(--burgundy)}
.preorder-price{font-family:var(--serif);font-size:30px;line-height:1.15;margin:8px 0 10px;color:var(--ink)}
.preorder-price .badge{vertical-align:5px;margin-left:8px}
.preorder-price-label{display:block;font-size:10px;letter-spacing:.12em;text-transform:uppercase;
  color:var(--warm-grey);margin-bottom:3px}
.preorder-note{font-size:13px;line-height:1.45;color:var(--muted);margin:8px 0}
.preorder-disclosure{font-family:var(--mono);font-size:11px;letter-spacing:.02em;color:var(--warm-grey);margin-top:-4px}
.preorder-block .buy-btn{margin-top:14px}
.preorder-title{font-family:var(--serif);font-size:19px;line-height:1.15;color:var(--ink);margin:0 0 8px}
.preorder-discount-note{font-size:12px;line-height:1.4;color:var(--burgundy);margin:2px 0 8px}
.preorder-rail{display:flex;align-items:flex-end;gap:6px}
.opt{flex:1 1 0;display:flex;flex-direction:column;align-items:center;gap:7px;background:transparent;border:0;cursor:pointer;padding:8px 2px 7px;border-radius:3px;position:relative;transition:background .14s ease}
.opt:hover{background:var(--surface)}
.opt .flac{display:flex;align-items:flex-end;height:62px}
.opt .flac svg{display:block}
.opt .flac img{max-width:38px;max-height:100%;object-fit:contain;display:block}
.opt .stroke{stroke:var(--muted)}
.opt .fillb{fill:transparent}
.opt .ml{font:600 12px/1 var(--mono);color:var(--ink);letter-spacing:.02em}
.opt .sub{font:500 9.5px/1 var(--mono);color:var(--warm-grey);letter-spacing:.08em;text-transform:uppercase}
.opt[aria-pressed="true"]{background:var(--sel,#FBF4F2)}
.opt[aria-pressed="true"] .stroke{stroke:var(--burgundy)}
.opt[aria-pressed="true"] .fillb{fill:var(--burgundy);fill-opacity:.07}
.opt[aria-pressed="true"] .ml{color:var(--burgundy)}
.opt[aria-pressed="true"]::after{content:"";position:absolute;left:14%;right:14%;bottom:0;height:2px;background:var(--burgundy);border-radius:2px}
.opt .tag{position:absolute;top:2px;right:6px;font:600 8.5px/1 var(--mono);letter-spacing:.04em;color:#fff;background:var(--burgundy);border-radius:2px;padding:2px 4px}
.opt:focus-visible{outline:2px solid var(--burgundy);outline-offset:2px}
.baseline{height:1px;background:var(--line);margin:2px 2px 0}
.preorder-pick-h{display:flex;justify-content:space-between;align-items:baseline}
.preorder-pick-hint{color:var(--warm-grey);font-weight:500;letter-spacing:.02em;text-transform:none}
@media (prefers-reduced-motion:reduce){.opt{transition:none}}

/* ---------- THE SEAL — hold-to-reserve (pointer-only enhancement) ----------
   Reserving is a held press, like pressing wax into a seal: on pointer
   devices (mouse/touch/pen) the .preorder-block buy-btn requires a ~1.8s hold
   (duration lives in detail.js sealPress()) before add() fires. An oxblood
   fill sweeps left->right under the pointer via clip-path (never width/left,
   so it stays a compositor-only transform-adjacent property); release early
   and the SAME clip-path snaps back over 200ms — no request is ever sent for
   an incomplete hold. On hold-complete a hairline frame fades+settles in
   (opacity/transform only) as the tactile "sealed" confirmation, same frame
   as the vibrate() call in JS. Keyboard/AT are untouched: detail.html's
   @click guards on event.detail===0 (true only for a keyboard/AT-generated
   click, never a pointer one) so Enter/Space still activates instantly —
   the classes below are inert for that path since sealing/sealed never
   turn true without a pointerdown. Single accent = --brand-seal(-hover)
   (== --burgundy / --burgundy-deep), no gold. */
.preorder-block .buy-btn{overflow:hidden;touch-action:none;-webkit-touch-callout:none;user-select:none}
.preorder-block .buy-btn>.buy-label-wrap,.preorder-block .buy-btn>.buy-icon{position:relative;z-index:1}
.preorder-block .buy-btn .seal-fill{position:absolute;inset:0;z-index:0;pointer-events:none;
  background:var(--brand-seal-hover);clip-path:inset(0 100% 0 0);
  transition:clip-path 200ms var(--ease-out)}
.preorder-block .buy-btn.is-sealing .seal-fill,
.preorder-block .buy-btn.is-sealed .seal-fill{clip-path:inset(0 0 0 0)}
.preorder-block .buy-btn.is-sealing .seal-fill{transition-duration:1800ms;transition-timing-function:linear}
.preorder-block .buy-btn::after{content:"";position:absolute;inset:3px;z-index:2;pointer-events:none;
  border:1px solid var(--cream);opacity:0;transform:scale(.96);
  transition:opacity 260ms var(--ease-out),transform 260ms var(--ease-out)}
.preorder-block .buy-btn.is-sealed::after{opacity:1;transform:scale(1)}
.buy-label-wrap{display:flex;flex-direction:column;align-items:flex-start;gap:2px;min-width:0}
.buy-hint{display:block;font-size:9.5px;letter-spacing:.06em;text-transform:uppercase;opacity:.78}
@media(any-pointer:none){.buy-hint{display:none}}
@media(prefers-reduced-motion:reduce){
  /* Same hold duration (JS timer, untouched) — only the visual sweep/trace is
     reduced to an instant fill/snap so the state is still legible without motion. */
  .preorder-block .buy-btn .seal-fill,
  .preorder-block .buy-btn.is-sealing .seal-fill{transition:none}
  .preorder-block .buy-btn::after{transition:none}
}
.soon-avail{margin:18px 0 6px;padding:15px 18px;background:var(--surface);
  border:1px solid var(--line-2);border-left:2px solid var(--burgundy)}
.soon-row{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 10px;padding:4px 0}
.soon-row + .soon-row{border-top:1px solid var(--line);margin-top:6px;padding-top:12px}
.soon-k{font-family:var(--mono);font-size:10px;letter-spacing:.12em;text-transform:uppercase;
  color:var(--warm-grey);flex:0 0 auto}
.soon-v{font-family:var(--serif);font-size:18px;color:var(--ink)}
.soon-note{flex-basis:100%;font-size:11px;color:var(--warm-grey);letter-spacing:.01em}
.soon-form{margin-top:22px;border-top:1px solid var(--line);padding-top:20px}
.soon-title{font-family:var(--serif);font-size:22px;line-height:1.1;margin-bottom:4px}
.soon-sub{color:var(--muted);font-size:14px;margin:0 0 16px}
.soon-field{display:flex;flex-direction:column;gap:6px;margin-bottom:14px}
.soon-lbl{font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--warm-grey)}
.soon-input{font-family:var(--sans);font-size:15px;padding:13px 14px;border:1px solid var(--line-2);
  background:var(--surface);color:var(--ink);min-height:48px}
.soon-input:focus{outline:2px solid var(--burgundy);outline-offset:-1px;border-color:var(--burgundy)}
.soon-input[aria-invalid=true]{border-color:var(--burgundy)}
.soon-err{font-size:12px;color:var(--burgundy);margin:0}
.soon-btn{width:100%;justify-content:center;margin-top:4px}
.soon-reveal-btn{width:100%;justify-content:center;min-height:44px;cursor:pointer}
.soon-reveal-btn:hover{background:var(--cream-2);border-color:var(--burgundy)}
.soon-dupe{margin:12px 0 0;font-size:13px;color:var(--muted)}
.soon-nopay{margin:12px 0 0;font-size:11px;letter-spacing:.02em;color:var(--warm-grey)}

/* Pre-order PDP: the notify option is the SUBORDINATE alternative to the paid
   "Pagar por adelantado" CTA (design ruling) — a hairline tile with an OUTLINE submit,
   never a second filled burgundy CTA (scarcity law: one filled CTA per screen). */
.soon-form.is-preorder-alt{margin-top:20px;border-top:0;border:1px solid var(--line);padding:16px 18px}
.soon-form.is-preorder-alt .soon-title{font-size:16px}
.soon-form.is-preorder-alt .soon-sub{font-size:13px;margin-bottom:12px}
.soon-form.is-preorder-alt .soon-btn{background:transparent;color:var(--burgundy);border:1px solid var(--line-2);min-height:44px}
.soon-form.is-preorder-alt .soon-btn:hover{background:var(--cream-2);border-color:var(--burgundy)}

.soon-ok{display:flex;align-items:center;gap:14px;margin-top:22px;padding:22px;
  background:var(--surface);border:1px solid var(--line-2);border-left:2px solid var(--good)}
.soon-ok-mark{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
  width:34px;height:34px;border-radius:50%;background:var(--good);color:var(--cream);font-size:18px}
.soon-ok p{margin:0;font-size:15px;color:var(--ink)}

/* honeypot: off-screen, NOT display:none (bots skip type=hidden / display:none) */
.hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}

/* ---------- scent story block (render-when-present) ---------- */
.pdp-story-body p{color:var(--muted);font-size:15px;line-height:1.7;margin:0 0 12px}   /* justify inherited from .prose-just on the container */
.pdp-story-body p:last-child{margin-bottom:0}

/* reduced motion: no slide, arrows always visible (no hover-reveal reliance) */
@media(prefers-reduced-motion:reduce){
  .reel-track{transition:none!important}
  .reel-nav{opacity:1}
  .reel-dot:hover{transform:none}
  /* FX-3.5: the zoom toggle still works (Alpine still flips .is-zoomed) but jumps
     straight to the zoomed-or-not state instead of animating the scale, and the
     button itself is always visible (no hover-reveal reliance). */
  .reel-img{transition:none!important}
  .zoom-btn{opacity:1}
}

/* ============================================================================
   LAYOUT-DENSITY prototype v1 · rail dossier — fills the desktop right-column void
   beneath the acta with the REAL verification process, framed as certificate
   furniture beside the buy decision. Hairline panel (mirrors the acta paper),
   burgundy step markers, mono eyebrow head. NO gold (retired); tokens only; AA holds.
   ============================================================================ */
.pdp-dossier{margin-top:24px;padding:18px 20px 4px;
  background:var(--surface);border:1px solid var(--line);border-radius:6px}
.pdp-dossier-h{margin:0 0 6px}
.pdp-dossier-lead{color:var(--muted);font-size:13px;line-height:1.5;margin:0 0 16px;max-width:36ch}
.pdp-dossier-steps{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:14px}
.pdp-dossier-steps li{display:flex;gap:14px;align-items:baseline;padding-top:14px;border-top:1px solid var(--line)}
.pdp-dossier-steps li:first-child{padding-top:0;border-top:0}
.pdp-dossier-n{flex:0 0 auto;font-size:12px;letter-spacing:.12em;color:var(--burgundy)}
.pdp-dossier-b h3{font-family:var(--serif);font-weight:500;font-size:16px;line-height:1.2;margin:0 0 4px;color:var(--ink)}
.pdp-dossier-b p{color:var(--muted);font-size:13px;line-height:1.5;margin:0}

/* ============================================================================
   PDP right-column dead-space fill (owner ruling) · community rating badge +
   compact "Te puede interesar" rail, both render-when-present. Single accent
   = --burgundy; no gold; reuses existing tokens/typography scale only.
   ============================================================================ */

/* ---------- community rating badge ----------
   A quiet hairline row (not a filled chip) beneath the buy box/acta — reads as a
   trust signal, not decoration. Coverage is a small subset of the catalog, so
   most PDPs render unchanged (the template gates on p.rating_count). */
.pdp-rating{display:flex;align-items:baseline;gap:8px;margin-top:20px;padding:11px 14px;
  background:var(--surface);border:1px solid var(--line);border-left:2px solid var(--burgundy)}
.pdp-rating-star{color:var(--burgundy);font-size:15px;line-height:1}
.pdp-rating-value{font-family:var(--serif);font-size:17px;color:var(--ink)}
.pdp-rating-sep{color:var(--line-2)}
.pdp-rating-count{font-size:12px;letter-spacing:.01em;color:var(--muted)}
/* Source credit under the community rating (catalog/_community_attribution.html):
   one quiet hairline, aligned to the badge's left edge — present, never loud. */
.community-attrib{margin-top:6px;font-size:10.5px;letter-spacing:.05em;color:var(--muted);opacity:.9}

/* ---------- "Te puede interesar" — scent-similarity recommendation cards ----------
   Owner directive 2026-07-13: BIGGER, proper cards with larger imagery, ranked by
   scent similarity (catalog.merchandising.related_products) — not the old thin
   thumbnail rows. A 2-up grid of compact vertical cards echoing the catalog card
   idiom (_card.html): image plate on top, mono house eyebrow, serif name, mono
   price. Collapses to one column only when the rail is very narrow. Still renders
   LAST in the right column (owner law) and keeps cover-or-fallback (NO-IMAGELESS
   LAW). */
.pdp-rail-related{margin-top:36px}
.pdp-rail-related-h{margin:0 0 18px}
.pdp-rail-related-grid{list-style:none;margin:0;padding:0;
  display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:22px 16px}
.pdp-rail-related-cell{min-width:0}
.pdp-rec-card{display:flex;flex-direction:column;text-decoration:none;color:inherit}
.pdp-rec-ph{position:relative;display:block;aspect-ratio:3/4;
  background:linear-gradient(150deg,var(--cream-2),var(--ph-1));
  border:1px solid var(--line);border-radius:4px;overflow:hidden}
/* higher specificity than the components.css `.card .ph img` fallback rule */
.pdp-rec-ph img{width:100%;height:100%;object-fit:contain;background:#FFFFFF;display:block}
.pdp-rec-soon-chip{position:absolute;top:8px;left:8px;z-index:1;
  font-family:var(--mono);font-size:9px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--cream);background:rgba(89,28,42,.92);padding:3px 7px;border-radius:2px}
.pdp-rec-house{font-family:var(--mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--warm-grey);margin:12px 0 4px}
.pdp-rec-name{font-family:var(--serif);font-size:18px;line-height:1.16;color:var(--ink);
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.pdp-rec-card:hover .pdp-rec-name{color:var(--burgundy)}
.pdp-rec-price{font-size:12.5px;color:var(--muted);margin-top:6px}
@media(max-width:380px){
  .pdp-rail-related-grid{grid-template-columns:1fr}
}

/* ---------- Carácter: the engraved instrument suite (owner directive 2026-07-18) ----------
   Replaces the retired simple bars: an engraved celestial pair (sun/moon SIZED by
   day/night share), sized season glyphs, a 24-hour tick rail (Duración) and
   scent-diffusion rings (Estela). Engraving = oxblood hairlines on white, one
   burgundy accent; the semantic season hue survives only on the underline data mark.
   Glyph size carries the data: --s is share-scaled per glyph (CSS scale = --s/100).
   Same [data-reveal]/.is-revealed contract as .fbars; transform/opacity only. */
.pdp .cel-wear[data-reveal],.pdp .cel-sky[data-reveal],.pdp .cel-seasons[data-reveal]{transform:none}
.celestial,.cel-year{display:block;width:100%;height:auto}
.cel-sky{margin:6px 0 2px}
.cel-seasons{margin-top:10px}
.cel-hair{stroke:var(--burgundy-deep);stroke-width:1.1;fill:none;opacity:.78}
.cel-faint{opacity:.38}
.cel-tint{fill:var(--burgundy);opacity:.06;stroke:none}
.cel-dot{fill:var(--burgundy-deep);stroke:none}
.cel-cres{fill:var(--burgundy);fill-opacity:.07;stroke:var(--burgundy-deep);stroke-width:1.1;opacity:.9}
.cel-hue{stroke-width:2.2;opacity:.9}
.cel-lit{stroke:var(--burgundy);stroke-width:1.4;fill:none;opacity:.92}
.cel-ring{fill:none;transform-box:fill-box;transform-origin:center}
.cel-glyph{transform-box:fill-box;transform-origin:center;transform:scale(calc(var(--s)/100))}
.cel-labels{display:grid;grid-template-columns:1fr 1fr;text-align:center;font-size:12px;letter-spacing:.08em;color:var(--ink);opacity:.82;margin-top:2px}
.cel-labels-4{grid-template-columns:repeat(4,1fr);font-size:11.5px}
.cel-row{display:grid;grid-template-columns:104px 1fr auto;column-gap:16px;align-items:center;margin:6px 0}
.cel-row .cel-k{font-size:12px;letter-spacing:.08em;color:var(--ink)}
.cel-ticks{width:100%;max-width:340px}
.cel-rings{width:100%;max-width:340px}
.cel-trail{font-size:15px;color:var(--burgundy)}

/* Raw-data reveal (owner ruling 2026-07-19, re-platformed 2026-07-20 per owner
   "el hover debe ser sobre la gráfica, no como botones de información"):
   Duración/Estela show a tick position + worded band only, never the number
   as visible text — the SVG graphic ITSELF is now the hover/focus/tap
   trigger (no (i) button chrome). Affordance without added chrome: cursor
   reads help and the lit ticks/rings deepen (150ms) on hover/focus, inviting
   the touch without any extra control. Mist tip is the same hairline/mono
   idiom + 180ms fade as before (home.css .inst-raw-tip twin); it is
   aria-hidden (decorative) because the SVG's own aria-label already carries
   the raw figure to screen readers — see detail.html for the a11y contract
   note. Both the tip fade and the stroke-deepen are off under reduced
   motion. */
.cel-ticks,.cel-rings{cursor:help}
.cel-ticks:focus-visible,.cel-rings:focus-visible{outline:2px solid var(--burgundy);outline-offset:3px}
.cel-ticks .cel-lit,.cel-ticks .cel-faint,.cel-rings .cel-lit,.cel-rings .cel-faint,.cel-rings .cel-dot{
  transition:opacity 150ms var(--ease-out),stroke 150ms var(--ease-out)}
.cel-ticks:hover .cel-lit,.cel-ticks:focus-visible .cel-lit,
.cel-rings:hover .cel-lit,.cel-rings:focus-visible .cel-lit{opacity:1;stroke:var(--burgundy-deep)}
.cel-ticks:hover .cel-faint,.cel-ticks:focus-visible .cel-faint,
.cel-rings:hover .cel-faint,.cel-rings:focus-visible .cel-faint{opacity:.62}
.cel-trail-cell{position:relative;display:flex;align-items:center;justify-content:flex-end;gap:6px}
.cel-raw-tip{position:absolute;right:0;bottom:calc(100% + 7px);z-index:4;
  background:rgba(238,240,234,.95);color:var(--ink);
  border:1px solid rgba(74,22,34,.4);border-radius:3px;
  font-size:11px;letter-spacing:.02em;white-space:nowrap;padding:3px 7px;
  opacity:0;pointer-events:none;transition:opacity 180ms var(--ease-out)}
.cel-raw-tip.is-open{opacity:1}
@media(prefers-reduced-motion:reduce){
  .cel-raw-tip{transition:none}
  .cel-ticks .cel-lit,.cel-ticks .cel-faint,.cel-rings .cel-lit,.cel-rings .cel-faint,.cel-rings .cel-dot{transition:none}
}
/* Reveal: glyphs settle from a slightly smaller engraving; ticks/rings fade in. */
.pdp .cel-sky[data-reveal] .cel-glyph,.pdp .cel-seasons[data-reveal] .cel-glyph{opacity:0;transform:scale(calc(var(--s)/100*0.72));transition:transform var(--dur-slow) var(--ease-out),opacity var(--dur-slow) var(--ease-out)}
.pdp .cel-sky.is-revealed .cel-glyph,.pdp .cel-seasons.is-revealed .cel-glyph{opacity:1;transform:scale(calc(var(--s)/100))}
.pdp .cel-sky.is-revealed g:nth-of-type(2)>.cel-glyph{transition-delay:120ms}
.pdp .cel-seasons.is-revealed g:nth-of-type(2)>.cel-glyph{transition-delay:80ms}
.pdp .cel-seasons.is-revealed g:nth-of-type(3)>.cel-glyph{transition-delay:160ms}
.pdp .cel-seasons.is-revealed g:nth-of-type(4)>.cel-glyph{transition-delay:240ms}
.pdp .cel-wear[data-reveal] svg{opacity:0;transition:opacity var(--dur-slow) var(--ease-out)}
.pdp .cel-wear.is-revealed svg{opacity:1}
/* Estela keeps its six real data rings; a short outward stagger only makes
   their existing diffusion encoding perceptible. No ring or value is added. */
.pdp .cel-wear.is-revealed .cel-ring.cel-lit{animation:cel-ring-expand 720ms var(--ease-out) both}
.pdp .cel-wear.is-revealed .cel-ring.cel-lit:nth-child(2){animation-delay:80ms}
.pdp .cel-wear.is-revealed .cel-ring.cel-lit:nth-child(3){animation-delay:160ms}
.pdp .cel-wear.is-revealed .cel-ring.cel-lit:nth-child(4){animation-delay:240ms}
.pdp .cel-wear.is-revealed .cel-ring.cel-lit:nth-child(5){animation-delay:320ms}
.pdp .cel-wear.is-revealed .cel-ring.cel-lit:nth-child(6){animation-delay:400ms}
@keyframes cel-ring-expand{from{transform:scale(.76)}to{transform:scale(1)}}
@media (prefers-reduced-motion:reduce){
  .pdp .cel-sky[data-reveal] .cel-glyph,.pdp .cel-seasons[data-reveal] .cel-glyph{opacity:1;transform:scale(calc(var(--s)/100));transition:none}
  .pdp .cel-wear[data-reveal] svg{opacity:1;transition:none}
  .pdp .cel-wear .cel-ring,
  .pdp .cel-wear.is-revealed .cel-ring.cel-lit{animation:none;transform:none}
}
@media (max-width:700px){
  .cel-row{grid-template-columns:84px 1fr auto;column-gap:12px}
  .cel-labels{font-size:11px}.cel-labels-4{font-size:10.5px}
}

/* ============================================================================
   THE STRIKE — the PDP entry ceremony (owner board #2). On load the gallery
   mat is "struck" like an engraving plate: a thin oxblood hairline traces the
   mat's border (stroke-dashoffset), a guilloché rosette mathematically unique
   to THIS product draws itself in beside it (server-generated, catalog/
   engraving.py — seeded from the product's own SKU/concentration[/lote]), and
   only once the plate is cut does the product photo settle onto it. Three
   independently-observed [data-reveal] nodes (trace / medallion / reel-track)
   reuse the SAME contract as the rest of the site (augurea-motion.js) —  the
   a-then-b-then-c choreography lives entirely in each element's own
   transition-delay below, not in JS. Motion: opacity + stroke-dashoffset
   ONLY. The frame/plate itself never fades — only the traced line, the
   rosette and the photo animate; reduced motion renders the finished struck
   plate + settled photo instantly (block at the end of this section).
   ============================================================================ */
.strike-trace[data-reveal]{position:absolute;inset:0;width:100%;height:100%;z-index:1;
  pointer-events:none;opacity:1;transform:none;transition:none}
.strike-trace rect{fill:none;stroke:var(--burgundy-deep);stroke-width:1;
  vector-effect:non-scaling-stroke;stroke-dasharray:1000;stroke-dashoffset:1000;
  transition:stroke-dashoffset 750ms var(--ease-out)}
.strike-trace.is-revealed rect{stroke-dashoffset:0}

/* The photo settles in only once the plate (hairline + rosette) is mostly cut —
   opacity only (never transform: .reel-track already owns the carousel's own
   transform for sliding between shots, via Alpine's :style binding). */
.reel-track[data-reveal]{opacity:0;
  transition:opacity 460ms var(--ease-out) 720ms,transform var(--dur-mid) var(--ease-out)}
.reel-track[data-reveal].is-revealed{opacity:1}

/* The medallion: a discreet engraved plaque beneath the mat, never a billboard.
   The plaque itself is static furniture (like the acta below it) — only its
   rosette line and caption draw in. */
.strike-medallion{display:flex;align-items:center;gap:14px;margin-top:14px}
.strike-medallion[data-reveal]{opacity:1;transform:none;transition:none}
.strike-medallion-art{flex:0 0 auto;display:block;width:96px;height:96px}
.strike-medallion-art svg{display:block;width:100%;height:100%}
.strike-medallion-art svg path{stroke-dasharray:1000;stroke-dashoffset:1000;
  transition:stroke-dashoffset 620ms var(--ease-out) 120ms}
.strike-medallion.is-revealed .strike-medallion-art svg path{stroke-dashoffset:0}
.strike-medallion-caption{margin:0;font-size:10.5px;line-height:1.55;letter-spacing:.02em;
  color:var(--warm-grey);max-width:30ch;opacity:0;transition:opacity 480ms var(--ease-out) 480ms}
.strike-medallion.is-revealed .strike-medallion-caption{opacity:1}

@media(max-width:600px){
  .strike-medallion{gap:10px}
  .strike-medallion-art{width:72px;height:72px}
}

@media(prefers-reduced-motion:reduce){
  .strike-trace rect{stroke-dashoffset:0!important;transition:none!important}
  .strike-medallion-art svg path{stroke-dashoffset:0!important;transition:none!important}
  .strike-medallion-caption{opacity:1!important;transition:none!important}
}

/* ---- THE TRACE (Notas): the pyramid's pulse — an engraved seismograph of the
   on-skin evolution. The line is server-computed DATA (catalog/trace.py): the
   real Carácter fills set the amplitude (estela) and how much of it survives
   into Fondo (duración); this block only draws what it is handed. Draw-in is
   stroke-dashoffset on the shared [data-reveal] contract (once, ease-out);
   reduced motion renders the finished line instantly. ---- */
.trace{margin:26px 0 4px;max-width:620px}
.trace-svg{display:block;width:100%;height:auto}
.trace-line{stroke:var(--burgundy);stroke-width:1.3;fill:none;opacity:.92;
  stroke-dasharray:1;stroke-dashoffset:1}
.trace.is-revealed .trace-line{stroke-dashoffset:0;
  transition:stroke-dashoffset 1400ms var(--ease-out,ease) 150ms}
.trace-current{stroke:var(--burgundy);stroke-width:4;stroke-linecap:round;
  fill:none;opacity:0;stroke-dasharray:.014 .986;stroke-dashoffset:1}
.trace.is-revealed .trace-current{animation:trace-current-pass 3.1s linear 1.55s 1 both}
@keyframes trace-current-pass{
  0%{opacity:0;stroke-dashoffset:1}
  12%,86%{opacity:.62}
  100%{opacity:0;stroke-dashoffset:0}
}
.trace-zones{display:flex;margin-top:2px;font-size:11px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--warm-grey)}
.trace-zones span{flex:1 1 0;text-align:center}
@media (prefers-reduced-motion:reduce){
  .trace-line,.trace.is-revealed .trace-line{stroke-dashoffset:0;transition:none}
  .trace-current,.trace.is-revealed .trace-current{animation:none;opacity:0}
}
