/* ============================================================
   SILICA WORKS - the rotating word
   ------------------------------------------------------------
   The services label is the animation. It reads "We" plus one word
   that swaps as the wave plates travel beside it - one line, one
   face. It sits under the "Our full capabilities" title and enumerates the
   offer, so the section's subject is the process and this line is
   the range of work that process covers.

   Everything here rides on .sw-label-display (css/slider.css),
   which sets the handwritten face, size and stroke. This file only
   handles the swap, so the fixed word and the changing one cannot
   drift apart typographically - there is nothing here that sets a
   font.

   Which word shows is decided by js/wave.js, from the same index
   that tells it which plate is nearest the middle of the viewport.
   ============================================================ */

.sw-story {
  /* flex, not inline-flex. The label is a child of .sw-split-copy,
     which is itself a flex column, so this element is a flex item
     and its display would be blockified either way - but saying
     `flex` states the layout that actually applies rather than
     relying on that blockification, and keeps the margin-top /
     margin-bottom that .sw-label-display sets working as the block
     margins they were tuned as.

     align-self keeps it shrink-wrapped to its content, so the flex
     line is only as wide as "We" plus the word rather than the
     whole column - otherwise the gap below would stretch. */
  display: flex;
  align-self: flex-start;
  /* Baseline rather than centre: these are two runs of the same
     face, so their baselines must agree or the swapping word rides
     visibly high against the fixed one.

     This only works because the slot carries a strut that gives it
     a real text baseline - see the note on .sw-story-slot. Without
     it the slot has no in-flow content, its baseline is synthesized
     from its bottom edge, and this line lifts the word instead of
     seating it. */
  align-items: baseline;
  /* The space between "We" and the word. The markup closes the gap
     between the spans with a comment so this is the only source of
     it - an HTML whitespace node would add a second, uncontrolled
     one on top. */
  gap: 0.28em;
}

/* "We" - the fixed half of the line. Declared empty on purpose:
   it must inherit the face, size, colour and stroke from
   .sw-label-display exactly as the swapping word does, and the
   moment anything is set here the two halves of the line can
   diverge. The hook exists for the markup's sake, not for styling.
   If you need to shift "We", shift .sw-story instead. */
.sw-story-lead {
  /* intentionally empty - see above */
}

/* ------------------------------------------------------------
   The swapping slot
   ------------------------------------------------------------
   The words are stacked out of flow, so the slot has to carry its
   own box or it collapses and the line loses its second half.

   Width is set inline by js/wave.js, measured from the widest
   word, so the slot never resizes mid-swap - a slot that grew and
   shrank with each word would drag the line's right edge about and
   read as a layout bug. Height is set here in em, which tracks the
   inherited font-size.

   overflow: hidden is what makes the swap read as a mechanism:
   the outgoing word leaves through the top edge and the incoming
   one arrives from below, both clipped by the slot, so they travel
   behind a fixed frame.
   ------------------------------------------------------------ */
/* ------------------------------------------------------------
   The slot's baseline
   ------------------------------------------------------------
   Every word inside the slot is absolutely positioned, which means
   the slot has NO in-flow content and therefore no real baseline.
   A flex item in that state has its baseline synthesized from its
   bottom margin edge, so `align-items: baseline` on the parent was
   lining the slot's FLOOR up with the "We" baseline - which lifted
   the word by roughly one descender's depth. That is the "pushed
   up" look.

   The fix is to give the slot one piece of in-flow content whose
   baseline is the real text baseline: a zero-width strut. It is a
   ::before carrying a zero-width space, so it contributes a line
   box (and therefore a baseline) in exactly this face at exactly
   this size, while adding nothing visible and nothing to the
   measured width.

   Height then has to come from the strut's own line box rather
   than from an explicit `height`, or the box and the baseline
   disagree again - hence line-height here and no height.
   ------------------------------------------------------------ */
/* Slack above and below the line box, so the clip does not shear
   the face. "Shadows Into Light" swings its descenders well below
   the baseline and its ascenders above the cap line, and
   .sw-label-display puts a -webkit-text-stroke on top of that,
   which spills a little further again. A slot sized to exactly one
   line box clips all of it - the `g` in "design." loses its tail.

   Applied as padding, so it grows the slot's box without moving
   the strut's baseline: "We" and the word stay on the same line
   and only the clip edge moves outward. The negative margins pull
   the enlarged box back so the extra height costs no layout space
   - the line below does not get pushed down. */
.sw-story-slot {
  position: relative;
  display: inline-block;
  /* The global reset sets border-box. js/wave.js writes the widest
     WORD's width here, which is a content measurement, so the slot
     has to size on content too - under border-box the horizontal
     padding below would be subtracted from that width and clip the
     word's right end, which is the bug this rule prevents. */
  box-sizing: content-box;
  /* Matches .sw-label-display's line-height */
  line-height: 1.3;
  /* Vertical slack clears the descenders and the stroke that rides
     on them; horizontal slack clears the leading glyph's left side
     bearing. The handwriting face starts several of these words
     with a stroke that reaches back past the glyph origin - the
     `d` of "design." is the worst - and a slot clipping flush at
     left: 0 shears it.

     Horizontal slack is smaller than vertical: it is paying for a
     side bearing and a stroke width, not a full descender. */
  padding-top: var(--slot-slack);
  padding-bottom: var(--slot-slack);
  padding-left: var(--slot-slack-x);
  padding-right: var(--slot-slack-x);
  margin-top: calc(var(--slot-slack) * -1);
  margin-bottom: calc(var(--slot-slack) * -1);
  margin-left: calc(var(--slot-slack-x) * -1);
  margin-right: calc(var(--slot-slack-x) * -1);
  overflow: hidden;
  /* Every word is out of flow, so the slot has no width of its own.
     js/wave.js measures the widest and writes it here as px. This
     is the fallback for the moment before that runs, and for a page
     where it never does - roughly the width of the longest word at
     this face, so the line is never visibly empty. */
  min-width: 6ch;
}

.sw-story {
  /* The slack, in em of the label's own size, so it scales with the
     face rather than being a fixed pixel guess. Generous enough to
     clear both the descender and the stroke that rides on it. */
  --slot-slack: 0.3em;
  /* Horizontal counterpart - pays for the leading glyph's left side
     bearing and its stroke, which is less than a descender needs. */
  --slot-slack-x: 0.12em;
}

/* The strut. Zero-width space: it establishes the line box that
   gives the slot its baseline and its height, and draws nothing. */
.sw-story-slot::before {
  content: "\200B";
}

/* The line must not break between "We" and the word - they are one
   phrase. The flex container will not wrap on its own, but the
   label sits in a narrow column, so this is stated rather than
   assumed. */
.sw-story {
  flex-wrap: nowrap;
  white-space: nowrap;
}

.sw-story-phrase {
  position: absolute;
  /* Inset to the content box, for the same reason as `top` below:
     an absolute child is positioned against the PADDING box, so
     left: 0 would sit the word in the horizontal slack and shift
     the whole line right by that much. Matching the padding
     cancels it, and the slack stays what it is for - room for the
     leading glyph's bearing to overhang into. */
  left: var(--slot-slack-x);
  /* Seated on the slot's CONTENT box, inside the slack the padding
     opens. An absolute child is positioned against the padding box,
     so `top: 0` would put the word up in the slack and lift it off
     the strut's baseline by exactly that much - the offset has to
     match the padding to cancel it.

     Top and bottom both pinned, so the word fills the same height
     as the strut's line box and the two baselines land together.

     `right` is deliberately NOT set: the word must keep its
     intrinsic width, because js/wave.js measures these elements to
     size the slot. Constraining them to the slot would make that
     measurement circular. */
  top: var(--slot-slack);
  bottom: var(--slot-slack);
  white-space: nowrap;
  /* Waiting below the slot's floor, out of sight behind the clip.
     is-active lifts it to 0; is-leaving carries it out through the
     top - one direction of travel throughout, so the sequence
     reads as advancing rather than toggling.

     Percentages here resolve against the WORD's own height, not the
     slot's, and the slot is now taller than the word by the slack
     at each end. 105% would leave a waiting word's ascenders still
     showing inside that slack; 165% clears the whole padded box
     with room to spare. */
  transform: translate3d(0, 165%, 0);
  opacity: 0;
  transition: transform 0.5s cubic-bezier(0.22, 1, 0.36, 1),
    opacity 0.3s ease;
  will-change: transform, opacity;
}

.sw-story-phrase.is-active {
  transform: translate3d(0, 0, 0);
  opacity: 1;
}

/* Set by js/wave.js for the length of the swap. The word exits
   upward - the same direction the next one entered from. Same
   165% as the waiting position, for the same reason. */
.sw-story-phrase.is-leaving {
  transform: translate3d(0, -165%, 0);
  opacity: 0;
}

/* ------------------------------------------------------------
   Motion-sensitive visitors. The swap is content, so it still
   happens - it cuts rather than travelling.
   ------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  .sw-story-phrase {
    transform: none;
    transition: opacity 0.2s ease;
    will-change: auto;
  }

  .sw-story-phrase.is-active,
  .sw-story-phrase.is-leaving {
    transform: none;
  }
}
