/**
 * @file
 * Formula section: two input columns in a shared panel, then a result column.
 *
 * Measured from tmp/Silver-and-Features.png (a 2x export of 1920x969): the
 * content group is 1460px wide, which is the site container to within the
 * export's own error, so --ln-container-max is used rather than a new number.
 * Radii, paddings and type sizes are the design system's, not measured — the
 * mockup is a lossy PNG with an unrendered video layer in it.
 */

.ln-formula {
  /* Stops at its native width: past 1920 the section centres instead of
     scaling the art up. */
  --ln-formula-max-width: 1920px;

  /**
   * Translucent, and that is what makes the blur below do anything: a
   * backdrop-filter under an opaque fill has nothing left to show through.
   *
   * The alpha is measured, not chosen. Across 82 rows of the comp where the
   * art behind the panel is locally smooth, a least-squares fit of
   * "white over backdrop" gives 0.669, to an RMS of 0.96 of one 8-bit level.
   * An opaque fill fits four times worse, and the proof it is not opaque is
   * plain in the export: where the art behind darkens to 178, the panel's
   * interior follows it down to 232.
   *
   * 0.65 and 0.70 fit within noise of each other, so if Figma states an exact
   * fill opacity, use that number over this one.
   */
  --ln-formula-panel-bg: rgba(255, 255, 255, 0.67);
  --ln-formula-panel-blur: 4px;

  --ln-formula-panel-radius: 16px;
  /**
   * Asymmetric, and measured: 32 at the sides — the media sits 31.5 in from the
   * panel's left edge and 31.5 from the right — against 64 top and bottom, from
   * the panel's top at y250 to the media's at y313.5, and the body's last line
   * at 770.7 to the panel's foot at 836.
   */
  --ln-formula-panel-padding-block: 64px;
  --ln-formula-panel-padding-inline: 32px;

  /**
   * Measured, not adopted. The mockup's panel sits on pure white below its
   * bottom edge, so the shadow's falloff reads cleanly there: fitting a
   * Gaussian CDF to that profile gives alpha 0.158, y-offset 6.8px and
   * sigma 4.9px (so a 9.8px CSS blur), to an RMS error of 0.0014 in alpha —
   * under one 8-bit level. Spread is 0: at the left edge and the top edge the
   * amplitude ratios agree with a zero-spread shadow to within a per cent.
   *
   * The rounded values below are the closest whole numbers to that fit,
   * reproducing the measured profile to within 1.74 levels out of 255.
   */
  --ln-formula-panel-shadow: 0 7px 10px rgba(0, 0, 0, 0.15);

  /**
   * The two inputs no longer butt together: the comp puts 87px between their
   * content boxes' tracks, which with 32px of padding either side reads as 151
   * between the two screenshots. They still share one surface — col 1's ::after
   * spans the gap as well as col 2 — so the panel is unbroken behind them.
   *
   * The result stands off by 57, measured panel edge to card edge.
   */
  --ln-formula-input-gap: 87px;
  --ln-formula-result-gap: 57px;

  /* 50px at the top end, from a 33.5px cap on the "H" of "How". The line box is
     52.5, which is 1.05 — tight, and deliberate. */
  --ln-formula-heading-size: clamp(2rem, 4vw, 50px);
  --ln-formula-heading-leading: 1.05;
  --ln-formula-heading-max: 720px;
  --ln-formula-title-size: 24px;
  --ln-formula-body-size: 18px;
  /* 22.6px between body baselines, against the 27 this used to set. */
  --ln-formula-body-leading: 1.25;

  --ln-formula-operator-size: 32px;
  --ln-formula-operator-color: var(--ln-color-ink, #1c1c1c);

  /**
   * The entrance. Every number the sequence depends on is here, so retiming it
   * is one block to edit and nothing below has a literal duration in it.
   *
   * The result column waits out a deliberate pause after the pair has landed:
   * it is the answer to the equation, and arriving with them would read as
   * three cards appearing rather than as a conclusion.
   */
  --ln-formula-reveal-duration: 600ms;
  --ln-formula-reveal-rise: 16px;
  --ln-formula-reveal-stagger: 350ms;
  /* The pair has landed by 950ms (350 stagger + 600 duration), so this leaves
     a second of stillness before the answer appears. Two seconds read as a
     stall rather than a beat. */
  --ln-formula-reveal-result-delay: 1950ms;

  /* Two laps, then the arc closes into a solid outline. Starts as the result
     column finishes arriving: 1950 + 600. */
  --ln-formula-ring-delay: 2550ms;
  --ln-formula-ring-duration: 2600ms;
  --ln-formula-ring-width: 2px;
  --ln-formula-ring-color: var(--ln-color-red, #cf0000);

  position: relative;
  box-sizing: border-box;
  max-inline-size: var(--ln-formula-max-width);
  margin-inline: auto;
  isolation: isolate;
  color: var(--ln-color-ink, #1c1c1c);
  font-family: var(--ln-font-sans, Outfit, sans-serif);
}

.ln-formula *,
.ln-formula *::before,
.ln-formula *::after {
  box-sizing: inherit;
}

/* Behind the copy, and out of flow so the section's height comes from the
   columns. ln-responsive-media in css/utilities.css does the object-fit. */
.ln-formula__background {
  position: absolute;
  inset: 0;
  /* -2, below the panel surfaces at -1. Tree order would give the same result,
     but the panels must win and this says so. */
  z-index: -2;
}

/* Gutter and max-width on the same box, so the 1440 is the outer edge. */
.ln-formula__inner {
  max-inline-size: var(--ln-container-max, 1440px);
  margin-inline: auto;
  padding-inline: var(--ln-container-gutter, 15px);
  padding-block: var(--ln-section-gap, 96px);
}

/**
 * Capped, because the comp breaks this heading over two lines where the full
 * sentence would otherwise sit on one — it needs 1301px and the row is 1436.
 * The break puts "work" at the end of line one, which brackets the cap between
 * 688 and 894; 720 sits inside that with room before a shorter heading wraps.
 * text-wrap: balance then evens the two lines, as the comp has them.
 */
.ln-formula__heading {
  max-inline-size: var(--ln-formula-heading-max);
  margin: 0 auto 64px;
  /* Required: Cohesion styles bare h2, and an element rule beats an inherited
     value however specific the ancestor. */
  color: inherit;
  font-size: var(--ln-formula-heading-size);
  font-weight: 500;
  line-height: var(--ln-formula-heading-leading);
  text-align: center;
  text-wrap: balance;
}

/**
 * Three equal columns with the result standing off from the pair, and three
 * shared rows the columns subscribe to.
 *
 * The result's gap is a track of its own rather than a margin on the column:
 * a margin would come out of that column alone and leave it narrower than the
 * other two, where a spacer track leaves all three at 1fr — which is what the
 * flex version did, and what the mockup draws.
 *
 * Three columns is the designed case, as the component metadata says. A fourth
 * paragraph lands in an implicit track after the result and wants a designer
 * before it ships.
 */
/**
 * Both gaps are spacer tracks, and column-gap stays 0.
 *
 * The result's gap was a track while the input gap was a column-gap, which was
 * fine only while that gap was 0: a column-gap applies between every pair of
 * tracks, so once it became 87 it was also being added either side of the
 * spacer and the result stood off by 231 instead of 57.
 */
.ln-formula__row {
  display: grid;
  grid-template-columns:
    [input-a] minmax(0, 1fr)
    var(--ln-formula-input-gap)
    [input-b] minmax(0, 1fr)
    var(--ln-formula-result-gap)
    [result] minmax(0, 1fr);
  /**
   * Two rows, not three: the media, then everything else.
   *
   * The copy used to have a track each, which equalised the title heights and
   * so pushed every body down to clear the tallest title. The comp does not do
   * that — only the images line up across the columns, and a two-line title
   * takes its own paragraph down with it and nobody else's.
   */
  grid-template-rows: auto auto;
  column-gap: 0;
  align-items: stretch;
}

/**
 * The columns carry only padding; the panel behind them is drawn by ::after.
 * :nth-child throughout, so an editor reordering the paragraphs cannot break
 * the grouping.
 *
 * subgrid is what lines the media up across the three columns: each column
 * adopts the row's two tracks instead of sizing its own, so a taller image in
 * one column no longer pushes that column's title below its neighbours'. Every
 * column carries the same padding, so the inset the padding puts on the first
 * and last track is identical in all three and the internal track edges still
 * agree.
 */
.ln-formula__col {
  position: relative;
  display: grid;
  grid-template-rows: subgrid;
  grid-row: 1 / -1;
  min-inline-size: 0;
  padding: var(--ln-formula-panel-padding-block)
    var(--ln-formula-panel-padding-inline);
}

/**
 * Every column is placed by name. Auto-placement fills tracks in order and has
 * no notion of a spacer, so it put column 2 in the input gap and sized it to
 * 87px; naming the lines is what keeps the two spacer tracks empty.
 */
.ln-formula__col:first-child {
  grid-column: input-a;
}

.ln-formula__col:nth-child(2) {
  grid-column: input-b;
}

.ln-formula__col:nth-child(n + 3) {
  grid-column: result;
}

/**
 * The panel surface — fill, radius and shadow together on a pseudo-element
 * rather than on the column.
 *
 * Load-bearing, not tidiness. Columns 1 and 2 read as ONE panel, and the
 * mockup has no seam where they meet: luminance is a flat 255 straight across
 * that boundary, measured at three different heights. A box-shadow per column
 * would spread about 8px of 7.5% black either side of the shared edge and draw
 * a grey stripe down the middle of the panel.
 *
 * z-index: -1 paints this after the column's own background but before its
 * in-flow content — which is why the column no longer carries a background of
 * its own, since that would cover this. The operators ride on ::before, which
 * is positioned and so paints later still, leaving them unshadowed as drawn.
 */
.ln-formula__col::after {
  content: '';
  position: absolute;
  z-index: -1;
  inset: 0;
  border-radius: var(--ln-formula-panel-radius);
  background-color: var(--ln-formula-panel-bg);
  /* Prefixed first: Safari needed -webkit- until 18, and the unprefixed
     declaration after it wins wherever both are understood. What gets blurred
     is the section's own background layer and nothing above it — .ln-formula
     carries isolation: isolate, which makes it the backdrop root. */
  -webkit-backdrop-filter: blur(var(--ln-formula-panel-blur));
  backdrop-filter: blur(var(--ln-formula-panel-blur));
  box-shadow: var(--ln-formula-panel-shadow);
}

/* Column 1's surface reaches across column 2 — the two 1fr tracks make them
   exactly as wide — so the pair gets one continuous shadow. The gap is in the
   sum, so this still holds if --ln-formula-input-gap stops being 0. */
.ln-formula__col:first-child::after {
  inset-inline-end: calc(-100% - var(--ln-formula-input-gap));
}

/* Already covered by column 1's. */
.ln-formula__col:nth-child(2)::after {
  content: none;
}

/**
 * The operators. Generated content rather than markup: it keeps them out of the
 * accessibility tree, where "plus" and "equals" read as noise between three
 * headed columns, and it means the count can never disagree with the columns.
 */
.ln-formula__col + .ln-formula__col::before {
  content: '+';
  position: absolute;
  inset-block-start: 50%;
  /* Centred in the gap this column opened, not on its own edge — the same
     correction the equals makes below. It read the same either way while the
     input gap was 0; at 87 it would sit against column 2's screenshot. */
  inset-inline-start: calc(var(--ln-formula-input-gap) / -2);
  transform: translate(-50%, -50%);
  color: var(--ln-formula-operator-color);
  font-size: var(--ln-formula-operator-size);
  font-weight: 300;
  line-height: 1;
}

.ln-formula__col:nth-child(n + 3)::before {
  content: '=';
  /* Centred in the gap this column opened, not on its own edge. */
  inset-inline-start: calc(var(--ln-formula-result-gap) / -2);
}

/**
 * Centred in the shared media track, which is why a column with a shorter image
 * still meets its neighbours at the title. The track is as tall as the tallest
 * media plus this margin, so the tallest sits flush at the top with the margin
 * below it, and every shorter one centres in the space that leaves.
 */
.ln-formula__col-media {
  grid-row: 1;
  align-self: center;
  margin-block-end: 32px;
}

.ln-formula__col-media :is(img, video, iframe) {
  display: block;
  inline-size: 100%;
  block-size: auto;
}

/**
 * The copy, as one grid item. Explicit row, so a column that omits its media
 * does not slide its copy up into the media track and out of line.
 *
 * align-self: start keeps it at the top of a track sized by the longest column,
 * rather than stretching a short column's copy box down — the panel's bottom
 * padding is measured from the copy, and a stretched box would move it.
 */
.ln-formula__col-content {
  grid-row: 2;
  align-self: start;
}

.ln-formula__col-title {
  margin: 0 0 16px;
  color: inherit;
  font-size: var(--ln-formula-title-size);
  font-weight: 500;
  line-height: 1.25;
}

/* In normal flow under its title, inside the copy box — no row of its own, which
   is what lets a two-line title carry it down. */
.ln-formula__col-body {
  font-size: var(--ln-formula-body-size);
  font-weight: 300;
  line-height: var(--ln-formula-body-leading);
  text-wrap: pretty;
}

/* The outer margins are zeroed by ln-ntmfc / ln-nbmlc on the element itself —
   they match at any depth, which a child selector here did not. */

/**
 * The checkmark list runs tighter here than the editor style sets it: the ticks
 * sit directly under one another, and the line introducing them stays close.
 *
 * Scoped to this component on purpose. .ln-check-list is an editor style, so
 * any CKEditor field on the site can carry it, and its own 0.4em between items
 * is right everywhere else.
 *
 * :has() is the only way to reach a paragraph because of what follows it. Where
 * it is unsupported that paragraph simply keeps its 1em, which is loose rather
 * than broken.
 */
.ln-formula__col-body .ln-check-list > li + li {
  margin-block-start: 0;
}

.ln-formula__col-body p:has(+ .ln-check-list) {
  margin-block-end: 6px;
}

/**
 * ---------------------------------------------------------------------------
 * The entrance
 * ---------------------------------------------------------------------------
 *
 * Gated on .ln-formula--js throughout. formula.js adds that class, so nothing
 * here can hide copy on a page where the script never ran.
 *
 * The panels themselves stay put: it is the contents that arrive. That is also
 * what leaves the result card's border something to do afterwards — the card is
 * already on screen when its outline draws itself.
 */

@keyframes ln-formula-rise {
  from {
    opacity: 0;
    transform: translateY(var(--ln-formula-reveal-rise));
  }

  to {
    opacity: 1;
    transform: none;
  }
}

/* The resting state before the section is reached. The operators go with their
   own column — the "+" belongs to the middle one, the "=" to the result — so
   the equation assembles a term at a time rather than sitting there waiting.
   The ring is excluded: it is not content, and it has a sequence of its own. */
.ln-formula--js .ln-formula__col > *:not(.ln-formula__ring),
.ln-formula--js .ln-formula__col::before {
  opacity: 0;
}

.ln-formula--js.is-revealed .ln-formula__col > *:not(.ln-formula__ring),
.ln-formula--js.is-revealed .ln-formula__col::before {
  animation: ln-formula-rise var(--ln-formula-reveal-duration) ease-out
    var(--ln-formula-col-delay, 0ms) both;
}

.ln-formula__col:nth-child(2) {
  --ln-formula-col-delay: var(--ln-formula-reveal-stagger);
}

.ln-formula__col:nth-child(n + 3) {
  --ln-formula-col-delay: var(--ln-formula-reveal-result-delay);
}

/**
 * ---------------------------------------------------------------------------
 * The result card's border
 * ---------------------------------------------------------------------------
 *
 * A red arc travels the card's perimeter twice and then closes into a solid
 * outline.
 *
 * The travelling is a dash pattern sliding along an SVG rectangle that
 * formula.js builds and keeps sized to the card. A dash moves by arc length,
 * so the arc keeps one length and one speed the whole way round; see the note
 * in formula.js for what the conic-gradient version did instead.
 *
 * --ln-formula-ring-length is the rectangle's perimeter in user units,
 * published by the script, which is what lets the keyframes talk in fractions
 * of a lap without knowing the card's size.
 */

.ln-formula__ring {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  /* It covers the whole card, so it must not intercept anything. */
  pointer-events: none;
}

.ln-formula__ring rect {
  fill: none;
  stroke: var(--ln-formula-ring-color);
  stroke-width: var(--ln-formula-ring-width);
  /* Nothing drawn until the animation runs: a zero-length dash. */
  stroke-dasharray: 0 var(--ln-formula-ring-length, 0);
}

@keyframes ln-formula-ring {
  from {
    stroke-dasharray: 0 var(--ln-formula-ring-length);
    stroke-dashoffset: 0;
  }

  /* The head opens to its running length almost at once, so the lap starts as
     a moving arc rather than growing into one. */
  3% {
    stroke-dasharray:
      calc(var(--ln-formula-ring-length) * 0.14)
      calc(var(--ln-formula-ring-length) * 0.86);
  }

  /* Two laps. The offset interpolates linearly from the `from` keyframe across
     this whole span, which is what makes the speed constant. Negative, so the
     dash runs the path's own direction — clockwise from the top-left. */
  85% {
    stroke-dasharray:
      calc(var(--ln-formula-ring-length) * 0.14)
      calc(var(--ln-formula-ring-length) * 0.86);
    stroke-dashoffset: calc(var(--ln-formula-ring-length) * -2);
  }

  /* The tail catches the head and the outline closes. */
  to {
    stroke-dasharray: var(--ln-formula-ring-length) 0;
    stroke-dashoffset: calc(var(--ln-formula-ring-length) * -2);
  }
}

.ln-formula--js.is-revealed .ln-formula__ring rect {
  animation: ln-formula-ring var(--ln-formula-ring-duration) linear
    var(--ln-formula-ring-delay) both;
}

/**
 * Reduced motion: the section arrives already assembled and the result card
 * already outlined. The end state of the sequence, with none of the sequence.
 */
@media (prefers-reduced-motion: reduce) {
  .ln-formula--js .ln-formula__col > *:not(.ln-formula__ring),
  .ln-formula--js .ln-formula__col::before,
  .ln-formula--js.is-revealed .ln-formula__col > *:not(.ln-formula__ring),
  .ln-formula--js.is-revealed .ln-formula__col::before {
    opacity: 1;
    transform: none;
    animation: none;
  }

  .ln-formula--js.is-revealed .ln-formula__ring rect {
    stroke-dasharray: none;
    animation: none;
  }
}

/**
 * Phone and tablet: the equation stacks, so the panel illusion is dropped and
 * every column becomes its own rounded card. The operators go with it — a "+"
 * rotated onto a vertical axis reads as a decoration, not an equation.
 */
@media (max-width: 63.99rem) {
  .ln-formula {
    --ln-formula-panel-padding-block: 32px;
    --ln-formula-panel-padding-inline: 24px;
    --ln-formula-input-gap: 16px;
    --ln-formula-result-gap: 16px;
  }

  /* One column, and no shared rows: stacked, there is nothing to line up
     across, and each card should be only as tall as its own content. */
  .ln-formula__row {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: none;
    row-gap: var(--ln-formula-input-gap);
  }

  .ln-formula__heading {
    margin-block-end: 32px;
  }

  /**
   * Back to block flow, which drops the subgrid, the explicit grid-row on each
   * part and the media's centring in one go — all of them inert outside grid.
   *
   * All three placements are named, and at matching specificity. The single
   * column above sets grid-template-columns with no named lines, so an
   * unreset `grid-column: input-a` would resolve against lines that no longer
   * exist and drop that column into an implicit track of its own. A plain
   * .ln-formula__col at (0,1,0) does not reach the (0,2,0) rules that set them.
   */
  .ln-formula__col,
  .ln-formula__col:first-child,
  .ln-formula__col:nth-child(2),
  .ln-formula__col:nth-child(n + 3) {
    display: block;
    grid-column: auto;
    grid-row: auto;
  }

  /* Each column becomes its own card, so each gets its own surface back and
     column 1's stops reaching across. */
  .ln-formula__col::after,
  .ln-formula__col:first-child::after,
  .ln-formula__col:nth-child(2)::after {
    content: '';
    inset: 0;
  }

  .ln-formula__col + .ln-formula__col::before {
    content: none;
  }
}

/**
 * Phone. A separate query from the one above on purpose: 63.99rem is where the
 * equation stops fitting side by side, which is a tablet concern, while 47.99rem
 * is the page breakpoint every other component calls mobile.
 *
 * Only the size is set. The body's line-height is unitless, so the line box
 * follows the size down on its own.
 */
@media (max-width: 47.99rem) {
  .ln-formula {
    --ln-formula-body-size: 16px;
  }
}
