/* ============================================================
   HOME PAGE — the only additions to the component set stylesheet.
   Two classes, both required by the spec and both absent from the
   set because the set is a library rather than a page:
     .skip          §53a "skip-to-content first in the DOM"
     .pending-inline  the [BRACKET] convention from the copy doc,
                      drawn in the one sanctioned off-palette pair
                      (--error-text) so a placeholder cannot reach
                      production unnoticed. §Media / §Numbers Law.
   No new colours, radii, sizes or durations are introduced.
   ============================================================ */

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 1000;
  background: var(--navy);
  color: var(--white);
  /* 19px to match the button rung, per §55b: at 700 weight this is large text
     under WCAG and the White-on-Navy label is judged at the 3:1 floor. */
  font: 700 19px/1 var(--font-body);
  padding: 16px 24px;
  border-radius: var(--radius-tile);
}
/* Solid Sky, not Sky at 60%, per §55c: the 60% ring blended to 1.9:1 on White
   and 2.4:1 on Navy, both under the 3:1 floor WCAG 1.4.11 sets for a focus
   indicator. The §54a floor covers this element anyway; the offset is the only
   thing this rule now overrides. */
.skip:focus {
  left: 16px;
  top: 16px;
  outline: 3px solid var(--sky);
  outline-offset: 2px;
}

/* A build-time placeholder. If one of these is still on the page, the page
   does not go live. Same rule as the component set's .nb--pending. */
.pending-inline {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: inherit;   /* a placeholder stands in at the size of the text it replaces,
                           so a bracketed figure inside an H3 sets at H3 size rather
                           than dropping to 14px and looking like a rendering fault */
  color: var(--error-text);
}
[data-family="dark"] .pending-inline { color: var(--amber-tint); }

/* ============================================================
   THE PHOTOGRAPH GROUND IS NOT NAVY — measured, 15 September
   ------------------------------------------------------------
   §55d raised the overlay to 0.78 and checked --mist-text, which
   passes: 4.91 in the hero and 4.96 in the bleed band, measured off
   rendered pixels against the brightest frame behind the text.

   It did not check --slate-on-dark, and that is where the gap is.
   §8 sends captions and eyebrows to --slate-on-dark "on Navy or
   Deep", and §Buttons calls a photograph "a dark ground by
   definition", so the caption rule gets applied to photographs by
   inheritance. Over a bright frame under the 0.78 overlay it
   measures 2.82 in the hero, 3.08 on the scroll cue and 2.76 on the
   bleed eyebrow, all at 12px against a 4.5 floor.

   A photograph is a fifth ground, not Navy. On it, captions and
   eyebrows take --mist-text, which is the reading colour that was
   actually measured. No new token, and it is the same move §55a
   made on the Estimate chip: take the failing colour off the text.
   ============================================================ */
.hero-full .hero-scroll,
.db-bleed .overlay .label,
.db-bleed .overlay .small {
  color: var(--mist-text);
}

/* The hero's media placeholder label sits on the bottom edge, where it lands on
   top of the Scroll cue. It clears it here. Both disappear together the day the
   real photograph arrives; the label is build-time furniture, not design. */
.hero-full .media .ph {
  padding-bottom: 64px;
}
@media (max-width: 767px) {
  .hero-full .media .ph { padding-bottom: 80px; }
}

/* ============================================================
   REAL MEDIA — the geometry the component set gives its .ph
   placeholders, handed to real <img> and <video> instead.
   Every rule below mirrors an existing .ph rule one for one. No
   new radii, ratios or durations. §27 ratios, §29 treatment.
   ============================================================ */

.db-img {
  display: block;
  width: 100%;
  height: auto;
  object-fit: cover;
  border-radius: var(--radius-card);   /* §29: photographs, no border */
}

/* Card strip, mirrors .zone-strip .ph */
.zone-strip .db-img {
  aspect-ratio: 20 / 7;
  height: 100%;
  border-radius: var(--radius-tile);
}
.zone-strip picture { display: block; }

/* Lift row photograph, mirrors .lift-shot .ph */
.lift-shot .db-img { aspect-ratio: 3 / 2; }
.lift-shot picture { display: block; }

/* Prose band, mirrors .pb-band .ph */
.pb-band picture { display: block; }
.pb-band .db-img { aspect-ratio: 16 / 9; max-height: 400px; }

/* Full-bleed band, mirrors .db-bleed .ph — the only unrounded element
   on the site, because a rounded full-bleed image has no edge to round
   against (§Layout Exceptions). */
.db-bleed picture { display: block; }
.db-bleed .db-bleed-img {
  aspect-ratio: 16 / 9;
  border-radius: 0;
}
@media (max-width: 767px) {
  .db-bleed .db-bleed-img { aspect-ratio: 4 / 3; }
}

/* ---------- Hero video (§05a) ----------
   The poster is painted first so the hero never opens empty. Under
   prefers-reduced-motion the video is swapped for the still, which is
   the same frame, so nothing moves and nothing is missing. */
.hero-full .media .hero-video,
.hero-full .media .hero-still {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 0;
  border: 0;
  display: block;
}
.hero-full .media .hero-still { display: none; }

@media (prefers-reduced-motion: reduce) {
  .hero-full .media .hero-video { display: none; }
  .hero-full .media .hero-still { display: block; }
}


/* ============================================================
   HERO — three changes on Vlad's instruction, 15 September
   ============================================================ */

/* 1. The descriptor line is removed from the hero. §Heading map calls it
      mandatory there and names it the name-collision control; it survives in
      the footer, which is where the legal and coverage information sits.
      Flagged in the README rather than argued here.

   2. The scroll cue moves from the container's left edge to its right edge.
      Same --db-inset, so it lands on the same margin every other section
      respects, just on the other side. */
.hero-scroll--right {
  left: auto;
  right: var(--db-inset);
}

/* 3. The overlay becomes a gradient, strong on the left, weaker on the right.
      ------------------------------------------------------------------
      THIS CONTRADICTS §29, which says: one overlay, rgba(14,42,68,0.78)
      flat, "No gradient scrims." It is here on instruction, so it is written
      as a knowing departure rather than smuggled in.

      Two things keep it honest. The stop values are built from the same Navy
      and never introduce a colour. And the left half, where every piece of
      text sits, is held at or ABOVE the 0.78 the flat overlay used, so nothing
      §55d measured gets weaker: 0.88 at the left edge, still 0.78 at 55%,
      falling to 0.42 only across the right half, which carries no text at any
      breakpoint. Measured after the change and reported in the README.

      At mobile the text column is full width, so the gradient flattens to the
      spec's own 0.78 rather than letting the right-hand copy sit on 0.42. */
.hero-full .scrim {
  background:
    /* c. the warm splash, on instruction. Navy stays the base; this is one soft
       radial in the top right, which is the corner the falloff already leaves
       weakest and the one furthest from both the text column and the Primary
       button in the bottom left. It reads as late sun across the roofs rather
       than as a second brand colour, which is the whole point of keeping it
       small and translucent — three stops to zero, peak alpha 0.34, and it stops
       well above the scroll cue. Deliberately NOT placed near the button: the
       Orange Law's job is to keep the Primary the only saturated orange thing on
       screen, and a glow behind it would be exactly the competition that rule
       exists to prevent. Flagged in full in the README. */
    radial-gradient(80% 62% at 86% 14%,
      rgba(232, 105, 42, 0.34) 0%,
      rgba(232, 105, 42, 0.14) 42%,
      rgba(232, 105, 42, 0) 74%),
    /* b. a bottom band, so the scroll cue in the weak corner is still legible */
    linear-gradient(to top,
      rgba(14, 42, 68, 0.62) 0,
      rgba(14, 42, 68, 0) 160px),
    /* a. the left-strong falloff Vlad asked for */
    linear-gradient(to right,
      rgba(14, 42, 68, 0.88) 0%,
      rgba(14, 42, 68, 0.84) 30%,
      rgba(14, 42, 68, 0.78) 58%,
      rgba(14, 42, 68, 0.60) 80%,
      rgba(14, 42, 68, 0.42) 100%);
}
/*    Why two layers rather than one. Measured against every frame of the clip at
      4fps: it contains pure-white specular pixels (car roofs, windows). Under the
      horizontal gradient alone the bottom-right corner sits at 0.42, where the
      scroll cue measures 1.67:1 — a real failure, not a close call. The bottom
      band composites with the horizontal one to 1-(1-0.42)(1-0.62) = 0.78 in that
      corner, which is exactly the flat value §29 specified, so the cue is held to
      the same floor everywhere the flat overlay held it. The band is 160px tall
      and starts well below the lead, so it changes nothing the eye reads as the
      gradient. */

@media (max-width: 1023px) {
  .hero-full .scrim { background: rgba(14, 42, 68, 0.78); }
}

/* The coverage line under the trust bar. The four cells are centred, so this
   sits with them rather than on the container's left edge; it is a caption to
   the strip, not a section header, so §33 does not apply. */
.trust-coverage {
  margin-top: 32px;
  text-align: center;
  max-width: 68ch;
  margin-inline: auto;
}

/* ============================================================
   SECONDARY BUTTON, LIGHT FAMILY — hover label goes White
   Vlad's instruction, 15 September
   ------------------------------------------------------------
   The set hovers a light Secondary to --orange-hover #F0803F with
   a Navy label at 5.49:1. White on that fill measures 2.67:1,
   under the 3:1 floor the 19px bold label is judged at, so the
   label alone could not change.

   The fill moves to --orange #E8692A, where White measures 3.24:1
   and passes. That is the same pair as the Primary's rest label,
   which the client adopted on 15 September and §55b closed, so it
   introduces no new value and no new exception.

   THE COST, and it needs a decision. The set chose --orange-hover
   over flat --orange precisely so "a flat-orange hover would not be
   pixel-identical to the Primary at rest". With a White label on a
   flat Orange fill, a hovered Secondary is now exactly a resting
   Primary: same fill, same label colour, and the 1px Orange border
   disappears into its own fill. One 1px change fixes it if you want
   it — a Navy border on hover gives the pill a rim the Primary does
   not have — but that is a state the spec does not define, so it is
   not applied here.
   ============================================================ */
[data-family="light"] .db-btn--secondary {
  --fill-hover: var(--orange);
  --label-hover: var(--white);
}

/* ============================================================
   S6 WHERE WE WORK — one photograph, text and pills laid over it
   Vlad's instruction, 15 September. Back to the V1 layout.
   ============================================================ */

/* §Layout Exceptions allows exactly one element that breaks the container. This
   band and the map below it are now two, where the spec permits one. That is a
   real deviation and it is flagged in the README rather than argued away.

   Height is content-driven, which is the instruction: standard section padding
   around whatever the column holds, and a floor so it cannot read as a strip.
   No aspect-ratio, so the band grows if the copy does. */
.db-band {
  position: relative;
  width: 100%;
  min-height: 520px;
  display: flex;
  align-items: center;
  isolation: isolate;
}
.db-band > picture { position: absolute; inset: 0; display: block; }
.db-band-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 78%;   /* pushed down on instruction: less sky, more village */
  display: block;
  border-radius: 0;           /* a full-bleed element has no edge to round */
}

/* Flat, no gradient scrim — §29's shape. The value is 0.80 rather than the
   stated 0.78, and that two-point change is doing one specific job: it is what
   takes the orange highlight below from 2.98:1 to a real pass against the 3:1
   large-text floor. §55d has already moved this number once (0.62 to 0.78), so
   it is a value the spec revises rather than a fixed one. Visually it is
   imperceptible; 12 percentage points of it would not be, which is why the
   highlight is not solved by darkening the picture instead. */
.db-band-scrim { position: absolute; inset: 0; background: rgba(14, 42, 68, 0.80); }

/* THE ORANGE HIGHLIGHT ON A PHOTOGRAPH, on instruction, and it needs a decision.

   Measured against the brightest pixel actually behind the highlight, at 0.78:
   --amber 2.24, --orange 2.46, --orange-hover 2.98, all under the 3:1 floor a
   44px heading is judged at. Only White (7.96) and Mist (7.39) passed, which is
   why the highlight was removed from this band in the first place.

   --orange-hover is the lightest warm in the palette and the only one that gets
   within reach; with the scrim at 0.80 it clears 3:1 on measurement. No new
   colour was invented to do it.

   What it costs: §Amber Law owns the highlight slot and says the highlight is
   Amber, precisely so the page can be warm without spending orange. This is not
   a breach of the Orange Law — that law fences saturated #E8692A, and this is
   #F0803F — but it does put a non-Amber warm in an Amber slot. The clean fix at
   spec level is a fifth ground in §Amber Law with a light amber defined for
   photographs. That adds a colour, so it is yours to approve rather than mine
   to add. */
.db-band .hl { color: var(--orange-hover); }

.db-band-inner { position: relative; width: 100%; padding-block: var(--pad-standard); }

/* Half the content width, left-aligned on the container like the V1 band. */
.db-band-col { width: calc(var(--db-content) * 0.5); }
.db-band-col > * + * { margin-top: 32px; }

/* The pills and the note sit OUTSIDE the half-width column, on the full 1400
   container, on instruction. Six across at wide, which is the only width where
   six 19px labels fit a row; §14's card-row logic below that — 3-up, then 2-up. */
.area-pills {
  /* NOT .area-list. The set's chip rules (`.area-list a`) outrank .db-btn on
     specificity and were overriding the button's height, padding, type and
     border on these pills; dropping the class is what stops that. The three
     list resets it also provided are restated here. */
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 16px;
  margin-top: 48px;
}
.area-pills > li { display: block; min-width: 0; }
.db-pill { width: 100%; padding-inline: 16px; }

/* Hover: the button's own motion, orange fill, White label. On instruction.
   This is the button component doing what it already does, not §42's motion
   borrowed by a chip: the pills ARE .db-btn--secondary now. White on --orange
   at 19px/700 is large text, 3.24:1 against a 3:1 floor. The set's dark-family
   Secondary hovers to a --mist fill with a Navy label, which is what these two
   lines override. */
[data-family="dark"] .db-pill {
  --fill-hover: var(--orange);
  --label-hover: var(--white);
}

.db-band-note { margin-top: 32px; }

@media (max-width: 1279px) { .area-pills { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width:  767px) { .area-pills { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; } }

/* §8 as measured, not as written. The spec sends captions and eyebrows to
   --slate-on-dark "on Navy or Deep"; over a photograph under the 0.78 overlay
   that measured 2.82 / 3.08 / 2.76 against a 4.5 floor. --mist-text measures
   4.90 to 5.46 on the same ground. */
.db-band .label,
.db-band .small { color: var(--mist-text); }

/* The .plain (dashed, non-interactive) pill is gone from this band: a uniform
   button hover was the instruction, so all six towns are now Secondary buttons
   pointing at their future service-area pages, the same href="#" every other
   unbuilt link on this page uses. What that loses is the set's own marker for
   "named, but no page yet", which was the dashed border. Worth putting back as
   soon as the four pages exist, or sooner if the distinction matters. */

@media (max-width: 1023px) {
  .db-band-col { width: var(--db-content); }
  .db-band { min-height: 460px; }
}
@media (max-width: 767px) {
  .db-band { min-height: 400px; }
  .db-band-col > * + * { margin-top: 24px; }
}

/* ---------- The coverage map, full viewport width ----------
   §Layout Exceptions allows exactly one element to break the container. The
   full-bleed photograph band is gone, so this takes that slot; it is a map
   rather than a photograph, which the spec does not currently anticipate.

   Ratio follows the band it replaces: 16:9 desktop, 4:3 mobile (§27), with a
   ceiling so a 1440px screen does not hand the map 810px of height. No radius,
   for the same reason the photograph band had none: a full-bleed element has no
   edge to round against.

   The 30% reduction asked for is taken off the ceiling and the floor, not off
   the ratio: §27 is a closed list of three ratios and 16:6.3 is not one of
   them. So the band is exactly 30% shorter wherever the ceiling governs, which
   is every viewport from about 796px up, and the reduction tapers below that
   as the ratio takes over. */
.db-mapband {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  max-height: 448px;   /* was 640: 30% shorter, on instruction */
  min-height: 294px;   /* was 420, reduced by the same 30% */
  background: var(--mist);
  border-block: 1px solid var(--line);
}
.db-mapband iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}
@media (max-width: 767px) {
  .db-mapband { aspect-ratio: 4 / 3; min-height: 252px; }  /* was 360 */
}

/* The pending state, until the My Maps document exists. Same dashed treatment
   as §54f's .db-empty, because it is the same idea: a slot with nothing in it
   says so rather than shipping a broken iframe or a Google permission error. */
.db-mapband--pending {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 12px;
  padding: 32px var(--db-gutter);
  border: 1px dashed var(--line);
  border-inline: 0;
}
.db-mapband--pending .inner {
  max-width: var(--db-content);
  margin-inline: auto;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 12px;
}
.db-mapband--pending .kind {
  font: 500 12px/1 var(--font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--error-text);
}
.db-mapband--pending p {
  margin: 0;
  font-size: 17px;
  line-height: 1.55;
  color: var(--navy);
  max-width: 56ch;
}

/* ---------- The zoom takeover, section 3 into section 4 ----------
   On Vlad's instruction, copied from the Rapid Despatch same-day-courier page.
   assets/home.js carries the reference's own numbers and the reasoning; this is
   only the two rules the effect needs to sit in.

   No overflow:hidden and no transform anywhere near section 3. The reference
   wrapper clips because it also drifts the outgoing section down 20rem; section
   3 here is the lift-rows component, whose .lift-list is position:sticky and
   whose script reads getBoundingClientRect() every frame, and either a
   transform or a clipping ancestor makes it a containing block and breaks it.

   The white ground is what shows through while the section is under-scale, and
   it matches section 3 above, so the seam is invisible and only the Mist band
   appears to grow. */
.db-takeover {
  position: relative;
  z-index: 1;
  background: var(--white);
}

/* ---------- FAQ questions as H3 ----------
   On instruction. HTML's content model for <summary> is phrasing content OR one
   element of heading content, so the plus/minus sign moves inside the h3 (and
   is aria-hidden, since <details> already announces its own open state). The
   h3 takes over summary's flex job. It does NOT inherit summary's type: the set
   sets summary to 21px, which is §2's H3 value at mobile only, so on a desktop
   the question was rendering two tiers under its own role. Dropping the
   inherit lets the h3 element default apply, which is §2's H3 exactly —
   Bricolage 600, 28/26/24/21, 1.25, -0.01em — and is what §2 means by "a
   section complies by deleting its own size classes". */
.db-faq summary > .faq-q {
  margin: 0;
  color: inherit;          /* so the dark family still turns it White */
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}

/* ---------- Service-area chips: a note, not a rule ----------
   A rule was added here for §7a when section 6 was briefly White. It was
   redundant: db-energy.css already carries
   `.g-white .area-list a,.area-list .plain{ background:var(--mist); }`.
   Removed rather than left duplicating a value.

   That line in the set has a real bug in it, though, and it outlives this
   section: the second half of the selector is missing its `.g-white`, so
   `.area-list .plain` fills --mist on EVERY ground. On a Mist section that is a
   §7a collision (mist on mist), saved only by the dashed border, and on the
   photograph band below the linked chips fill --white while the plain ones fill
   --mist. Two pill fills where the component means one. It is the set's file,
   which is never edited here, so it is reported rather than patched. */

/* ---------- Case-study titles at the §2 H3 size ----------
   The set overrides the H3 element default inside the carousel: .cx-text h3 is
   36px, dropping to 30px under 1024. Neither is a value in §2's scale — 36px is
   H2's tablet size — and the override outranks the element default at every
   width, so the case titles were an h3 wearing an h2's size.

   §2 says a section complies by deleting its own size classes. db-energy.css is
   the component set's file and is never edited here, so the four tiers are
   restated at the same specificity instead. These are §2's H3 row verbatim. */
.cx-text h3 { margin: 16px 0 0; font-size: 28px; line-height: 1.25; }
@media (max-width: 1279px) { .cx-text h3 { font-size: 26px; } }
@media (max-width: 1023px) { .cx-text h3 { font-size: 24px; } }
@media (max-width:  767px) { .cx-text h3 { font-size: 21px; } }

/* ============================================================
   MOTION — one global fade-in, and the card hover put back
   Vlad's instruction, 15 September
   ============================================================ */

/* THE CARD HOVER IS BACK. The set's 15 September revision removed it entirely:
   "the card has no hover state at all", border frozen, photograph frozen, and
   the pan rule left commented out in db-energy.css. That is reverted here on
   instruction. The set's file is never edited, so the rule is restored rather
   than uncommented, and its 600ms transition is pulled onto --motion-reveal
   (320ms) at the same time — 600ms is a fourth duration and §42 allows two
   outside the button component, so the set was off its own spec there. */
/* No border change. Restored last pass and taken straight back out on
   instruction: the outline is not what should react to a pointer, the picture
   is. The .pan rule that was also restored is left out for a different reason —
   .pan is the placeholder's stripe, and section 1 carries real photographs, so
   on this page it never matched anything. */
.zone-strip picture { display: block; overflow: hidden; border-radius: var(--radius-tile); }
.zone-strip .db-img { transition: transform var(--motion-reveal); transform-origin: 50% 50%; }
.db-card:hover .zone-strip .db-img { transform: scale(1.06); }

/* §19 says, as revised on 15 September, that "a card has no hover state... no
   photograph inside it pans, and nothing lifts, scales or shadows". This scales
   a photograph inside a card, so it is a straight contradiction of that clause
   and it is in on instruction. The duration is --motion-reveal (320ms), which is
   the slower of the two §42 allows outside the button component; the set's own
   pan ran 600ms, a fourth duration it was not entitled to. If 320ms reads as
   snappy rather than smooth, the next step up is a third duration and therefore
   a second deviation rather than a tweak. */

/* THE PER-CARD ENTRANCE IS OFF. The set runs three animations on .card-in — the
   card rising 28px over 520ms, a 640ms strip wipe and a 700ms icon stroke draw,
   staggered 90ms. None of 520, 640, 700 or 90 is a value in §42 or §43, and all
   of it is replaced by the one global fade-up below.

   The icon reset is not optional: the set parks every card icon at
   stroke-dashoffset:var(--draw-len) OUTSIDE the .card-in selector, so killing
   the draw animation without this line would leave the icons invisible. */
@media (prefers-reduced-motion: no-preference) {
  .db-card.card-in,
  .db-card.card-in .zone-strip .ph { animation: none; }
  .db-card .db-icon svg *,
  .db-card.card-in .db-icon svg * {
    stroke-dasharray: none;
    stroke-dashoffset: 0;
    animation: none;
  }
}

/* THE GLOBAL FADE-IN. §43 exactly: opacity 0→1, translateY 10px→0, 320ms,
   cubic-bezier(0.16,1,0.3,1), 60ms stagger between siblings, fires once,
   disabled under prefers-reduced-motion. The set defines .db-reveal and the
   dbFadeUp keyframes and then says, in its own comment, "wire .db-reveal to an
   IntersectionObserver at build time". assets/home.js is that wiring.

   Nothing is parked at opacity 0 by the stylesheet alone. The .db-reveal-on
   class is put on <html> by the script, and only after it has confirmed it can
   un-park what it parks — so with JS off, or under reduced motion, or without
   IntersectionObserver, every element renders at full opacity and the page is
   still legible in a screenshot. That was the set's stated reason for leaving
   this unarmed, and it is kept. */
@media (prefers-reduced-motion: no-preference) {
  :root {
    /* §43 says 10px over 320ms. Those values are in the two comments beside
       these, and putting them back is a two-number edit.

       They were not visible, and not only because of the bug above. On
       cubic-bezier(0.16,1,0.3,1) — §43's own easing, unchanged here — 75% of the
       travel happens in the first fifth of the duration, so a 10px move over
       320ms is 84% finished 90ms after it starts. Measured on the built page at
       §43's values: opacity 0 → 0.51 → 0.84 → 0.95 → 1 in under 200ms, across
       ten pixels. That is a settle, not an entrance, and at normal scroll speed
       it is below the threshold of being noticed.

       24px and 480ms is a real deviation from §43 and is in on instruction. Two
       things worth knowing before deciding. The easing, the stagger, the
       opacity ramp and the fires-once rule are all still §43 exactly, so this
       changes two numbers rather than the behaviour. And the set's own card
       entrance — the one just deleted — ran 28px over 520ms, which suggests the
       people who built it had reached much the same conclusion about what is
       actually visible; the difference now is one global rule instead of three
       bespoke animations. */
    --db-rise: 24px;   /* §43: 10px  */
    --db-fade: 480ms;  /* §43: 320ms */
  }
  .db-reveal-on .db-reveal { opacity: 0; }
  .db-reveal-on .db-reveal.is-in {
    animation: dbRise var(--db-fade) cubic-bezier(0.16, 1, 0.3, 1) both;
    animation-delay: calc(var(--i, 0) * 60ms);
  }
  @keyframes dbRise {
    from { opacity: 0; transform: translateY(var(--db-rise)); }
    to   { opacity: 1; transform: none; }
  }
}
