/* ═══════════════════════════════════════════════════════════
   SCENE 03 — APPROACH
   Picture left, argument right. Pinned, because the scroll is
   driving four beats in sequence — the picture arriving, the
   title wiping in, the note, the points — and each notch of it
   advances one of them.
   ═══════════════════════════════════════════════════════════ */

.fx-stage {
  background: var(--paper);
  color: var(--ink);
  overflow: hidden;
  display: grid;
  align-content: center;
}

/* ── The runway ──────────────────────────────────────────
   This section held NO runway for a long time, deliberately: its
   content arrives when the section arrives and then stays put, and
   pinning it only meant the copy appeared once the section had
   taken the whole screen and then had to be scrolled through to
   leave, which read as arriving empty.

   It has one now for a different reason. The entrance still is not
   a position on the scroll — it plays once, on arrival, exactly as
   before. What the runway buys is the beat AFTER it: the picture
   opening out of its slot to fill the screen while the copy fades,
   which IS a position on the scroll and needs length to run in.

   IT HOLDS FIVE SCROLLS FIRST, THEN OPENS. The first ~38% of this
   length is a pause on the finished section — see HOLD in
   js/core/travel.js, which takes it as a FRACTION of this runway
   rather than a length of its own, so the two cannot drift apart.
   182vh at .385 is about 70vh of hold, which is five notches of
   the wheel at the 14vh notch the statement's hold is measured in,
   and leaves the opening the ~112vh it needs.

   THE HOLD GREW, THE OPENING DID NOT. Both numbers moved together
   for that: lengthening the pause inside a fixed runway would have
   taken the extra scroll out of the opening and made the picture
   snap open as a side effect of asking it to wait longer.

   ABOUT A SCREEN FOR THE OPENING ITSELF. The picture travels from
   a square in the left column to the whole viewport, which is a
   long way for the eye; much less than that and it snaps rather
   than opens. It is also the last thing on the page, so this
   runway is the page's own tail — there is nothing after it to
   arrive too early.

   js/core/travel.js measures both the hold and the opening against
   this section's top and bottom, so this one height sets the pace
   of the whole tail and nothing else needs touching. */
.fx-runway { height: 182vh; }
/* Same proportions, shorter overall — a viewport-height of hold is
   a much longer thumb gesture on a phone. */
/* Narrow widths set this again much lower further down — the
   photograph is not opening out there, so the runway only has a
   short fade to cover. */

/* ── The ground ──────────────────────────────────────────
   Deeper than the statement section's, because this one has a
   photograph anchoring the left and can carry more without the
   page tipping. Still nothing with an edge you could name. */
.fx-stage::before,
.fx-stage::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}
/* CONTAINED — neither wash reaches an edge. See the long note on
   .st-stage::before in statement.css: the seam between these two
   sections can only be closed by both being plain paper where they
   meet, not by matching their gradients to each other. */
.fx-stage::before {
  background:
    radial-gradient(56% 52% at 82% 47%, rgba(201,160,99,.2) 0%, rgba(201,160,99,0) 66%),
    radial-gradient(50% 46% at 7% 53%, rgba(27,39,66,.05) 0%, rgba(27,39,66,0) 64%);
}
/* Same generated noise as the loader and the statement — plain
   alpha, never a blend mode, which resolves to nothing on a
   near-white ground. */
.fx-stage::after {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E");
  background-size: 200px 200px;
  opacity: .04;
}


.fx-inner { position: relative; z-index: 1; align-items: center; }

/* ── The picture ─────────────────────────────────────────
   A grid item on the real content track, like the statement
   panel — its edges land on the page's own lines rather than on
   a percentage that drifts between breakpoints. */
/* The other slot. Same contract as .st-frame in the statement:
   empty, invisible, and the only thing defining where the
   travelling picture lands. */
.fx-media {
  grid-column: content-start / span 7;
  /* Square — see the note on .st-frame in statement.css. The two
     slots must agree on ratio or the picture changes shape while
     it travels. */
  aspect-ratio: 1;
  visibility: hidden;
}

/* ── The argument ────────────────────────────────────────── */
/* EIGHT COLUMNS, not seven. The picture holds seven on the left,
   so at seven each there were two empty columns plus their gaps
   between them — a gutter wide enough that the two halves stopped
   reading as one row. Widening the copy closes it from the copy's
   side, which is the side with room to give; widening the picture
   instead would make it taller, since its slot is square. */
.fx-body { grid-column: span 8 / content-end; }

.fx-eyebrow {
  display: flex;
  align-items: center;
  gap: 1.4rem;
  margin-bottom: 2.2rem;
  font-family: var(--font-sans);
  font-size: 1.2rem;
  font-weight: 500;
  letter-spacing: .28em;
  text-transform: uppercase;
  color: var(--gold-deep);
}
.fx-eyebrow i { width: 3.4rem; height: var(--hair); background: currentColor; opacity: .5; }

/* ── The title ───────────────────────────────────────────
   Each line is a track holding the text and the gold bar that
   reveals it. The bar starts flush left at full width and slides
   right; the text is clipped to the bar's trailing edge, so the
   words appear out of the bar rather than under it. */
/* THE BODY FACE, not the display one. The display face is the
   hero claim's and the loader mark's — one place each, where a
   single line of type is the whole image. Used again here it stops
   being the thing that marks the hero out.

   Sized DOWN from the serif it replaced: a geometric sans sets a
   much larger visual image at the same point size, so carrying the
   old 6.4rem across would have crowded the column. */
.fx-title {
  /* Flex column — same reason as .hero-title in hero.css: the
     lines' negative margins would otherwise collapse against each
     other and leave most of the clip padding as visible slack. */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  font-family: var(--font-sans);
  /* 500, up a step from the 400 the body text is set in. At the
     same weight as its own description the title was carrying the
     block on size alone; one step is enough to separate them
     without the type starting to look bold. */
  font-weight: 500;
  /* Sized to the COLUMN it now sits in. The copy went from seven
     tracks to eight when the picture gutter was closed, and 5.2rem
     left the longer of the two lines finishing barely past halfway
     — the type was still sized for the narrower column it used to
     have. */
  /* 5.6rem, so the longer line fits the column on one line. The
     first line was short at 6.4rem — there was room to its right
     for more words, and none for them to be set in. */
  font-size: 5.6rem;
  line-height: 1.1;
  letter-spacing: -.024em;
  color: var(--ink);
}
/* A CLIPPING SLOT NEEDS ROOM ON EVERY SIDE.

   overflow:hidden clips horizontally as well as vertically, and
   this face overflows its box in both directions: the caps sit
   above a line-height:1.1 box, the descenders below it, and every
   glyph has ink outside its advance width. Padding gives the ink
   room; the matching negative margin takes that room straight back
   out of the layout, so only the clip rectangle grows and nothing
   moves.

   TWO THINGS CLIP THIS TEXT, NOT ONE. The overflow above is only
   half of it — the wipe in js/sections/approach.js drives a
   clip-path on .fx-text, and an inset() clips to that element's
   tight border box no matter how much padding its parent has. So
   the descender on every g and y was being shaved flat by the
   clip-path while this padding sat unused around it, which is
   exactly what a cut-off letter looks like and why measuring the
   text against .fx-line reported it as fine.

   These two custom properties are the single measurement both
   clips work from: the padding below, and the negative insets the
   script writes into the clip-path. They are declared on the title
   so .fx-text inherits them and the script can use them by name
   rather than carrying a copy of the number. */
.fx-title { --ink-room: .34em; --ink-side: .14em; }
.fx-line {
  position: relative;
  display: block;
  /* SHRINK TO THE TEXT. As a full-width block the line is far wider
     than its own words, so the sweep — which is a percentage of the
     line — crossed mostly empty space and had finished uncovering
     the words long before it reached the end. Sized to its content,
     the bar travels exactly the length of the line it is revealing,
     and each line gets its own correct distance. */
  width: fit-content;
  overflow: hidden;
  padding: var(--ink-room) var(--ink-side);
  margin: calc(var(--ink-room) * -1) calc(var(--ink-side) * -1);
}
.fx-text { display: block; }
.fx-bar {
  position: absolute;
  inset: 0;
  /* Keep in step with BW in js/sections/approach.js — the script
     converts between line-space and this width, so the two values
     must agree. */
  width: 14%;
  /* Solid at the leading edge and falling away behind, so it reads
     as light crossing the line rather than a block dragged over
     it. */
  background: linear-gradient(90deg, rgba(201,160,99,0) 0%, var(--gold) 62%, var(--gold) 100%);
  pointer-events: none;
}

/* The pre-JS state, guarded so the title still renders as ordinary
   text if GSAP never loads: bar off the left, words clipped away. */
.has-gsap .fx-bar { transform: translateX(-100%); }
/* Same four sides as the script writes — see the note on
   --ink-room above. Only the right-hand value differs, and at 100%
   the line is hidden either way. */
.has-gsap .fx-text {
  clip-path: inset(calc(var(--ink-room) * -1) 100% calc(var(--ink-room) * -1) calc(var(--ink-side) * -1));
}

.fx-note {
  margin-top: 2.4rem;
  /* NO max-width. It had one while the copy was a narrow column
     beside a picture; in a full-width block it only made the text
     break early against empty paper to its right. The three claims
     below set the measure now — the description should run to the
     same edge they do. */
  font-family: var(--font-sans);
  font-size: 1.65rem;
  font-weight: 400;
  line-height: 1.6;
  color: var(--on-light);
}

/* ── The three ───────────────────────────────────────────
   Quality, consistency, trust — across in one row, the way the
   reference set them, but each carrying a line that says
   something rather than sitting under an icon on its own.

   A NAME IS NOT A CLAIM. Anyone can write "quality" under a badge.
   Each word here has a sentence specific enough that a competitor
   could not paste it onto their own page.

   ONE ROW, THREE COLUMNS. Stacked rows filled the left of the
   block and left the right half empty; term-beside-sentence used
   the width but made each row a long thin thing. Three columns
   spend the whole width and give each claim the same footprint,
   which is also the only arrangement that says the three are
   equal — a list implies an order, and these are not ranked. */
/* The rule is now .fx-rule above — see the note in index.html.
   Keeping it as a border here meant it was painted the instant the
   section existed, ahead of everything it heads. */
.fx-rule {
  display: block;
  margin-top: 3.2rem;
  height: var(--hair);
  background: var(--line-light);
  transform-origin: left center;
}
/* Not drawn at all until its beat. */
.has-gsap .fx-rule { transform: scaleX(0); }

.fx-points {
  margin-top: 2.8rem;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}

/* Divided by rules rather than spaced apart. Three columns with
   only air between them read as three separate things; a hairline
   makes them one table of three entries. */
.fx-point {
  padding: 0 2rem;
  border-left: var(--hair) solid var(--line-light);
}
.fx-point:first-child { border-left: 0; padding-left: 0; }
.fx-point:last-child { padding-right: 0; }

/* ICONS, NOT NUMBERS. 01/02/03 said only that there were three of
   them, which the row already shows. Each mark now says what its
   column is about: a plumb bob for quality, the oldest test of
   whether a thing is true; three identical modules for
   consistency, which is not a value but the same thing built the
   same way again; a key for trust, because for a developer trust
   is not a sentiment — it is the handover happening on the day
   promised.

   DRAWN, not a font or an image set. They inherit currentColor, so
   the hover tints them for free, and they stay hairline at any
   size the way every other line on this page does. */
.fx-ico {
  display: block;
  width: 2.8rem;
  height: 2.8rem;
  margin-bottom: 1.6rem;
  color: var(--gold-deep);
  transition: color .5s var(--ease), transform .7s var(--ease-arch);
}
.fx-ico svg { width: 100%; height: 100%; display: block; }

.fx-term {
  display: block;
  margin-bottom: .8rem;
  font-family: var(--font-sans);
  font-weight: 400;
  /* Sized to the COLUMN, not to the old full-width row. At 3.4rem
     "Consistency" filled a third of the block edge to edge; this
     leaves it room inside its own column. */
  font-size: 2.4rem;
  line-height: 1.1;
  letter-spacing: -.014em;
  color: var(--ink);
  transition: transform .7s var(--ease-arch);
}

.fx-said {
  display: block;
  font-family: var(--font-sans);
  /* A step up from body weight. These sit small and at reduced
     contrast in three narrow columns, which is exactly where a 400
     starts to thin out. */
  font-weight: 500;
  font-size: 1.5rem;
  line-height: 1.55;
  color: var(--on-light);
}

/* The column answers as a whole — the same gesture the menu rows
   use, so a set of claims behaves like every other list here. */
@media (hover: hover) {
  .fx-point:hover .fx-ico { color: var(--gold-lift); transform: translateY(-.3rem); }
  .fx-point:hover .fx-term { transform: translateX(.4rem); }
}

@media (max-width: 1200px) {
  .fx-title { font-size: 5.2rem; }
}
@media (max-width: 991px) {
  /* ═══ THE ARGUMENT SITS ON THE PHOTOGRAPH ═══════════════
     At this width the picture never travels and never has a slot
     beside the copy — it arrives here already full-bleed, as this
     section's ground, and the argument is set over it. See the
     note on `narrow` in js/core/travel.js.

     That inverts the section: on a wide screen it is dark type on
     paper with a photograph beside it; here it is light type on a
     photograph. Everything below is that inversion, and none of it
     is a smaller version of the desktop rule — it is a different
     treatment for a different ground. */

  /* THE PHOTOGRAPH IS THIS SECTION'S OWN BACKGROUND HERE, not a
     fixed layer shared with anything. It scrolls in with the
     section, under the section's own content, exactly like any
     other background — which is what makes this read as an
     ordinary section reached by ordinary scrolling. See the note on
     --ground-veil in core.css. */
  .fx {
    position: relative;
    z-index: 6;
    background: transparent;

    /* NO NEGATIVE MARGIN HERE. It was pulled up by a screen while
       the photograph was a fixed layer, because the layer arrived
       finished while the section was still sliding and the two
       disagreed. With the picture as this section's own background
       there is nothing to disagree with: image and content move
       together, so the ordinary slide-in is exactly right and
       overlapping it would only hide a screen of the statement. */
  }
  .fx-stage {
    /* NO GROUND OF ITS OWN. The photograph is .ground in core.css —
       one fixed element behind both this section and the timeline,
       so neither can crop it differently from the other and neither
       arrives carrying a copy of it. */
    background: transparent;
    color: var(--paper);
  }
  /* NOTHING PAINTED ON THE STAGE AT ALL. ::before is the paper
     washes and ::after the grain — both are for a light ground and
     wrong over a photograph.

     ::after also carried the veil for a while, from when this stage
     held the picture itself. When the photograph moved out to
     .ground, which brings its own veil, this one stayed — so the
     approach section had TWO veils stacked and the timeline had
     one. The doubled band ended exactly where this stage did, which
     is the horizontal seam that made the ground look like two
     layers sliding over each other. One ground, one veil. */
  .fx-stage::before,
  .fx-stage::after { display: none; }

  /* The slot the picture used to land in. There is nothing to land. */
  .fx-media { display: none; }

  .fx-inner { align-content: center; }
  .fx-body  { grid-column: content; }
  .fx-title { font-size: 4rem; color: var(--paper); }

  /* Light type, and the hairlines lifted to match — .fx-point's
     dividers and the rule are set against a light ground on the
     desktop layout and vanish against this one. */
  .fx-note { color: rgba(255, 255, 255, .84); }
  .fx-term { color: var(--paper); }
  .fx-said { color: rgba(255, 255, 255, .76); }
  .fx-rule { background: rgba(255, 255, 255, .3); }
  .fx-point { border-left-color: rgba(255, 255, 255, .2); }
  .fx-ico { color: var(--gold-lift); }

  /* The edge rules and the margin ticks are drawn for paper. */
  .fx .edge-rule { background: rgba(255, 255, 255, .16); }
  .fx .survey-rail {
    background-image: repeating-linear-gradient(
      180deg,
      rgba(255, 255, 255, .22) 0 var(--hair),
      transparent var(--hair) 20%);
  }
}

@media (max-width: 767px) {
  .fx-title { font-size: 2.8rem; }
  .fx-note { font-size: 1.5rem; margin-top: 2.2rem; }
  .fx-rule { margin-top: 2.6rem; }
  /* One claim at a time. Three across a phone gives each about a
     hundred pixels, which is not a column; the dividers turn
     horizontal with them. */
  .fx-points { grid-template-columns: 1fr; margin-top: 2rem; }
  .fx-point {
    padding: 2rem 0;
    border-left: 0;
    border-top: var(--hair) solid rgba(255, 255, 255, .2);
  }
  .fx-point:first-child { padding-top: 0; border-top: 0; }
  .fx-point:last-child { padding-bottom: 0; }
  .fx-ico { width: 2.3rem; height: 2.3rem; margin-bottom: 1rem; }
  .fx-term { font-size: 2.2rem; }
  .fx-said { font-size: 1.45rem; }
}

@media (max-width: 991px) {
  /* ── PINNED, AND THE CONTENT SWAPS OVER IT ────────────
     This section holds the screen. Its argument does not scroll
     away — it fades, and the timeline's years arrive in the same
     place, over the same photograph, which never moves.

     THAT REQUIRES A PIN. It was briefly an ordinary section, and
     ordinary sections scroll: the argument left by moving up and
     the years arrived by moving up behind it, which is a second
     section arriving however identical its background is. Only a
     pin can hold the ground still while what is on it changes.

     The runway below is the scroll that fade happens over. The
     timeline is pulled up a screen (see .tl) so its own stage takes
     over at the moment this one's copy has gone — and because both
     grounds are viewport-aligned rather than sized to their own
     boxes, that handover paints the identical pixels and cannot be
     seen. */
  /* SYMMETRIC. The top value has to clear the header, and the
     bottom was set independently — so the block sat with a header's
     height more space above it than below, which is exactly what it
     looked like. One value on both sides, and it clears the header
     because that is the larger of the two constraints. */
  .fx-stage {
    padding: calc(var(--header-h) + 4rem) 0;
    align-content: center;
  }
  /* THREE SCROLLS, then the timeline. It was 120vh — nearly a
     screen and a half of holding a section that has already been
     read. At the 14vh the statement's hold is measured in, 45vh is
     about three notches: one to register that it is being held, two
     for the argument to go, and the years are there. */
  /* The shared tail — see --fx-tail in core.css. This is the scroll
     the crossfade happens over, and the timeline is pulled up by
     exactly the same amount so the two sit on one screen. */
  .fx-runway { height: var(--fx-tail); }
}
@media (max-width: 767px) {
  .fx-stage { padding: calc(var(--header-h) + 2.5rem) 0; }
}

/* ── The scroll cue ──────────────────────────────────────
   BOTTOM RIGHT, ON THE CONTENT COLUMN'S EDGE. --pad is the page
   gutter, which is where the copy beside it ends, so the cue lines
   up with the column rather than floating in the margin.

   DESKTOP ONLY, and that is not a size decision. What it reports is
   progress through a PINNED screen, and below 992 this section is
   not pinned — it scrolls like any other. A bar filling against
   nothing being held would be measuring a thing that is not
   happening. */
.fx-cue {
  position: absolute;
  right: var(--pad);
  bottom: 5rem;
  z-index: 3;
  color: var(--on-light-mute);
  /* The deep gold, and a 2px bar rather than a hairline. One device
     pixel of a light metal on a white ground is a rumour; the hero
     gets away with a hairline because it draws on a photograph. */
  --cue-fill: var(--gold-deep);
}
.fx-cue .cue-bar { height: 2px; }

@media (max-width: 991px) {
  .fx-cue { display: none; }
}
