/* Ladi Luck Foundation component styles.
   Loads after tokens.css and base.css. References token names only:
   no hex values, and no raw pixel values outside the spacing scale.

   The header and footer are held in the foundation register on every page,
   including drive pages, so the site chrome stays constant and the h1 lands
   at the same height on all nine pages. */


/* ---------- Shared rhythm inside the container ----------
   Not page specific. Sections and standalone blocks sit one section gap
   apart wherever they appear. */

.container > section,
.container > ul,
.container > form,
.container > div {
    margin-top: var(--section-pad);
    margin-bottom: var(--section-pad);
}

/* A bare h2 that is a direct child also starts a major block. */
.container > h2 {
    margin-top: var(--section-pad);
}

/* A heading owns the space beneath it, so the block that follows never adds
   its own. This is what keeps the h1 rhythm identical on every page. */
.container > h1 + *,
.container > h2 + *,
.container > h3 + * {
    margin-top: 0;
}

/* The container owns the top and the bottom of the page. */
.container > *:first-child {
    margin-top: 0;
}

/* The second selector stops a nested last paragraph collapsing its margin
   out through a section. */
.container > *:last-child,
.container > *:last-child > *:last-child {
    margin-bottom: 0;
}


/* ---------- Header ----------
   Full bleed purple band. The padding shorthand centers the contents to
   --content-max while the background runs edge to edge, with no wrapper. */

header {
    background: var(--purple);
    color: var(--text-on-purple);
    padding-block: var(--space-3);
    padding-inline: max(var(--space-3), calc((100% - var(--content-max)) / 2));
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2) var(--space-3);
    border-bottom: var(--border-thick) solid var(--ink);
}

/* Logo. The inlined SVG paints with currentColor, so this color is what the
   mark renders in. Ink on the purple header band is 6.18:1. */
.logo {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    color: var(--ink);
    text-decoration: none;
}

.logo svg {
    width: var(--logo-size);
    height: var(--logo-size);
}

/* ---------- Nav ---------- */

header nav ul {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-1) var(--space-3);
    margin: 0;
    padding: 0;
    list-style: none;
}

header nav a {
    display: inline-block;
    padding-block: var(--space-1);
    border-bottom: var(--border-thick) solid transparent;
    color: var(--text-on-purple);
    font-family: var(--font-heading);
    font-weight: var(--weight-label);
    font-size: var(--size-label);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    text-decoration: none;
}

header nav a:hover {
    border-bottom-color: var(--ink);
}

/* Active page. Solid rule plus a heavier face, so it does not rely on
   color alone. */
header nav a[aria-current="page"] {
    border-bottom-color: var(--ink);
    font-weight: var(--weight-heading);
}


/* ---------- Footer ---------- */

footer {
    background: var(--ink);
    color: var(--cream);
    padding-block: var(--space-4);
    padding-inline: max(var(--space-3), calc((100% - var(--content-max)) / 2));
    border-top: var(--border-thick) solid var(--ink);
}

footer p {
    max-width: none;
    margin: 0 0 var(--space-1);
    font-family: var(--font-heading);
    font-weight: var(--weight-label);
    font-size: var(--size-label);
    letter-spacing: var(--tracking-label);
}

footer p:last-child {
    margin-bottom: 0;
}


/* ---------- Focus ---------- */

a:focus-visible,
button:focus-visible,
input:focus-visible,
textarea:focus-visible {
    outline: var(--focus-ring) solid var(--ink);
    outline-offset: var(--focus-offset);
}


/* ---------- Links in body copy ---------- */

.container a {
    color: var(--ink);
    text-decoration-thickness: var(--border-hair);
    text-underline-offset: var(--focus-ring);
}

.container a:hover {
    text-decoration-thickness: var(--border-thick);
}


/* ---------- Home stat blocks ----------
   Purple fills carrying ink type, 6.18:1. Purple is never the type color
   itself. */

.stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(var(--stat-min), 1fr));
    gap: var(--space-1);
}

.stat {
    background: var(--purple);
    color: var(--text-on-purple);
    padding: var(--space-3);
    text-align: center;
}

.stat p {
    margin: 0;
    max-width: none;
}

.stat p:first-child {
    font-family: var(--display-font);
    font-weight: var(--display-weight);
    font-size: var(--size-h1);
    line-height: var(--leading-display);
    letter-spacing: var(--display-tracking);
}

.stat p:last-child {
    font-family: var(--font-heading);
    font-weight: var(--weight-label);
    font-size: var(--size-label);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
}


/* ---------- Notice ----------
   A warning that has to be read before the thing it warns about, so it takes
   a fill rather than sitting in the body copy. Ink on purple is 6.18:1. */

.notice,
.confirmation {
    max-width: none;
    padding: var(--space-3);
    background: var(--purple);
    color: var(--text-on-purple);
    border: var(--border-thick) solid var(--ink);
}

.notice a,
.confirmation a {
    color: var(--text-on-purple);
}

.confirmation h2 {
    margin-bottom: var(--space-2);
}

.confirmation p:last-child {
    margin-bottom: 0;
}

/* Field errors. Ink type, because neither purple nor pink passes on cream at this
   size. The accent bar carries the signal instead. */
.form .errorlist {
    margin: 0;
    padding-left: var(--space-2);
    border-left: var(--border-thick) solid var(--accent);
    list-style: none;
    color: var(--ink);
    font-family: var(--font-heading);
    font-weight: var(--weight-label);
    font-size: var(--size-label);
    letter-spacing: var(--tracking-label);
}


/* ---------- Sneaker Stop cards and sponsor tiers ----------
   One card pattern, shared by the stop list and the sponsor tier list so
   the two cannot drift apart. */

.stops,
.tiers {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-2);
    padding: 0;
    list-style: none;
}

.stop-card,
.tiers li {
    background: var(--cream);
    border: var(--border-hair) solid var(--ink);
    border-top: var(--space-1) solid var(--accent);
    padding: var(--space-3);
}

.stop-card h3,
.tiers li h3 {
    margin-bottom: var(--space-2);
}

.stop-card p,
.tiers li p {
    max-width: none;
    margin: 0 0 var(--space-1);
    font-size: var(--size-body);
}

.stop-card p:last-child {
    margin-bottom: 0;
    margin-top: var(--space-2);
    padding-top: var(--space-2);
    border-top: var(--border-hair) solid var(--ink);
    font-size: var(--size-label);
    letter-spacing: var(--tracking-label);
}

/* The address element, which browsers italicise by default. It is a postal
   address, not an aside, so it reads as body copy. */
.stop-card address {
    margin: 0;
    font-style: normal;
}

/* Hours. Absent until the client sends them, and the block is not rendered at
   all when the note is empty, so this never leaves a gap. */
.stop-card .stop-hours {
    margin-top: var(--space-1);
    font-weight: var(--weight-label);
}

/* A stop card ends with a note, which takes the rule above. A tier ends with
   its description, so it only needs the trailing margin cleared. */
.tiers li p:last-child {
    margin-bottom: 0;
}


/* ---------- Map placeholder ---------- */

/* The map is a portrait shape, roughly 3:4, and the content column is wider
   than it is tall at desktop. Run at full column width it would stand about
   1380px tall and you would scroll past the city rather than see it, so the
   width is capped and the map is centred. When the stop list is real, the
   better answer is a band with the list beside it. */
#map {
    min-height: var(--map-min);
    max-width: var(--map-max);
    margin-inline: auto;
}

.map {
    display: block;
    width: 100%;
    height: auto;
}

/* Land takes the foundation accent, water takes the page ground, so the
   harbour reads as the page showing through the city rather than as a second
   colour competing with it. That keeps the water quiet under the markers that
   land on top of it next. Strokes do not scale with the artwork, so the
   border tokens render at their real pixel widths whether the map is 320 or
   480 wide. */
.map-land path {
    fill: var(--purple);
    stroke: var(--ink);
    stroke-width: var(--border-thick);
    vector-effect: non-scaling-stroke;
}

.map-water path {
    fill: var(--cream);
    stroke: var(--ink);
    stroke-width: var(--border-hair);
    vector-effect: non-scaling-stroke;
}


/* ---------- Host a Stop form ---------- */

.form {
    max-width: var(--measure);
    padding: var(--space-4);
    border: var(--border-hair) solid var(--ink);
}

/* Each field is a paragraph holding a label then an input. Stacking the
   paragraph puts the label above its field. */
.form p {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    max-width: none;
    margin: 0 0 var(--space-3);
}

.form p:last-child {
    margin-bottom: 0;
}

.form label {
    font-family: var(--font-heading);
    font-weight: var(--weight-label);
    font-size: var(--size-label);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
}

.form input,
.form textarea {
    width: 100%;
    padding: var(--space-1) var(--space-2);
    background: var(--cream);
    border: var(--border-hair) solid var(--ink);
    color: var(--ink);
    font-family: var(--font-body);
    font-weight: var(--weight-body);
    font-size: var(--size-body);
    line-height: var(--leading-body);
}

.form textarea {
    resize: vertical;
}

.form input:focus,
.form textarea:focus {
    border-width: var(--border-thick);
    outline: var(--focus-ring) solid var(--ink);
    outline-offset: var(--focus-offset);
}

button,
a.button {
    padding: var(--space-2) var(--space-4);
    background: var(--accent);
    color: var(--text-on-accent);
    border: var(--border-thick) solid var(--ink);
    font-family: var(--font-heading);
    font-weight: var(--weight-label);
    font-size: var(--size-body);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    cursor: pointer;
}

/* An anchor styled as a button. It needs the box and the underline reset that
   a real button gets from the user agent. Qualified with the element so it
   outranks the .container a rule, which would otherwise keep the underline. */
a.button {
    display: inline-block;
    text-decoration: none;
}

button:hover,
a.button:hover {
    background: var(--ink);
    color: var(--cream);
}


/* Ways to give. A list, so adding Givebutter later is one more item and not a
   restructure, and so the options read as a set rather than as one button with
   another stuck next to it. */
.give-options {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin: 0 0 var(--space-2);
    padding: 0;
    list-style: none;
}

/* All in a row or all stacked, never a ragged two and one.
   The basis is a width that flips sign at --give-row. In a row narrower than
   that it is a large positive number, so every item is too wide to share a
   line and wrap puts each on its own, where shrink pulls it back to the full
   row width. In a wider row the calc goes negative, flex-basis clamps it to
   zero, and grow shares the row out equally. The multiplier only has to be
   big enough that the sign swamps the number.
   It keys off the row's own width, which a media query cannot do here: the
   band is one column below 768 and two above, so this row is 696px wide at a
   744px viewport and 475px at 872px, and a min-width rule would stack the
   wide case and leave the narrow one in a row. */
.give-options li {
    flex: 1 1 calc((var(--give-row) - 100%) * 999);
}

/* Block, so the whole item is the target and the labels centre against each
   other rather than sitting left in boxes of different widths. */
.give-options a.button {
    display: block;
    text-align: center;
}


/* ---------- Foundation photos and bands ----------
   Used on the home page. Nothing here is register specific, so it sits above
   the drive divider. */

/* The lead image sits in the bleed block, a direct child of main rather than
   of the container, so width: 100% is the full viewport. Doing it this way
   rather than with a 100vw breakout matters: scrollbar-gutter: stable is set
   on html, so 100vw is always wider than the content area and would overflow
   sideways by the width of the gutter. */
.photo-lead {
    display: block;
    width: 100%;
    aspect-ratio: 16 / 9;
    object-fit: cover;
}

/* The lead heading laps over the bottom of the image, the way the drive page
   h1 laps over its hero. It has to climb back over the container's own top
   padding before it starts overlapping, which is what the --section-pad term
   pays for. The purple fill is the foundation answer to the drive slab: the
   heading needs an opaque ground to stay readable over a photo.

   position: relative is load bearing, not tidiness. The image is a block level
   replaced element in an earlier part of the tree, and it paints over the
   heading's background, leaving the text sitting straight on the photo.
   Positioning the heading lifts the whole slab, fill included, above it. The
   drive page does not need this because its hero and h1 are siblings in the
   same container. */
.lead-title {
    display: table;
    position: relative;
    background: var(--accent);
    color: var(--text-on-accent);
    padding: var(--slab-pad-y) var(--slab-pad-x);
    margin-left: calc(-1 * var(--slab-overhang));
}

/* Qualified with the element so it outranks .container > *:first-child, which
   would otherwise zero the pull. */
.container > h1.lead-title {
    margin-top: calc(-1 * (var(--section-pad) + var(--space-5)));
}

/* A band pairs one image with its related text. Single column below the
   breakpoint, two columns above it, alternating which side the image takes.
   The gap inside a band is small and the space between bands is large, so a
   band reads as one unit. */
.band {
    display: grid;
    gap: var(--band-gap);
}

.band + .band {
    margin-top: var(--band-space);
}

/* A <picture> is only a wrapper for choosing a format, so it has to behave as
   the grid item and let the image inside carry the sizing. Without this the
   band would lay out against an inline box and the image would not fill its
   column. */
.band > picture {
    display: block;
    min-width: 0;
}

.photo-band {
    display: block;
    width: 100%;
    /* Load bearing once an image carries width and height attributes. Those
       attributes are presentational hints that set a used height, and a used
       height beats aspect-ratio, so without this the image renders at its full
       intrinsic height instead of the shape its band asked for. */
    height: auto;
    object-fit: cover;
}

/* Three shapes rather than three squares. Bands 1 and 3 take the source's own
   9:16, so nothing is cropped there at all; band 2 is the square source and is
   the only one that pays for its shape. */
.band-tall .photo-band {
    aspect-ratio: 9 / 16;
}

.band-wide .photo-band {
    aspect-ratio: 4 / 3;
}

.band-small .photo-band {
    aspect-ratio: 3 / 4;
}

/* Inside a band column the auto-fit stat grid would fit two blocks and orphan
   the third, so the three stack instead. */
.band .stats {
    grid-template-columns: 1fr;
}

/* Stop cards inside a band. The two column rule below the desktop breakpoint
   is written for the full content width; in a band's narrower text column it
   squeezes an address onto four lines. Sizing by a floor instead of a count
   means the list also survives growing: one column in a band at 768, two when
   there is room, without a second breakpoint to keep in step. */
.band .stops {
    grid-template-columns: repeat(auto-fit, minmax(var(--stop-min), 1fr));
}

/* The text side of a band. Its last block drops its bottom margin so the two
   columns optically centre against each other rather than sitting a paragraph
   gap low. */
.band-text > *:last-child {
    margin-bottom: 0;
}

/* For a portrait source whose subject sits high in the frame. A centered
   square crop of a tall photo takes the top of the head off, so these are
   anchored to the top edge instead. */
.photo-top {
    object-position: center top;
}

/* An embedded video in a band, in place of a photo. A figure rather than a
   bare iframe, so the station and the year are tied to the video they belong
   to rather than being a loose line of text underneath it.
   Same two rules a <picture> needs to behave as the grid item: block, so the
   band lays out against it rather than against an inline box, and min-width 0,
   so it can be narrower than its content wants and the band never pushes the
   page sideways. */
.band > .video {
    display: block;
    min-width: 0;
    margin: 0;
}

/* aspect-ratio rather than a padding box. The iframe is a replaced element
   with a default intrinsic size, so it needs the width and the ratio stated;
   given both, it scales to its column at every width with nothing absolutely
   positioned and nothing to keep in step. */
.video iframe {
    display: block;
    width: 100%;
    aspect-ratio: 16 / 9;
    border: 0;
}

.video figcaption {
    margin-top: var(--space-1);
}

/* A cutout has no ground of its own. Covering one crops the subject, and there
   is no background for the crop to be forgiven by, so a cutout is contained
   inside its slot instead. The empty space that leaves is invisible against
   the page. */
.photo-cutout {
    object-fit: contain;
}


/* ---------- Founder avatar in the nav ----------
   Drive register only. The foundation pages are donor and sponsor facing and
   do not carry it. It is off by default and the image URL is only declared
   under the drive register, so a foundation page never requests the file
   rather than downloading one it then hides.

   It sits inside the About list item, so the two are one flex item and the gap
   between them is ours rather than the nav's. It points at the same URL as the
   About link, which is why it is aria-hidden and tabindex -1: a duplicate
   destination should not be a second stop for a screen reader or a keyboard.
   The circle is border-radius, not a baked in transparent PNG. */

.nav-avatar {
    display: none;
}

[data-register="drive"] .nav-about {
    display: flex;
    align-items: center;
    gap: var(--avatar-gap);
}

/* header nav a gives every link padding and an underline slot. The avatar is
   not a text link and wants neither. */
[data-register="drive"] .nav-avatar {
    display: block;
    padding-block: 0;
    border-bottom: 0;
    line-height: 0;
}

[data-register="drive"] .nav-avatar-face {
    display: block;
    width: var(--avatar-size);
    height: var(--avatar-size);
    border-radius: 50%;
    border: var(--avatar-ring) solid var(--ink);
    background-image: url("../img/placeholder-dali-avatar-64.18dd4da46046.jpg");
    background-size: cover;
    background-position: center;
    background-clip: padding-box;
}


/* ---------- Scroll reveal ----------
   Bands fade up as they come into view. Home only: it is the page the band
   pattern was designed on and the only one carrying all three variants, and
   the effect is opt in per page because nothing here fires until the inline
   script in the head sets data-motion on <html>.

   The hidden state lives entirely behind that flag, so a page with no script,
   a browser without IntersectionObserver, and a reader who has asked for
   reduced motion all get every band at full opacity with nothing to undo. The
   templates never carry a hidden class.

   The lead image and the h1 slab are not bands and do not move. */

html[data-motion="on"] .band {
    opacity: 0;
    transform: translateY(var(--reveal-rise));
    transition:
        opacity var(--reveal-duration) var(--reveal-ease) var(--reveal-delay),
        transform var(--reveal-duration) var(--reveal-ease) var(--reveal-delay);
    will-change: opacity, transform;
}

html[data-motion="on"] .band.is-revealed {
    opacity: 1;
    transform: none;
    will-change: auto;
}

/* Belt and braces. The inline script already declines to set the flag when
   reduced motion is asked for, so this should never be the thing doing the
   work. It is here in case the flag is set some other way later. */
@media (prefers-reduced-motion: reduce) {
    html[data-motion="on"] .band,
    html[data-motion="on"] .band.is-revealed {
        opacity: 1;
        transform: none;
        transition: none;
    }
}


/* ---------- Desktop ---------- */

@media (min-width: 768px) {
    .stops,
    .tiers {
        grid-template-columns: repeat(2, 1fr);
    }

    .band {
        align-items: center;
    }

    /* Every second band sends its image to the right. The DOM order is always
       image first, which is what the single column layout below this
       breakpoint wants, so only the desktop case needs reordering. */
    .band:nth-of-type(even) > .photo-band,
    .band:nth-of-type(even) > picture {
        order: 2;
    }

    /* A video always takes the second cell, whichever band it lands in, which
       in band-wide is the 3fr side. A photo can be cropped to whatever width
       its cell gives it; a video cannot, and in the 2fr cell it comes out
       408px with the player's own controls and title bar crammed into it. The
       cost is that a video band does not alternate with the band above it. */
    .band > .video {
        order: 2;
    }

    /* Asymmetric splits, so the columns match the shape sitting in them. The
       image takes the first cell in an odd band and the second in an even one,
       which is why band-tall and band-wide share a template and still end up
       with different sized images. */
    .band-tall {
        grid-template-columns: 2fr 3fr;   /* image 2fr, the narrower cell */
    }

    .band-wide {
        grid-template-columns: 2fr 3fr;   /* image 3fr, reordered into cell 2 */
    }

    .band-small {
        grid-template-columns: 1fr 2fr;   /* image 1fr, the smallest of the three */
    }
}


/* ============================================================
   DRIVE REGISTER
   Everything below applies only under data-register="drive".
   The header and footer are deliberately excluded, so the site chrome
   stays foundation on every page.
   ============================================================ */

/* Full bleed seam directly below the header, marking entry into the drive
   section. main runs the full viewport width, so no wrapper is needed. */
[data-register="drive"] main::before {
    content: "";
    display: block;
    height: var(--seam-height);
    background: var(--pink);
}

/* The seam is flush under the header and must not push content down, so the
   container gives back exactly the seam's height. Without this, drive pages
   start their content one seam lower than the foundation pages. */
[data-register="drive"] .container {
    padding-top: calc(var(--section-pad) - var(--seam-height));
}

/* ---------- Drive display type ---------- */

/* h2 already resolves to the marker face through --display-font. This pins
   the pairing explicitly so it cannot be lost if the base rule changes. */
[data-register="drive"] .container h2 {
    font-family: var(--font-drive);
    font-weight: var(--weight-drive);
    letter-spacing: var(--tracking-drive);
    /* Hard offset flipped to pink, because these sit on cream and pink tint.
       One hair rather than the slab's two: Kalam's strokes are thinner than
       the marker face this replaced, and at 2px the shadow read as a doubled
       outline rather than an offset. The glyph itself is still ink on cream
       at 15.18:1, the offset only ever lands beside it. */
    text-shadow: var(--border-hair) var(--border-hair) 0 var(--pink);
}

/* The page h1 sits on a pink slab, ink on pink at 4.57:1, using the same
   slab padding tokens as the style guide component. */
[data-register="drive"] .container > h1 {
    width: fit-content;
    padding: var(--slab-pad-y) var(--slab-pad-x);
    /* The overhang has to swallow the border as well as the padding, or the
       4px outline pushes the type off the body copy's left edge. */
    margin-left: calc(-1 * (var(--slab-overhang) + var(--border-chunky)));
    background: var(--pink);
    color: var(--text-on-pink);
    border: var(--border-chunky) solid var(--ink);
    /* Hard offset, no blur. Cream against the pink ground, so the ink type
       reads as cut out rather than shadowed. The offset never sits under the
       glyph, so ink on pink stays 4.57:1. */
    text-shadow: var(--border-thick) var(--border-thick) 0 var(--cream);
    position: relative;
}

/* ---------- Alternating section grounds ----------
   Bands sit flush against each other, so the alternation reads as bands
   rather than as tinted boxes floating in cream. */
[data-register="drive"] .container > section {
    margin-block: 0;
    padding-block: var(--section-pad);
    padding-inline: var(--space-3);
    margin-inline: calc(-1 * var(--space-3));
    /* Anchor for the decorative shapes, which sit in the padding bands. */
    position: relative;
}

[data-register="drive"] .container > section:nth-of-type(even) {
    /* The veil, not the tint. Identical over cream, but translucent so the
       falling layer behind it is not swallowed by this band. */
    background: var(--pink-veil);
}

/* ---------- Stop cards ---------- */

[data-register="drive"] .stop-card {
    border-color: var(--pink);
    border-top-color: var(--pink);
}

/* Card titles are Oswald 600 at 22px. Pink on cream is 3.32:1, which only
   clears the 3:1 bar if this counts as large text, and 600 is below the 700
   that WCAG treats as bold at this size. No stop cards render today, so this
   is latent, but the pink title needs 24px or weight 700 before any do. The
   13px note label below stays ink, where pink would fail outright. */
[data-register="drive"] .stop-card h3 {
    color: var(--pink);
}

[data-register="drive"] .stop-card p:last-child {
    border-top-color: var(--pink);
}

/* ---------- Links and buttons ---------- */

/* Pink carries the link, but as the underline rather than the text color.
   Pink type on cream is 3.32:1 and fails at 17px. */
[data-register="drive"] .container a {
    text-decoration-color: var(--pink);
    text-decoration-thickness: var(--border-thick);
}

[data-register="drive"] .container a:hover {
    background: var(--pink);
    color: var(--text-on-pink);
}

[data-register="drive"] button {
    background: var(--pink);
    color: var(--text-on-pink);
}

[data-register="drive"] button:hover {
    background: var(--ink);
    color: var(--cream);
}

/* ---------- Photos ----------
   The hero is a real <img>. The .stop-card rules below are dormant: the cards
   were pulled from both drive templates when the stops became a holding
   message, so nothing matches them until the cards come back. */

/* The 16:9 slot is the shape the page was designed around, so the image is
   cropped to fit it rather than the layout bending to the photo. An <img> is
   not in the .container > div rhythm list, so the section gap above it is
   restated here. The first-child and drive register rules still win where
   they apply, which keeps the spacing identical to the div this replaced. */
.photo-hero {
    display: block;
    width: 100%;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    margin-top: var(--section-pad);
}

[data-register="drive"] .stop-card::before {
    content: "PHOTO";
    font-family: var(--font-heading);
    font-weight: var(--weight-label);
    font-size: var(--size-label);
    letter-spacing: var(--tracking-label);
    color: var(--ink);
}

/* The h1 slab laps over the bottom edge of the hero image. The hero drops the
   section gap below it, otherwise that gap cancels the pull and the slab lands
   below the image instead of on it. */
[data-register="drive"] .photo-hero {
    margin-bottom: 0;
}

[data-register="drive"] .photo-hero + h1 {
    margin-top: calc(-1 * var(--space-5));
}

/* Square slot on every stop card, on both drive pages, without touching the
   card markup. */
[data-register="drive"] .stop-card::before {
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--photo-slot);
    aspect-ratio: 1;
    margin-bottom: var(--space-2);
    border: var(--border-thick) dashed var(--pink);
}


/* ---------- Drive decoration ----------
   Original flat shapes from _drive_decor.svg.html, in pink and purple.

   They live in the section padding bands, never in the reading column. The
   band is --section-pad tall and holds no text at any width, so a shape
   parked there cannot collide with a heading or a paragraph however wide the
   text runs. Nothing sits behind body copy, so no opacity is needed to
   protect contrast and ink on cream stays 15.18:1 throughout.

   Below the 768px breakpoint the reading column is the full width of the
   page, so there is no free space left and the decoration is simply not
   drawn. */

.deco-sprite {
    position: absolute;
    width: 0;
    height: 0;
    overflow: hidden;
}

[data-register="drive"] .deco {
    display: none;
    position: absolute;
    color: var(--pink);
    pointer-events: none;
}

[data-register="drive"] .deco-purple {
    color: var(--purple);
}

@media (min-width: 768px) {
    [data-register="drive"] .deco {
        display: block;
    }

    /* The slab is width: fit-content, and at this width the marker face fills
       nearly the whole container, so there is no room beside it. Measured at
       768px the pair pushed 20px past the viewport. They come back below. */
    [data-register="drive"] .container > h1 .deco {
        display: none;
    }
}

@media (min-width: 1024px) {
    [data-register="drive"] .container > h1 .deco {
        display: block;
    }
}

.deco-sm { width: var(--decor-sm); height: var(--decor-sm); }
.deco-md { width: var(--decor-md); height: var(--decor-md); }
.deco-lg { width: var(--decor-lg); height: var(--decor-lg); }

/* Top and bottom padding bands, right hand side, clear of the text column. */
.deco-top-right    { top: var(--space-2);    right: 0; }
.deco-top-right-2  { top: var(--space-5);    right: var(--space-8); }
.deco-bottom-right { bottom: var(--space-2); right: 0; }
.deco-bottom-mid   { bottom: var(--space-5); right: var(--space-10); }

/* Beside the slab. The slab is width: fit-content, so 100% is its own right
   edge and these sit outside the pink, never on top of the type. */
[data-register="drive"] .container > h1 .deco {
    left: calc(100% + var(--space-3));
}

.deco-slab-high { top: calc(-1 * var(--space-2)); }
.deco-slab-low  { bottom: calc(-1 * var(--space-1)); left: calc(100% + var(--space-8)); }


/* ---------- Drive snowfall ----------
   A fixed sheet at z-index -1. body's cream background propagates to the
   canvas, so a negative layer still paints above it. pointer-events none, and
   overflow hidden so a drifting shape can never widen the page, which matters
   more now the shapes are large.

   Snow is plentiful and small, the objects are rare, slow and large. Purple
   carries most of it because it is the lighter of the two, with pink on every
   fourth flake.

   Weight comes from size, not from opacity. Flakes run 10px to 30px and
   objects 64px to 96px, roughly two and a half times the first pass, while
   opacity is held at a 0.18 ceiling for snow and 0.14 for objects. Pushing
   opacity instead would have cost real contrast: measured, a 0.30 flake drops
   body copy in the tinted band to 8.96:1. Size costs nothing.

   The entire layer is gated behind prefers-reduced-motion: no-preference. Ask
   for reduced motion and it is not drawn at all, not merely paused. */

.snowfall {
    display: none;
}

@media (prefers-reduced-motion: no-preference) {
    .snowfall {
        display: block;
        position: fixed;
        inset: 0;
        z-index: -1;
        overflow: hidden;
        pointer-events: none;
    }

    .snowfall > * {
        position: absolute;
        top: 0;
        --drift: 0;
        --spin: 0deg;
        color: var(--purple);
        animation-name: drive-fall;
        animation-timing-function: linear;
        animation-iteration-count: infinite;
    }

    .snowfall .drifter {
        color: var(--pink);
    }

    @keyframes drive-fall {
        from { transform: translate3d(0, -14vh, 0) rotate(0deg); }
        to   { transform: translate3d(var(--drift), 114vh, 0) rotate(var(--spin)); }
    }

    .snowfall > *:nth-child(1) {
        left: 72.3%;
        width: calc(var(--space-2) * 1.125); height: calc(var(--space-2) * 1.125);
        opacity: 0.16;
        --drift: 4.3vw;
        animation-duration: 21.9s;
        animation-delay: -10.9s;
    }

    .snowfall > *:nth-child(2) {
        left: 72.5%;
        width: calc(var(--space-3) * 1.25); height: calc(var(--space-3) * 1.25);
        opacity: 0.17;
        --drift: 2.7vw;
        animation-duration: 16.6s;
        animation-delay: -20.7s;
    }

    .snowfall > *:nth-child(3) {
        left: 69.1%;
        width: calc(var(--space-2) * 0.875); height: calc(var(--space-2) * 0.875);
        opacity: 0.11;
        --drift: -2.4vw;
        animation-duration: 15.2s;
        animation-delay: -6.4s;
    }

    .snowfall > *:nth-child(4) {
        left: 66.5%;
        width: var(--space-3); height: var(--space-3);
        opacity: 0.17;
        --drift: -1.0vw; color: var(--pink);
        animation-duration: 12.6s;
        animation-delay: -17.2s;
    }

    .snowfall > *:nth-child(5) {
        left: 25.7%;
        width: var(--space-3); height: var(--space-3);
        opacity: 0.16;
        --drift: -3.5vw;
        animation-duration: 15.3s;
        animation-delay: -21.1s;
    }

    .snowfall > *:nth-child(6) {
        left: 53.3%;
        width: calc(var(--space-3) * 1.25); height: calc(var(--space-3) * 1.25);
        opacity: 0.12;
        --drift: -2.9vw;
        animation-duration: 13.6s;
        animation-delay: -2.6s;
    }

    .snowfall > *:nth-child(7) {
        left: 54.5%;
        width: calc(var(--space-1) * 1.25); height: calc(var(--space-1) * 1.25);
        opacity: 0.12;
        --drift: 0.9vw;
        animation-duration: 23.1s;
        animation-delay: -13.1s;
    }

    .snowfall > *:nth-child(8) {
        left: 48.0%;
        width: calc(var(--space-2) * 1.125); height: calc(var(--space-2) * 1.125);
        opacity: 0.14;
        --drift: -2.1vw; color: var(--pink);
        animation-duration: 17.2s;
        animation-delay: -25.2s;
    }

    .snowfall > *:nth-child(9) {
        left: 85.0%;
        width: calc(var(--space-2) * 0.875); height: calc(var(--space-2) * 0.875);
        opacity: 0.17;
        --drift: -2.2vw;
        animation-duration: 14.3s;
        animation-delay: -15.4s;
    }

    .snowfall > *:nth-child(10) {
        left: 65.7%;
        width: calc(var(--space-1) * 1.25); height: calc(var(--space-1) * 1.25);
        opacity: 0.16;
        --drift: 1.4vw;
        animation-duration: 15.7s;
        animation-delay: -4.2s;
    }

    .snowfall > *:nth-child(11) {
        left: 34.0%;
        width: calc(var(--space-2) * 0.875); height: calc(var(--space-2) * 0.875);
        opacity: 0.16;
        --drift: -3.7vw;
        animation-duration: 16.9s;
        animation-delay: -22.5s;
    }

    .snowfall > *:nth-child(12) {
        left: 52.6%;
        width: calc(var(--space-3) * 1.25); height: calc(var(--space-3) * 1.25);
        opacity: 0.16;
        --drift: 1.3vw; color: var(--pink);
        animation-duration: 14.8s;
        animation-delay: -13.6s;
    }

    .snowfall > *:nth-child(13) {
        left: 30.2%;
        width: calc(var(--space-3) * 1.25); height: calc(var(--space-3) * 1.25);
        opacity: 0.15;
        --drift: -2.5vw;
        animation-duration: 12.8s;
        animation-delay: -1.7s;
    }

    .snowfall > *:nth-child(14) {
        left: 22.9%;
        width: calc(var(--space-2) * 0.875); height: calc(var(--space-2) * 0.875);
        opacity: 0.11;
        --drift: 1.9vw;
        animation-duration: 14.9s;
        animation-delay: -2.7s;
    }

    .snowfall > *:nth-child(15) {
        left: 37.0%;
        width: var(--space-3); height: var(--space-3);
        opacity: 0.13;
        --drift: -1.6vw;
        animation-duration: 14.9s;
        animation-delay: -7.0s;
    }

    .snowfall > *:nth-child(16) {
        left: 48.5%;
        width: calc(var(--space-1) * 1.25); height: calc(var(--space-1) * 1.25);
        opacity: 0.14;
        --drift: 2.9vw; color: var(--pink);
        animation-duration: 17.2s;
        animation-delay: -25.3s;
    }

    .snowfall > *:nth-child(17) {
        left: 4.9%;
        width: calc(var(--space-2) * 0.875); height: calc(var(--space-2) * 0.875);
        opacity: 0.11;
        --drift: 1.3vw;
        animation-duration: 25.7s;
        animation-delay: -25.7s;
    }

    .snowfall > *:nth-child(18) {
        left: 10.2%;
        width: calc(var(--space-2) * 1.125); height: calc(var(--space-2) * 1.125);
        opacity: 0.18;
        --drift: 0.3vw;
        animation-duration: 21.0s;
        animation-delay: -9.8s;
    }

    .snowfall > *:nth-child(19) {
        left: 25.9%;
        width: var(--space-3); height: var(--space-3);
        opacity: 0.11;
        --drift: -2.7vw;
        animation-duration: 23.0s;
        animation-delay: -14.4s;
    }

    .snowfall > *:nth-child(20) {
        left: 51.9%;
        width: calc(var(--space-2) * 0.875); height: calc(var(--space-2) * 0.875);
        opacity: 0.16;
        --drift: 2.4vw; color: var(--pink);
        animation-duration: 19.2s;
        animation-delay: -8.8s;
    }

    .snowfall > *:nth-child(21) {
        left: 20.1%;
        width: calc(var(--space-3) * 1.25); height: calc(var(--space-3) * 1.25);
        opacity: 0.14;
        --drift: 0.7vw;
        animation-duration: 12.2s;
        animation-delay: -11.3s;
    }

    .snowfall > *:nth-child(22) {
        left: 72.3%;
        width: calc(var(--space-3) * 1.25); height: calc(var(--space-3) * 1.25);
        opacity: 0.13;
        --drift: -0.0vw;
        animation-duration: 12.5s;
        animation-delay: -2.9s;
    }

    .snowfall > *:nth-child(23) {
        left: 24.6%;
        width: calc(var(--space-3) * 1.25); height: calc(var(--space-3) * 1.25);
        opacity: 0.1;
        --drift: -1.3vw;
        animation-duration: 14.6s;
        animation-delay: -19.9s;
    }

    .snowfall > *:nth-child(24) {
        left: 59.3%;
        width: var(--space-3); height: var(--space-3);
        opacity: 0.17;
        --drift: -2.7vw; color: var(--pink);
        animation-duration: 21.2s;
        animation-delay: -18.6s;
    }

    .snowfall > *:nth-child(25) {
        left: 70.7%;
        width: calc(var(--space-2) * 1.125); height: calc(var(--space-2) * 1.125);
        opacity: 0.16;
        --drift: 1.2vw;
        animation-duration: 24.0s;
        animation-delay: -2.3s;
    }

    .snowfall > *:nth-child(26) {
        left: 86.1%;
        width: calc(var(--space-3) * 1.25); height: calc(var(--space-3) * 1.25);
        opacity: 0.17;
        --drift: 2.7vw;
        animation-duration: 23.2s;
        animation-delay: -21.8s;
    }

    .snowfall > *:nth-child(27) {
        left: 83.9%;
        width: calc(var(--space-3) * 1.25); height: calc(var(--space-3) * 1.25);
        opacity: 0.16;
        --drift: -3.7vw;
        animation-duration: 12.0s;
        animation-delay: -21.3s;
    }

    .snowfall > *:nth-child(28) {
        left: 1.2%;
        width: calc(var(--space-3) * 1.25); height: calc(var(--space-3) * 1.25);
        opacity: 0.15;
        --drift: -1.1vw; color: var(--pink);
        animation-duration: 17.8s;
        animation-delay: -20.0s;
    }

    /* deco-sneaker, one pass roughly every 51s */
    .snowfall > *:nth-child(29) {
        left: 23.9%;
        width: var(--decor-xl); height: var(--decor-xl);
        opacity: 0.13;
        --drift: -2.7vw;
        --spin: -31deg;
        animation-duration: 51.2s;
        animation-delay: -3.8s;
    }

    /* deco-present, one pass roughly every 57s */
    .snowfall > *:nth-child(30) {
        left: 24.3%;
        width: var(--decor-lg); height: var(--decor-lg);
        opacity: 0.1;
        --drift: -1.3vw;
        --spin: 15deg;
        animation-duration: 57.2s;
        animation-delay: -11.8s;
    }

    /* deco-star, one pass roughly every 59s */
    .snowfall > *:nth-child(31) {
        left: 64.3%;
        width: var(--decor-lg); height: var(--decor-lg);
        opacity: 0.09;
        --drift: -0.8vw;
        --spin: -27deg;
        animation-duration: 59.2s;
        animation-delay: -21.6s;
    }

    /* deco-shoebox, one pass roughly every 51s */
    .snowfall > *:nth-child(32) {
        left: 12.7%;
        width: var(--decor-xl); height: var(--decor-xl);
        opacity: 0.14;
        --drift: 1.7vw;
        --spin: 8deg;
        animation-duration: 50.9s;
        animation-delay: -31.6s;
    }

    /* deco-cap, one pass roughly every 54s */
    .snowfall > *:nth-child(33) {
        left: 65.5%;
        width: var(--decor-lg); height: var(--decor-lg);
        opacity: 0.1;
        --drift: 2.7vw;
        --spin: -40deg;
        animation-duration: 54.5s;
        animation-delay: -38.9s;
    }

    /* deco-sparkle, one pass roughly every 71s */
    .snowfall > *:nth-child(34) {
        left: 58.1%;
        width: var(--decor-lg); height: var(--decor-lg);
        opacity: 0.1;
        --drift: 1.0vw;
        --spin: 6deg;
        animation-duration: 70.6s;
        animation-delay: -46.9s;
    }
}


/* Sizes we need right now. Driven by what she has counted in, so it appears
   and disappears on its own and has to look deliberate either way. Set apart
   from the paragraph above it with a rule rather than a panel: it is a short
   list inside a band that already carries cards, and another box would be one
   box too many. */
.need-now {
    margin-top: var(--space-3);
    /* Matched to the gap above it, so the list reads as its own thing rather
       than running into whatever the band says next. */
    margin-bottom: var(--space-3);
    padding-top: var(--space-2);
    border-top: var(--border-thick) solid var(--accent);
}

.need-now h3 {
    margin-bottom: var(--space-1);
}

.need-now ul {
    margin: 0;
    padding: 0;
    list-style: none;
}

.need-now li {
    margin-bottom: var(--space-1);
    font-size: var(--size-body);
}

.need-now li:last-child {
    margin-bottom: 0;
}

/* The category reads first, then its sizes. Same label treatment as the rest
   of the site, so it is a label and not a heading. */
.need-category {
    font-family: var(--font-heading);
    font-weight: var(--weight-label);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
}


/* ---------- Progress bars ----------
   How far each category has come. Percentages only, so nothing here publishes
   a pair count or a target.

   The fill is purple, which is 2.46:1 against cream and so cannot be what
   tells you where the bar ends. The track carries an ink outline at 15.18:1,
   which draws the shape, and the percentage is written out beside it. Remove
   every colour and the row still says everything it said before. */

.progress {
    margin-top: var(--space-3);
}

.progress h3 {
    margin-bottom: var(--space-2);
}

/* The whole row is the control, so the target is the full column width rather
   than a disclosure triangle. The native marker is kept: it is a non colour
   affordance that already announces its state. */
.progress-group > summary {
    cursor: pointer;
    padding: var(--space-1) 0;
}

.progress-group > summary:focus-visible {
    outline: var(--focus-ring) solid var(--ink);
    outline-offset: var(--focus-offset);
}

/* Not a block: the disclosure marker shares the first line box with whatever
   starts the summary, so a block here pushed the marker onto a line of its
   own above the label. */
.progress-line {
    font-size: var(--size-body);
}

.bar {
    display: block;
    margin-top: var(--space-1);
    height: var(--bar-height);
    background: var(--cream);
    border: var(--border-hair) solid var(--ink);
}

/* --fill is a percentage set per row in the template. It is data, not a design
   value, which is why it is the one number that arrives from the markup. */
.bar::before {
    content: "";
    display: block;
    width: var(--fill, 0%);
    height: 100%;
    background: var(--accent);
}

.progress-sizes {
    margin: var(--space-1) 0 var(--space-2);
    padding-left: var(--space-3);
    list-style: none;
}

.progress-sizes li {
    margin-bottom: var(--space-1);
}

.progress-size {
    display: inline-block;
    min-width: 3ch;
    font-family: var(--font-heading);
    font-weight: var(--weight-label);
    letter-spacing: var(--tracking-label);
}
