/* Cydora — one block per section, in page order.

   Section 06's card offsets and section 09's slot are where the layout has to
   agree with the drawings, so they are the likeliest to move when a scene is
   integrated. */

/* ================================================================
   01 · HERO
   ================================================================ */
/* Full screen, and laid out as two rows: the drawing takes whatever height
   is left, the card sits under it. Both are in flow from the start — the
   intro moves the drawing with a transform rather than by changing the
   layout, so nothing reflows when the card appears. */
.hero {
    /* Up under the header.

       The header is sticky, which means it still takes its 68px out of the
       flow — so the hero began at y=68 and was *also* a full 100vh, and hung
       that 68px below the fold. Its centre was the screen's centre plus the
       whole header. Nothing was the wrong size; the section was in the wrong
       place. Now it starts at 0 and is the screen, so centring in it is
       centring on it.

       Safe to do because the header is paper on paper and no longer reads as
       a bar. The padding below keeps the content out from under it anyway. */
    margin-top: calc(var(--nav-h) * -1);

    min-height: 100vh;
    min-height: 100svh;      /* mobile browsers, where 100vh lies */
    display: grid;
    grid-template-rows: 1fr auto;
    align-items: center;

    /* Symmetric, or `align-content: center` is centring inside a box that is
       not centred — 4rem against 3rem was the other 7px of the drop. The
       --nav-h floor is what stops the headline sliding under the logo now
       that the section reaches the top of the screen. */
    padding: max(var(--nav-h), clamp(2rem, 5vw, 4rem)) var(--gut);
}

.hero__art {
    width: min(920px, 88vw);
    margin: 0 auto;
    justify-self: center;
}

.hero__art .slot {
    --ar: 1152 / 768;        /* the drawing's own viewBox */
    max-height: 56vh;
    margin: 0 auto;
}

/* The div stays, the panel does not. It is still capping the measure,
   centring the text and keeping the headline off the drawing above it — but
   it is no longer a sand block with the hero printed on it.

   Which is why the vertical padding came down from 4.5rem. That was the inset
   of a filled panel; with nothing filled it was 144px of air inside a section
   that has to fit a 495px headline block and the header's clearance into one
   screen. The horizontal padding is untouched — that one is still doing the
   measure. */
.hero__card {
    max-width: var(--wrap);
    margin: 0 auto;
    padding: clamp(1.5rem, 3vw, 2.5rem) clamp(1.5rem, 5vw, 4rem);
    text-align: center;
}

.hero__card .eyebrow { margin-bottom: 1.5rem; }
.hero__card h1 { margin: 0 auto; max-width: 16ch; }
.hero__card .lede { margin: 1.75rem auto; max-width: 54ch; }

/* ---- the intro takeover -------------------------------------------
   While .is-intro is on the page there is nothing but the drawing: the nav
   is out of sight, the card under it is invisible but still occupying its
   space, and the page cannot be scrolled away from. js/scenes/hero.js takes
   all three back at the moment the mark becomes the logotype. */
.is-intro,
.is-intro body { overflow: hidden; }

.is-intro .nav { opacity: 0; pointer-events: none; }

.is-intro .hero__card { visibility: hidden; }

/* Once the drawing leaves the flow the hero is just the headline, so it takes
   the middle of the screen. .is-flying is set the moment the lockup pins
   itself for the flight — the headline is still invisible then, so it settles
   into place before anyone sees it rather than jumping there afterwards.
   .is-clear is the same thing once the drawing is gone for good. */
.hero.is-flying,
.hero.is-clear { grid-template-rows: auto; align-content: center; }

.hero.is-clear .hero__art { display: none; }

/* ================================================================
   02 · PROBLEM
   The wireframe's problem and transition sections, told as one
   pinned story: five dots multiply, two of them split, they fall
   and jam at the bottom of the frame, and then a wire is drawn
   through them. Three chapters of copy ride over that drawing.

   Everything here is position. The motion is js/scenes/problem.js
   and the numbers behind it are the prototype's.
   ================================================================ */
.problem {
    min-height: 100vh;
    min-height: 100svh;
    display: flex;
    align-items: center;
    justify-content: center;
    /* The camera opens tight — 1.12 — so the drawing is bigger than its own
       box for the first half of the story, and this is what keeps the overrun
       inside the section instead of over the page. Padding comes from the
       shared pinned-stage rule further down. */
    overflow: hidden;
}

/* One box for the artwork and the copy both, at the drawing's own 3:2, so a
   percentage means the same thing to either: `left: 3%` on a paragraph is 3%
   of the same 1152x768 the nodes are measured in. Width is whichever is
   smaller — the room across, or the room the height allows — so the frame
   never overflows the stage it has to fit inside. */
.problem__frame {
    --h: calc(100svh - var(--nav-h) - var(--stage-pad) * 2);
    position: relative;
    width: min(100%, calc(var(--h) * 1.5));
    aspect-ratio: 1152 / 768;

    /* …and the copy inside it is sized in cqw, which is a percentage of this
       box, for the same reason its position is. A short wide window — 1600 by
       800 — gives a frame of only 824px but a `vw` still measures 1600, so
       vw-sized type filled half the frame and the friction headline finished
       at 50% down, on top of a wire that starts at 47%. One unit for both,
       and the corner holds at every window size. */
    container-type: inline-size;
}

.problem__art { position: absolute; inset: 0; }

.problem__art .slot {
    --ar: 1152 / 768;      /* the drawing's own viewBox */
    width: 100%;
    height: 100%;
}

/* ---- the three chapters -------------------------------------------
   Parked in the two corners of the frame the drawing never uses.

   Chapter 1 sits down the left: the dots start in the top right and fall
   through the middle, so the left third is free for the whole first half.
   Chapters 2 and 3 sit along the top, which is free from the moment the pile
   lands at the bottom. The swap is invisible because chapter 1 has faded out
   before either of them arrives — see COPY in js/scenes/problem.js, where
   every `out` is a full fade ahead of the `in` that follows it. */
.problem__copy { position: absolute; }
.problem__copy > * { margin: 0 0 1.1em; }
.problem__copy > *:last-child { margin-bottom: 0; }

.problem__copy--problem {
    left: 3%;
    top: 50%;
    transform: translateY(-50%);
    width: 33%;
}

.problem__copy--friction,
.problem__copy--connect {
    /* 19%, not the 8% this was, and the number is not a nudge: the box is
       62% wide, so (100 - 62) / 2 is the only left that puts its centre on
       the frame's centre — and the frame's centre is the viewport's, exactly,
       because .problem is a centring flex row and the frame is its only
       child. Measured: +0px.

       At 8% the box ran 8% to 70%, so its own centre sat at 39% of the frame
       and everything in it was 128px left of the middle of the screen. The
       copy was correctly centred in that box the whole time, which is why it
       could be centred and still look left — two different references, both
       right about their own. This makes the box agree with the screen, so
       there is only one reference left to be right about.

       ?debug=guides draws all of them if this ever comes up again. */
    left: 19%;
    top: 13%;
    width: 62%;

    /* Centred, unlike chapter 1. Chapter 1 is a column down the left edge and
       reads as one, but these two are a headline with a line under it sitting
       across the top of the frame, and a centred headline is what that shape
       wants. */
    text-align: center;
}

/* These two share a corner with a headline in it, so the gap under the
   headline is smaller than the gap between the paragraphs of chapter 1. */
.problem__copy--friction > *,
.problem__copy--connect > * {
    margin-bottom: 0.8em;

    /* The other half of centring these, and the half that is easy to miss.
       Every block in here carries a capped measure — 24ch on the headline,
       46ch and 52ch on the paragraphs — so `text-align: center` above only
       centres the text *inside* a box that is still hard against the left of
       the div. This centres the box. Without it the headline reads as
       centred and the paragraph under it does not, which looks like a
       mistake rather than a choice.

       margin-inline rather than folding it into the shorthand: the rule for
       .problem__copy > * further up is `margin: 0 0 1.1em`, which resets the
       inline margins to 0, and this has to out-rank that half of it without
       restating the vertical rhythm. */
    margin-inline: auto;
}

/* Index's type, sized for the corner it has to fit in. Family, italic em,
   letter-spacing and the muted secondary colour are the site's, unchanged;
   what is tuned here is size, and it is tuned against the drawing rather than
   by eye. The camera lands with the finished wire filling everything below
   47% of the frame, so chapters 2 and 3 have the 34% above it: 13% down to
   47% down, and no further. At the page's full display size the friction
   headline ran to four lines and finished at 61%, sitting on top of the very
   dots it is about. Wider measure, one size down, three lines. */
.problem__copy .display {
    font-size: clamp(1.15rem, 3.8cqw, 2.4rem);
    line-height: 1.12;
    max-width: 24ch;
}

.problem__copy p {
    font-size: clamp(0.75rem, 1.75cqw, 1.0625rem);
    line-height: 1.55;
    color: var(--muted);
    max-width: 46ch;
}

/* two classes, so these win over the rule above on their own */
.problem__copy .strong {
    font-size: clamp(0.8rem, 1.95cqw, 1.15rem);
    color: var(--ink);
}

.problem__copy .lede {
    font-size: clamp(0.8rem, 1.8cqw, 1.1rem);
    max-width: 52ch;
}

.problem__copy .note { max-width: 52ch; }
/* Chapter 1's heading. Smaller than the one chapters 2 and 3 carry, because
   it lives in a 33%-wide column down the left while they span 62% across the
   top — the same size in a third of the width is a headline that wraps to
   five lines. Left-aligned, like the column it is in; the centring further up
   is only for the two chapters that sit across the frame.

   Tighter under it than between the paragraphs: the gap after a heading
   belongs to the heading, and 1.1em of it reads as a gap between two
   unrelated blocks. */
.problem__copy--problem .display {
    font-size: clamp(1rem, 2.5cqw, 1.65rem);
    line-height: 1.15;
    max-width: 18ch;
    margin-bottom: 0.7em;
}

/* ---- narrow screens: the frame stops being one box for both ------------
   On a wide screen the artwork and the copy share a single 3:2 frame, and the
   copy is parked in corners the drawing never uses. None of that survives
   being made narrow, and the reason is in the artwork rather than the layout:
   the seven dots occupy 477x180 of a 1152x768 viewBox — 41% of the width and
   23% of the height, in the lower right. The rest is empty canvas the desktop
   camera is deliberately not zooming into, because it is holding the left
   third clear for chapter 1's column.

   So on a phone the drawing rendered about 166px across in a 440px screen,
   the three chapters were crammed into the corners of a 267px-tall box, and
   600px of screen underneath sat empty.

   Here the frame becomes two rows instead: the copy on top, the drawing
   underneath. Read down the page and you get the claim first and the picture
   of it second, which is the order the chapters are written in.

   The drawing gets a tall box and *covers* it — `slice` rather than the
   default `meet`, so it fills the box and the empty sides of the viewBox are
   cropped away instead of being letterboxed into view. js/scenes/problem.js
   sets that, and swaps to a camera that centres the ink rather than dodging a
   column that is no longer beside it. The copy is at a size meant for reading
   rather than for fitting in a corner. */
@media (max-width: 780px) {
    .problem { align-items: stretch; }

    /* auto then 1fr, and that way round matters: the copy row takes exactly
       the height of the tallest chapter and the drawing gets everything left
       over. The other way round the copy row was `1fr` — 331px holding 266px
       of text, with the 65px difference sitting as air between the two.

       The row cannot collapse under the drawing either: chapter 1 always
       lives here (it needs 281px and the band above the ink is 53px at its
       tightest, so it will never be placed into the artwork), and it is the
       tallest of the three. So the row's height is chapter 1's height, and it
       does not move when the other two are placed elsewhere.

       No gap — the copy sits directly on the artwork, and any air between
       them belongs to the drawing, which already opens on a good deal of
       empty paper above the dots. */
    .problem__frame {
        width: 100%;
        height: 100%;
        aspect-ratio: auto;
        display: grid;
        grid-template-rows: auto 1fr;
        gap: 0;
    }

    /* Taller than the drawing's own 3:2, which is the point: with `slice` the
       artwork covers this box, so a taller box means bigger ink. overflow is
       clipped here rather than on the svg — `.slot.is-filled > svg` keeps
       `overflow: visible` because mid-animation strokes ride outside the
       viewBox, and that has to stay true. */
    /* Full bleed, and no ratio of its own.

       The ratio went because it was a cap: at 4:5 the drawing could only ever
       be 500px tall however much room the row was given, so the height won
       back above had nowhere to go. `height: 100%` takes the row instead, and
       js/scenes/problem.js measures whatever shape that turns out to be —
       nothing downstream needs to be told.

       The width comes from the section's gutter being handed back. A gutter
       is for text; this is a drawing, and 20px of paper either side of it is
       20px the dots are not using. The copy keeps its own, so only the
       artwork reaches the edges. .problem is `overflow: hidden`, so the bleed
       cannot spill onto the page.

       grid-column is explicit because a chapter can be placed into this same
       cell (see data-home below). With only a row named, auto placement looks
       for the first *free* column and would invent a column 2 for whichever
       arrived second, collapsing the artwork beside the text. */
    .problem__art {
        position: relative;
        inset: auto;
        grid-row: 2;
        grid-column: 1;
        height: 100%;
        width: calc(100% + var(--gut) * 2);
        margin-inline: calc(var(--gut) * -1);
        overflow: hidden;
    }

    /* All three chapters share the second row, so they still stack and
       cross-fade exactly as they do on a wide screen — nothing in
       js/scenes/problem.js changes for this. */
    /* All three share the top row, so they still stack and cross-fade in
       place, and they need a common edge or the swap reads as a jump — they
       are 266px, 198px and 189px tall.

       `end`, not `start`. The row is taller than any of them, so aligning the
       tops left that difference sitting between the text and the drawing, and
       a different amount of it per chapter: 36px under chapter 1 against
       113px under chapter 3. Aligning the bottoms puts every chapter directly
       on the artwork and moves the slack above the text, where the varying
       length is not something you can see. */
    .problem__copy {
        position: relative;
        grid-row: 1;
        grid-column: 1;
        top: auto;
        left: auto;
        width: auto;
        transform: none;
        align-self: end;
    }

    /* ---- a chapter that fits the drawing's free space goes into it -------
       js/scenes/problem.js walks each chapter's run, finds the tightest the
       band above and below the ink ever gets across it, and writes the answer
       here as data-home plus the band's height in --band.

       Three outcomes, and the third is the important one. `top` and `bottom`
       put the chapter over the artwork in whichever band is bigger for its
       whole time on screen. `row` is what it gets when it fits neither — the
       row above the drawing, which is where every chapter sits by default and
       always has room. So on a screen where the copy cannot share space with
       the drawing, it simply does not try.

       These are grid items in the artwork's cell and come after it in the
       markup, so they paint over it; z-index only says so out loud. They are
       not clipped by its `overflow: hidden` either — that is on
       .problem__art, and these are its siblings. */
    .problem__copy[data-home="top"],
    .problem__copy[data-home="bottom"] {
        grid-row: 2;
        grid-column: 1;
        max-height: var(--band);
        z-index: 1;
        pointer-events: none;
    }

    .problem__copy[data-home="top"]    { align-self: start; }
    .problem__copy[data-home="bottom"] { align-self: end; }

    /* The corner sizes were tuned to fit 34% of a frame. This copy has room
       of its own now and can be read at the size the rest of the page uses. */
    .problem__copy .display { font-size: clamp(1.45rem, 7cqw, 2.4rem); }
    .problem__copy p       { font-size: clamp(0.95rem, 4.2cqw, 1.0625rem); }
    .problem__copy .strong { font-size: clamp(1rem, 4.6cqw, 1.15rem); }
    .problem__copy .lede   { font-size: clamp(1rem, 4.4cqw, 1.1rem); }
    .problem__copy .note   { max-width: none; }
    .problem__copy p       { max-width: none; }
}

/* ---- the still frame ----------------------------------------------
   No GSAP, or motion turned down. The three chapters are parked in three
   different corners and only ever one of them is up at a time, so with
   nothing to fade them they land on top of each other and none can be read.
   There is no story to tell here, so the section stops pretending to be one:
   the copy becomes a column and the drawing follows it at its finished frame.

   Set from JS rather than from a media query, because it also has to cover
   GSAP simply not being there. */
.problem.is-still {
    min-height: 0;
    display: block;
    overflow: visible;
    /* back to the band's own spacing: the stage rule's padding exists to
       clear a sticky header off a pinned section, and nothing is pinned now */
    padding-block: clamp(4rem, 10vw, 8.5rem);
}

.problem.is-still .problem__frame {
    width: 100%;
    max-width: var(--wrap);
    aspect-ratio: auto;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    gap: clamp(2rem, 5vw, 3.5rem);
}

.problem.is-still .problem__art {
    order: 2;
    position: relative;
    inset: auto;
    aspect-ratio: 1152 / 768;
}

.problem.is-still .problem__copy {
    position: static;
    transform: none;
    width: auto;
    max-width: 62ch;

    /* Left, even for the two chapters the pinned story centres. Centring
       works for a headline across the top of a frame; a 62ch column of
       centred body copy, which is what this path is, does not. */
    text-align: left;
}

.problem.is-still .problem__copy > * { margin-inline: 0; }

/* ================================================================
   04 · APPROACH
   The spiral in the middle, its four steps parked at the corners
   where the wireframe puts them. On narrow screens the artwork
   drops out of the flow and the steps become an ordered list.
   ================================================================ */
/* A pinned stage rather than a section you scroll past: the line draws as you
   scroll, and each step's text arrives with the marker it belongs to. The
   heading stays put above it the whole way, so the reader keeps the frame.

   Flex, not grid: `display: grid` with `align-content: center` collapses a
   1fr row to its content, which left the figure 773x270 and the drawing 185px
   across. Centring a single child is what flex is for. */
.approach {
    min-height: 100vh;
    min-height: 100svh;
    display: flex;
    align-items: center;
    /* Padding comes from the shared stage rule below — the band's usual
       136px top and bottom is 272px the spiral would not get. */
}

.approach > .wrap { width: 100%; }

/* Smaller than a section title elsewhere on the page: these have to share a
   single screen with a drawing and several blocks of text, and every pixel
   they give up goes to the artwork. Sizes are in the shared rule above. */
.approach__head h2 { max-width: 20ch; }
.approach__head .note { max-width: 52ch; font-size: 15px; }

/* The spiral takes the middle and the four steps sit around it, the way the
   wireframe draws them — placed on the figure rather than in columns beside
   it, so the drawing gets the width instead of the labels. */
.approach__figure { position: relative; }

.approach__art {
    width: min(720px, 60%);
    margin: 0 auto;
}

.approach__art .slot {
    --ar: 973 / 675;      /* the drawing's own viewBox */
    max-height: 46vh;
    margin: 0 auto;
}

.step { position: absolute; width: min(17rem, 26%); }
.step h3 { font-size: 1.2rem; margin-bottom: 0.6rem; }
.step p { font-size: 14px; color: var(--muted); margin: 0; }

.step--1 { top: 0; left: 0; }
.step--2 { top: 0; right: 0; text-align: right; }
.step--3 { bottom: 0; left: 0; }
.step--4 { bottom: 0; right: 0; text-align: right; }

/* Narrow: the steps become a list, and the drawing does not come with them.

   It used to: it gave up the middle and sat on top of the list as a finished
   spiral. But a spiral is a drawing of a journey, and the whole of what it
   had to say was in the drawing of it — js/scenes/approach.js goes static at
   this same width, so what was left was the answer to a story nobody had
   been told, holding half a screen above the four steps that say it in
   words. The steps are the content here; the spiral was the telling.

   The wrapper, not the slot inside it. .services__art hides its slot because
   there is a card underneath that stays, but this is a bare grid item — hide
   the slot and the item survives as an empty row, and the figure's 2rem gap
   is still spent on it. There is also a fetch in it: js/main.js watches the
   slot with an IntersectionObserver, and an element with no box never
   intersects, so the drawing is not merely hidden on a phone, it never
   arrives.

   The number is js/scenes/approach.js's and css/layout.css's .cols, and the
   three have to agree. */
@media (max-width: 900px) {
    .approach { display: block; min-height: 0; }
    .approach__figure { display: grid; gap: 2rem; }
    .approach__art { display: none; }
    .step { position: static; width: auto; text-align: left !important; }
}

/* ================================================================
   05 · DIAGNOSTIC PILOT
   ================================================================ */
.pilot .eyebrow { color: #9b958a; margin-bottom: 1.25rem; }
.pilot h2 { max-width: 10ch; }
.pilot .lede { margin: 1.5rem 0 2rem; }

/* The scan sits beside the pitch, the way the about section's drawing sits
   beside its copy, rather than as a full-width block under it — which is
   what it was, and which pushed "what you get" most of a screen further
   down for no gain.

   `align-self: start` so the square drawing does not stretch to whatever
   height the text column happens to be, and a cap so it stays a companion
   to the copy rather than the biggest thing in the section. */
.pilot__scan {
    --ar: 2266 / 2360;      /* the drawing's own viewBox */
    align-self: start;
    /* The whole of its column, which is half of .cols less its gap, so the
       scan scales with the measure instead of stopping at a number. It was
       capped at 460px before, which on a wide screen left it floating in the
       middle of an empty half. Centred anyway, so it stays put if anything
       ever does cap it again. */
    width: 100%;
    margin-inline: auto;
}

/* One column from here down, and the scan becomes a square sitting under the
   copy. It has to stay square — the lit sheet travels the whole box — but
   only one plane of the lattice is lit at a time, so at full width most of
   that square is empty and it costs half a screen of scrolling to say very
   little. Held to something that reads as a companion to the text instead.

   Not display:none, which is what the approach drawing gets: this is the
   only picture the section has, and hiding it would also cancel its lazy
   fetch and leave the slot's reserved space behind. */
@media (max-width: 900px) {
    /* One column from here down, so there is no half to fill: a full-width
       square under the copy is most of a screen of mostly-empty lattice. */
    .pilot__scan {
        width: min(300px, 72%);
        margin-top: clamp(1.75rem, 6vw, 2.75rem);
    }
}

.pilot__gets { margin-top: clamp(2rem, 5vw, 3.5rem); }
.pilot__gets h3 {
    font-family: var(--body);
    font-size: 14px;
    color: #9b958a;
    margin-bottom: 1.5rem;
}

.pilot__gets ul { margin: 0; padding: 0; list-style: none; }
.pilot__gets li {
    font-size: clamp(1.1rem, 2.2vw, 1.6rem);
    line-height: 1.5;
    margin-bottom: 0.75rem;
}

/* ================================================================
   06 · SERVICES
   A pinned stage, like the approach: the line runs down the middle and
   branches left, right, left, right, and each service arrives as its
   branch reaches it. The cards sit at the height of their own dot —
   29.7%, 53.0%, 74.7% and 97.8% down the drawing, measured from it —
   so a branch ends where its card is rather than near it.
   ================================================================ */
/* ---- the pinned stages ------------------------------------------------
   Every story section shares this. A pinned section starts with its top at
   the top of the viewport, which is exactly where the sticky header is, so
   each one has to reserve the header's height or the title ends up underneath
   it on the way back up. The approach's spacing is the reference; the others
   follow it rather than keeping their own numbers.

   The problem stage reads --stage-pad back out in .problem__frame's --h, so
   the frame knows how much of the screen is actually left for it. */
.problem,
.approach,
.services {
    --stage-pad: clamp(1.25rem, 3vh, 2.5rem);
    padding-top: calc(var(--nav-h) + var(--stage-pad));
    padding-bottom: var(--stage-pad);
}

.approach__head,
.services h2 {
    margin-bottom: clamp(1rem, 2.5vw, 2rem);
}

.approach__head h2,
.services h2 {
    font-size: clamp(1.9rem, 3.6vw, 2.9rem);
}

.services {
    /* --screens is how many screenfuls tall the drawing is. At 1 the whole
       spine fits at once and every card has to be small; at 2 the reader is
       taken down it in two passes and the illustrations get their proper
       shape back. 4 gives a screen per service — cinematic, but four screens
       of pinned scroll for four cards. */
    --screens: 2;

    /* one number for the card's width, the tailroom that follows from it, and
       the scene's preferred width in js/scenes/services.js */
    --card-w: 20rem;

    min-height: 100vh;
    min-height: 100svh;
    height: 100svh;
    display: flex;
    align-items: stretch;
    overflow: hidden;
}

.services > .wrap {
    width: 100%;
    display: flex;
    flex-direction: column;
}

/* The drawing is 450x922 — very tall and narrow — so it takes the middle
   column and the cards hang off it on either side. */
/* The window the camera looks through. The drawing is taller than this and
   slides up behind it as the line grows, so what is clipped here is the part
   of the story the reader has not reached or has already passed. */
.services__figure {
    position: relative;
    flex: 1;
    overflow: hidden;
}

/* Exactly as wide as the drawing, no wider. The box used to be set by a width
   while the height was capped by max-height, so the drawing sat 210px wide
   inside a 256px box — and since the cards are placed against this box, every
   one of them ended up 23px further from its dot than intended. Driving the
   width from the height instead makes the box and the drawing the same thing. */
.services__art {
    position: relative;
    width: fit-content;
    margin: 0 auto;
    /* Room for exactly one card past the end of the drawing, and nothing more.

       The fourth dot sits at 97.8% of the drawing and its card hangs below it,
       so the camera has to be able to pan a card's height past the last
       branch or the card has nowhere to go. Derived from the card's own size
       rather than picked as a round number: --card-w wide, the illustration
       on top of it at the artwork's own 1520:1745 (1.148 tall for every 1
       across), plus its title, button and the clearance under the dot.

       It has moved every time the art's shape has — 1.25 for the original 4:5
       placeholder, 1.346 for the cropped photograph, and now the SVG's crop.
       Each time it was left too short, so it is worth reading off the drawing
       rather than nudging until it fits.

       It costs nothing in scrolling — the scene's length is measured against
       the drawing, not this. Border-box keeps the drawing itself at exactly
       --screens worth of stage. */
    --tailroom: calc(var(--card-w) * 1.148 + 9rem);
    height: calc(var(--screens) * 100% + var(--tailroom));
    padding-bottom: var(--tailroom);
    will-change: transform;
}

/* Direct child only. The cards live inside this box, so an unscoped
   `.services__art .slot` also caught every card's illustration and gave it the
   spine's 450x922 ratio — 623px tall placeholders that overlapped each other
   by 320px. */
.services__art > .slot {
    --ar: 450 / 922;      /* the drawing's own viewBox */
    height: 100%;
    width: auto;          /* derived from the height by the aspect ratio */
    margin: 0 auto;
}

/* Cards are placed against the drawing, not the figure, so they track the
   dots whatever height the drawing ends up. --at is the dot's own position. */
/* Four cards share one screen alongside the spine, and the two on each side
   are only ~45% of the drawing's height apart. That is the whole budget: a
   card can be no taller than that gap, which is what keeps the illustration a
   wide band here rather than the portrait block the wireframe draws. */
/* Below its dot, not centred on it. Centring put half of each card above the
   line it belongs to, which at this zoom pushed the top of the card out of
   frame — and read as the card floating beside the branch rather than hanging
   off it.

   --at is only a fallback. js/scenes/services.js overwrites `top` with the
   dot's measured position, because a percentage resolves against this box,
   which includes the tailroom padding — so 29.7% of the box is 132px below
   29.7% of the drawing. */
.card {
    position: absolute;
    top: var(--at);
    width: min(var(--card-w), 30vw);   /* overwritten by the scene */
}

/* --reach is the gap between the drawing's edge and the card. Small on
   purpose: the branch has to look like it arrives at the card, not like it
   stops short of one. */
/* Fallbacks only — js/scenes/services.js measures each dot and its branch and
   overwrites top, the inner edge and the width. These keep the cards roughly
   right if the scene never runs. */
.card--1 { --at: 29.7%; right: 92%; }
.card--2 { --at: 53.0%; left: 92%; }
.card--3 { --at: 74.7%; right: 94%; }
.card--4 { --at: 97.8%; left: 94%; }

/* ---- read more -------------------------------------------------------
   This used to turn the card over. It opens in place instead: the
   illustration shrinks where it stands and the description arrives under it,
   so the reader keeps the picture they were reading about rather than having
   it swapped for its own back.

   It also drops the flip's built-in limit. Both faces had to be the same
   size, which meant the description could only ever be as long as the front
   happened to be tall — the copy is not written yet, and it should not have
   to be written to a height nobody chose on purpose. */
.card__flip { position: relative; }

/* What the illustration keeps of its width once the description is open.
   It is 1.148 tall for every 1 across, so at --card-w 20rem this hands back
   about 130px of height — a short paragraph and its button, which is what
   has to go there. Down from a card that was already 85% of its old size, so
   the whole thing stays inside the space it had. */
.card { --open-art: 0.65; }

.card.is-open .card__media { width: calc(var(--open-art) * 100%); }

.card__back {
    display: flex;
    flex-direction: column;
    gap: .6rem;
    padding-top: .25rem;
}

/* js/flip.js closes it with the `hidden` attribute, which takes it out of
   the tab order and away from a screen reader as well as off the page — but
   the UA's `[hidden] { display: none }` loses to the `display: flex` above,
   so it has to be said again here or a closed card shows its description. */
.card__back[hidden] { display: none; }

.card.is-open .card__back { animation: card-open .32s ease both; }

@keyframes card-open {
    from { opacity: 0; transform: translateY(-0.4rem); }
    to { opacity: 1; transform: none; }
}

/* Portrait again, as the wireframe draws it. With the drawing two screens
   tall the two cards on each side are most of a screen apart, so the height
   budget that forced a 12:5 band is gone. */
/* The illustration. All four share one shape — the crop tools/extract.py
   takes out of the SVG master's A4 page, 1520x1745 — so nothing is
   letterboxed and the set stays a set while the cards are converted one at a
   time. A placeholder and a real drawing occupy exactly the same box, which
   is what makes swapping one for the other invisible to the rest of the page.

   These are .slot now rather than <img>: the drawing goes into the DOM as
   real SVG so it can be scaled and animated. --ar is what .slot reads. */
.card__media {
    --ar: 1520 / 1745;
    border-radius: 2px;

    /* 100%, which is what a block div is anyway — but it has to be said, or
       opening the card snaps instead of moving. A transition needs two
       lengths to interpolate between, and `auto` is not one: auto -> 65%
       computes as a jump, which is what this did until it was measured. */
    width: 100%;
    transition: width .45s cubic-bezier(.4, 0, .2, 1);
}

/* The plate the drawing is printed on, once there is a drawing. The art is
   dark ink and pale colour on nothing, so it needs light under it in both
   themes — on --paper gone dark it all but vanishes. See --plate.

   Only when filled: an empty slot is a dashed outline and a label, and the
   swatch modifiers on cards 2-4 bring their own colour. */
.card__media.is-filled { background: var(--plate); }
.card h3 { font-size: 1.15rem; line-height: 1.25; margin: .7rem 0 .4rem; }
.card__back p { font-size: 13px; color: var(--muted); margin: 0; }
.card__back p.is-pending { font-style: italic; }

.card__more {
    align-self: flex-start;
    border: 0;
    background: none;
    padding: 0;
    font: inherit;
    font-size: 12px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ink);
    cursor: pointer;
    border-bottom: 1px solid var(--rule);
    padding-bottom: 2px;
}

.card__more:hover { border-color: var(--ink); }

/* The rule under the label *is* the label's underline, so this control
   cannot be grown with padding — the line would drift away from the text it
   belongs to. The hit area grows instead: an invisible box centred on the
   label, which turns a 12px word with a 22px box into a 44px target without
   moving a pixel of what you can see. */
.card__more { position: relative; }

.card__more::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    translate: 0 -50%;
    min-width: 44px;
    height: 44px;
}

/* The drawing needs its width; below this the story becomes a list.

   js/scenes/services.js matches this same 900px and returns before it pins
   anything or places anything, so what is left here is a plain column. */
@media (max-width: 900px) {
    .services { display: block; min-height: 0; height: auto; overflow: visible; }
    .services__figure { overflow: visible; }

    /* Not display:none, which is what this used to be. The cards live *inside*
       the art box — that is what makes them ride the camera — so hiding the
       box took all four services with it, and below 900px the section was a
       heading and nothing else. Only the drawing goes. */
    .services__art {
        height: auto;
        width: auto;
        padding-bottom: 0;
        will-change: auto;
    }

    .services__art > .slot { display: none; }

    /* Capped, not full width: without a limit the card stretches to the whole
       column, which on a 900px screen is a 1200px-tall illustration. The
       number matches the 24rem in each image's `sizes`.

       !important on the three the scene writes inline. It only writes them
       when it runs, and it does not run at this width — but a window resized
       across the breakpoint keeps whatever it set on the way past, and an
       inline style is not something a stylesheet can outrank any other way. */
    /* Centred, and 85% of the size it was.

       `max-width` with no auto margins is what left the column pinned to the
       left of the screen: on a phone the cards fill the width and it never
       shows, but anywhere between the cap and the 900px breakpoint — a small
       tablet, a half-width window — the whole section sat in the left third
       with the rest of the row empty. */
    .card {
        position: static;
        width: auto !important;
        top: auto !important;
        left: auto !important;
        max-width: 20.4rem;          /* was 24rem */
        margin: 0 auto 2.5rem;
    }
}

@media (prefers-reduced-motion: reduce) {
    .card__media { transition: none; }
    .card.is-open .card__back { animation: none; }
}

/* ================================================================
   07 · USE CASES
   A list on the left with the hand-drawn ring around whichever
   item is live, and its detail panel on the right. Clicking an
   item re-circles it and swaps the panel — js/scenes/use-cases.js
   owns both, and everything here is only where things sit.
   ================================================================ */
.cases h2 { margin-bottom: clamp(3rem, 8vw, 5rem); max-width: 20ch; }

/* The ring's positioning context. It is placed over the live item from
   measurements, so it needs one box that does not move while it does. */
.cases__stack { position: relative; }

.cases__list { margin: 0; padding: 0; list-style: none; }

/* The ring is drawn wider than the words it circles — 18px of clear paper
   each side, and the drawing's own 0.914 inner ratio on top of that, which on
   the longest label comes to 28px of overhang. Against the 20px gutter a
   phone has, that put the left arc 12px off the side of the screen and the
   circle arrived cut in half.

   So the stack takes the gutter back and becomes full-bleed, which is where
   the ink needs to be, and the list hands it straight back as padding plus
   the 16px the overhang is still short of. The words move 16px; the circle
   lands. js/scenes/use-cases.js measures the button against the stack rather
   than against the page, so it follows both of these without being told. */
@media (max-width: 760px) {
    .cases__stack { margin-inline: calc(var(--gut) * -1); }
    .cases__list  { padding-inline: calc(var(--gut) + 1rem); }
}

.cases__item {
    font-family: var(--display);
    font-size: clamp(1.2rem, 2.2vw, 1.6rem);
    padding: 0.9rem 0;
}

.cases__item button {
    position: relative;
    z-index: 1;      /* the text sits over its own circle, not under it */
    border: 0;
    background: none;
    padding: 0;
    font: inherit;
    text-align: left;
    color: var(--ink);
    cursor: pointer;
    opacity: 0.55;
    transition: opacity 0.25s ease;
}

.cases__item button[aria-current="true"],
.cases__item button:hover,
.cases__item button:focus-visible { opacity: 1; }

/* The ring from cydora-use-cases/. Size and position come from the scene
   file, which measures the live item and the drawing's own proportions —
   there is no ratio to type here, and typing one would only be a second
   opinion that goes stale. It starts undrawn: the artwork ships with
   stroke-dashoffset="1", which is the one drawing in svg/ that does. */
.cases__ring {
    position: absolute;
    left: 0;
    top: 0;
    border: 0;                /* no dashed placeholder over live text */
    aspect-ratio: auto;
    pointer-events: none;
}

/* ---- the panel ------------------------------------------------------
   All six details share one grid cell, so the panel is as tall as the
   longest of them and swapping one for another moves nothing else on the
   page. Only the live one is visible; the scene fades between them, and
   the rule below is what the page looks like before it has. */
.cases__panel {
    display: grid;
    background: var(--sage);
    border-radius: 14px;
    padding: clamp(2rem, 4vw, 3rem);
    align-self: start;
}

.cases__detail { grid-area: 1 / 1; }

.cases__detail:not(.is-current) { opacity: 0; visibility: hidden; }

/* On the detail, not on the panel: below 900px the details move out of the
   panel and into the list, and type that was keyed to where they used to
   live would have been left behind. */
.cases__detail p { font-size: clamp(1.05rem, 1.8vw, 1.35rem); }

/* Mixed rather than sampled: the panel's own green is a token that swaps with
   the theme, and the fixed #4f5545 that used to be here was a dark green on a
   dark green once the OS went dark. */
.cases__detail .label {
    margin: 2rem 0 0;
    font-size: 13px;
    color: color-mix(in srgb, var(--ink) 62%, var(--sage));
}

/* ---- one column: the panel becomes an accordion ---------------------
   Below .cols's breakpoint the panel lands under all six items, so the
   answer is off screen at the moment the question is tapped. js/scenes/
   use-cases.js moves each detail into its own <li>, inside a drawer, and
   marks the section — so the panel it left is empty and goes.

   The drawer is the only thing that moves: closed at zero height with its
   content clipped, opened by the scene, which is the only place that knows
   what the item it belongs to is doing. Nothing here opens one, which is
   why there is no `.is-open` — the height is animated, and a class that
   also set it would be a second opinion arriving mid-tween.

   The width is .cols's number and has to stay it: below it there is one
   column, and one column is the whole reason for this. */
@media (max-width: 900px) {
    .cases.is-accordion .cases__panel { display: none; }

    .cases.is-accordion .cases__drawer {
        height: 0;
        overflow: hidden;
    }

    /* Out of the shared grid cell it was sharing with five others, and into
       being a card of its own: the panel's paper comes with it, since the
       panel is not on the page any more.

       The gap above is --ring-drop, which the scene measures: it is how far
       the ring's bottom arc hangs below the words, so the card starts just
       under the ink rather than beneath it. The fallback is only for the
       frame before the scene has measured anything. */
    .cases.is-accordion .cases__detail {
        grid-area: auto;
        margin-top: calc(var(--ring-drop, 1.8rem) + 0.35rem);
        background: var(--sage);
        border-radius: 14px;
        padding: clamp(1.1rem, 4.5vw, 1.75rem);
    }

    .cases.is-accordion .cases__detail .label { margin-top: 1.25rem; }
}

/* ================================================================
   08 · ABOUT
   ================================================================ */
.about h2 { max-width: 16ch; }
.about .slot { --ar: 1912.91 / 1928.54; }   /* the drawing's own viewBox */

.team { margin-top: clamp(4rem, 10vw, 7rem); }
.team > h3 {
    font-family: var(--body);
    font-weight: 500;
    font-size: 1.35rem;
    margin-bottom: clamp(2rem, 5vw, 3.5rem);
}

.team__grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
    gap: clamp(1.5rem, 4vw, 3rem);
}

.person .slot { --ar: 3 / 4; }
.person h4 {
    margin: 1.25rem 0 0.35rem;
    font: 500 1.25rem/1.2 var(--body);
    text-transform: uppercase;
    letter-spacing: 0.02em;
}

.person .role { margin: 0 0 1rem; font-size: 14px; color: var(--muted); }
.person .bio { font-size: 14px; color: var(--muted); }

/* the founder's card runs wide, portrait beside the bio */
.person--lead {
    grid-column: span 2;
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);
    gap: clamp(1.5rem, 4vw, 2.5rem);
    align-items: start;
}

.person--lead h4 { margin-top: 0; }

@media (max-width: 760px) {
    .person--lead { grid-column: span 1; grid-template-columns: 1fr; }
}

/* ================================================================
   09 · WHY
   ================================================================ */
/* ---- the pinned stage -------------------------------------------------
   The whole section is held now, title included. The title used to sit in a
   .wrap above this and scroll away on the way in, which meant the claim the
   seven points argue for was off screen for the entire argument.

   The header's height is reserved here for the same reason it is on the
   other pinned sections: a pin starts with its top at the top of the
   viewport, which is exactly where the sticky header is. */
/* Three children — the title, the drawing, the points — spread down the
   stage with `space-between`, which is what makes the two gaps equal by
   definition rather than by a number that has to be kept in step with the
   title's height. Title against the top, points against the bottom, drawing
   in the middle with the same air either side of it. */
.why__stage {
    --stage-pad: clamp(1.25rem, 3vh, 2.5rem);
    min-height: 100vh;
    min-height: 100svh;
    padding: calc(var(--nav-h) + var(--stage-pad)) var(--gut) var(--stage-pad);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: space-between;
    overflow: hidden;
}

/* Constant, and at the top. `flex: none` keeps it out of the centring below
   — the drawing is centred in what the title leaves, not in the stage, which
   are different by half the title's height. */
.why__title {
    flex: none;
    max-width: 18ch;
    margin: 0 auto;
    text-align: center;
}

/* Sized to the drawing, not to the leftover. `flex: 0 1 auto` is the whole
   difference: at `1 1 auto` this box swallowed every spare pixel and the slot
   inside, having nothing to fill, collapsed to its intrinsic 300x200 in the
   middle of it. It shrinks if it has to and never grows — which is also what
   leaves `space-between` some free space to distribute. */
.why__art {
    flex: 0 1 auto;
    min-height: 0;
    width: min(760px, 92vw);
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Width drives, height follows from the ratio, and max-height caps it on a
   short screen.

   That way round because vertical space is the scarce one. Driving from the
   height instead left the box at its full 52svh while max-width clamped the
   width — aspect-ratio does not survive both being constrained — so on a
   phone the box was 405x497 holding a drawing 405x270, and the 227px
   difference sat as a gap between the mark and the points. Capped by height,
   the leftover goes sideways instead, where nobody can see it. */
.why__art .slot {
    --ar: 1152 / 768;      /* the drawing's own viewBox */
    width: 100%;
    height: auto;
    max-height: 52svh;
    margin: 0 auto;
    border: 0;
}

/* ---- the reasons ------------------------------------------------------
   Stacked on top of each other and faded through, so the column never
   reflows as the text changes and the drawing above it never shifts. The
   height is reserved for the longest one; if a reason outgrows the space,
   raise min-height rather than letting it push the artwork. */
/* 100%, not 92vw. The viewport is the wrong thing to measure against: this
   sits inside .why__stage, which is padded, so on a 375px screen 92vw came to
   345px inside a box with room for 335 and every point that was long enough to
   wrap ran five pixels off each side and was clipped. A percentage resolves
   against the box it is actually in, so it cannot disagree with the padding,
   and it keeps agreeing if the padding ever changes. */
.why__reasons {
    position: relative;
    flex: none;
    width: min(680px, 100%);
    min-height: 9rem;
}

.reason {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    text-align: center;
}

/* The 01 / 07 counter, kept as a spacer.

   `visibility: hidden` rather than `display: none` or deleting it: the point
   underneath is centred in a fixed-height box, and taking the counter out of
   the flow would pull every point up by its line plus the gap — the drawing
   above would then sit at a different distance from the text than the title
   does from the drawing, which is the rhythm this section was just given.
   Hidden, it keeps its box and costs nothing to restore.

   It goes from the accessibility tree with it, which is right: "01 / 07" read
   aloud before each point is noise once the numbers are not on screen. */
.reason__num {
    visibility: hidden;
    font-size: 11px;
    letter-spacing: 0.14em;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}

/* One statement per point, so it is the point rather than a caption under a
   heading — these carried an h3 and a muted paragraph when there were six of
   them and five were placeholders. The size is the h3's; the colour is the
   ink, not --muted, because there is nothing above it to be secondary to. */
.reason p {
    margin: 0 auto;
    max-width: 46ch;
    font-size: clamp(1.05rem, 2.2vw, 1.4rem);
    line-height: 1.4;
    color: var(--ink);
}

.reason p.is-pending { font-style: italic; }

/* ---- the still frame --------------------------------------------------
   No GSAP, or motion turned down. Stacked and faded, the seven points land on
   top of each other and none of them can be read — so the stage stops being
   a stage and they become the list they already are in the markup. */
.why.is-static .why__stage {
    min-height: 0;
    display: block;
    padding: clamp(3rem, 8vw, 6rem) var(--gut);
    overflow: visible;
}


.why.is-static .why__title {
    max-width: 18ch;
    margin: 0 0 clamp(2.5rem, 6vw, 4rem);
    text-align: left;
}

.why.is-static .why__reasons {
    min-height: 0;
    display: grid;
    gap: 2.5rem;
    margin: clamp(3rem, 8vw, 6rem) auto 0;
}

.why.is-static .reason {
    position: static;
    inset: auto;
    opacity: 1;
    text-align: left;
}

/* ================================================================
   10 · FINAL CTA
   ================================================================ */
.final { text-align: center; }
.final h2 { margin: 0 auto 2rem; max-width: 16ch; }
.final p { margin: 0 auto 1.25rem; max-width: 52ch; }
.final .btn { margin-top: 1.5rem; }
