/* ═══════════════════════════════════════════════════════════════════════
   fellowship.css — THE FELLOWSHIP PAGE, and nothing else.

   Every rule here names a component only fellowship.html uses (plus the globe
   study, _globe-study, which loads this file too). Shared rules — tokens, nav,
   footer, buttons, the page banner, and the envelope that the home page also
   uses — stay in style.css, which loads FIRST, so a rule here wins over a
   style.css rule of equal specificity.

   TWO LAYOUTS, ONE BREAKPOINT. A laptop and a phone; tablets are not a
   target. Every phone rule here is `max-width: 900px`, the width at which the
   nav and this page's scripts (stage.js) already switch. Do not introduce
   another width.

   Split out of style.css verbatim on 5 Oct 2026, original order kept.
   ═══════════════════════════════════════════════════════════════════════ */

/* ─── THE PHONE'S RHYTHM ───────────────────────────────────────────────
   `.section` is a flat 72px top AND bottom in style.css, on every page and
   every width, so two sections met with 144px of paper between them — on a
   390px screen, a sixth of the viewport blank at every seam. The home page
   runs its acts ~56px apart on a phone (`--act-gap`); this page now does the
   same, half on each side of a seam. Laptop untouched. Scoped here rather than
   in style.css because `.section` is on ten other pages. */
@media (max-width: 900px) {
  .section { padding: clamp(28px, 7vw, 42px) 0; }
}

.section-lead { max-width: 640px; color: var(--ink); }

/* --- FAQ accordion --- */
/* --- FAQ — the pattern from bharatdigital.io: ONE enclosing card holding
       every question, a + marker on the LEFT (not a chevron on the right),
       and hairline rules between rows. Rebuilt here in paper and ink.
       `name` makes the rows a native exclusive accordion, so opening one
       closes the last without any JavaScript. --- */
.faq-card {
  /* transparent rather than a fill, so the card takes whatever ground the
     section sits on instead of introducing a second paper tone */
  background: transparent; border-radius: 20px;
  box-shadow: inset 0 0 0 1px rgba(35, 35, 35, .14);

  /* ⚠️ THE CARD NEEDED ITS OWN TOP MARGIN because this section has no
     `.section-lead`. Everywhere else the lead sits between the heading and the
     content and makes the gap: measured, #record runs 89px from h2 to the rail
     and #what-to-expect 106px to its columns. The FAQ had **10px** — just the
     h2's own bottom margin — so the card hung straight off the heading and
     read as though it belonged to it.

     Sized off the rhythm rather than to match those totals: content elsewhere
     sits 45-63px below the LAST TEXT ELEMENT, and here that element is the
     heading itself. Matching 89 would have opened a void where a lead isn't. */
  margin-top: clamp(32px, 4.5vh, 52px);

  /* 4px top and bottom so the first question clears the border by 24px — the
     same as the horizontal padding — instead of the 20px the summary's own
     padding was giving it. The card was inset 24px on three sides and 20px on
     one. */
  padding: 4px 24px;
}

.faq { position: relative; }

.faq summary {
  cursor: pointer; list-style: none;
  display: flex; align-items: flex-start; gap: 20px; padding: 20px 0;
}

.faq summary::-webkit-details-marker { display: none; }

.faq-q { font-size: 16px; font-weight: 500; line-height: 1.5; color: var(--ink); }

.faq summary:hover .faq-q { color: var(--black); }

/* the + / − mark, drawn rather than typed so it can animate */
.faq-mark { position: relative; flex: 0 0 16px; width: 16px; height: 16px; margin-top: 5px; }

.faq-mark::before, .faq-mark::after {
  content: ""; position: absolute; background: var(--ink); border-radius: 1px;
}

.faq-mark::before { left: 0; top: 7px; width: 16px; height: 1.5px; }

.faq-mark::after  { top: 0; left: 7px; width: 1.5px; height: 16px; }

/* open turns the plus into a cross — the whole mark rotates, so both bars
   are kept and the movement reads as one gesture */
.faq-mark { transition: transform .26s cubic-bezier(.22,1,.36,1); }

.faq[open] .faq-mark { transform: rotate(45deg); }

.faq-a { padding: 0 0 20px 36px; }

/* 16px mark + 20px gap = aligns to the question */
/* body, not a fourth size: this was 15.5px/1.65 against the page's 16/1.6 */
.faq-a p { margin: 0; font-size: 16px; line-height: 1.6; color: var(--muted); max-width: 62ch; }

.faq[open] .faq-a { animation: faqIn .24s ease both; }

@media (prefers-reduced-motion: reduce) {
  .faq-mark { transition: none; }
  .faq[open] .faq-a { animation: none; }
}

/* ── the rotating first word — `.rot` > `.rot-w`, driven by nav.js [data-rot] ──
   ⚠️ WRITTEN 4 Oct 2026 BECAUSE THERE WAS NOTHING HERE. nav.js sets `--rot-w`
   on `.rot` and toggles `.swap` on `.rot-w`, and the rules that answered them
   (`.rotator` / `.rotator-word`, below the hero) belonged to the OLD home hero,
   which the Constitution hero replaced on 23 Sep. The fellowship title kept
   the markup and the driver and lost the styles, so the word hard-cut and the
   box jumped — "cut, cut, cut". These are the rules the driver expects.

   ⚠️ `clip-path`, NOT `overflow: hidden`. An inline-block with overflow other
   than visible takes its baseline from its BOTTOM MARGIN EDGE, which would
   float the word a descender above "for Bharat". clip-path clips without
   touching the baseline, and the inset is negative top/bottom so the g in
   Design and the y in Security keep their tails; it only clips at the sides,
   where the new word may be wider than the box for the tail of the width
   transition. */
.rot {
  display: inline-block; vertical-align: baseline; white-space: nowrap;
  width: var(--rot-w, auto);
  transition: width .62s cubic-bezier(.22, 1, .36, 1);
  clip-path: inset(-.3em 0 -.3em 0);
}

.rot-w {
  display: inline-block;
  /* in: slower than out, so the new word settles rather than snaps */
  transition: opacity .44s ease, filter .44s ease, transform .52s cubic-bezier(.22, 1, .36, 1);
}

.rot-w.swap {
  opacity: 0; filter: blur(5px); transform: translateY(.12em);
  /* out: nav.js swaps the text 260ms in, so the old word must be gone by then */
  transition-duration: .26s, .26s, .26s;
}

@media (prefers-reduced-motion: reduce) {
  .rot, .rot-w { transition: none; }
}

/* --- fellowship page: components added for the live-site content --- */
/* 4-up on the full measure: 269px cards against section 7's 368px. Narrower,
   because four tracks on one line is the point — the set has to be readable
   as a set of four, which a 2x2 or a 3+1 does not do. */

/* The divider the partner cards do not have. It rides on the eyebrow rather
   than on the thumb — a border on the thumb reads as a frame around the
   image, where this reads as a rule between the picture and the words. */


/* the track's own mark, standing in for photography that does not exist yet.
   Sized and padded like the partner marks in section 7, so the two grids
   weigh the same. */


/* the word for a screen reader where the visible mark is decoration — the
   mentor ring's tracks, and anywhere else that needs it */
.u-sr {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ═══════════════════════════════════════════════════════════════════════
   WHAT FELLOWS HAVE BUILT — a horizontal rail

   `.pgrid` lived here: a 3-up of `.work-card`, which put the six projects in
   two rows and made the second row read as an afterthought. On a rail they are
   one run of equal things. Its rules are deleted rather than left — a
   superseded layout's leftovers have caused three live bugs on this site
   already, and the last `.pgrid` note recorded two of them.

   ⚠️ THIS IS NOT `.work-card` AND MUST NOT BECOME IT. That component is shared
   with Services and Labs; this treatment is its opposite — no surface, no
   radius, hairline rules between cells instead. Restyling `.work-card` to get
   this look would have silently changed two other pages.

   ⚠️ FULL BLEED BY MARKUP, NOT BY MATH. The rail sits OUTSIDE `.wrap` — the
   wrap is closed before it and reopened after — so it is a plain full-width
   child of the section and reaches both screen edges at every width.

   The first attempt was `margin-inline: calc(var(--gutter) * -1)`, which only
   eats the wrap's padding: that is edge-to-edge below 1240 and stops at the
   measure above it. Tested at 1180 and looked right, which is exactly how that
   kind of bug survives. The other tempting fix, `calc(50% - 50vw)`, is worse:
   100vw INCLUDES the scrollbar on Windows and desktop Linux, so it puts the
   page into horizontal overflow on the machines nobody here tests on. Neither
   is needed — a full-width child has no measure to escape.

   THE RULES THEREFORE REACH THE SCREEN EDGES and the card content is inset by
   `--gutter` from them, which is what the reference does. The consequence:
   above 1240 the first card's text no longer lines up with the section heading
   above it, because the heading is on the measure and the card is on the
   screen. That is the trade the reference itself makes.
   ═══════════════════════════════════════════════════════════════════════ */
.rail {
  /* 28px -> a screen-scaled gap: the rail used to sit under a sub-head that
     carried its own 72px top margin and a rule. With that gone it hangs
     straight off the section lead and has to make the separation itself. */
  margin: clamp(48px, 7vh, 100px) 0 0;
  padding: 0;
  overflow-x: auto;
  /* ⚠️ SIDEWAYS ONLY (5 Oct 2026, the user: "there's a vertical scroll inside
     the section. We don't need that"). `overflow-x: auto` makes the other
     axis `auto` too, so any sub-pixel overhang of a card grew a second,
     vertical scrollbar inside the section. The rail scrolls one way. */
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  /* the band's rules live on .rail-track, not here — see the textured-rule
     block below for why a scroll container cannot carry them */
  /* ⚠️ NO SCROLLBAR (5 Oct 2026, the user: "remove the horizontal scroll
     visual UI — it should still scroll ... it's not aligned to the larger
     visual layout"). The rail still scrolls by trackpad, wheel, drag on
     touch and keyboard; the cut-off card at the right edge is the cue. */
  scrollbar-width: none;
}
.rail::-webkit-scrollbar { display: none; }

.rail-track {
  display: flex; margin: 0; padding: 0; list-style: none;
  width: max-content;
}

/* Rules BETWEEN the cells, and one on the outside of each end, so the run
   reads as a ruled table rather than as floating tiles. */
.rail-cell {
  flex: 0 0 auto;
  width: clamp(268px, 26vw, 380px);
  scroll-snap-align: start;
}

/* THE FOOT SITS ON THE BOTTOM EDGE OF EVERY CELL. `margin-top: auto` inside a
   full-height flex column — six titles running two, three and four lines
   otherwise put six meta rows at six different heights, which is the one thing
   that would stop this reading as a ruled run. */
.rail-card {
  display: flex; flex-direction: column; height: 100%;
  padding: 28px var(--gutter) 24px;
  text-decoration: none; color: inherit;
  /* ⚠️ IT IS A <button> — it opens the dialog, it does not navigate. Which
     means undoing everything a UA gives a button: its own font stack, centred
     text, a border, a grey face and a fixed width. `font: inherit` is the one
     that matters — without it the serif h3 inside still renders serif but the
     `.rail-meta` below it silently falls back to the UA's 13px sans. */
  font: inherit; text-align: left; width: 100%;
  background: none; border: 0; cursor: pointer;
  transition: background .2s ease;
}

.rail-card:hover { background: var(--surface); }

.rail-shot {
  /* ⚠️ A WINDOW RISING OUT OF THE PLATE (5 Oct 2026, the user: "the images
     have become so small that I'm not able to understand ... some are half
     made and half hanging in between, and some are full ... these images
     should look amazing"). Containing every screenshot whole on the plate
     fixed the bad crops and made a worse problem: the shots run from a
     1200x122 strip to a 1200x2600 phone screen, so each card was a
     different-sized picture floating in a different-sized hole, most of
     them too small to read.
     Now every card is the SAME composition, whatever the screenshot: a
     window the same size in the same place — 8% in from the sides, 12% down
     from the top — rounded at the top and running off the plate's bottom
     edge. The screenshot covers that window from its TOP-LEFT, so a card
     always shows the top of the interface — the header, the hero, the
     product's name — which is the part that says what the project is; only
     the bottom ever leaves the frame, and it leaves it on purpose. The plate
     is 16:10 to suit the screenshots' common shape. Shared with
     `.proj-thumb-in` on the projects page. */
  position: relative; display: block;
  aspect-ratio: 16 / 10; margin-bottom: 26px;
  background: var(--brand-violet); overflow: hidden;
}
.rail-shot img {
  position: absolute; left: 8%; top: 12%; width: 84%; height: 88%;
  object-fit: cover; object-position: left top;
  border-radius: 8px 8px 0 0;
  /* a hairline, so a white UI does not dissolve into a pale plate */
  box-shadow: 0 0 0 1px rgba(35, 35, 35, .12);
}

.rail-card h3 {
  margin: 0 0 28px; font-family: var(--serif); font-weight: 400;
  font-size: clamp(21px, 1.9vw, 27px); line-height: 1.22;
  letter-spacing: -.2px; color: var(--ink);
}

/* The foot is now TWO rows — the partner's mark, then the department and the
   arrow. `margin-top: auto` still pins the whole thing to the cell's bottom
   edge, which is what keeps the meta lines level across six titles of
   different lengths. */
.rail-foot { display: block; }
/* ⚠️ THE FOOT HANGS FROM THE TITLE, AT ONE HEIGHT (5 Oct 2026, the user: "the
   ministry name and the icons are up and down ... the names from the top
   should be starting from the same place; excess space at the bottom is
   fine"). It was pinned to the cell's BOTTOM with `margin-top: auto`, so a
   ministry name that wrapped to three lines lifted its logo above its
   neighbours'. Now every title box is as tall as the tallest one in the rail
   (projects.js sets --rail-title-h from the live layout, so it is right at
   every width), and the logo and the name follow it at the same y in every
   card; any slack collects under the name. */
.rail-card h3 { min-height: var(--rail-title-h, 0); }

/* THE MARK SITS IN A FIXED-HEIGHT BAND, not at its natural size. These are six
   600x600 transparent PNGs drawn to wildly different proportions — NHAI's is a
   wide device, MoSPI's is an emblem over two lines of type — so sizing by width
   would have them landing at six different heights and the meta lines below
   would stop agreeing. A fixed band with `contain` and a bottom-left origin
   makes every mark sit on the same baseline whatever shape it is.

   The band holds its height with no image in it, so a project with no partner
   mark still lines up. */
/* ⚠️ NORMALISED PER LOGO, because sizing the BOX sizes the wrong thing. All six
   sources are 600x600 transparent squares, but the ink inside fills wildly
   different fractions of them — measured off each file's alpha bounding box:

       CEG / Karnataka  33% of the canvas height
       MoSPI            48%
       NHAI             51%
       GIS              56%
       Panchayati Raj   64%
       Tamil Nadu       80%

   So at any single band height CEG renders less than HALF the size of Tamil
   Nadu, and no amount of making the band bigger fixes that — it scales the
   disparity with it.

   Each card therefore carries `--logo-k` = 600 / its own ink height, and the
   image is sized `--logo-ink * --logo-k`, so every MARK lands at the same
   optical height whatever padding its file was drawn with.

   THE IMAGE IS TAKEN OUT OF FLOW to make that safe: a 3x scale on CEG means a
   ~127px box holding 42px of ink, and in flow that box would set the band's
   height and shove the layout around. Absolute, centred on a band sized to the
   INK, means the surplus — which is transparent padding and paints nothing —
   overflows harmlessly and every meta line below still agrees. */
.rail-logo {
  /* 42px -> 56px. ONE NUMBER MOVES ALL SIX: --logo-ink is the optical height
     every MARK lands at, and each card's --logo-k is 600 / its own ink height,
     so the per-logo whitespace is already compensated and none of them need
     touching. Resist the urge to nudge a --logo-k to "fix" one that looks off
     after a resize — that is what the k is for, and editing it desynchronises
     that logo from the other five. */
  /* ⚠️ CAPPED ON BOTH AXES, NOT JUST HEIGHT — and that is the change the real
     department marks forced. `--logo-k` (600 / a mark's own ink height) is
     still right for `.plc-cell`, whose logos are all the same KIND of mark
     with different amounts of transparent padding. These seven are not: they
     run from a square state seal (RDPR, 88x86) to a full wordmark lockup
     (DIC, 421x73). Sized to one height, the lockups came out five times the
     width of the seals and each card's foot read as a different weight.
     A box both dimensions must fit inside sizes a seal by its height and a
     lockup by its width, which is how the eye compares them anyway.
     They are already tightly cropped (k 1.0-1.35), so there is no padding
     left to compensate for. */
  --logo-h: 44px;                   /* the tallest a mark may be */
  --logo-w: 140px;                  /* the widest a mark may be */
  display: block; height: var(--logo-h); margin-bottom: 20px;
}

.rail-logo img {
  display: block;
  max-height: 100%; max-width: var(--logo-w);
  width: auto; height: auto; object-fit: contain; object-position: left center;
}

.rail-metarow {
  /* flex-start, not center: centred against the arrow, a ministry name that
     wraps to three lines started higher than one on two, and the names no
     longer began on one line (5 Oct 2026) */
  display: flex; align-items: flex-start;
  justify-content: space-between; gap: 16px;
}

/* 10px / .07em, not 10.5 / .11em. The house label tracking is .11em, and at
   that setting "NATIONAL HIGHWAY AUTHORITY OF INDIA" wrapped and its arrow
   drifted off the others' line.

   ⚠️ MEASURE THE LABEL'S OWN BOX, NOT THE CARD'S. The box this line sits in is
   248px — it shares the foot's first row — and reading the CARD's inner width
   instead gives 294 and a false 46px of headroom. That mistake is what makes a
   "it fits" verdict here untrustworthy.

   Measured after the type pass, at 10px/.14em: the longest name on the page,
   NATIONAL HIGHWAY AUTHORITY OF INDIA, sets 214.6px of ink in that 248.4px box
   — one line, 33.8px of slack. So the SIZE is a local exception and the VOICE
   is not: tracking, casing and weight are the page's label system and only the
   step down to 10px is this component's own.

   Going up to the 11px the other micro labels use has to be re-measured, not
   assumed, and a wrap here is not merely untidy: the foot is two rows pinned
   with `margin-top: auto`, so one wrapped label lifts its arrow off the line
   every other cell's sits on. */
.rail-meta {
  font-family: var(--mono); font-weight: 500; font-size: 10px;
  letter-spacing: .14em; text-transform: uppercase; color: var(--accent);
}

.rail-go {
  flex: 0 0 auto; display: grid; place-items: center;
  width: 30px; height: 30px; font-size: 13px; color: var(--ink);
  background: rgba(35, 35, 35, .07);
  transition: background .2s ease, transform .2s ease;
}

.rail-card:hover .rail-go { background: rgba(35, 35, 35, .14); transform: translate(2px, -2px); }

/* A rail's affordance is the cut-off cell at the right edge, so on a phone the
   cells have to be narrow enough that the next one is always showing. */
@media (max-width: 900px) {
  /* 11px, the other micro labels' size. The 10px above protects the LAPTOP's
     one-line foot; at 76vw the longest names wrap whatever the size, so the
     phone gets the readable step and the arrow rides the label's last line. */
  .rail-meta { font-size: 11px; }

  .rail-cell { width: 76vw; }
  .rail-card { padding: 22px var(--gutter) 20px; }
  .rail-card h3 { margin-bottom: 22px; }
  .rail-logo { --logo-ink: 44px; margin-bottom: 16px; }
}

@media (prefers-reduced-motion: reduce) {
  .rail { scroll-behavior: auto; }
  .rail-card, .rail-go { transition: none; }
  .rail-card:hover .rail-go { transform: none; }
}

/* --- the aim — the page's thesis. The old .statement set it in muted grey
       italic at 40px, which read as a caption; full ink is what makes it the
       claim it is.

       SIZED TO THE FIGMA FRAME (430:1321), 20 Sep 2026: 40px / 63.44px
       (1.586) / -.4px, Libertinus Serif REGULAR, on a 1138px measure. Three
       changes from the earlier cut, all of them the frame's: it is smaller
       (52px -> 40px cap), it is no longer part-italic (the <em> on the three
       verbs is gone from the markup with it), and the leading is much airier.
       1.22 was a display setting; at 40px with four figures underneath it this
       is a paragraph, not a headline.

       The cap is 40px and the fluid step 2.9vw, so it reaches 40 at ~1380 and
       holds. 1138px is the frame's own text box, which is within a hair of
       this site's content measure (1160 at 1440) — the design's claim spans
       the full measure and so does this. --- */
.aim {
  font-family: var(--serif); font-weight: 400; font-style: normal;
  font-size: clamp(24px, 2.9vw, 40px); line-height: 1.58; letter-spacing: -.4px;
  color: var(--ink); max-width: 1138px; margin: 0; text-wrap: balance;
}

.mission { position: relative; padding: 0; }

.mission > .wrap { z-index: 1; }

/* Centred, in both axes. The wrap already centres it vertically; this centres
   the measure horizontally and sets the copy centred to match the mockup —
   the mission is the one place on the site that is a statement rather than
   body copy, so it sits in the middle of the scene instead of on the left
   rail everything else hangs off. */
/* CENTRED BOX, LEFT-ALIGNED COPY (20 Sep 2026, per the frame). The measure
   sits in the middle of the scene — the mission is a statement, not body copy
   on the left rail — but the lines inside it range left, which is what the
   frame does and what stops a three-line claim reading as a pull quote.

   justify-self, not margin-inline: auto. This is a GRID ITEM (the wrap is the
   pin, and the pin is a grid), and with max-width capping it below the column
   the box parks at the column start — text-align centred the copy inside a box
   that was itself 60px off centre. */
.mission .aim {
  justify-self: center; margin-inline: auto;
  width: 100%; max-width: 1138px;
  text-align: center;
  /* leaves with the wash — stage.js drives both off the same exit curve */
  opacity: var(--mission-out, 1);
  will-change: opacity;
}

/* `.mstats` / `.mstat` lived here — a 2x2 of rule, sans label and serif value,
   transcribed off Figma frame 430:1321 and revealed in a stagger under the
   lifted claim. Section and rules both deleted 20 Sep 2026; the scene states
   the claim alone. The frame is still the source for the CLAIM's type above. */

/* 68svh -> 38svh. Measured at 1400x800: the sentence completed 250px into the
   scene and then the reader had 530px of pinned scroll in which NOTHING
   happened before the exit began. That dead middle, not the fade, was what
   made the ending feel long — the fade itself was 440px and is now 288.
   38svh leaves ~290px of dwell, which is a beat to read the sentence on
   rather than a wait.

   ⚠️ SHORTENING THE RUNWAY SPEEDS UP THE REVEAL TOO, because every window is a
   fraction of the whole scene span and the span includes the runway. The
   windows in stage.js were rescaled at the same time to hold their absolute
   positions; change one and you have changed the other. */
/* 38svh -> 56svh. The reveal moved to AFTER the pin (see sceneStart in
   stage.js), so the pinned range now has to carry the opening line, the rest
   of the sentence, the gradient shift AND a beat to read on — where before it
   only had to carry the dwell. The lead-in is a flat 28svh now rather than the
   page header's height, so the two together are still well short of the 68svh
   this started at. */
/* 56svh -> 70svh, to pay for the hold between the opening line and the rest
   (see the windows in stage.js). That hold is what makes the staging legible
   at all, and it cannot be bought out of the dwell. */
.mission .scene-runway { height: 70svh; }

/* The wash — the Figma frame fill, transcribed exactly.

   Source: a 1728x2968 frame, gradientUnits="userSpaceOnUse", transform
   translate(864 406.5) rotate(90) scale(2561.5 3302.92). The rotate SWAPS the
   scale axes, so the ellipse is 3302.92 wide by 2561.5 tall, centred at
   (864, 406.5) — 191.14% x 86.31% of the frame, at 50% 13.70%. Percentages
   rather than pixels so the composition holds at any viewport.

   The shape of it drives the whole animation: the CORE is paper and the colour
   lives on the OUTSIDE, in rings — warm first, then cool, then lilac at the
   ending shape. So the ellipse CONTRACTS to bring colour in, and because the
   warm ring is the inner one it crosses the viewport first: a thin orange band
   low on the screen, which widens and climbs, and only once the ellipse is
   near full size does the cool ring follow it in from the bottom. Orange, then
   orange giving way to purple, on one gradient — and it lands on the Figma
   fill exactly, because the end of the contraction IS scale 1.

   The coloured AREA therefore expands the whole time the ellipse shrinks,
   which is the thing the reader actually sees: a small wash sliding in and
   opening out. transform-origin sits on the gradient's own centre so scaling
   changes the radii and nothing else. Scale never drops below 1 — at k < 1 the
   layer would be smaller than the viewport.

   Reaching the Figma fill is now a WAYPOINT, not the end. The fill leaves the
   top ~40% of the screen as paper, because the gradient's centre sits at 13.7%
   and its core is paper. To let the wash take the whole page the layer then
   RISES — the paper core travels up and off the top edge, and orange, cool and
   lilac fill the viewport end to end. Only then does it fade, so the pillars
   arrive on clean paper.

   position is FIXED, not absolute: "bleeding across the entire page" means the
   viewport, and an absolute veil would be clipped to the section box. z-index
   is -1 so it sits above the page's own ground but beneath every piece of
   content, the nav included — at 0 it painted over the bar, because
   header.site carries no z-index of its own and the veil comes later in the
   document. */
.mission-veil {
  position: fixed; inset: 0; z-index: -1; pointer-events: none;
  /* columns are taller than the viewport by design; clip rather than let them
     extend the page */
  overflow: hidden;
  opacity: var(--field, 0);
  will-change: opacity;
}

/* will-change only while the field is actually on screen — it is what forces
   a layer to exist, and this one is position:fixed for the whole page */
.mission-veil:not(.is-off) .mv-col { will-change: transform; }

.mission-veil:not(.ramp-b-off) .mv-ramp--b { will-change: opacity; }

/* whichever ramp is contributing nothing stops being a layer. A is fully
   covered by an opaque B at mix 1; B is fully transparent at mix 0. */
.mission-veil.ramp-a-off .mv-ramp--a,
.mission-veil.ramp-b-off .mv-ramp--b { visibility: hidden; }

/* ⚠️ THE FIELD IS FIXED AND FULL-VIEWPORT, SO IT IS ALIVE FOR THE WHOLE PAGE.
   Without this its columns stay composited from the banner to the footer —
   sixteen promoted layers, each 158px x 310svh — and every other section on
   the page scrolls against that. opacity:0 is not enough to retire a layer
   that carries will-change; visibility:hidden is. stage.js sets it whenever
   the field's own opacity reaches 0. */
.mission-veil.is-off { visibility: hidden; }

/* ⚠️ THIS RULE WAS DELETED BY ACCIDENT AND IT IS THE ONE THAT HIDES THE WORDS.
   It sat between the veil rule and the reduced-motion block; a later rewrite
   bracketed that whole range to swap the column CSS and took this with it. The
   result is silent and looks exactly like a timing bug: stage.js keeps setting
   `.is-in` perfectly, but with nothing holding the words at opacity 0 the
   entire sentence renders from the first frame. Two rounds of "all the text is
   already there" were this, not the windows.

   The lesson: probes that count `.is-in` CLASSES prove the driver, not the
   picture. Verify rendered opacity, which is what the reader sees.

   The mission, word by word: stage.js wraps every word and reveals them in
   visual order, so the cascade is left-to-right, top-to-bottom, and the rag
   stays the natural one. inline-block is what lets a word carry a transform. */
.mission .aim .w {
  display: inline-block;
  opacity: 0; transform: translateY(.30em);
  /* ⚠️ .55s, NOT 1.05s. The reveal is per word and scroll-driven, so a normal
     flick hands `is-in` to twenty words inside half a second — at 1.05s each
     they all come up together and the stagger is invisible. .55s lets a word
     land before its neighbours start, and is still soft. */
  transition: opacity .55s cubic-bezier(.22, 1, .36, 1),
              transform .55s cubic-bezier(.22, 1, .36, 1);
}

.mission .aim .w.is-in { opacity: 1; transform: none; }

/* before the script has taken charge, the paragraph is just a paragraph */
.mission:not(.scene-ready) .aim .w { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .mission .aim .w { opacity: 1; transform: none; transition: none; }
  .mission .aim { opacity: 1; }
  /* the field is built and placed by stage.js either way; it simply never
     moves again once it has been positioned */
  .mission-veil { opacity: .9; }
}

/* Pinning a scene inside a phone viewport buries the reader — the same call
   the sheets and the process scene both make. The wash stays, softly and
   statically, because it is the section's character rather than its motion. */
@media (max-width: 900px) {
  .mission > .wrap { position: static; min-height: 0; }
  .mission .scene-runway { display: none; }
  .mission .aim .w { opacity: 1; transform: none; transition: none; }
  /* belt and braces with exitProgress()'s own no-runway guard: there is no
     exit here, so the copy must never be able to inherit a faded one. */
  .mission .aim { opacity: 1; }
  .mission-veil { position: absolute; opacity: calc(var(--field, 1) * .9); }
  /* ⚠️ THE WHOLE SCREEN, END TO END (5 Oct 2026, the user: "it's taking up
     half the page ... that section needs to be end to end completely"). It
     was as tall as its copy — 446px of an 844px phone — with the field's
     columns cropped to that. It now fills the screen under the bar and the
     statement sits in the middle of it; the field (which stage.js now plays
     on a clock here, `autoP`) has the whole frame to rise and shift in. */
  .mission > .wrap { box-sizing: border-box; min-height: calc(100svh - var(--bar-h)); align-content: center; }
  /* A 100vmax spread shadow used to live here, and had to be cancelled on
     phones: it painted a lilac field around a z-index:-1 box and came up
     THROUGH every transparent section on the page. The column field needs no
     such shadow — each column paints its own ground and the container clips —
     so the whole class of bug is gone with the radial. Worth remembering
     anyway: a huge spread shadow on a z-index:-1 element is invisible on
     desktop only because something drives its opacity to 0. */
}

/* ═══════════════════════════════════════════════════════════════════════
   WHAT THE FELLOWSHIP HOLDS — a punched card over a dithered photograph,
   with the raw photograph transitioning beside it.

   Figma 505:1089. The MACHINE is unchanged — six items read straight down the
   left, the right column sticks and cross-fades to whichever is level with it.
   Only the skin is borrowed: the left column became ONE LONG CARD that the six
   items live inside, so sections keep arriving into the same sheet rather than
   each being their own panel.

   GEOMETRY IS THE FRAME'S, scaled. In the 659px-wide Figma card the punches are
   26px across, centred 29px in, pitched 79px apart with the first at y=76, and
   the margin rule is a 1px black line 95px in with the text at 125px. Those are
   held as custom properties below at ~.83 of frame scale, so the card can be
   retuned from one place.

   ⚠️ THE CARD'S FILL IS INFERRED. `Subtract.svg` exports with `fill="none"` —
   Figma keeps a layer fill outside the path data — so this is --field-cool
   (#BAC7FD), the periwinkle already transcribed from this same file for the
   mission field, which matches the render closely. If the real hex differs it
   is a one-line correction.
   ═══════════════════════════════════════════════════════════════════════ */
/* ⚠️ THE PHOTOGRAPHS ARE FULL BLEED, so `.ex` lives OUTSIDE the measure — a
   plain full-width child of the section, never `calc(50% - 50vw)`, which
   counts the scrollbar and puts Windows and desktop Linux into horizontal
   overflow (the trap `.rail` documents).

   The pair STICKS and the card SCROLLS OVER IT. The card is pulled up by the
   pair's height less the frame's 67px gap, so it starts just inside the top of
   the photographs; because `.ex` then ends where the CARD ends, the sticky
   range runs exactly from the first item to the last — the pair holds for as
   long as there is card to read and lets go with it. */
.ex {
  position: relative; margin-top: clamp(32px, 5vh, 64px);
  /* ⚠️⚠️ READ THE FRAME'S LAYER ORDER BEFORE TRUSTING ITS NUMBERS. In
     505:1089 the RIGHT PHOTOGRAPH IS THE LAST ELEMENT — it paints ON TOP of
     the card. The card's box runs 337 -> 996, but the photograph covers it
     from 899, so the card only ever APPEARS to end at the seam. Four rounds
     went into matching the card's box to the frame; the box was never the
     thing on screen.

     So the composition is: LEFT HALF = dithered photograph + the card over it,
     RIGHT HALF = the colour photograph. Equal halves, card on the left, card
     visually ending on the seam — which is every constraint at once, and it
     only looked impossible while the card was being sized to its Figma box
     instead of to what the frame shows.

     Built by ending the card AT the seam rather than by stacking the
     photograph over it: same picture, no z-index surgery, and the card cannot
     be caught peeking out on a width where the two disagree. Left edge is the
     frame's 337/1728 = 19.5%.

     The body stays at the frame's measure anyway — 30.5vw less the 104px
     inset leaves ~291px at 1400, against the frame's 368 scaled to 298. */
  --card-l: 19.5vw;
  --card-w: calc(50% - var(--card-l));
}

.ex-pair {
  position: sticky; top: var(--bar-h); z-index: 0;
  height: calc(100svh - var(--bar-h));
  /* equal halves, full stop — the card is placed against the VIEWPORT, not
     against this seam, so nothing here has to be kept in step with it */
  display: grid; grid-template-columns: 1fr 1fr;
}

.ex-back, .ex-figs { position: relative; height: 100%; overflow: hidden; }

/* The source stays in the DOM — it is what the canvas is drawn FROM, and it
   is what shows if the script never runs. stage.js hides it once it has a
   dithered canvas to put in its place. */
.ex-back img, .ex-back canvas {
  display: block; width: 100%; height: 100%; object-fit: cover;
}

/* ⚠️ pixelated, and the canvas is drawn at CSS-pixel size on purpose. An
   ordered dither smooth-upscaled on a 2x display averages into flat colour —
   the whole point is that the cells stay cells. */
.ex-back canvas { image-rendering: pixelated; }

.ex-back.is-dithered img { display: none; }

.ex-fig { position: absolute; inset: 0; margin: 0; overflow: hidden; }

.ex-fig img { display: block; width: 100%; height: 100%; object-fit: cover; }

/* ⚠️ THE HIDDEN STATE IS SCOPED TO .is-ready, which stage.js adds. Authored on
   .ex-fig directly, a dead script leaves the right half of the pair blank. */
.ex.is-ready .ex-fig { opacity: 0; transition: opacity .5s ease; }

.ex.is-ready .ex-fig.is-in { opacity: 1; }

/* ─── THE CARD ─────────────────────────────────────────────────────────── */
.ex-list {
  --punch-d: 22px;        /* 26 at frame scale  */
  --punch-x: 24px;        /* 29                 */
  --punch-pitch: 66px;    /* 79                 */
  --punch-first: 63px;    /* 76                 */
  --rule-x: 79px;         /* 95                 */
  --card-pad: 104px;      /* 125 — clears holes AND rule */

  /* ⚠️ THE CARD SCROLLS; the photographs behind it stick. A sticky panel whose
     contents are REPLACED was tried and rejected — swapping the whole card
     loses the sense of one long sheet you are moving down. The items stay in
     flow, so the next point is already visible under the live one and you can
     see what is coming.

     Horizontal placement is measured off the VIEWPORT, not the measure: the
     card is laid ON the photographs. See --card-l / --card-w above. */
  position: relative; z-index: 1;
  margin: calc(var(--bar-h) - 100svh + 67px) 0 0 var(--card-l);
  width: var(--card-w);
  padding: 8px 32px 8px 0;
  list-style: none;
  /* ⚠️ --brand-violet, NOT --field-cool (5 Oct 2026). This card is a SURFACE
     that carries type, so it belongs to the brand scale; it was reaching into
     the illustration field's palette for #BAC7FD because the photographs
     behind it come from that frame. Since the scale moved onto Figma 803:565
     the brand violet is #B6C4FD — two points off what was here, so nothing
     visibly changed, but the page no longer has a tenth purple in it. */
  background: var(--brand-violet);
  /* the punches. One tile per pitch, repeated down — the alternative is eight
     authored pseudo-elements and a card that cannot grow. */
  -webkit-mask-image: radial-gradient(circle at var(--punch-x) calc(var(--punch-pitch) / 2),
                      transparent calc(var(--punch-d) / 2),
                      #000 calc(var(--punch-d) / 2 + .5px));
          mask-image: radial-gradient(circle at var(--punch-x) calc(var(--punch-pitch) / 2),
                      transparent calc(var(--punch-d) / 2),
                      #000 calc(var(--punch-d) / 2 + .5px));
  -webkit-mask-size: 100% var(--punch-pitch);
          mask-size: 100% var(--punch-pitch);
  -webkit-mask-repeat: repeat-y;
          mask-repeat: repeat-y;
  -webkit-mask-position: 0 calc(var(--punch-first) - var(--punch-pitch) / 2);
          mask-position: 0 calc(var(--punch-first) - var(--punch-pitch) / 2);
}

/* the ruled margin, inside the punches */
.ex-list::before {
  content: ''; position: absolute; top: 0; bottom: 0;
  left: var(--rule-x); width: 1px; background: var(--line);
}

/* in flow, each given a screen-share of scroll so its photograph gets a turn */
.ex-item {
  display: flex; flex-direction: column; justify-content: center;
  min-height: 38svh;
  padding: clamp(28px, 5vh, 56px) 0 clamp(28px, 5vh, 56px) var(--card-pad);
}

/* ONE POINT IN FOCUS, the rest held back — not hidden. The next point stays
   legible underneath so you can see what is coming, which is the whole reason
   the items are still a list rather than a stack of replacements.

   ⚠️ Scoped to .is-ready: with no script nothing would ever be `.is-on`, and
   every point would sit at the dimmed value forever. */
.ex.is-ready .ex-item {
  opacity: .3; transition: opacity .5s cubic-bezier(.22, 1, .36, 1);
}

.ex.is-ready .ex-item.is-on { opacity: 1; }

/* the number: DM Mono, not the olive mono eyebrow — on the card it is a
   folio, so it sits in ink at reading size rather than as a label */
.ex-item .eyebrow {
  /* SIZE and COLOUR are the folio's own — it is a number on a card, not a
     label, so it stays in ink at reading size. TRACKING and WEIGHT are the
     label system's, because those are what made it read as a different
     typeface from every other mono line on the page. */
  font-family: var(--mono); font-weight: 500;
  font-size: clamp(17px, 1.4vw, 20px); letter-spacing: .14em;
  text-transform: none; color: var(--ink);
  margin: 0 0 6px; transition: opacity .4s ease; opacity: .55;
}

.ex-item.is-on .eyebrow { opacity: 1; }

.ex-item h3 {
  margin: 0 0 14px;
  font-family: var(--serif); font-weight: 400;
  font-size: clamp(26px, 2.6vw, 36px); line-height: 1.22; letter-spacing: -.2px;
  color: var(--ink);
}

.ex-item > p:not(.eyebrow):not(.ex-credit) {
  margin: 0; max-width: 38ch;
  font-family: var(--sans);
  /* ⚠️ THE LEAD SIZE, NOT A SIZE OF ITS OWN. This capped at 20px and measured
     18.72 at 1440 — a fourth body size on a page that already had 15.5, 16 and
     18. The page runs exactly two: 16/1.6 for body and 18/1.6 for a lead, and
     this is a lead. Leading comes to 1.6 with it. */
  font-size: clamp(16px, 1.25vw, 18px); line-height: 1.6;
  /* ⚠️ --ink, NOT --muted (5 Oct 2026). The system has two prose colours and
     --muted is the secondary one — but its 5.3:1 is measured against PAPER,
     and this copy sits on the violet card, where #696969 on #B6C4FD is 3.2:1:
     under AA for body text on every screen size. On the card the hierarchy is
     carried by face and size (serif h3 over sans body), so the body takes the
     primary colour. */
  color: var(--ink);
}

/* The photograph's credit, in DM Mono under the copy — it belongs to the image
   currently on the right, which is why there is one per item rather than one
   for the section.

   ⚠️ IT USED TO BE THE ONE MIXED-CASE MONO LINE ON THE PAGE, at 13px/.01em in
   rgba(0,0,0,.5) — a size, a tracking and a colour shared with nothing else.
   The old note argued a credit carries a person's name and SHOUTING A NAME IS
   NOT A LABEL, which is a fair point in isolation and was indefensible in
   place: `.copy-pending` below is the SAME ELEMENT in its empty state and was
   already uppercase, so one line changed voice depending on whether it had
   content. Both are the micro label now, and the only thing left distinguishing
   the pending state is that it says so. */
.ex-credit {
  margin: clamp(18px, 2.4vh, 30px) 0 0; max-width: 38ch;
  font-family: var(--mono); font-weight: 500;
  font-size: 11px; line-height: 1.5;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink);   /* on the violet card — see the body copy above */
}

/* the empty state keeps the secondary grey ON PURPOSE: it is a placeholder,
   and reading as one is its job */
.ex-credit.copy-pending { color: var(--muted); }

@media (prefers-reduced-motion: reduce) {
  .ex.is-ready .ex-fig,
  .ex.is-ready .ex-item { transition: none; }
  .ex-item .eyebrow { transition: none; }
}

@media (max-width: 900px) {
  .ex { margin-top: 24px; }
  /* The transitioning stack needs a column beside it to transition AGAINST;
     in one column it would just sit on top of the card. When the real images
     land the phone wants one image PER ITEM, which needs the markup to change
     — so this is a hide and a decision to revisit, not a layout. The dithered
     backdrop goes with it: there is nothing for the card to be laid on. */
  .ex-pair { display: none; }
  /* the card takes the measure, and the punches shrink with it. ⚠️ The margin
     is the card's identity, not its content: at 54/72 it took 72px of a 350px
     card — a fifth of the screen — before a word was set. 40/54 keeps the
     holes, the ruled line and the gap between them, and gives the copy and the
     photographs 18px back. */
  .ex-list {
    --punch-d: 14px; --punch-x: 14px; --punch-pitch: 44px;
    --punch-first: 40px; --rule-x: 36px; --card-pad: 54px;
    /* the negative pull exists to lift the card over the pair; with no pair
       there is nothing to lift over, and leaving it would drag the card up
       through the section lead. */
    margin: 20px var(--gutter) 0; width: auto;
    padding: 4px 20px 4px 0;
  }
  /* nothing to focus against without the pinned photographs — every point
     reads at full strength */
  .ex-item { min-height: 0; padding: 26px 0 26px var(--card-pad); }
  .ex.is-ready .ex-item { opacity: 1; }
}

/* The quote takes the slack, so the caption sits on the card's bottom edge on
   every card whatever the quote's length — five cards of ragged captions is
   what makes a testimonial wall look assembled rather than designed. */


/* The rule between the words and the person is the card's only internal
   structure — no second ground, no tint. */

/* --- 2 · a slot that LOOKS like a slot -------------------------------------
   The same instrument as the FAQ's `.answer-pending`: mono, small, caps,
   muted. It must not be mistakable for copy — a greyed sentence in the body
   face reads as written and shipped, and this page sits next to one set of
   invented project records already. --- */
.copy-pending {
  font-family: var(--mono); font-size: 11px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--muted); font-weight: 500;
}

/* inside a quote the slot must beat the serif rule above it */


/* --- 3 · the record -------------------------------------------------------- */
/* The `#record .factstrip` overrides lived here — a scoped size bump so the
   numerals read as content rather than as a header strip. The strip itself came
   out of the page on 20 Sep 2026: the mission scene now opens the page with the
   same four figures, and one cohort's numbers should not be stated twice.
   `.factstrip` survives as an unused generic component, at its authored size.
   Its `<small>` detail — the seven departments by name, the four track names —
   went with it; nothing else on this page carries the department list. */

/* `.record-sub` lived here — a sub-head sitting on a rule, for when §8 held
   three blocks that each needed naming. It holds one now. */

/* ═══════════════════════════════════════════════════════════════════════
   TEXTURED RULES — every straight rule in these components is the scatter
   brush, the same stroke the fellow index and the modal divider are drawn in,
   rather than a CSS hairline. A `::before` (or `::after`) band straddles the
   edge — half a tile either side — filled with a colour and MASKED by the
   tile, so the ink stays a CSS value while the edge keeps its fringe.

   ⚠️ THE RAIL'S BANDS LIVE ON `.rail-track`, NOT `.rail`. `.rail` is a scroll
   container, and an absolutely positioned child of one is laid out against its
   PADDING box — i.e. the visible width — so `left:0; right:0` there would draw
   a rule only as long as the viewport and slide it away as the reader scrolled.
   `.rail-track` is `width: max-content`, so a band on it runs the full length
   of the row and travels with it. (Abspos children of a flex container are not
   flex items, so this does not disturb the layout.)

   ⚠️ THE ALPHAS ARE HIGHER THAN THE BORDERS THEY REPLACE (.3 -> .42,
   .14 -> .26). The brush is ~1.4px of core inside a 3px tile and its edges
   feather, so a mask at the old value reads markedly lighter than the 1px
   solid line it replaced.
   ═══════════════════════════════════════════════════════════════════════ */
.rail-track, .rail-cell, .faq { position: relative; }

/* horizontal bands */
.rail-track::before, .rail-track::after,
.faq::before {
  content: ""; position: absolute; left: 0; right: 0;
  height: var(--tex-t); pointer-events: none;
  -webkit-mask-image: var(--tex-h); mask-image: var(--tex-h);
  -webkit-mask-size: var(--tex-len) var(--tex-t); mask-size: var(--tex-len) var(--tex-t);
  -webkit-mask-repeat: repeat-x; mask-repeat: repeat-x;
}

.rail-track::before, .faq::before { top: calc(var(--tex-t) / -2); }

.rail-track::after { bottom: calc(var(--tex-t) / -2); }

/* vertical bands */
.rail-cell::before, .rail-cell:last-child::after {
  content: ""; position: absolute; top: 0; bottom: 0;
  width: var(--tex-t); pointer-events: none;
  -webkit-mask-image: var(--tex-v); mask-image: var(--tex-v);
  -webkit-mask-size: var(--tex-t) var(--tex-len); mask-size: var(--tex-t) var(--tex-len);
  -webkit-mask-repeat: repeat-y; mask-repeat: repeat-y;
}

.rail-cell::before { left: calc(var(--tex-t) / -2); }

.rail-cell:last-child::after { right: calc(var(--tex-t) / -2); }

/* ⚠️⚠️ z-index 2 OR THE RULES VANISH UNDER A HOVERED CARD (5 Oct 2026, the
   user: "on hover over any of the projects, the lines that are creating those
   boxes suddenly disappear"). Every one of these rules is a 3px band
   STRADDLING an edge — half of it lies inside the cell, half outside — and
   `.rail-card:hover` paints an opaque `--surface` across the whole cell. The
   card's background is a non-positioned block background, so in paint order it
   sits above `.rail-track`'s two bands (which are earlier in tree order than
   the cells) and over the inner half of the cell's own divider. The result was
   the top rule, the bottom rule and the left divider all dropping out together
   the moment the pointer landed, which is why it read as the box disappearing
   rather than as a rule thinning.
   The cells are positioned at `z-index: auto`, so a 2 here lifts the bands
   clear of every card in the track. They already carry `pointer-events: none`,
   so nothing about hit-testing changes. A hairline at the cell's edge over a
   card's padding costs nothing visually.
   ⚠️ DO NOT "fix" this by dropping the hover background instead — the tint is
   the only feedback a mouse user gets that these cells are buttons. */
.rail-track::before, .rail-track::after,
.rail-cell::before, .rail-cell:last-child::after { z-index: 2; }

/* the ink, per component — each keeps the weight its border had */
.rail-track::before, .rail-track::after,
.rail-cell::before, .rail-cell:last-child::after { background: var(--line); }

.faq::before { background: rgba(35, 35, 35, .26); }

/* where a rule should not be: the first track column and the first FAQ row */
.faq:first-of-type::before { content: none; }

/* ═══════════════════════════════════════════════════════════════════════
   THE TRACKS — the page's last section, and its only ask.

   ⚠️ OLIVE GROUND, INK TYPE, AT FULL STRENGTH — NO ALPHA ON ANY TEXT.
   --brand-olive (#92A06A) against --ink is **5.58:1** (--ink is #232323, not
   black — a first pass assumed black and claimed 7.5:1). 5.58 clears AA for
   body copy with room.

   ⚠️ BUT ALPHA KILLS IT. Holding the body back to rgba(35,35,35,.78)
   composites to 3.85:1 and the eyebrow at .72 to 3.45:1 — both FAIL AA, and
   both looked perfectly fine on screen. On a mid-tone ground there is no room
   to de-emphasise text with opacity: differentiate with size and face instead.
   Rules may take alpha; they are not text.

   Rules are ink at .3, not a second green. A band that is one colour plus ink
   stays one colour; a band with a tinted rule reads as two.
   ═══════════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════
   THE TRACKS — an envelope, and the card that slides out of it
   Figma "New ebsite 2026" — 540:3453
   ═══════════════════════════════════════════════════════════════════════
   Moved above the FAQ on 22 Sep 2026. The `.tk-*` band it replaced is
   retired below.

   ⚠️ EVERY PIECE OF THE ENVELOPE IS THE PAGE'S OWN PAPER. #FFFBF2 at .5
   (pocket, both flaps) and .3 (back wall), with Figma's grain over each. It
   reads as folds because of how many translucent panels are stacked at a
   given point, not because anything is tinted — so it must never be given a
   fill of its own, and it only works ON paper.

   ⚠️ 540:3581 (Rectangle 7816) IS NOT BUILT. The file paints it third, then
   paints an opaque #FFFBF2 rect over the whole frame fourth, so it is
   invisible in the design of record — most likely the back flap, kept for a
   state the file does not show. If a "card fully inside" frame turns up and
   there is a panel above the pocket mouth in it, that is this shape.

   THE UNIT IS cqw ON `.env-stage` — 1cqw = 1% of the envelope's width — so
   every number below is a Figma unit over 1156 and survives any width. The
   CARD is its own container, so ITS children are over 1081.
   ⚠️ An element cannot query its own container: the card's width and travel
   are in the STAGE's cqw, everything inside it is in the CARD's. */
/* ⚠️ 150vh. It started at 250, where the pin ran 1350px against a card travel
   of 584 — better than half the track spent holding a finished picture. With
   the slide now firing BEFORE the pin (see stage.js), the pin's only remaining
   job is to hold the finished card still long enough for the 1.1s travel to
   finish and then hand over; 150vh leaves ~730px between the card coming out
   and the exit beat, which is enough for that and not much more. Shorter than
   this and a fast scroll would cut the slide off mid-flight. */
.env-wrap { position: relative; height: 150vh; }

.env-scene {
  /* sticks below the bar for the same reason `.svc` does — a scene pinned at
     0 has its top 68px painted over by the header's opaque band */
  position: sticky; top: var(--bar-h); height: calc(100svh - var(--bar-h));
  overflow: hidden; background: var(--bg);
  --env-frame: calc(100svh - var(--bar-h));
  /* ⚠️ THE THIRD TERM IS WHAT KEEPS THIS OFF A SHORT WINDOW'S FLOOR. The
     assembly stands 0.738 x its own width tall (card overhang 0.234 + envelope
     0.505), and the artboard lets the envelope run off the bottom — 854 units
     of it in a 764-unit frame. Fine at 1440x900, where the measure wins and
     only ~42px of pocket is lost; at 1440x700 the un-capped measure would push
     the pocket mouth itself past the fold and the section would read as a
     green card with a stray edge under it. 0.62 keeps the card plus about half
     the pocket inside the frame at every height. */
  --env-w: min(calc(100vw - 2 * var(--gutter)), 1160px, calc(var(--env-frame) / .62));
  --env-air: calc(var(--env-frame) * .022);
}

/* ─── beats ────────────────────────────────────────────────────────────── */
/* n → in → out → off, set by the driver in stage.js.
   ⚠️ `off` MUST REPEAT `out`'S CARD STATE. The exit band is short and a fast
   scroll can land on `off` having never painted `out`; without the repeat the
   card would drop back into the envelope on the way past. */
.env-scene[data-env=n] .env-stage {
  opacity: 0; transform: translateX(-50%) translateY(6%);
}

.env-scene[data-env=n] .env-card,
.env-scene[data-env=in] .env-card { transform: translateY(50.356cqw); }

/* the small lift on the way out — "a slight movement", not a second exit */
.env-scene[data-env=off] .env-stage {
  transform: translateX(-50%) translateY(-3.2%);
}

@media (prefers-reduced-motion: reduce) {
  .env-scene[data-env=n] .env-stage { opacity: 1; transform: translateX(-50%); }
  .env-scene[data-env=n] .env-card,
  .env-scene[data-env=in] .env-card { transform: none; }
}

@media (max-width: 900px) {
  .env-wrap { height: auto; }
  .env-scene {
    position: static; height: auto; overflow: visible;
    padding: clamp(40px, 8vw, 72px) var(--gutter);
  }
  .env-scene[data-env] .env-card {
    position: static; width: 100%; aspect-ratio: auto;
    transform: none; container-type: normal;
    padding-bottom: clamp(18px, 4vw, 28px);
  }
}

/* screen-reader-only — the tracks card carries no visible h2, but the page's
   outline still needs one */
.u-sr {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* No top margin: the eyebrow above already carries 14px and these are ordinary
   flow siblings inside `.plc-say`, so the two collapsed and the gap measured 14
   either way. Dropping it makes the gap explicitly the label's, the way it is
   under every other head on the page, rather than a coincidence of collapsing. */
.plc-claim {
  margin: 0;
  font-family: var(--serif); font-weight: 400;
  /* ⚠️ THE HEADING TIER, EXACTLY (5 Oct 2026, the user: "when you are saying
     60%, the font being used is a different, very different size. It should
     use the same font and font size that we are using for headings"). It sat
     a step under the h2 (38px) because it once lived in a 0.7fr column; it
     has stood on its own line, at a 58ch measure, since — so it takes the
     h2's own setting and the page has one heading size, not two. */
  font-size: clamp(30px, 3.2vw, 44px); line-height: 1.18;
  letter-spacing: -.2px; color: var(--ink);
}

/* The figure is the same type as the sentence it is in — the display face a
   size up made "60%" the one line on the page in a different font and size.
   <em> stays for the meaning; only its italic is taken off. */
.plc-claim em { font: inherit; font-style: normal; letter-spacing: inherit; }

/* The record's heading row: title left, the way out of the section right.
   `align-items: center` rather than baseline — a button is a box with its own
   padding, and baseline-aligning it against a 44px serif title hangs it low.
   h2 carries its own bottom margin, so the row does not add one. */
.rec-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 20px 28px; flex-wrap: wrap;
}

/* ⚠️ ZEROING THE h2 HERE LEFT THE LEAD TOUCHING IT. The h2 sits in a flex row
   beside the button, so its own bottom margin would push the ROW apart rather
   than the heading off its lead — hence the zero. But the lead is the row's
   next sibling and had nothing of its own, so the record was the one section
   whose heading and standfirst ran together. The gap belongs to the row. */
.rec-head h2 { margin-bottom: 0; }

.rec-head { margin-bottom: 14px; }

.rec-head .btn { flex: 0 0 auto; margin: 0; }

/* PHONE: heading, then its lead, THEN the way out, then the cards. Stacked as
   the markup has it, the button came between the title and the sentence that
   explains it, and read as part of the heading. The row is dissolved
   (`display: contents`) so the title and the button become items of the
   section's own column and can be put in reading order; the markup — and the
   laptop's one-line row — are unchanged. */
@media (max-width: 900px) {
  #record > .wrap { display: flex; flex-direction: column; }
  .rec-head { display: contents; }
  .rec-head h2 { order: -3; margin-bottom: 14px; }
  #record .section-lead { order: -2; margin-top: 0; }
  .rec-head .btn { order: -1; align-self: flex-start; margin: 0 0 28px; }
}

.bs-orb { position: absolute; inset: 0; }

/* Geometry, straight off the Figma node as a percentage of the 4173x1331
   frame. Do not round these further — the disc and the archway are registered
   to within a few pixels at 4173 wide and drift is visible as a crescent of
   periwinkle along one edge of the arch. */
/* ⚠️ THE DISC AND THE GLOBE ARE ONE BOX, AND THAT IS THE WHOLE FIX (26 Sep
   2026). They used to be two: the disc at 36.74% wide and the globe in a
   50.42%-wide box with the sphere drawn at 73.2% of it. Two consequences,
   both of which showed:

     · THEY WERE NOT CONCENTRIC. Sphere centre landed at 51.52% of the frame
       against the disc's 52.19% — 28px apart at 4173 — so a crescent of
       periwinkle sat along one side and the two read as a ball in front of
       a circle rather than as one object.
     · THE SPHERE WAS CROPPED BY ITS OWN CANVAS. The box was 82.37% of the
       frame tall, 1096px, and the sphere is 1540px across. The canvas cut
       340px off the top and bottom with a hard straight edge — which is
       what "the globe is cropped at the top" was.

   One square box, taken from the ellipse node, and the sphere drawn to fill
   it edge to edge (`R = W / 2` in globe.js). Concentric by construction:
   there is no second number to drift. The periwinkle is not behind the
   globe any more, it IS the globe's paper — the disc shows through wherever
   the dither leaves the ink out, which is why the limb comes up light.

   THE 7.4% LIFT IS GONE WITH IT. The globe used to ride 4.5% above the
   Figma's own top "on request", to sit the Earth higher in the archway. It
   cannot any more without separating from the disc again, and it no longer
   needs to: the redrawn arch moved the disc up 130px on its own (the node's
   top is -12.2464% now, against -2.4793% before), which is the same move.

   Both boxes are the ellipse node 481:874 over the frame's 4173x1331, and
   the box renders SQUARE because the stage holds the frame's 3.135:1 — so
   36.7446% of its width and 115.2029% of its height are the same number of
   pixels. Do not round these further: the disc and the archway register to
   within a few pixels at 4173 wide. */
.bs-halo,
.bs-globe  { left:  33.8163%; top: -12.2464%; width:  36.7446%; height: 115.2029%; }

/* ⚠️ RE-MEASURED 26 Sep 2026 AGAINST THE REDRAWN ARTWORK. Both layers were
   replaced from Figma frame 8214 — the arcade is a finer dither of the same
   composition, the desk is a DIFFERENT illustration (a woman with a ponytail
   at a dressed desk, where it was a dark figure at a bare one). The desk did
   not just change picture, it changed PLACE: 58.16% wide at 24.12% down
   becomes 50.97% at 28.70%, so it sits smaller and lower in the arch. Every
   number here is the node's own box over the frame's 4173x1331 — nothing is
   eyeballed, and nothing should be nudged without re-reading the node. */
.bs-arcade { left:  -1.7734%; top: -82.7949%; width: 102.9955%; height: 200.1502%; }

.bs-desk   { left:  24.5147%; top:  28.7002%; width:  50.9705%; height:  75.0563%; }

/* ⚠️ BOTH LAYERS NOW UPSCALE, SO THEY MUST NOT BE SMOOTHED. Figma exports
   these at 1030x638 and 661x310; at a 1440 banner the stage is ~1671 wide, so
   the arcade renders at 1.67x its source and the desk at 1.29x. These are
   halftones — bilinear smoothing turns a dot grid into grey mush, which is
   the same reason `assets/cutouts/` carries this. The desk is the one to
   watch: the picture it replaced was 1038px and DOWNscaled. */
.bs-arcade, .bs-desk { image-rendering: pixelated; }

/* ⚠️ WITHOUT THIS THE GLOBE IS A WHITE BOX IN THE ARCHWAY. It is a greyscale
   photograph on a white ground, not a cut-out; multiply is what drops the white
   and tints the Earth to the disc's periwinkle. */
.bs-globe { mix-blend-mode: multiply; }

/* The canvas is empty until its texture has decoded and the projection
   tables are built. Hidden until then, so the arch holds its periwinkle
   disc rather than flashing a blank rectangle over it.
   ⚠️ NO TRANSFORM HERE. `.bs-orb` — this canvas's own parent — is already
   being slid up into the archway by the load-in below, and a second
   transform on the child reads as the globe wobbling inside a box that is
   itself moving. The opening is carried by the rotation and the develop,
   which are drawn INTO the canvas and cannot fight a layout animation. */
/* no fade (4 Oct 2026): the first pose is painted before `is-ready` lands, and
   the scene now waits for it, so an instant show is what keeps the Earth and
   the disc one object from their first frame. */
canvas.bs-globe { opacity: 0; }

canvas.bs-globe.is-ready { opacity: 1; }

/* --- the load-in: three layers rising into an empty frame ----------------
   NOT A FADE. An earlier cut faded the layers up from opacity 0 and read as
   the picture "developing" rather than arriving. Nothing fades and nothing
   scales here — each layer starts wholly BELOW the frame and slides up into
   it, so the banner begins as empty paper and the scene assembles itself from
   the bottom edge.

   THE ORDER IS THE POINT, and it is NOT back-to-front:
     1 · the woman at the desk, a little early — she sets the pace
     2 · the arcade rises behind her
     3 · the globe last, seating itself into the archway

   Animation order is independent of z-order, which stays orb → arcade → desk
   in the markup. So the frontmost thing arrives first and the backmost last,
   and because the archway is transparent you watch the globe climb up THROUGH
   it into place. That is the "fits into it" beat and it only works in this
   order.

   ⚠️ THE TRAVEL IS IN `cqh`, AND THAT IS WHY IT IS LEGIBLE. A percentage in a
   transform resolves against THE ELEMENT'S OWN BOX, so `translateY(30%)` moves
   the arcade (208% of the frame tall) nearly two and a half times as far as the
   desk (86%) — three numbers that look alike and behave nothing alike. `cqh` is
   a percentage of the SCENE's height for every layer, because `.banner-scene`
   is a size container, so 30cqh means the same distance everywhere and the
   three values can be read against each other.

   Travel was a full frame height at first, which cleared the frame completely
   and was too much — the layers now come up from just under the lower edge
   (22-34% of the banner) rather than from nowhere.

   `backwards` fill is still REQUIRED: without it the two delayed layers sit at
   their REST position for the length of their delay and then snap down to the
   start pose — a flinch on every load, and far more obvious now the travel is
   a full frame height.

   Easing is a hard ease-out (.16,1,.3,1) rather than the house
   (.22,1,.36,1): a slide this long needs to arrive decisively and settle,
   otherwise it drifts. --- */
@media (prefers-reduced-motion: no-preference) {
  .banner-scene[data-banner-parallax] .bs-desk,
  .banner-scene[data-banner-parallax] .bs-arcade,
  .banner-scene[data-banner-parallax] .bs-orb {
    animation-fill-mode: backwards;
    animation-timing-function: cubic-bezier(.16, 1, .3, 1);
  }
  /* 1 · the woman, early */
  .banner-scene[data-banner-parallax] .bs-desk {
    animation-name: bs-slide-desk;   animation-duration: .95s;
  }
  /* 2 · the arcade behind her */
  .banner-scene[data-banner-parallax] .bs-arcade {
    animation-name: bs-slide-arcade; animation-duration: 1.00s; animation-delay: .20s;
  }
  /* 3 · the globe, seating into the arch */
  .banner-scene[data-banner-parallax] .bs-orb {
    animation-name: bs-slide-orb;    animation-duration: 1.10s; animation-delay: .42s;
  }
  /* ⚠️ THE WHOLE SCENE WAITS FOR THE GLOBE (4 Oct 2026). The three slides
     used to start on a CSS clock while the Earth came up on its own (texture
     fetched, decoded, tables built) — so the periwinkle disc was in the frame
     before the planet was in the disc. A first fix gated only the orb, which
     was worse: its start pose is 34cqh down, NOT below the frame, so the empty
     disc sat in the lower banner waiting. Now every layer is hidden until
     globe.js has painted the first pose and put `is-ready` on .banner-scene;
     the three animations start at that instant with their own delays, so the
     choreography (desk → arcade → orb) is unchanged and the Earth is in the
     disc from the first frame it exists. Blank paper before that, which is
     how the load-in begins anyway. The no-JS reader gets the unconditional
     slides from a <noscript> style in fellowship.html. */
  .banner-scene[data-banner-parallax]:not(.is-ready) .bs-desk,
  .banner-scene[data-banner-parallax]:not(.is-ready) .bs-arcade,
  .banner-scene[data-banner-parallax]:not(.is-ready) .bs-orb {
    visibility: hidden; animation-name: none;
  }
}

@media (max-width: 900px) {
  /* ─── 2 · THE MISSION KEEPS ITS CASCADE, NOT ITS PIN ───────────────────
     The site's standing call is that pinning a scene inside a phone viewport
     buries the reader, and that still holds — the pin stays off. But the pin
     was never the scene; the LINE-AT-A-TIME arrival was, and forcing every
     word to opacity 1 here threw it away along with the pin. stage.js drives
     the same word cascade off the block's own travel through the viewport
     instead (see `wordProgress`), so the copy still assembles as you read it.
     ⚠️ Only the WORDS are driven. The wash and its columns stay static — they
     are the section's character, and animating a full-bleed gradient field on
     a phone costs more than it returns. */
  .mission .aim .w { opacity: 0; transform: translateY(.18em); }
  .mission .aim .w.is-in { opacity: 1; transform: none; }
  /* the words RISE in, they do not pop: an earlier phone rule set
     `transition: none` for the static wash, so every word appeared at full
     strength in one frame — half of why the reveal felt off. stage.js
     staggers them 40ms apart (`transition-delay`) when the scene plays. */
  @media (prefers-reduced-motion: no-preference) {
    .mission .aim .w { transition: opacity .55s cubic-bezier(.22, 1, .36, 1), transform .55s cubic-bezier(.22, 1, .36, 1); }
  }
  /* ─── 3 · ONE PHOTOGRAPH PER ITEM ──────────────────────────────────────
     The desktop pairing is a sticky stack beside the list. In portrait there
     is no beside, so the photograph becomes the item's own header — which is
     what the old `display: none` note said the phone would eventually want.
     stage.js moves each figure into its item at this width and moves them
     back above it; the markup is unchanged.

     ⚠️ `.ex-fig` IS `position: absolute; inset: 0` FOR THE DESKTOP STACK and
     has to be un-absoluted here, or it collapses its new parent the same way
     `.ex-figs` collapsed on the phone in the first place. */
  .ex-item .ex-fig {
    position: static; display: block;
    /* ⚠️ IT STAYS IN THE CONTENT COLUMN, AND THE INDENT IS NOT SLACK. 54px of
       `--card-pad` looks like room to reclaim for a bigger picture — it is
       not: `.ex-list::before` draws the card's ruled margin at --rule-x, 36px
       in, and the punch column sits inside that again. Pulling the photograph
       back past the rule puts it straight across it. There are 18px between the
       rule and the content edge and nothing else, so the picture keeps the
       column and the card keeps its margin — which is the right way round
       anyway: this reads as a photograph laid into a ruled card, which is what
       the card is. */
    margin: 0 0 18px;
    aspect-ratio: 16 / 10; width: 100%; overflow: hidden;
    background: rgba(35, 35, 35, .07);
  }
  .ex-item .ex-fig img { width: 100%; height: 100%; object-fit: cover; display: block; }
  /* ⚠️ THE `.is-ready` HIDE MUST NOT REACH THE RELOCATED FIGURES. stage.js
     sets `.ex-fig { opacity: 0 }` under `.is-ready` so the desktop stack can
     cross-fade; inside an item there is nothing to cross-fade against and it
     would blank every photograph on the page. */
  .ex.is-ready .ex-item .ex-fig,
  .ex.is-ready .ex-item .ex-fig.is-in { opacity: 1; transition: none; }
  /* the folio sits over the photograph's foot, the way a plate number does */
  .ex-item { padding-top: 22px; padding-bottom: 30px; }
  .ex-item .eyebrow { margin-bottom: 2px; }
  /* with a photograph above it the credit is a caption again, and belongs to
     the image rather than floating under the copy */
  .ex-item .ex-credit { margin-top: 16px; }
  /* ─── 4 · THE RAIL IS ALREADY THE RIGHT IDEA ───────────────────────────
     A horizontal swipe is more native on a phone than it is on a desktop, so
     the rail only needs sharpening: a firm snap, and the scroll padding that
     stops a snapped card sitting flush against the screen edge. The 93px of
     peek is deliberate — it is what says there is another one. */
  .rail { scroll-snap-type: x mandatory; scroll-padding-left: var(--gutter); }
  .rail-card { scroll-snap-align: start; }
}

@media (max-width: 900px) {
  /* the scene comes back */
  .env-wrap { height: 190vh; }
  .env-scene {
    position: sticky; top: var(--bar-h); height: calc(100svh - var(--bar-h));
    overflow: hidden; padding: 0;
  }
  /* ⚠️⚠️ ANCHORED IN cqw, NOT IN PERCENT — AND THAT IS NOT A STYLE CHOICE.
     The card is absolutely positioned inside `.env-slot`, so a percentage
     bottom resolves against the SLOT, which is 420% of the stage tall to give
     the card somewhere to rise from. `bottom: 39.5%` read 380px instead of the
     73 it was meant to be and parked the card 195px clear of the envelope,
     floating in paper. cqw is the stage's own width, which is the only frame
     of reference here that means anything: the pocket mouth is 25cqw above the
     stage's foot, so 15cqw puts the card's own foot a third of the pocket's
     depth inside it, at any screen width.

     Anchored by the BOTTOM because the card's height is its content's now — a
     `top` would move the whole sheet every time a track name wrapped. */
  .env-scene[data-env] .env-card {
    position: absolute; left: 3.287cqw; right: 3.287cqw;
    bottom: 15cqw; top: auto;
    width: auto; height: auto; aspect-ratio: auto;
    container-type: normal;
    padding-bottom: 18px;
    transform: none;
    transition: transform 1s cubic-bezier(.2,.72,.24,1);
  }
  /* IN: down by its own height, which puts its TOP below the pocket mouth and
     the whole sheet behind the front panel. 104% for margin. */
  .env-scene[data-env=n] .env-card,
  .env-scene[data-env=in] .env-card { transform: translateY(104%); }
  .env-scene[data-env=n] .env-stage { opacity: 0; transform: translateY(5%); }
}

@media (max-width: 900px) and (prefers-reduced-motion: reduce) {
  .env-scene[data-env] .env-card,
  .env-scene[data-env=n] .env-card,
  .env-scene[data-env=in] .env-card { transform: none; transition: none; }
  .env-scene[data-env=n] .env-stage { opacity: 1; transform: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   THE MARK STRIP — the fellowship's placements, 26 Sep 2026.

   Replaces the 3x3 panel. Two things were wrong with that grid and only one
   of them was the layout.

   ⚠️ EQUAL INK HEIGHT IS NOT EQUAL SIZE, and that was the real fault. The
   panel normalised every mark to the same ink HEIGHT — measured, all nine at
   exactly 60px — and it still read as a jumble, because ARTPARK at 4.53:1
   then carries four times the ink of Tamil Nadu's 0.91:1 emblem. Height
   parity flatters square marks and bloats wordmarks.

   So the strip normalises toward equal AREA instead: ink height ∝ 1/aspect^p.
   `--logo-m` is `(600 / the mark's own ink height) / aspect^0.4`, computed per
   mark off the actual alpha bounding box, so `--mark` sets the optical size
   and the exponent decides how much of the aspect difference is absorbed.

   ⚠️ p = 0.4, NOT 0.5. True equal area (p = 0.5) drops ARTPARK's ink to 24px
   tall, which puts its "AI & Robotics Technology Park @ IISc" tagline at
   about 7px and unreadable. 0.4 takes out most of the imbalance and keeps the
   tagline legible: the nine now run 32–60px of ink height instead of a flat
   60, and 55–144px wide instead of 55–281.
   ─────────────────────────────────────────────────────────────────────── */
.plc .wrap .plc-claim {
  /* The claim stands on its own line now rather than in a 0.7fr column, so it
     needs a measure of its own. Full-bleed at this size is the mistake the
     About headline made — see §3c of ../03-design-system.md. But 24ch put it
     in a four-line column narrower than the one it just came out of, which
     was the whole problem. 58ch sets it in two lines and still stops short
     of the measure. */
  max-width: 58ch;
}

.plc-strip {
  /* 52px floor, not 38 (4 Oct 2026): the floor is the INK height of every
     mark (see --logo-m), and at 38 the phone's logos read as specks under
     the headline. 4vw still takes over above 1300px. */
  --mark: clamp(62px, 4.8vw, 70px);
  --mark-gap: clamp(40px, 5vw, 88px);
  position: relative; overflow: hidden;
  margin-top: clamp(32px, 3.4vw, 48px);
  padding: clamp(22px, 2.2vw, 30px) 0;
  /* no ground behind the marks (4 Oct 2026) — the strip sat on --surface as a
     band and read as a plate behind the logos; the marks are transparent
     PNGs and sit on the paper like everything else */
  /* the strip runs edge to edge; the marks fade out rather than being cut */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}

.plc-track {
  display: flex; align-items: center; width: max-content;
  gap: var(--mark-gap);
  margin: 0; padding: 0 var(--mark-gap) 0 0; list-style: none;
  animation: plc-marquee 72s linear infinite;
}

.plc-strip:hover .plc-track,
.plc-strip:focus-within .plc-track { animation-play-state: paused; }

.plc-mark { flex: 0 0 auto; display: flex; align-items: center; }

.plc-mark img {
  display: block; width: auto;
  height: calc(var(--mark) * var(--logo-m, 1.6));
}

@media (prefers-reduced-motion: reduce) {
  /* no clock at all — the strip becomes an ordinary scrollable row */
  .plc-track { animation: none; }
  .plc-strip { overflow-x: auto; }
}

@media (max-width: 900px) {
  .plc .wrap .plc-claim { max-width: none; }
}

@media (max-width: 900px) {
  .env-wrap { height: auto; }
  .env-scene, .env-scene[data-env] {
    position: static; height: auto; overflow: visible;
    padding: clamp(40px, 8vw, 72px) var(--gutter);
  }
  .env-scene[data-env] .env-card,
  .env-scene[data-env=n] .env-card,
  .env-scene[data-env=in] .env-card {
    position: relative; left: auto; right: auto; top: auto; bottom: auto;
    width: 100%; height: auto; aspect-ratio: auto;
    transform: none; transition: none;
  }
}

/* ─── THE APPLY CARD'S TYPE IS THE PAGE'S TWO TIERS (5 Oct 2026, the user:
   "the deadline font size should be the same as the heading font size ...
   for each of the tracks ... 36, the inside content heading") ───────────────
   The card set both in cqw of itself, so they were right at one card width
   and drifted at every other — on a phone the deadline came out 25px against
   the 30px headings and the track names 20px, in the display face. They now
   name the tiers outright: the deadline is the h2 (style.css `h2`), the track
   names are the in-card heading (`.ex-item h3`, 36px on a laptop). Scoped to
   this page's envelope; the home page's copy of the card is its own. */
.env-scene .env-deadline {
  font-family: var(--serif); font-weight: 400;
  font-size: clamp(30px, 3.2vw, 44px); line-height: 1.18; letter-spacing: -.2px;
}
.env-scene .env-tname {
  font-family: var(--serif); font-weight: 400;
  font-size: clamp(26px, 2.6vw, 36px); line-height: 1.22; letter-spacing: -.2px;
}

/* ─── THE TRACK NAME IS THE HOVER TARGET, AS ON THE HOME PAGE (5 Oct 2026, the
   user: "implement the same thing that we did on the home: just hovering on
   the names, the images show up") ────────────────────────────────────────────
   stage.js's cursor photograph already listens on `.env-tname` in any
   `.env-row[data-cursor-img]`; here, as on home, the name was a grid item
   stretched across the row, so the hover zone ran under the Apply pill. It
   hugs its words; the indent moves from padding to margin so nothing moves. */
@media (hover: hover) and (pointer: fine) {
  .env-scene .env-tname {
    justify-self: start; width: fit-content;
    padding-left: 0; margin-left: 4.903cqw;
  }
}

/* the orientation's video link — the page's outline button, sitting under the
   copy and over the credit; it keeps its own width in the column */
.ex-item .ex-cta { align-self: flex-start; margin: 20px 0 0; }

/* ─── THE APPLY CARD IS IN ITS ENVELOPE ON A PHONE TOO (5 Oct 2026, the user:
   "we can still have an envelope or a folder that the card is in ... take
   inspiration from the home page, the transparent one in which the card is
   there, so the envelope is refitted for a mobile screen") ──────────────────
   The phone used to drop the envelope's papers and show the bare card. This
   is the home page's phone envelope (home.css, `.fel-env`), on this page's
   card: the same two papers — `preserveAspectRatio="none"` on both SVGs,
   which costs nothing on a laptop where the stage is the 1156:584 viewBox
   to the decimal — stretched round a card that sits IN FLOW, so the stage
   grows with the copy:
     .env-back   the back panel, over the whole stage
     .env-card   the letter, z-index 1, between the two papers
     .env-front  the pocket, pinned to the floor, over the card's last band
   --env-tuck is how much of the card the pocket covers: small enough to clear
   the last track. The pocket's mouth is at y295 of 584 (50.5% down), so its
   height is sized so the 49.5% below the mouth equals --env-tuck. The punch
   holes go, as on home — a binding you cannot read at this width — and the
   card's insets become one paired --env-pad on both sides. */
@media (max-width: 900px) {
  .env-scene .env-stage, .env-scene[data-env] .env-stage, .env-scene[data-env=n] .env-stage {
    --env-tuck: 52px;
    position: relative; padding: 22px 0 calc(var(--env-tuck) * .62);
  }
  .env-scene .env-paper { display: block; position: absolute; }
  .env-scene .env-back  { inset: 0; width: 100%; height: 100%; }
  .env-scene .env-front {
    left: 0; right: 0; top: auto; bottom: 0;
    width: 100%; height: calc(var(--env-tuck) / .495);
  }
  .env-scene .env-slot { position: static; overflow: visible; z-index: 1; }
  .env-scene .env-card, .env-scene[data-env] .env-card, .env-scene[data-env=in] .env-card {
    position: relative; z-index: 1;
    left: auto; top: auto; aspect-ratio: auto; transform: none;
    width: calc(100% - 32px); margin: 0 auto;
    container-type: inline-size; --env-pad: 7.5cqw;
    padding-bottom: calc(var(--env-tuck) + clamp(14px, 3.5vw, 22px));
  }
  .env-scene .env-holes { display: none; }
  .env-scene .env-deadline,
  .env-scene .env-row, .env-scene .env-row:first-child { padding-left: var(--env-pad); padding-right: var(--env-pad); }
  .env-scene .env-row::after { left: var(--env-pad); right: var(--env-pad); }
  .env-scene .env-apply { right: var(--env-pad); }
}
