/* ==========================================================================
   AMIN APAC — profile-story.css
   "Our story": the scrollytelling band that opens the profile body.

   A two-column section where the LEFT PANEL PINS and the RIGHT COLUMN SCROLLS
   past it, and the copy in the pinned panel lights word by word as it goes.
   Built from the composition on unitedcarriers.com/about, which is the
   reference profile-v2.html is being cut against.

   ADDITIVE AND SCOPED. Everything here is under .story, which exists only on
   profile-v2.html. It loads after profile.css and touches nothing that sheet
   owns, so profile.html is unaffected by every rule in this file. Deletable as
   a set with js/profile-story.js and the one <section class="story"> in the
   page.

   WHAT IT REPLACES, because the page must not say the same thing twice: this
   band carries the member's `about` copy, which was the .profile-lead block in
   the column below. js/profile-story.js hides that block once it has taken the
   copy. The block stays IN THE DOM rather than being removed, because
   js/member-profile.js anchors the Field notes section on it.

   WHY THE UNLIT WORD IS --ash AND NOT A LIGHT GREY. The reference sets its
   unread copy at roughly #B4B4B4, which is 2.3:1 on white - below the floor for
   body text, and the state a reader who never scrolls is left in. --ash is
   8.3:1 unlit and --black is 21:1 lit, so the effect is a shift in EMPHASIS
   between two readable tones rather than a reveal from an unreadable one. The
   paragraph is legible with the script removed, with JavaScript off, and at
   every point in between.
   ========================================================================== */

/* Registered so --p is a real number rather than a token string: an
   unregistered custom property inside calc() is substituted textually, which
   works, but registering it also gives the fallback an initial value of 0 and
   lets the reduced-motion rule below set it once and be done. The whole rule is
   dropped by engines that do not support @property, which is harmless - the
   script sets --p on the element either way. */
@property --p{syntax:"<number>";inherits:true;initial-value:0}

.story{
  /* Local geometry, declared once at the top the way profile.css declares its
     own. Not on the spacing scale because these are measures and run
     lengths, which the scale does not cover.

       --story-measure   the reading column. 560px at 18px Libre Franklin is
                         ~66 characters, inside the 60-75 rule.
       --story-pin       where the panel pins. The .pf-bar is fixed and 60px
                         tall and arrives exactly when this section does, so
                         anything less than 60 pins the copy underneath it.
       --story-gap       the air between one picture and the next. Deliberately
                         a viewport unit and deliberately about half a screen:
                         it is what makes each picture arrive on its own, and it
                         is the difference between a sequence and a gallery.
       --story-run       the FLOOR under the field's height. The pin only exists
                         while the field is taller than the viewport, and the
                         figures are UNCROPPED (see below) so their own heights
                         cannot be relied on to provide it - two landscape files
                         can come in short. Above the floor the gaps set the
                         length, so a member with more pictures gets a longer
                         section rather than the same one stretched. */
  --story-measure:560px;
  --story-pin:calc(60px + var(--s8));
  /* MEASURED, then halved and halved again. At 44vh the gap rendered 396px
     between figures that are themselves only 328-406px tall - each picture had
     nearly its own height of empty room under it, which stopped reading as
     pacing and started reading as a broken layout. 13vh is ~117px at a 900px
     viewport, about a third of a figure: enough that the pictures arrive one
     at a time, not so much that the column looks like it failed to load.

     --story-run CAME DOWN WITH IT, and it has to. It is a min-height floor on
     the field, so with it left at 170vh (1530px) the shortened field simply
     stretched back to the old height and the gap reduction bought nothing -
     the space moved from between the pictures to underneath them. 118vh still
     clears the viewport, which is the only thing the pin actually requires. */
  --story-gap:13vh;
  --story-run:118vh;

  padding:var(--sec) 0;
  background:var(--surface);
}

/* FULL BLEED, the same exception the hero makes and for the same reason. This
   is a .wrap, so it centred in 1400px; the reference runs its story band to the
   window's own edges and the picture field is the half that pays for the
   difference - at a 1400 cap the figures were sized off a 713px field, and at
   1600 they are now sized off ~880px.

   The padding is hero V's number exactly - clamp(24px, 3.4vw, 72px) - so the
   copy's left edge and the agency name's left edge sit on the same vertical
   the whole way down the page. That alignment is the point; if the hero's
   padding is ever changed, change this with it.

   THE MEASURE IS UNAFFECTED, which is what makes this safe. The copy column is
   now far wider than 560px at any large viewport, and .story-panel's max-width
   caps the copy at --story-measure regardless - so the reading line does not
   grow with the band. Only the picture field does. */
.story-grid{
  display:grid;
  /* 44/56, AND THE SPLIT MOVED BECAUSE THE GUTTER DID. Measured: the gutter was
     var(--s6), a flat 24px, at every width from 1025 up - a reading column and a
     field of photographs 24px apart on a 1600px screen, which is closer than
     two paragraphs of the same text would be set. Worse, the 1024 breakpoint
     below widened it to var(--s10)/40px, so the gap was TIGHTER on the screens
     with the most room to spare and looser on the ones with least.

     The gutter is now a real one and scales: 32px at the narrow end, 88px at
     the wide. fr tracks divide what is left AFTER the gap, so the split had to
     move with it or the copy column would have shrunk under its own measure -
     at a 1400 wrap and a 70px gutter, 0.42 leaves 538px against a 560px
     measure, and .story-panel would simply have been capped short and reset
     the copy to a narrower line. 0.44 leaves 564px, which still holds the
     measure exactly, and the 56 that remains is still the larger share.

     THE 78px CEILING IS THE ARITHMETIC, not a taste call. At the 1400 wrap the
     content box is 1352px, so holding a 560px measure needs 0.44 x (1352 - g)
     >= 560, which resolves to g <= 79.3. An 88px gutter was tried first and
     measured 556px at 1920 - four pixels under the measure, invisible but
     wrong in the direction that matters, since the whole point of the split is
     that the column holds the line length exactly. 78 clears it at every width
     with 0.6px to spare.

     EVERYTHING LEFT OVER IS THE PICTURE FIELD - not a picture column. The
     figures do not fill it; each one takes a different slice of it, which is
     what section 03 is about. The void in the composition is the part of the
     field a given figure does not use, so it moves as you scroll instead of
     being a fixed empty gutter. */
  /* 44/56 IN THE 1400px CONTAINER, which is the geometry the arithmetic above
     was written for: "at the 1400 wrap the content box is 1352px" is this
     layout, not the full-bleed one it was briefly given. */
  grid-template-columns:minmax(0,44fr) minmax(0,56fr);
  column-gap:clamp(32px, 5vw, 78px);
  /* NO align-items:start HERE, AND THAT IS THE PIN. A sticky element can only
     travel inside its own containing block, which is the grid ITEM it sits in.
     Start-aligned, that item is exactly as tall as the panel, so the panel has
     nowhere to stick to and scrolls off the top of the screen looking like the
     rule was never applied. Stretch is the default and it is load-bearing: it
     makes the copy column as tall as the picture column, which is the track
     the pin runs in. */
}

/* --------------------------------------------------------------------------
   00. The curtain
   The hero holds while this section rises over it. Three rules and one number.

   THE PIN IS BOUNDED BY THE WRAPPER, not by a duration - a sticky element
   travels inside its containing block and nothing else, so .pf-curtain's
   padding-bottom IS the length of the hold. See the note on the wrapper in
   profile-v2.html for why it is bounded at all rather than running the length
   of the band.

   THE PAGE DOES NOT GET LONGER. The padding the curtain adds is cancelled by an
   equal negative margin here. --story-lift is used twice and must stay one
   number: raise it and the hero holds longer and the section climbs further, in
   step, with no third value to keep in sync.

   THE STACKING IS THE OVERLAP. .pf-hero carries isolation:isolate, so the
   curtain is given its own stacking context at z-index 0 and this section takes
   1 - which is what puts white over the banner rather than the banner over
   white. The section's own --surface ground is load-bearing for the same
   reason: a transparent section would climb over the hero and show it through.
   ON body, NOT ON .story. The curtain wraps the hero and is a SIBLING of this
   section, not an ancestor, so a custom property declared on .story is not in
   scope for it - the padding would resolve to nothing and the hold would
   silently be zero while the negative margin still fired, pulling the section
   up over a hero that never held. It has to be declared above both of them.
   -------------------------------------------------------------------------- */
body{
  /* How far the section climbs over the hero, and equally how long the hero
     holds before it lets go: the two are the same number by construction.
     About three quarters of a screen, measured off the reference, where the
     banner is roughly half covered at the first step and three quarters at the
     second. */
  --story-lift:70vh;
}

/* THE RUN IS A GENERATED BLOCK, NOT padding-bottom, and this is the whole trap
   in the mechanism. A sticky box is constrained by its CONTAINING BLOCK, which
   is the parent's CONTENT box - padding is outside it. Written as
   padding-bottom the curtain measured 1530px, the hero measured 900px, and it
   still had exactly zero travel, because the content box was 900px and the hold
   was entirely inside the padding the sticky box was not allowed to enter. The
   symptom is the worst kind: computed style says `position: sticky; top: 0px`,
   every ancestor is overflow:visible, and the element scrolls away like it was
   never declared. An empty ::after is a real child in the content box, so the
   containing block actually grows and the hold is real. */
.pf-curtain{position:relative;z-index:0}
.pf-curtain::after{content:"";display:block;height:var(--story-lift)}
.pf-curtain .pf-hero{position:sticky;top:0}

/* AND THIS RULE IS THE CLIMB ITSELF - the half that is easy to lose. The
   curtain above only makes the hero HOLD; without the negative margin here the
   section simply starts after the run and the two scroll past each other edge
   to edge, with --story-lift of empty page between them. It is also what pays
   the run back, so the page is not one pixel longer than it was.

   z-index:1 against the curtain's 0 is what puts white over the banner rather
   than the banner over white, and the section's --surface ground (in the block
   at the top of this sheet) is load-bearing for the same reason: a transparent
   section would climb over the hero and show it through. */
.story{
  position:relative;z-index:1;
  margin-top:calc(-1 * var(--story-lift));
}

/* THE HERO'S COPY RIDES UP AHEAD OF THE EDGE, and without this the overlap is
   backwards. Hero V sets its copy on the BASELINE of the frame, so a section
   climbing from the bottom covers the member's name first and the empty sky
   last - the reader loses the one thing the banner is for while the part with
   nothing in it stays on screen. In the reference the eyebrow and the standfirst
   are still readable in the strip above the white edge with the headline
   half-eaten underneath them, which is the whole look: the copy is being pushed
   up by the arriving section, not buried under it.

   0.62, MEASURED, NOT PICKED. Across the reference's frames the copy travels
   0.55 of a viewport while the white edge travels 0.90 - the copy moves at 
   about six tenths of the edge's rate, so the gap between them closes slowly and
   the headline is cropped rather than dropped. At 1 they would never touch and
   there would be no overlap at all; at 0 it is what we had before.

   THE SCRIM AND THE GHOST TRAVEL WITH IT, because the scrim is a
   bottom-weighted gradient and it is the only thing making white text legible
   over a photograph. Left behind, the copy would climb out of its own contrast.
   Moving it up exposes the foot of the frame - which is safe by construction:
   the scrim's bottom lands at 100vh - 0.62c and the section's edge at 100vh - c,
   and 0.62c is always less than c, so the part it vacates is already under
   white.

   .pf-stage IS DELIBERATELY NOT IN THE LIST. The media has its own parallax on
   --sy from js/profile-hero-v.js, and moving the picture at a second rate on top
   of that is two parallaxes fighting over one image. */
.pf-curtain .pf-scrim,
.pf-curtain .pf-ghost,
.pf-curtain .pf-copy,
.pf-curtain .pf-cue{
  transform:translate3d(0,calc(var(--climb,0px) * -0.62),0);
  will-change:transform;
}

/* --------------------------------------------------------------------------
   00b. What is left of the profile body
   The two-column block below this section was removed from profile-v2.html and
   only its wrapper survives, as the anchor js/member-profile.js needs to hang
   Field notes on (see the note in the page). base.css sets .profile-cols to
   `minmax(0,1fr) 320px`, so with the sidebar gone that 320px track and its
   --s14 gap would hold ~380px of empty column open beside the writing.

   Scoped to body.pf-v so profile.html, which still has the whole body, is
   untouched. Delete this rule when the body comes back.
   -------------------------------------------------------------------------- */
body.pf-v .profile-cols{grid-template-columns:minmax(0,1fr)}

/* AND NO PADDING WHEN IT IS EMPTY. base.css gives .profile-cols `--sec` block
   padding, which on this page was 192px of white holding nothing but a hidden
   section - sitting between the "Built around" band and the footer, i.e. a
   white sliver between two coloured grounds, which is exactly what the surface
   rule in base.css exists to prevent.

   The :has() guard is what keeps this safe: js/member-profile.js appends Field
   notes into this column as a VISIBLE .profile-block, and when it does, the
   wrapper gets its padding back and the writing keeps its air. Only the empty
   case collapses. */
body.pf-v .profile-cols:not(:has(.profile-block:not([hidden]))){ padding-block:0 }

/* AND THE WORK SECTION GOES WITH ITS STRIP WHEN THERE IS NOTHING ELSE IN IT.
   The image strip was hidden on this page (client direction, 2026-09-02) but
   the div stays, because js/member-cases.js builds the case study cards out of
   it - see the note in profile-v2.html. That leaves #workSec holding a heading,
   the cards, and a hidden strip.

   For a member with case studies that is exactly right. For one WITHOUT any it
   is a heading over nothing: measured, GVN, Brew and Harper each have three
   work images and no published case study, so all three rendered "Work from
   <member>" and then stopped. profile.js's own guard cannot catch it - it hides
   the section when there are no IMAGES, and these members have images; they are
   simply no longer being shown.

   :has() rather than a script because it is a presentational consequence of a
   presentational decision, and because .profile-cols two rules above already
   depends on it. Scoped to body.pf-v: profile.html still shows its strip and
   its section is unaffected. */
body.pf-v #workSec:not(:has(.mc-cases)){ display:none }

/* NO GUTTER OVERRIDE HERE. This block carried the full-bleed clamp for one
   round; it is back on .wrap's own 1400px container with every other band
   (client direction, 2026-09-02), so base.css's rule is left to do its job. */

/* --------------------------------------------------------------------------
   01. The pinned panel
   -------------------------------------------------------------------------- */
/* Stretched by the grid, and stated rather than inherited so a later hand
   adding align-items to the grid above does not quietly unpin the panel. */
.story-copy{position:relative;align-self:stretch}

/* A CONTENT-HEIGHT STICKY BOX, NOT A VIEWPORT-HEIGHT ONE, and this is the
   difference between our pin and the reference's. Both end up centred; only one
   of them stays there.

   What this used to be: a box of min-height 100vh with the copy flex-centred
   inside it. That does lock the copy at the centre - measured, content centre
   480 against a viewport centre of 450 - but a sticky element holds for
   (container - box - top), and the box was 776px of a 1249px container, so it
   let go after 381px and scrolled away with 40% of the pictures still to come.

   Measured on unitedcarriers.com/about, their .about-intro-stick is 580px - the
   copy's own height - inside a 2246px wrapper. A short box in a tall container
   is what buys the long hold.

   So the box is the copy now, and the centring moves into the offset instead:
   half the viewport less half the panel. --story-panel-h is written by
   js/profile-story.js because CSS cannot ask an element how tall it is; the
   600px fallback is close to the measured average and keeps this sane if the
   script never runs. max() with --story-pin is the floor, so a panel taller
   than the viewport pins under the bar rather than being pushed off the top.

   The travel it buys: 1249 - ~600 - ~150 = ~500px of a 541px scroll, so the
   copy now holds the centre for effectively the whole picture run. */
.story-panel{
  position:sticky;
  top:max(var(--story-pin), calc(50vh - var(--story-panel-h, 600px) / 2));
  align-self:start;
  max-width:var(--story-measure);
  /* The band's width in words, on the panel because the heading and the
     paragraphs are one run and both read it. */
  /* Narrowed from 14. Over a ~120 word run a 14-word band is nearly two whole
     lines in transition at once, which reads as a soft wash rather than as a
     sweep with a direction. 9 keeps the edge soft enough not to step word by
     word but defined enough to see travelling. */
  --fade:9;
}

/* SHORT VIEWPORTS LOSE THE PIN, and this is a correctness rule rather than a
   refinement. The panel runs about 670px - eyebrow, a two-line heading and
   three paragraphs at the 560px measure, measured against the longest `about`
   on file - and it pins 92px down. Under roughly 760px of viewport it does not
   fit, and a PINNED element that does not fit is not merely cropped: the panel
   holds still while the page scrolls, so the tail of the last paragraph is
   unreachable for as long as the pin lasts. There is no scroll that recovers
   it.

   The first cut of this rule only switched the panel to top-aligned, which
   moved the overflow from the top edge to the bottom and left it just as
   unreadable. Unpinning is the honest answer: the section falls back to two
   plain columns, the copy scrolls like copy, and the word reveal - which is
   driven by the section's own progress, not by the pin - carries on exactly as
   it does above the threshold. A 1440x768 laptop with browser chrome lands
   here, so this is a real configuration, not an edge case.

   Deliberately a HEIGHT query with no width bound: a tall phone keeps the
   reveal and a short desktop loses the pin, which is the right way round. */
@media (max-height:760px){
  .story-panel{position:static;min-height:0;display:block}
}

/* The mono came off here on 2026-09-02 - see the eyebrow note in
   css/profile-v-system.css, which holds the reason and the one-line reversal.
   --s6 was already the right gap and is now the gap all three bands use. */
.story-eyebrow{
  margin:0 0 var(--s6);
}

/* 34/42 is not a new size: it is the value base.css already gives
   .profile-lead h2, which is the heading this one is promoted FROM. Reusing it
   keeps the type scale where it was rather than inventing a step between --t-h3
   (30) and --t-h2 (36-56) for one section, and it keeps the section heading
   clearly under the hero's H1 two screens above. */
/* NOW FLUID, CAP UNCHANGED. 34/42 was the value base.css gives
   .profile-lead h2 and reusing it kept the scale honest at desktop - but it
   was the only FIXED heading on the page, so at 768 and below it overtook
   .svc-h2 (floored at 30) and inverted the very hierarchy the services band's
   own note says it was raised to fix. --pf-h3 caps at 34, so every width from
   1278 up is byte-identical to what shipped; 1024 sets 26 and so does 390. */
.story-head{
  font-size:var(--pf-h3);line-height:1.235;
  font-weight:500;
  /* --ash, not --black: the heading's words are in the reveal now, so this is
     the tone they start from and the one a script failure leaves them on. */
  color:var(--ash);
  margin:0 0 var(--s5);
  text-wrap:balance;
}
/* THE TRACKING IS BEHIND A QUERY NOW, for the same reason .approach-lede's is
   in css/profile-approach.css: base.css's rule is that nothing under 30px
   carries negative tracking, and this heading only reaches 30 at 2.4vw =
   1250px of viewport. Unconditional -.01em meant a 26px heading tracked tight
   on every tablet and phone. Desktop is unchanged. */
@media (min-width:1250px){
  .story-head{ letter-spacing:-.01em }
}

/* --------------------------------------------------------------------------
   02. The reveal
   Two tones and a moving band between them. The band is --fade words wide, so
   the edge is a gradient of about a line and a half rather than a hard cursor
   stepping word to word.
   -------------------------------------------------------------------------- */
.story-body{
  font-size:18px;line-height:27px;
  /* The floor. Every word inherits this, so a failure anywhere below - the
     script not running, color-mix unsupported, --p never set - lands on a tone
     that clears 4.5:1 rather than on the light grey the effect starts from. */
  color:var(--ash);
}
.story-body p{margin:0;color:inherit;text-wrap:pretty}
.story-body p + p{margin-top:var(--s5)}

.story-w{
  /* How far this word is through the band: 0 unlit, 1 lit, fractional inside
     it. --i is the word's index and --n the count, both written by the script;
     --p is the section's scroll progress, 0 to 1.

     The + var(--fade) is what makes the LAST word finish: without it the band's
     leading edge only reaches word --n at p=1, so the tail of the paragraph
     lights on the final pixel of travel. */
  --lit:clamp(0, calc((var(--p,0) * (var(--n,1) + var(--fade)) - var(--i,0)) / var(--fade)), 1);
  color:color-mix(in srgb, var(--black) calc(var(--lit) * 100%), var(--story-unlit));
}

/* THE UNLIT TONE IS THE WHOLE EFFECT, and it was --ash, which is why nobody
   could see this working. Measured: --ash #4A4E57 has a relative luminance of
   0.076 against #000's 0.000, so a word travelled from "dark text" to "dark
   text" and the sweep was doing its job invisibly. The mechanism was never
   broken - the two ends of it were the same colour.

   #969BA5 was the first correction and it did not go far enough (client
   direction, 2026-09-02: "it doesnt look a text reveal animation"). Measured
   on white it is 2.8:1, which is a legible grey - so the run read as two
   paragraphs of ordinary grey copy that happened to have a black paragraph
   above them, rather than as text that has not arrived yet.

   #B4B9C2 is 1.97:1 on white and 10.7:1 against the lit black, up from 7.5:1.
   Same hue, same family - it is #969BA5 lightened, not a new grey - so the
   unlit state now sits closer to the page's ground than to its copy, and the
   band travelling through it is the loudest thing in the column.

   THE ACCESSIBILITY TRADE, stated because it is a real one and it just got
   larger: unlit copy sits near 2:1 on white rather than clearing 4.5:1. It is deliberately the
   TRANSIENT state - the run finishes at 85% of the pin's travel, reduced
   motion pins --p to 1 so every word is black from the start, and the .story-
   body floor below still catches any failure at --ash. If this has to clear
   4.5:1 in the unlit state as well, #6B7079 is the darkest tone that does and
   the effect survives it, just quieter. */
.story-panel{--story-unlit:#B4B9C2}

/* --------------------------------------------------------------------------
   03. The figures
   A STAGGERED FIELD, NOT A COLUMN. The first cut stacked them full-width down
   the right-hand side, evenly spaced, which is a gallery: four pictures at one
   size on one axis read as a contact sheet, and the eye stops travelling after
   the first. The reference does something else - each picture takes a
   DIFFERENT slice of the right-hand field, at a different width and a different
   shape, so the thing moving past the pinned copy changes proportion as well as
   position. That is what makes the scroll read as a sequence rather than a list.

   THE FOUR SLICES, expressed as a share of the FIELD rather than of the page,
   so they hold at every width the field has:

     1   right, large, square     24% in, 76% wide, 1:1
     2   flush left, medium        0% in, 58% wide, 3:2
     3   right, small, square     54% in, 46% wide, 1:1
     4   left of centre, large    12% in, 76% wide, 4:5

   One flush left, two right, one small, one portrait. They cycle on 4n, so a
   member with two pictures gets the first two - the widest contrast in the set -
   and one with nine gets the pattern twice over.

   REBUILT, 2026-09-02, AND THAT IS WORTH KNOWING. This section was deleted on
   a request to remove one photograph that was read as "remove the pictures",
   and these files are not in git. The VALUES here are the ones measured off the
   rendered page before the deletion and are what was running; the prose is
   reconstructed. If any of it reads thinner than the rest of this file, that is
   why - the mechanism is right, the archaeology is not the original.
   -------------------------------------------------------------------------- */

/* THE GAP IS A MARGIN BETWEEN FIGURES, NOT row-gap ON THE FIELD, and the
   difference is 400px of dead scroll. The credit line is a child of this
   container too - it is off the page now, but it was live when this was
   measured - so a row-gap put half a screen of nothing between the last
   picture and one line of 13px grey text. An adjacent-sibling margin applies
   the pacing only where
   pacing is wanted: between one picture and the next. */
.story-figs{
  display:grid;
  min-height:var(--story-run);
  align-content:start;
}
.story-fig + .story-fig{margin-top:var(--story-gap)}

.story-fig{
  margin:0;
  border-radius:var(--r-md);
  overflow:hidden;
  background:var(--haze);
  /* The slice. Percentages resolve against the field, so the pattern survives
     the breakpoints without being restated. */
  width:var(--fw,100%);
  margin-left:var(--fx,0);
}
.story-fig:nth-child(4n+1){--fx:24%;--fw:76%;--far:1}
.story-fig:nth-child(4n+2){--fx:0;   --fw:58%;--far:3/2}
.story-fig:nth-child(4n+3){--fx:54%; --fw:46%;--far:1}
.story-fig:nth-child(4n+4){--fx:12%; --fw:76%;--far:4/5}

/* THE LAST FIGURE IS NEVER THE SMALL ONE (2026-09-03). js/profile-story.js caps
   the band at three - `pool.slice(0, 3)` - so slice 4 has never rendered on any
   member, and every one of the eleven ends on 4n+3: the 46% square, the runt of
   the cycle. The pattern was drawn for four pictures and is shown three, so the
   sequence closes on its quietest note instead of its loudest.

   NO NEW VALUES. This borrows from the set already declared above - slice 1's
   24%/76% and slice 2's 3:2 - so the band opens and closes flush to the same
   right edge, and nothing has to be carried as a fourth slice.

   3:2 IS ALSO THE HONEST CROP. The sources are 16:9 - brew-3.jpg is 915x528 -
   and the square discarded 44% of the width, which on a group photograph takes
   the people at both ends off. 3:2 discards 9%.

   :last-child, NOT a bare :nth-child(3), so this is exactly the "the pattern
   ran out early" case. Lift the slice(0, 3) cap and a member with four or more
   pictures gets the full 4n cycle back untouched. */
/* IN A MEDIA QUERY, and it has to be. The mobile block at the foot of this file
   resets every figure to `.story-fig{width:100%;margin-left:0;aspect-ratio:auto}`
   at (0,1,0), and a media query adds no specificity - so a bare
   :nth-child(3):last-child (0,3,0) would win there and hand a 390px screen one
   figure indented 24% with a fixed 3:2 crop. 768 is the same boundary the
   mobile block uses (767 max), so the two meet with no gap. */
@media (min-width:768px){
  .story-fig:nth-child(3):last-child{--fx:24%; --fw:76%;--far:3/2}
}

/* THE BOX HAS A SHAPE AND THE PICTURE FILLS IT. aspect-ratio on the figure is
   what gives the field its rhythm - four boxes of one proportion is a contact
   sheet however they are offset - so 1:1, 3:2, 1:1 and 4:5 cycle down the
   column and the thing passing the pinned copy changes proportion as well as
   position.

   cover, and this was measured rather than assumed: at 1440 the field rendered
   a 364px-wide figure 243px tall, which is exactly 3:2, so the box governs and
   the file is cropped into it. A first pass at rebuilding this section used
   contain instead and every figure came back letterboxed with --haze above and
   below - visibly wrong, and the reason this note now says which of the two it
   is.

   --haze stays as the ground underneath: it is what shows while a file decodes,
   and it is why a slow picture reads as a frame rather than as a hole. */
.story-fig{aspect-ratio:var(--far,1)}
.story-fig img{
  display:block;
  width:100%;height:100%;
  object-fit:cover;
  /* WHERE THE CROP KEEPS ITS SUBJECT. The frames are 1:1, 3:2, 1:1 and 4:5 and
     the sources are 16:9, so a square figure discards 44% of the width and the
     4:5 portrait discards 55%. Without this every one of those cuts came off
     the centre by default, whether or not the subject was there.

     --pos comes from the image record's `pos` in data.js, written onto the
     figure by js/profile-story.js. The fallback is the browser default, so an
     image with nothing on file behaves exactly as it did before. */
  object-position:var(--pos, 50% 50%);
}

/* The entrance. Opacity and --s6 of rise, once each on the way in - see the
   observer at the foot of js/profile-story.js, which unobserves after it fires
   so a picture cannot fade back out when it leaves the top of the screen. */
.story-fig{
  opacity:0;transform:translate3d(0,var(--s6),0);
  transition:opacity .7s var(--ease), transform .7s var(--ease);
}
.story-fig.is-in{opacity:1;transform:none}

/* Dormant, and kept because the rule that needs it has not changed. The credit
   line under the field is off the page by client direction (2026-09-02) - see
   the note in js/profile-story.js, which holds the history and the trade. The
   attribution RULE has not been repealed: if a member ever supplies imagery
   under an attribution condition, the commented block there is uncommented and
   this styles it. Six lines and no CSS work. */
.story-credit{
  margin-top:var(--s5);
  color:var(--ash);font-size:13px;line-height:20px;
}

/* --------------------------------------------------------------------------
   04. Breakpoints
   -------------------------------------------------------------------------- */

/* Tablet: the void between the columns is the first thing to go - at 900px it
   stops being composition and starts being a gap the layout cannot afford. The
   measure caps the copy anyway, so the copy column can take the smaller share. */
/* The split is already 44/56 above, and the gutter's own clamp has it at 51px
   here and falling - so this breakpoint's two overrides are both what the base
   rule now does. Left as a comment rather than a rule so the next hand does not
   restore a flat gutter on top of the scaling one. */

/* Mobile: one column, and THE PIN COMES OFF. A sticky panel needs a second
   column scrolling past it to be pinned against; stacked, it would pin against
   its own figures directly underneath it, which reads as the page having
   frozen. The reveal stays - it is driven by the section's own progress and
   works on a static block - so the section keeps its behaviour and loses only
   the composition that needs two columns to exist.

   SOURCE ORDER: copy leads. The story is the point and the pictures are the
   evidence for it. */
/* 767, not 768, to land on base.css's mobile boundary. Both were max-width, so
   at exactly 768px - iPad portrait - base.css was still serving tablet type and
   spacing while this sheet had already switched the story to its phone layout.
   The same 1px seam the three 1024 sheets had, on the other common tablet
   width. */
@media (max-width:767px){
  /* Less climb on a phone, and on body for the reason section 00 gives - the
     curtain cannot see a value set on .story. 70vh of overlap on an 844px
     screen swallows the hero's own copy before it has been read, and that copy
     is the member's name; it is not decoration you can afford to cover early. */
  body{--story-lift:34vh}
  .story{padding:var(--sec-sm) 0}
  .story-grid{grid-template-columns:1fr;row-gap:var(--s10)}
  .story-panel{position:static;min-height:0;display:block;max-width:none}
  .story-panel{--fade:7}
  /* The floor and the stagger both come off with the pin. There is nothing left
     to give travel to, holding a 118vh column on a phone would print three
     pictures with two empty screens between them, and a 46%-wide picture on a
     390px screen is a thumbnail. Full width, ordinary spacing. */
  .story-figs{min-height:0}
  .story-fig{width:100%;margin-left:0;aspect-ratio:auto}
  .story-fig + .story-fig{margin-top:var(--s6)}
  .story-fig img{max-height:none;height:auto}
}

/* --------------------------------------------------------------------------
   05. Reduced motion
   The pin is not motion - nothing moves, the page scrolls past a fixed element
   - so it stays. The word reveal and the figure entrance are, so both are
   resolved to their finished state and the script is told to stop scrubbing.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion:reduce){
  .story-panel{--p:1}
  /* The pin is not motion; the drift is. The copy sits where the hero put it
     and is covered from the bottom, which is less good and entirely readable. */
  .pf-curtain .pf-scrim,
  .pf-curtain .pf-ghost,
  .pf-curtain .pf-copy,
  .pf-curtain .pf-cue{transform:none}
  .story-fig{opacity:1;transform:none;transition:none}
}
