/* WYLD demo — components: pinned track, species index, tables, filters,
   bands, journal list, trust block, chips, accordion */

/* ---------- pinned horizontal ---------- */
.pin { position: relative; }
.pin__vp { position: sticky; top: 0; height: 100vh; overflow: hidden; }
.pin__track { display: flex; height: 100%; will-change: transform; }
.panel { position: relative; flex: 0 0 auto; height: 100%; }
.panel--img { width: min(74vw, 1000px); }
.panel--type { width: min(58vw, 780px); display: grid; align-content: center; padding-inline: clamp(28px, 5vw, 92px); }
.panel--data { width: min(84vw, 1180px); display: grid; grid-template-columns: 1.35fr .65fr; }
.panel__fig { position: relative; overflow: hidden; height: 100%; }
.panel__fig img { width: 100%; height: 100%; object-fit: cover; }
.panel__grad { position: absolute; inset: auto 0 0 0; height: 55%; background: linear-gradient(to top, rgba(10,11,10,.86), transparent); }
.panel__cap { position: absolute; inset: auto 0 0 0; padding: clamp(22px, 4vh, 46px) clamp(22px, 3.4vw, 54px); display: grid; gap: 14px; }
.panel__num { font-family: var(--f-display); font-size: clamp(3rem, 7vw, 7rem); line-height: .8; font-weight: 300; letter-spacing: -.02em; color: rgba(241,237,228,.34); }
.panel__side { border-left: 1px solid var(--line-d); padding: clamp(22px, 4vh, 46px) clamp(20px, 2.4vw, 38px); display: grid; align-content: end; gap: 22px; }
.pin__bar { position: absolute; inset: auto var(--gutter) clamp(20px, 3.4vh, 40px) var(--gutter); display: flex; align-items: center; gap: 20px; }
.pin__line { flex: 1; height: 1px; background: var(--line-d); position: relative; }
.pin__line i { position: absolute; inset: 0 auto 0 0; background: var(--bone); transform: scaleX(0); transform-origin: left; }
.pin__pad { display: grid; align-content: center; padding-inline: var(--gutter); }
.pin__pad--start { width: min(46vw, 620px); }
.pin__pad--end { width: clamp(40px, 12vw, 220px); }

.dl-m { display: grid; grid-template-columns: auto 1fr; column-gap: clamp(16px, 2vw, 34px); row-gap: 9px; }
.dl-m dt { color: var(--stone); }
.dl-m dd { text-align: right; }
.g-dark .dl-m dt { color: rgba(var(--bone-rgb), .5); }

/* ---------- species index ---------- */
.spx { position: relative; display: grid; grid-template-columns: 1fr 1fr; gap: 0; }
.spx__list { border-top: 1px solid var(--line); }
.spx__row {
  display: grid; grid-template-columns: auto 1fr auto; align-items: center;
  gap: clamp(14px, 2vw, 30px);
  width: 100%; text-align: left;
  padding: clamp(16px, 2.2vh, 26px) 0;
  border-bottom: 1px solid var(--line);
  color: var(--ink); transition: color .35s ease;
}
/* The home page renders each row as a link to the species page; the row's own type
   treatment is the affordance, so the inherited underline comes off. */
a.spx__row { text-decoration: none; color: inherit; }
.spx__row .d { transition: transform .5s var(--ease), opacity .35s ease; opacity: .62; }
.spx__row.is-on .d, .spx__row:hover .d { opacity: 1; transform: translateX(clamp(6px, 1vw, 18px)); }
.spx__row.is-on .spx__n, .spx__row:hover .spx__n { color: var(--ember-text); }
.spx__n { color: var(--stone); transition: color .3s ease; }
.spx__thumb { display: none; width: 56px; height: 56px; object-fit: cover; }
.spx__stage { position: relative; overflow: hidden; background: var(--paper); }
.spx__frames { position: absolute; inset: 0; }

/* The stage arrives from the right, against a list that is already rising on the
   left, so the two halves of the section converge rather than both drifting up.

   Two things move and they move together: the mask uncovers the frame right to
   left, and the photograph inside travels the same way a little slower. Mask
   leading its own content is what separates a photograph being revealed from a
   div sliding across the page — the picture is never seen in motion against a
   still edge. The caption and its glass do not travel at all: they belong to the
   frame, and the frame is the mask. */
@media (scripting: enabled) and (prefers-reduced-motion: no-preference) {
  .spx__stage.rv-wipe:not(.is-in) .spx__frames { transform: translate3d(clamp(48px, 6vw, 96px), 0, 0); }
  .spx__stage .spx__frames {
    /* Longer than the mask, so the photograph is still settling after the frame
       is fully open — the same relationship .rv-img sets up for its scale. */
    transition: transform calc(var(--mo-slow) + 260ms) var(--ease);
  }
}
.spx__stage img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0; transform: scale(1.04); transition: opacity .7s var(--ease), transform 1.2s var(--ease); }
.spx__stage img.is-on { opacity: 1; transform: none; }

/* The route out of the preview into the species' own page.
   Same control as the guest galleries' .gal__cue — a small square mark at rest that
   unfurls its label on hover — so the site has one "open this" affordance rather than
   two. Only the pill takes the pointer: the rest of the frame stays inert so moving
   across it does not steal the hover from the name list beside it. */
.spx__open { position: absolute; inset: 0; z-index: 2; pointer-events: none; }
.spx__cue {
  pointer-events: auto;
  position: absolute; top: 14px; right: 14px;
  display: inline-flex; align-items: center; gap: 0;
  height: 34px; padding: 0 10px;
  background: rgba(10,11,10,.62); color: #F1EDE4;
  transition: background-color .35s ease, gap .4s var(--ease), padding .4s var(--ease);
}
.spx__cue i { font-size: 11px; }
.spx__cue > span {
  max-width: 0; overflow: hidden; white-space: nowrap;
  transition: max-width .45s var(--ease);
}
/* Expands on hovering the frame, on hovering the pill itself, and on keyboard focus. */
.spx__stage:hover .spx__cue,
.spx__open:focus-visible .spx__cue {
  background: var(--ember); color: var(--on-ember); gap: 8px;
}
.spx__stage:hover .spx__cue > span,
.spx__open:focus-visible .spx__cue > span { max-width: 260px; }
.spx__open:focus-visible { outline: none; }
@media (pointer: coarse) {
  .spx__cue { gap: 8px; }
  .spx__cue > span { max-width: 260px; }
}
/* The same progressive blur the hero wears, brought down onto the species
   stage. Five backdrop-filter layers with offset masks bend the light out of
   the type's way instead of laying a slab of ink over the photograph, so the
   frame survives underneath the caption. Ordered coarsest-first so the finest
   blur composites last.

   The band is sized in pixels rather than a percentage of the stage: the
   stage's height tracks the species list beside it, and a percentage ramp
   would move the glass line every time a species is added. */
.spx__veil {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 1;
  height: clamp(240px, 44%, 460px); pointer-events: none;
  background: linear-gradient(to top,
    rgba(10,11,10,.62) 0%, rgba(10,11,10,.42) 34%, rgba(10,11,10,.16) 66%, rgba(10,11,10,0) 100%);
}
.spx__veil > i { position: absolute; inset: 0; display: block; pointer-events: none; }
/* One knob for the whole ramp; each layer takes a fixed share of it. Halved from
   the original 16px: the band sat over the bottom of the frame, which on a
   wildlife hero is exactly where the animal is. The tint below carries the type,
   so the glass does not have to. */
.spx__veil { --veil-blur: 9px; }
.spx__veil > i:nth-child(1) { -webkit-backdrop-filter: blur(var(--veil-blur)); backdrop-filter: blur(var(--veil-blur));
  -webkit-mask-image: linear-gradient(to top, #000 0%, #000 20%, transparent 42%);
          mask-image: linear-gradient(to top, #000 0%, #000 20%, transparent 42%); }
.spx__veil > i:nth-child(2) { -webkit-backdrop-filter: blur(calc(var(--veil-blur) * .5)); backdrop-filter: blur(calc(var(--veil-blur) * .5));
  -webkit-mask-image: linear-gradient(to top, #000 0%, #000 34%, transparent 56%);
          mask-image: linear-gradient(to top, #000 0%, #000 34%, transparent 56%); }
.spx__veil > i:nth-child(3) { -webkit-backdrop-filter: blur(calc(var(--veil-blur) * .25)); backdrop-filter: blur(calc(var(--veil-blur) * .25));
  -webkit-mask-image: linear-gradient(to top, #000 0%, #000 48%, transparent 70%);
          mask-image: linear-gradient(to top, #000 0%, #000 48%, transparent 70%); }
.spx__veil > i:nth-child(4) { -webkit-backdrop-filter: blur(calc(var(--veil-blur) * .125)); backdrop-filter: blur(calc(var(--veil-blur) * .125));
  -webkit-mask-image: linear-gradient(to top, #000 0%, #000 62%, transparent 84%);
          mask-image: linear-gradient(to top, #000 0%, #000 62%, transparent 84%); }
.spx__veil > i:nth-child(5) { -webkit-backdrop-filter: blur(calc(var(--veil-blur) * .0625)); backdrop-filter: blur(calc(var(--veil-blur) * .0625));
  -webkit-mask-image: linear-gradient(to top, #000 0%, #000 76%, transparent 100%);
          mask-image: linear-gradient(to top, #000 0%, #000 76%, transparent 100%); }
/* Where the backdrop cannot be blurred the photograph arrives at full local
   contrast, so the ink has to carry the whole ratio again. */
@supports not (backdrop-filter: blur(1px)) {
  .spx__veil { background: linear-gradient(to top,
    rgba(10,11,10,.86) 0%, rgba(10,11,10,.68) 34%, rgba(10,11,10,.28) 66%, rgba(10,11,10,0) 100%); }
}

/* The caption now rides on the veil rather than carrying its own scrim: one
   piece of glass under the type, not two stacked treatments arguing. */
.spx__meta {
  position: absolute; inset: auto 0 0 0; z-index: 2;
  padding: 0 clamp(18px, 3vh, 34px) clamp(18px, 3vh, 34px);
  color: var(--bone); display: grid; gap: 12px;
}
.spx__note { max-width: 44ch; font-size: .9375rem; line-height: 1.55; color: rgba(241,237,228,.86); }
/* Five composited backdrop-filters is a real cost on a phone GPU. Keep the
   two coarsest, as the hero does. */
@media (max-width: 734px) {
  .spx__veil > i:nth-child(3), .spx__veil > i:nth-child(4), .spx__veil > i:nth-child(5) { display: none; }
}
@media (max-width: 900px) {
  .spx { grid-template-columns: 1fr; }
  .spx__stage { display: none; }
  .spx__thumb { display: block; }
  .spx__row { grid-template-columns: auto 1fr auto; }
}

/* ---------- bands ---------- */
.band { display: grid; grid-template-columns: 1fr 1fr; align-items: center; gap: clamp(24px, 5vw, 96px); padding-block: clamp(36px, 6vh, 78px); border-bottom: 1px solid var(--line); }
.band:first-child { border-top: 1px solid var(--line); }
.band--flip > .band__fig { order: 2; }
.band__fig { position: relative; overflow: hidden; aspect-ratio: 3/2; }
.band__fig img { width: 100%; height: 100%; object-fit: cover; }
.band__txt { display: grid; gap: 18px; max-width: 46ch; }

/* ---------- layered depth ----------
   The photograph and the copy beside it travel in opposite directions across
   the row's pass through the viewport. Two rates is what reads as depth; one
   layer on its own reads as a slow background. The figure carries the larger
   share because it is the thing with weight, and the totals are deliberately
   small — 60px against 28px, so ~88px of separation. Apple's own layers move
   120-200px, but those are full-bleed backgrounds rather than a boxed figure
   sitting in a bordered grid row, where anything larger starts to collide with
   the hairline above and below. */
.band__fig[data-parallax] { --px-start: -52px; --px-end: 52px; }
.band__txt[data-parallax] { --px-start: 24px; --px-end: -24px; }
.trust__fig[data-parallax] { --px-start: -48px; --px-end: 48px; }
/* On the image inside the frame, not on the frame. .jfeat__fig sits directly above
   the entry's title with a 30px gap, and 44px of travel on the box itself put the
   photograph over the type at the end of its run — visible as the picture eating the
   headline on the way past. The figure clips, so the same drift inside it reads
   identically and can never leave the frame. Same technique as .day__bg img. */
.jfeat__fig img[data-parallax] { --px-start: -44px; --px-end: 44px; }

/* Stacked, the two halves sit one above the other rather than side by side, so
   counter-motion has nothing to work against. Halve it. */
@media (max-width: 900px) {
  .band__fig[data-parallax] { --px-start: -26px; --px-end: 26px; }
  .band__txt[data-parallax] { --px-start: 12px; --px-end: -12px; }
  .trust__fig[data-parallax] { --px-start: -24px; --px-end: 24px; }
  .jfeat__fig img[data-parallax] { --px-start: -22px; --px-end: 22px; }
}
@media (max-width: 900px) { .band, .band--flip > .band__fig { grid-template-columns: 1fr; order: 0; } }

/* ---------- table ---------- */
.tbl { font-family: var(--f-ui); }
.tbl th, .tbl td { text-align: left; vertical-align: middle; padding: 0 clamp(10px, 1.4vw, 24px); }
.tbl th:first-child, .tbl td:first-child { padding-left: 0; }
.tbl th:last-child, .tbl td:last-child { padding-right: 0; text-align: right; }
.tbl thead th { padding-bottom: 14px; border-bottom: 1px solid var(--line); color: var(--stone); font-weight: 400; white-space: nowrap; }
.tbl tbody tr { border-bottom: 1px solid var(--line); transition: background-color .3s ease, opacity .4s var(--ease), transform .4s var(--ease); }
.tbl tbody tr:hover { background: rgba(10,11,10,.035); }
.tbl tbody td { height: 74px; }
.tbl .c-exp { font-family: var(--f-display); font-size: clamp(1rem, 1.25vw, 1.25rem); letter-spacing: -.015em; line-height: 1.1; text-transform: none; }
.tbl .c-num { font-family: var(--f-mono); font-variant-numeric: tabular-nums; letter-spacing: .06em; font-size: 12px; }
.tbl tr.is-out { opacity: 0; transform: translateY(8px); }
.dot { display: inline-block; flex: 0 0 auto; width: 5px; height: 5px; border-radius: 50%; background: var(--ember); margin-right: 8px; vertical-align: middle; }
.cta-cell { display: inline-flex; align-items: center; gap: 8px; position: relative; color: var(--ember-text); }
.cta-cell i { color: var(--ember); }
.cta-cell::after { content: ''; position: absolute; left: 0; right: 0; bottom: -3px; height: 1px; background: currentColor; transform: scaleX(0); transform-origin: left; transition: transform .4s var(--ease); }
tr:hover .cta-cell::after, .cta-cell:hover::after { transform: scaleX(1); }
@media (max-width: 900px) {
  .tbl .h-sm, .tbl .c-sm { display: none; }
  .tbl tbody td { height: 64px; }
}

/* ---------- row-hover preview ---------- */
.peek {
  position: fixed; top: 0; left: 0; z-index: 640;
  width: clamp(200px, 21vw, 320px); aspect-ratio: 3/2;
  margin: 0; overflow: hidden; background: var(--ink);
  pointer-events: none;
  opacity: 0; transition: opacity .32s var(--ease);
}
.peek img { width: 100%; height: 100%; object-fit: cover; transform: scale(1.06); transition: transform .5s var(--ease); }
.peek.is-on { opacity: 1; }
.peek.is-on img { transform: none; }
@media (pointer: coarse) { .peek { display: none; } }
@media (prefers-reduced-motion: reduce) { .peek { display: none; } }

/* ---------- chips / filters ---------- */
/* d-flex flex-wrap gap-2 carries the row. */
.chip {
  --bs-btn-border-color: var(--line);
  --bs-btn-color: var(--moss);
  --bs-btn-bg: transparent;
  --bs-btn-hover-bg: transparent;
  --bs-btn-hover-border-color: rgba(var(--ink-rgb), .34);
  --bs-btn-hover-color: var(--ink);
  --bs-btn-active-bg: var(--ember);
  --bs-btn-active-border-color: var(--ember);
  --bs-btn-active-color: var(--on-ember);
  --bs-btn-padding-y: 8px;
  --bs-btn-padding-x: 13px;
  --bs-btn-font-size: inherit;
  --bs-btn-font-family: inherit;
  --bs-btn-border-radius: 0;
}

/* An active filter is a state the visitor set, so it takes the accent. An
   inactive one stays neutral — colouring both would make the accent mean
   "filter" rather than "on". */
.chip[aria-pressed="true"] {
  background-color: var(--ember) !important;
  border-color: var(--ember) !important;
  color: var(--on-ember) !important;
}

/* ---------- journal list ---------- */
.jfeat { display: grid; gap: clamp(18px, 3vh, 34px); }
.jfeat__fig { --jfeat-travel: 44px; position: relative; overflow: hidden; aspect-ratio: 16/9; }
/* Taller than the frame by the whole travel, top and bottom, plus 2px: at exactly the
   travel the image lands flush with the frame edge at the end of its run, which is one
   sub-pixel rounding away from showing a hairline of background. */
.jfeat__fig img {
  position: absolute; left: 0; top: calc((var(--jfeat-travel) + 2px) * -1);
  width: 100%; height: calc(100% + (var(--jfeat-travel) + 2px) * 2);
  object-fit: cover;
}
@media (max-width: 900px) { .jfeat__fig { --jfeat-travel: 22px; } }
.jlist { border-top: 1px solid var(--line); }
.jrow { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: clamp(18px, 3vw, 54px); padding-block: clamp(18px, 2.6vh, 30px); border-bottom: 1px solid var(--line); }
.jrow__txt { display: grid; gap: 10px; }
.jrow__t { transition: opacity .3s ease; }
.jrow:hover .jrow__t { opacity: .55; }
.jrow img { width: clamp(96px, 12vw, 168px); aspect-ratio: 4/3; object-fit: cover; }

/* ---------- trust ---------- */
.quote { display: grid; gap: 16px; align-content: start; }
.quote p { font-family: var(--f-display); font-size: clamp(1.0625rem, 1.35vw, 1.3125rem); line-height: 1.32; letter-spacing: -.01em; }
/* .quote is a grid, so the stars are a row of it and need no margin of their own.
   --ember-text rather than --ember: these are ~11px glyphs carrying meaning, which puts
   them at the 3:1 bar, and the raw accent sits at 1.82:1 on paper. */
.quote__stars { display: inline-flex; gap: 3px; font-size: 11px; color: var(--ember-text); }
.quote__star--off { opacity: .22; }
.quote footer { display: flex; flex-wrap: wrap; gap: .1em .7em; align-items: baseline; }
.furn { display: flex; flex-wrap: wrap; gap: 10px clamp(18px, 2.6vw, 44px); }
.furn a { position: relative; }
.furn a::after { content: ''; position: absolute; left: 0; right: 0; bottom: -4px; height: 1px; background: currentColor; transform: scaleX(0); transform-origin: left; transition: transform .4s var(--ease); }
.furn a:hover::after { transform: scaleX(1); }

/* Selective focus: the rows you are not pointing at fall very slightly out of
   focus, the way everything outside the plane of focus does at f/2.8. Small
   enough that it registers as attention rather than as a blur effect. */
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
  .spx__list .spx__row, .tbl tbody tr, .jlist .jrow {
    transition: filter .35s var(--ease), opacity .35s var(--ease),
                background-color .3s ease, transform .4s var(--ease);
  }
  .spx__list:hover .spx__row:not(:hover),
  .tbl tbody:hover tr:not(:hover),
  .jlist:hover .jrow:not(:hover) { filter: blur(.7px); opacity: .58; }
}

/* Illuminated fraction of the moon on a departure date. Wildlife moves
   differently on a full moon and trackers plan around it; nobody else will
   look twice. Waxing fills from the right limb, waning from the left. */
.moon {
  display: inline-block; width: 9px; height: 9px; border-radius: 50%;
  border: 1px solid currentColor; opacity: .5; margin-right: 7px;
  vertical-align: -1px; overflow: hidden; position: relative;
}
.moon::after {
  content: ''; position: absolute; top: -1px; bottom: -1px; width: calc(var(--lit, 0) * 100%);
  background: currentColor;
  left: auto; right: 0;
}
.moon--waning::after { left: 0; right: auto; }
tr:hover .moon { opacity: .9; }

/* Film frame counter on the drag galleries — a 36-exposure roll is a shared
   memory for anyone who shot one. */
.gal-count { font-variant-numeric: tabular-nums; }

.ph {
  display: inline-block; vertical-align: super;
  font-family: var(--f-mono); font-size: 9px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--stone); margin-left: .5em;
}

/* ---------- palette switcher (DEMO TOOL) ---------- */
/* The container is a fixed anchor sized by the toggle alone: the list is taken
   out of flow so it cannot push the button away from the corner, and nothing
   invisible is ever a hit target. */
.pbar {
  position: fixed; left: clamp(14px, 2vw, 26px); bottom: clamp(14px, 2vw, 26px);
  z-index: 600; display: grid; justify-items: start;
  pointer-events: none;
}
.pbar__toggle {
  display: inline-flex; align-items: center; gap: 9px;
  background: var(--bone); color: var(--ink);
  border: 1px solid var(--line); padding: 10px 13px;
  pointer-events: auto;
}
.pbar__dot { width: 8px; height: 8px; background: var(--ember); display: block; }
.pbar__list {
  position: absolute; bottom: calc(100% + 8px); left: 0;
  display: grid; gap: 1px; background: var(--bone); border: 1px solid var(--line);
  padding: 10px; min-width: 176px;
  opacity: 0; transform: translateY(8px); pointer-events: none;
  transition: opacity .3s var(--ease), transform .3s var(--ease);
}
.pbar.is-open .pbar__list { opacity: 1; transform: none; pointer-events: auto; }
.pbar__note { color: var(--stone); padding: 2px 6px 8px; }
.pbar__opt {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 8px 6px; color: var(--moss); text-align: left;
  transition: color .25s ease, background-color .25s ease;
}
.pbar__opt:hover { color: var(--ink); background: rgba(var(--ink-rgb), .04); }
.pbar__opt[aria-pressed="true"] { color: var(--ink); }
.pbar__opt[aria-pressed="true"] .pbar__sw { outline: 1px solid var(--ink); outline-offset: 2px; }
.pbar__sw { display: flex; flex: 0 0 auto; }
.pbar__sw i { display: block; width: 11px; height: 11px; }

@media (max-width: 640px) {
  .pbar { left: 10px; bottom: 10px; }
  .pbar__toggle span:not(.pbar__dot) { display: none; }
  .pbar__toggle { padding: 11px; }
}
/* ---------- reduced motion / no-JS degradation ---------- */
@media (prefers-reduced-motion: reduce) {
  .pin { height: auto !important; }
  .pin__vp { position: static; height: auto; }
  .pin__track { transform: none !important; overflow-x: auto; scrollbar-width: none; }
  .pin__track::-webkit-scrollbar { display: none; }
  .panel { height: min(78vh, 720px); }
  .pin__bar { display: none; }
  .pin__pad { display: none; }
}
html:not(.js) .pin { height: auto !important; }
html:not(.js) .pin__vp { position: static; height: auto; }
html:not(.js) .pin__track { flex-direction: column; }
html:not(.js) .panel { width: 100%; height: auto; min-height: 60vh; }
html:not(.js) .pin__bar, html:not(.js) .pin__pad, html:not(.js) .pre, html:not(.js) .cur, html:not(.js) .cur-ring { display: none; }
html:not(.js) .spx__stage { display: none; }
html:not(.js) .spx { grid-template-columns: 1fr; }
/* Without JS the strip has no image to show and nothing to advance. */
html:not(.js) .hero__peek, html:not(.js) .hero__nav { display: none; }
html:not(.js) .hero__in { padding-right: var(--gutter); }

/* ---------- rail step buttons ----------
   Square, hairline, no fill until hover. The accent arrives on interaction
   rather than sitting there, so a row of them does not read as decoration. */
/* d-flex justify-content-end gap-2 carries the row. */
.rail-nav { padding-top: 14px; }
.rail-btn {
  --bs-btn-border-color: var(--line);
  --bs-btn-color: var(--ink);
  --bs-btn-bg: transparent;
  --bs-btn-hover-bg: transparent;
  --bs-btn-hover-border-color: var(--ember);
  /* Same correction as .cform__submit: the raw accent is a ground, and these arrows sit
     on paper. It is a glyph rather than text, so the bar is 3:1 — the raw accent misses
     that at 1.82:1 too. */
  --bs-btn-hover-color: var(--ember-text);
  --bs-btn-disabled-bg: transparent;
  --bs-btn-disabled-border-color: var(--line);
  --bs-btn-disabled-color: var(--ink);
  --bs-btn-padding-x: 0; --bs-btn-padding-y: 0;
  --bs-btn-border-radius: 0;
  width: 38px; height: 38px; display: grid; place-items: center;
}
.g-dark .rail-btn, .g-dark-2 .rail-btn { border-color: var(--line-d); color: var(--bone); }

.rail-btn:disabled { opacity: .3; cursor: default; }
.rail-btn i { display: block; font-size: 13px; transition: transform .3s var(--ease); }
.rail-btn:hover:not(:disabled)[data-dir="1"] i { transform: translateX(2px); }
.rail-btn:hover:not(:disabled)[data-dir="-1"] i { transform: translateX(-2px); }
@media (pointer: coarse) { .rail-btn { width: 44px; height: 44px; } }

/* ---------- restrained motion additions ----------
   Four only, all transform and opacity, all on elements that had none. The
   site already carries a full reveal system; anything more than this competes
   with the photography rather than supporting it. */

/* 1. Table rows arrive in sequence rather than as a block. The stagger is
      capped so a long calendar does not become a slow reveal. */
@media (scripting: enabled) and (prefers-reduced-motion: no-preference) {
  .tbl tbody tr.rv-row:not(.is-in),
  .cmp__t tbody tr.rv-row:not(.is-in) { opacity: 0; transform: translateY(10px); }
  /* Later rows take fractionally longer as well as starting later, so the tail
     of a long calendar decelerates instead of machine-gunning. 14islands drive
     their case-study list the same way, and it is the whole difference between
     a list that is animated and a list that reads as considered. */
  .tbl tbody tr.rv-row,
  .cmp__t tbody tr.rv-row {
    transition: opacity calc(var(--mo-base) * .8 + var(--r, 0) * 12ms) var(--ease),
                transform calc(var(--mo-base) * .8 + var(--r, 0) * 12ms) var(--ease);
    transition-delay: calc(var(--r, 0) * 45ms);
  }
}

/* 2. A hairline draws itself in under a section heading instead of appearing.
      One line, once, as the section arrives. */
.head { position: relative; }
.head__rule {
  position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: var(--line); transform-origin: left;
}
@media (scripting: enabled) and (prefers-reduced-motion: no-preference) {
  .head__rule:not(.is-in) { transform: scaleX(0); }
  .head__rule { transition: transform var(--mo-slow) var(--ease) .1s; }
}
/* A head immediately followed by a tab strip drops its rule: the strip has a
   border of its own, and the two together read as a doubled line. */
.head:has(+ .rtab) .head__rule { display: none; }

/* 3. Numerals in the section eyebrows count into place. Small vertical travel
      only — the number itself does not animate, which would be a gimmick. */
.eyebrow__n { display: inline-block; }
@media (scripting: enabled) and (prefers-reduced-motion: no-preference) {
  .eyebrow__n:not(.is-in) { opacity: 0; transform: translateY(6px); }
  .eyebrow__n { transition: opacity var(--mo-base) var(--ease), transform var(--mo-base) var(--ease); }
}

/* 4. Cards lift a little on hover — two pixels, no shadow, no scale. Enough to
      register as interactive on a page with no card borders. */
@media (pointer: fine) and (prefers-reduced-motion: no-preference) {
  .rrail__i, .vgal__i, .jrow, .gal__i {
    transition: transform .45s var(--ease);
  }
  .rrail__i:hover, .vgal__i:hover, .gal__i:hover { transform: translateY(-2px); }
  .jrow:hover { transform: translateX(3px); }
}

/* No reduced-motion overrides needed: the three reveals above only ever hide
   anything inside a `no-preference` guard, so the finished state IS the
   default state. See the note in tokens.css. */

.tbl tbody tr.rv-row.is-instant, .cmp__t tbody tr.rv-row.is-instant,
.head__rule.is-instant, .eyebrow__n.is-instant {
  transition: none !important;
}

/* ---------- caption on glass ----------
   One frame, its own caption, sitting on the photograph rather than parked
   under it. Used by the home page's region rail and by the destination and
   expedition cards on the index pages — three consumers, one implementation,
   because three copies of a five-layer backdrop-filter stack is three places
   for it to drift.

   Markup contract:

     <a class="… gcard">                 the hover/focus host
       <figure>
         <img>
         <span class="veil"><i>×5</i></span>
         <figcaption class="gcap">
           <p class="m gcap__eyebrow">…</p>      optional, above the name
           <p class="d d--3 gcap__name"><span>Name</span><i class="… gcap__arw"></i></p>
           <p class="m gcap__meta">…</p>         optional, below the name
         </figcaption>
       </figure>
     </a>

   The figure must be position:relative and overflow:hidden; the consumer owns
   its aspect ratio. */

/* The hero's progressive blur, shrunk to a card. Five backdrop-filter layers
   with offset masks bend the light out of the type's way; a flat gradient of
   ink over the frame would flatten the photograph, and the photography is the
   only asset here that cannot be bought. Coarsest-first so the finest blur
   composites last. The tint underneath finishes the contrast ratio and, where
   backdrop-filter is unsupported, supplies all of it. */
.veil {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 1;
  height: 58%; pointer-events: none; display: block;
  background: linear-gradient(to top,
    rgba(10,11,10,.66) 0%, rgba(10,11,10,.44) 36%, rgba(10,11,10,.16) 68%, rgba(10,11,10,0) 100%);
}
/* The hover lift is a second gradient fading in over the first, not a flat
   background-color on the veil: a solid colour would paint to the veil's top
   edge and draw a hard line across the photograph where the glass begins. */
.veil::after {
  content: ''; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: linear-gradient(to top,
    rgba(10,11,10,.22) 0%, rgba(10,11,10,.13) 45%, rgba(10,11,10,0) 100%);
  opacity: 0; transition: opacity .5s var(--ease);
}
/* Blur strength is one number, --veil-blur, and each layer takes a fixed share
   of it, so the ramp keeps its shape at any strength and the hover step is a
   single-property change rather than five.

   It is deliberately light at rest. The blur is NOT what makes the caption
   legible here — the tint under it already carries the type at 5.6:1 over the
   worst case, a blown-out sky — so the glass is free to be a suggestion rather
   than a wall. At the old 14px the band ate the bottom of the frame: a leopard
   on the rocks in the Jawai card, the elephant in Kabini, both smeared to
   nothing. That is the exact failure NAMING.md warns about under "captions sit
   on the bottom third", and it is the one thing a photography site cannot
   afford. Pointing at the card raises it — the picture is still readable, but
   the glass firms up under the hand that is about to click it. */
.veil { --veil-blur: 7px; }
.gcard:hover .veil,
.gcard:focus-visible .veil { --veil-blur: 12px; }

.veil > i {
  position: absolute; inset: 0; display: block; pointer-events: none;
  transition: -webkit-backdrop-filter .5s var(--ease), backdrop-filter .5s var(--ease);
}
.veil > i:nth-child(1) {
  -webkit-backdrop-filter: blur(var(--veil-blur));
          backdrop-filter: blur(var(--veil-blur));
  -webkit-mask-image: linear-gradient(to top, #000 0%, #000 22%, transparent 44%);
          mask-image: linear-gradient(to top, #000 0%, #000 22%, transparent 44%); }
.veil > i:nth-child(2) {
  -webkit-backdrop-filter: blur(calc(var(--veil-blur) * .5));
          backdrop-filter: blur(calc(var(--veil-blur) * .5));
  -webkit-mask-image: linear-gradient(to top, #000 0%, #000 36%, transparent 58%);
          mask-image: linear-gradient(to top, #000 0%, #000 36%, transparent 58%); }
.veil > i:nth-child(3) {
  -webkit-backdrop-filter: blur(calc(var(--veil-blur) * .29));
          backdrop-filter: blur(calc(var(--veil-blur) * .29));
  -webkit-mask-image: linear-gradient(to top, #000 0%, #000 50%, transparent 72%);
          mask-image: linear-gradient(to top, #000 0%, #000 50%, transparent 72%); }
.veil > i:nth-child(4) {
  -webkit-backdrop-filter: blur(calc(var(--veil-blur) * .14));
          backdrop-filter: blur(calc(var(--veil-blur) * .14));
  -webkit-mask-image: linear-gradient(to top, #000 0%, #000 64%, transparent 86%);
          mask-image: linear-gradient(to top, #000 0%, #000 64%, transparent 86%); }
.veil > i:nth-child(5) {
  -webkit-backdrop-filter: blur(calc(var(--veil-blur) * .07));
          backdrop-filter: blur(calc(var(--veil-blur) * .07));
  -webkit-mask-image: linear-gradient(to top, #000 0%, #000 78%, transparent 100%);
          mask-image: linear-gradient(to top, #000 0%, #000 78%, transparent 100%); }
@media (prefers-reduced-motion: reduce) {
  .veil > i { transition: none; }
}
@supports not (backdrop-filter: blur(1px)) {
  .veil { background: linear-gradient(to top,
    rgba(10,11,10,.88) 0%, rgba(10,11,10,.70) 36%, rgba(10,11,10,.30) 68%, rgba(10,11,10,0) 100%); }
}
/* Two of five on a phone: the cards are small there and the composite cost is not. */
@media (max-width: 734px) {
  .veil > i:nth-child(3), .veil > i:nth-child(4), .veil > i:nth-child(5) { display: none; }
}
/* A taller caption needs more glass under it. Set --veil-h on the consumer. */
.veil { height: var(--veil-h, 58%); }

.gcap {
  position: absolute; inset: auto 0 0 0; z-index: 2;
  padding: 0 clamp(14px, 1.6vw, 22px) clamp(15px, 1.8vw, 22px);
  color: var(--bone); display: grid; gap: 7px;
  transition: transform .55s var(--ease);
}
.gcap__eyebrow { color: rgba(var(--bone-rgb), .72); }
/* Block, not flex: an expedition name is "South Luangwa Elephant & Light", not
   "Jawai", and a flex row with width:max-content pushed it straight out of the card
   and across its neighbour. The arrow is an inline child of the last line. */
.gcap__name { display: block; text-wrap: pretty; }
/* The rule is drawn on the name itself and wipes in from the leading edge, so
   the card answers a hover the way the site's text links already do. Painted as a
   background rather than an absolutely-positioned ::after, because the name wraps:
   box-decoration-break clones the gradient onto every line, which an ::after
   pinned to the span's border box cannot do. */
.gcap__name > span {
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: 0 calc(100% - .04em);
  background-size: 0 1px;
  padding-bottom: .06em;
  -webkit-box-decoration-break: clone;
          box-decoration-break: clone;
  transition: background-size .55s var(--ease);
}
.gcap__arw {
  font-size: .42em; opacity: 0; margin-left: 0; transform: translateX(-6px);
  white-space: nowrap;
  transition: opacity .4s ease, margin-left .5s var(--ease), transform .5s var(--ease);
}
.gcap__meta {
  color: rgba(var(--bone-rgb), .74);
  transition: color .45s ease, transform .55s var(--ease);
}
.gcap .ph { color: rgba(var(--bone-rgb), .5); }
/* The qualifier that rides the name — a state, a country — is a caption on it,
   not a second line under it. */
.gcap__qual { font-family: var(--f-mono); letter-spacing: .14em; text-transform: uppercase;
  font-size: .42em; margin-left: .7em; color: rgba(var(--bone-rgb), .78);
  /* It wraps as one unit or not at all: "MADHYA / PRADESH" broken across two lines
     reads as a second name rather than as a qualifier on the first. */
  white-space: nowrap; }

/* No photograph, no glass. A caption-on-glass card with an empty figure is
   bone-coloured type on an empty box, so the few records that have no frame chosen
   yet fall back to the caption-on-paper layout the list view uses. */
.gcard--flat .veil { display: none; }
.gcard--flat .gcap {
  position: static; padding: 0; color: inherit; transform: none;
}
.gcard--flat .gcap__eyebrow, .gcard--flat .gcap__qual { color: var(--stone); }
.gcard--flat .gcap__meta { color: var(--moss); }
.gcard--flat .gcap__arw { display: none; }

.gcard:hover .gcap,
.gcard:focus-visible .gcap { transform: translateY(-5px); }
.gcard:hover .gcap__name > span,
.gcard:focus-visible .gcap__name > span { background-size: 100% 1px; }
.gcard:hover .gcap__arw,
.gcard:focus-visible .gcap__arw { opacity: 1; margin-left: .5em; transform: none; }
.gcard:hover .gcap__meta,
.gcard:focus-visible .gcap__meta { color: rgba(var(--bone-rgb), .95); }
.gcard:hover .veil::after,
.gcard:focus-visible .veil::after { opacity: 1; }
.gcard:focus-visible { outline: 2px solid var(--ember); outline-offset: 3px; }

/* A card being dragged is not being hovered — the rail's press-and-scroll would
   otherwise leave the caption lifted under the finger. */
.is-dragging .gcap { transform: none; }
@media (prefers-reduced-motion: reduce) {
  .gcap, .gcap__name > span, .gcap__arw, .gcap__meta { transition: none; }
  .gcard:hover .gcap, .gcard:focus-visible .gcap { transform: none; }
}
