/* tasteskill / base.css
   A neutral foundation: reset, token names, fluid type steps, a 12-column grid, states, the motion
   helpers from references/motion.md and the seven entrances draw.mjs deals. It has no look of its own. Copy it into the project, replace the
   token values with the ones on the direction sheet, and build on top. Delete what you do not use. */

/* ---------- tokens (replace with the dealt palette and type system) ---------- */
:root {
  --bg: #ffffff;
  --surface: #f2f2f2;
  --ink: #111111;
  --muted: #5c5c5c;
  --accent: #111111;
  --accent-ink: #ffffff;

  --font-display: system-ui, sans-serif;
  --font-text: system-ui, sans-serif;

  --step-small: clamp(0.8125rem, 0.79rem + 0.1vw, 0.875rem);
  --step-body: clamp(1.0625rem, 1rem + 0.25vw, 1.1875rem);
  --step-lead: clamp(1.25rem, 1.1rem + 0.7vw, 1.625rem);
  --step-head: clamp(2rem, 1.3rem + 3.2vw, 4rem);
  --step-display: clamp(3rem, 1rem + 9vw, 9rem); /* set from the hero blueprint */

  --margin: clamp(1rem, 2.2vw, 2rem);    /* page side margin: tight, so content reaches the edges */
  --gutter: clamp(1rem, 2vw, 2rem);      /* column gap */
  --section: clamp(5rem, 12vw, 13rem);   /* space between sections */
  --radius: 0px;                          /* one radius for the whole page: 0, or large, decided once */

  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);        /* entrances, moving hovers, UI under 600ms */
  --ease-expo: cubic-bezier(0.16, 1, 0.3, 1);        /* reveals of 800ms and longer, line rises, openings */
  --ease-in-out: cubic-bezier(0.76, 0, 0.24, 1);     /* wipes, panels, things that travel while visible */
  --ease-soft: cubic-bezier(0.33, 1, 0.68, 1);       /* slow scale and drift on photographs */
  --ease-exit: cubic-bezier(0.55, 0.055, 0.675, 0.19); /* something leaving for good, short */
  --t-press: 140ms;
  --t-hover: 180ms;
  --t-ui: 240ms;
  --t-reveal: 800ms;
  --t-open: 1200ms;                                  /* picture openings and clip wipes */
  --stagger-line: 90ms;
  --stagger-item: 70ms;
}

/* ---------- reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; scroll-behavior: auto; }
body { margin: 0; background: var(--bg); color: var(--ink); font-family: var(--font-text); font-size: var(--step-body); line-height: 1.5; font-kerning: normal; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; }
h1, h2, h3, h4, p, figure, blockquote, ul, ol, dl, dd { margin: 0; }
ul[class], ol[class] { padding: 0; list-style: none; }
img, video, svg, canvas { display: block; max-width: 100%; }
img, video { height: auto; background: var(--surface); }
button, input, select, textarea { font: inherit; color: inherit; }
button { background: none; border: 0; padding: 0; cursor: pointer; }
a { color: inherit; }
:target { scroll-margin-top: 6rem; }
::selection { background: var(--accent); color: var(--accent-ink); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* ---------- type ---------- */
h1, h2, h3 { font-family: var(--font-display); font-weight: inherit; text-wrap: balance; hyphens: none; }
.display { font-size: var(--step-display); line-height: 0.95; letter-spacing: -0.035em; }
.head { font-size: var(--step-head); line-height: 1.02; letter-spacing: -0.025em; }
.lead { font-size: var(--step-lead); line-height: 1.35; max-width: 40ch; }
.small { font-size: var(--step-small); line-height: 1.45; }
.muted { color: var(--muted); }
p { max-width: 62ch; text-wrap: pretty; }
p + p { margin-top: 0.9em; }
.nums { font-variant-numeric: tabular-nums; }
.only-wide { display: none; }
@media (min-width: 48em) { .only-wide { display: inline; } }

/* ---------- layout ---------- */
.wrap { padding-inline: var(--margin); }
.grid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--gutter); }
.section { padding-block: calc(var(--section) / 2); }
.section + .section { margin-top: 0; }
.bleed { margin-inline: calc(var(--margin) * -1); }
.cover { width: 100%; height: 100%; object-fit: cover; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* ---------- controls ---------- */
.button { display: inline-flex; align-items: center; justify-content: center; min-height: 3rem; padding: 0 1.4rem; background: var(--accent); color: var(--accent-ink); border-radius: var(--radius); text-decoration: none; white-space: nowrap; transition: transform var(--t-press) var(--ease-out), background-color var(--t-hover) ease, color var(--t-hover) ease; }
.button:active { transform: scale(0.97); }
.link { text-decoration: underline; text-decoration-color: transparent; text-underline-offset: 0.2em; text-decoration-thickness: 0.07em; transition: text-decoration-color var(--t-hover) ease; }
.link-plain { text-decoration: underline; text-underline-offset: 0.18em; text-decoration-thickness: 0.07em; }
@media (hover: hover) and (pointer: fine) {
  .link:hover { text-decoration-color: currentColor; }
}
.field { width: 100%; min-height: 3rem; padding: 0.7rem 0.9rem; background: var(--surface); border: 0; border-radius: var(--radius); font-size: max(1rem, var(--step-body)); }

/* ---------- motion helpers (content stays visible without JavaScript) ---------- */
/* A masked line (the "Masked line reveal" recipe and the set-type entrance): each .line is a block, and while motion
   is allowed it clips its [data-line] with room for accents above and descenders below. The margins give that room
   back, so the lines sit exactly where they sit without the mask. */
.lines .line, [data-entrance="set-type"] [data-e="headline"] .line { display: block; }
.lines [data-line], [data-entrance="set-type"] [data-e="headline"] [data-line] { display: block; }
@media (prefers-reduced-motion: no-preference) {
  .js :is(.lines, [data-entrance="set-type"] [data-e="headline"], [data-entrance="set-type"] .is-split) .line { overflow: clip; padding: 0.2em 0 0.3em; margin: -0.2em 0 0; }
  .js :is(.lines, [data-entrance="set-type"] [data-e="headline"], [data-entrance="set-type"] .is-split) .line + .line { margin-top: -0.5em; }
  .js :is(.lines, [data-entrance="set-type"] [data-e="headline"], [data-entrance="set-type"] .is-split) .line:last-child { margin-bottom: -0.3em; }
}
.shutter { overflow: hidden; }
/* Rolling figures (motion.md): the columns its script builds */
.roll__digits { display: inline-flex; font-variant-numeric: tabular-nums; line-height: 1; }
.roll__col { display: inline-block; height: 1em; overflow: clip; }
.roll__strip { display: flex; flex-direction: column; transform: translateY(calc(var(--d) * -1em)); }
.roll__strip > span { height: 1em; }
/* older pages, built before the entrance was dealt (no data-entrance on the body): the line rise, the fade-up and the
   shutter as they were. The shutter also stays for a page whose dealt entrance is set-type. */
@media (prefers-reduced-motion: no-preference) {
  .js body:not([data-entrance]) [data-rise] { opacity: 0; transform: translateY(18px); animation: ad-rise var(--t-reveal) var(--ease-out) forwards; animation-delay: calc(var(--i, 0) * 90ms + 120ms); }
  .js body:not([data-entrance]) .lines [data-line] { transform: translateY(calc(100% + 0.6em)); animation: ad-line 900ms var(--ease-out) forwards; animation-delay: calc(var(--i, 0) * 110ms + 150ms); }
  .js body:not([data-entrance]) [data-reveal] { opacity: 0; transform: translateY(24px); transition: opacity 700ms var(--ease-out), transform 900ms var(--ease-out); }
  .js body:not([data-entrance]) [data-reveal].is-in { opacity: 1; transform: none; }
  .js :is(body:not([data-entrance]), [data-entrance="set-type"]) .shutter[data-reveal] { opacity: 1; transform: none; clip-path: inset(0 0 100% 0); transition: clip-path 1100ms var(--ease-in-out); }
  .js :is(body:not([data-entrance]), [data-entrance="set-type"]) .shutter[data-reveal] img { transform: scale(1.12); transition: transform 1500ms var(--ease-soft); }
  .js :is(body:not([data-entrance]), [data-entrance="set-type"]) .shutter[data-reveal].is-in { clip-path: inset(0); }
  .js :is(body:not([data-entrance]), [data-entrance="set-type"]) .shutter[data-reveal].is-in img { transform: none; }
}
@keyframes ad-rise { to { opacity: 1; transform: none; } }
@keyframes ad-line { to { transform: none; } }

/* ---------- the entrance (dealt by draw.mjs; references/motion.md "The entrance is dealt") ----------
   <body data-entrance="ID">. The first screen is [data-hero]; its parts carry data-e="headline", "lead", "action" and
   "picture" (the element that already crops the picture, with --picture-ground sampled from it; its first child is
   the img, video, picture or [data-ui]). Every hidden state is the first keyframe of an animation that fills
   backwards, inside html.js and no-preference, so with no script, reduced motion or no animations the visitor sees
   the finished first screen, and once an animation ends the element is simply its own style again (no filter or
   transform left on it). Nothing waits for the load event; all is still by 1.24s; the header never moves. A hero
   picture that has not arrived at the first frame waits on its ground (data-e-wait, set by the "hero picture"
   snippet in base.js, pasted inline right after the hero). Below the fold, data-reveal means only what the dealt
   entrance says, and any other data-reveal stays put: no fade-up on every block.
   Keep the shared rules and the dealt block; the other six can go. */
[data-entrance] [data-e="picture"] { background: var(--picture-ground, transparent); }
:is([data-entrance="focus"], [data-entrance="aperture"], [data-entrance="set-type"]) [data-e="picture"] { overflow: clip; }
[data-entrance] [data-e="picture"] > picture { display: block; height: 100%; }
[data-entrance] [data-e="picture"] > :is(img, video), [data-entrance] [data-e="picture"] > picture > img { background: none; }
@media (prefers-reduced-motion: no-preference) {
  .js [data-entrance] [data-reveal] { opacity: 1; transform: none; }
  .js [data-entrance] [data-e="picture"][data-e-wait], .js [data-entrance] [data-e="picture"][data-e-wait] > * { animation: none; }
  .js [data-entrance] [data-e="picture"][data-e-wait] > :not(figcaption) { opacity: 0; }
  .js [data-entrance] [data-e="picture"][data-e-wait="late"] > :not(figcaption) { opacity: 1; transition: opacity 320ms ease; }
}

/* already-there: nothing arrives; the first frame is the finished first screen. A hero picture that had not painted
   yet waits on its own colour and fades in as it lands. Below the fold: nothing enters; lazy pictures get the same
   courtesy (base.js). */
@media (prefers-reduced-motion: no-preference) {
  .js [data-entrance="already-there"] img.is-waiting { opacity: 0; }
  .js [data-entrance="already-there"] img.is-arriving { transition: opacity 320ms ease; }
}

/* focus: a rack focus. The picture starts soft and a touch large and comes into focus over a second; as it sharpens,
   the headline comes into focus from half strength and 4px of blur, with no travel. The sentence and the action are
   sharp from the first frame. A phone's picture only settles in scale (--lens: 0px), so crossing the breakpoint
   restarts nothing. Below the fold: chapter headings (data-reveal="heading", one per section) sharpen once. */
[data-entrance="focus"] { --lens: 8px; }
@media (max-width: 47.99em) { [data-entrance="focus"] { --lens: 0px; } }
@media (prefers-reduced-motion: no-preference) {
  .js [data-entrance="focus"] [data-e="picture"] > :not(figcaption) { animation: e-focus-lens 1000ms var(--ease-soft) backwards; }
  .js [data-entrance="focus"] [data-e="headline"] { animation: e-focus-type 700ms var(--ease-soft) 260ms backwards; }
  .js [data-entrance="focus"] [data-reveal="heading"] { opacity: 0.45; filter: blur(4px); transition: opacity 700ms var(--ease-out), filter 700ms var(--ease-soft); }
  .js [data-entrance="focus"] [data-reveal="heading"].is-in { opacity: 1; filter: none; }
}
@keyframes e-focus-lens { from { filter: blur(var(--lens, 8px)); transform: scale(1.03); } }
@keyframes e-focus-type { from { filter: blur(4px); opacity: 0.5; } }

/* exposure: the first screen is in place at once; the photograph starts pale, as if over-exposed, and its own light
   and colour come up by 1.22s. Nothing else moves. On a dark ground set --exposure-from: brightness(0.35)
   saturate(0.5) on the body. A photo treatment's filter goes on the frame, never on the img. Below the fold: large
   pictures (data-reveal="picture") take their colour once. */
[data-entrance="exposure"] { --exposure-from: brightness(1.45) contrast(0.75) saturate(0.4); }
@media (prefers-reduced-motion: no-preference) {
  .js [data-entrance="exposure"] [data-e="picture"] > :not(figcaption) { animation: e-exp-light 1100ms ease 120ms backwards; }
  .js [data-entrance="exposure"] [data-reveal="picture"] > :not(figcaption) { filter: saturate(0.2); transition: filter 900ms ease; }
  .js [data-entrance="exposure"] [data-reveal="picture"].is-in > :not(figcaption) { filter: none; }
}
@keyframes e-exp-light { from { filter: var(--exposure-from); } }

/* product-at-work: the interface fragment is complete and readable at the first frame, then shows it is live in four
   short steps: the key figure ticks up to its value (data-e="figure" with data-roll, split inline by the Rolling
   figures script; only its last two digits move), the chart's latest stretch draws in (data-e="chart"), the newest
   row lands as the rows under it make room (data-e="new-row"; --ui-row-gap is the list's row gap), and one row is
   selected (data-e="select"; the fragment sets --ui-row-bg and --ui-row-ink to the unselected colours). The words
   never move. A fragment whose top is below 60% of the screen (a phone) holds all this until it is in view
   (data-e-hold, base.js). Below the fold: further fragments (data-reveal="ui") each play one data moment once
   (data-moment="chart" or "row"). */
[data-entrance="product-at-work"] [data-hero] [data-e="figure"] { font-variant-numeric: tabular-nums; }
@media (prefers-reduced-motion: no-preference) {
  .js [data-entrance="product-at-work"] [data-hero] .roll .roll__strip { transform: translateY(calc(var(--d) * -1em)); transition: none; }
  .js [data-entrance="product-at-work"] [data-hero] .roll .roll__col:nth-last-child(1) .roll__strip { --from: max(var(--d) - 3, 0); animation: e-ui-roll 800ms var(--ease-expo) 200ms backwards; }
  .js [data-entrance="product-at-work"] [data-hero] .roll .roll__col:nth-last-child(2) .roll__strip { --from: max(var(--d) - 1, 0); animation: e-ui-roll 800ms var(--ease-expo) 200ms backwards; }
  .js [data-entrance="product-at-work"] [data-hero] [data-e="chart"] svg { animation: e-ui-plot 800ms var(--ease-in-out) 250ms backwards; }
  .js [data-entrance="product-at-work"] [data-hero] [data-e="new-row"] { animation: e-ui-row 420ms var(--ease-out) 560ms backwards; }
  .js [data-entrance="product-at-work"] [data-hero] [data-e="new-row"] ~ * { animation: e-ui-push 420ms var(--ease-out) 560ms backwards; }
  .js [data-entrance="product-at-work"] [data-hero] [data-e="select"] { animation: e-ui-select 240ms ease 1000ms backwards; }
  .js [data-entrance="product-at-work"] [data-hero] [data-e="new-row"] ~ [data-e="select"] { animation: e-ui-push 420ms var(--ease-out) 560ms backwards, e-ui-select 240ms ease 1000ms backwards; }
  .js [data-e-hold], .js [data-e-hold] * { animation-play-state: paused !important; }
  .js [data-entrance="product-at-work"] [data-reveal="ui"]:not(.is-in):not([data-replay], [data-replay] *) [data-moment="chart"] svg { clip-path: inset(0 100% 0 0); }
  .js [data-entrance="product-at-work"] [data-reveal="ui"].is-in:not([data-replay], [data-replay] *) [data-moment="chart"] svg { clip-path: inset(0); transition: clip-path 900ms var(--ease-in-out); }
  .js [data-entrance="product-at-work"] [data-reveal="ui"]:not(.is-in):not([data-replay], [data-replay] *) [data-moment="row"] { opacity: 0; transform: translateY(-6px); }
  .js [data-entrance="product-at-work"] [data-reveal="ui"].is-in:not([data-replay], [data-replay] *) [data-moment="row"] { transition: opacity 420ms var(--ease-out) 200ms, transform 420ms var(--ease-out) 200ms; }
}
@keyframes e-ui-roll { from { transform: translateY(calc(var(--from, 0) * -1em)); } }
@keyframes e-ui-plot { from { clip-path: inset(0 15% 0 0); } to { clip-path: inset(0); } }
@keyframes e-ui-row { from { opacity: 0; transform: translateY(-30%); } }
@keyframes e-ui-push { from { transform: translateY(calc(-100% - var(--ui-row-gap, 0px))); } }
@keyframes e-ui-select { from { background-color: var(--ui-row-bg, transparent); color: var(--ui-row-ink, var(--ink)); } }

/* the product replay (assets/replay.js, references/motion.md "Product replay"): the window is written in its
   final state; the script takes it back to its first state as it arms and plays the beats once in view.
   Nothing here hides anything without the script, under reduced motion or before .is-armed. The window's text is
   18px or more on desktop and 16px on a phone (a page rule wins: zero specificity); each beat changes about a
   third of the window, and a panel that opens takes the page's own tokens (--ink on a light page, --accent on a
   dark one) over whole rows. */
.replay { position: relative; margin: 0; }
:where(.replay > [data-ui]) { font-size: clamp(1rem, 0.8rem + 0.55vw, 1.25rem); }
[data-replay] [data-roll-at] { font-variant-numeric: tabular-nums; }
[data-replay] [data-open-at][hidden] { display: none !important; }
[data-replay] [data-frame-at] { outline-offset: 2px; }
@media (prefers-reduced-motion: no-preference) {
  .js [data-replay].is-armed [data-at]:not(.is-on):not([data-from]) { opacity: 0; transform: translateY(-6px); }
  .js [data-replay] [data-at] { transition: opacity 320ms var(--ease-out), transform 420ms var(--ease-out), background-color 320ms ease, color 320ms ease; }
  .js [data-replay] [data-press-at] { transition: transform var(--t-press, 120ms) var(--ease-out); }
  .js [data-replay] [data-press-at].is-pressed { transform: translateY(2px); }
  .js [data-replay] .is-framed { outline: 2px solid var(--accent); }
  .js [data-replay] [data-select-at] { transition: background-color 240ms ease, color 240ms ease; }
}

/* aperture: the picture opens from a small window in its middle out past every edge of its frame while it eases back
   from 1.2, settled by 1.2s; the words never move. Centre the window on the subject with four insets
   (--window: top right bottom left on the frame). Below the fold: up to three large pictures
   (data-reveal="picture") open once from a centred window. */
[data-entrance="aperture"] { --ease-open: cubic-bezier(0.6, 0, 0, 1); }
[data-entrance="aperture"] [data-e="picture"] { --window: 34% 36%; }
@media (max-width: 47.99em) { [data-entrance="aperture"] [data-e="picture"] { --window: 24% 16%; } }
@media (prefers-reduced-motion: no-preference) {
  .js [data-entrance="aperture"] [data-e="picture"] { animation: e-ap-open 1100ms var(--ease-open) 80ms backwards; }
  .js [data-entrance="aperture"] [data-e="picture"] > :not(figcaption) { animation: e-ap-settle 1200ms var(--ease-open) backwards; }
  .js [data-entrance="aperture"] [data-reveal="picture"] { overflow: clip; clip-path: inset(12% 14% round var(--radius)); transition: clip-path 1000ms var(--ease-open); }
  .js [data-entrance="aperture"] [data-reveal="picture"] > :not(figcaption) { transform: scale(1.08); transition: transform 1100ms var(--ease-open); }
  .js [data-entrance="aperture"] [data-reveal="picture"].is-in { clip-path: inset(0 round 0px); }
  .js [data-entrance="aperture"] [data-reveal="picture"].is-in > :not(figcaption) { transform: none; }
}
@keyframes e-ap-open { from { clip-path: inset(var(--window) round var(--radius)); } to { clip-path: inset(0 round 0px); } }
@keyframes e-ap-settle { from { transform: scale(var(--from-scale, 1.2)); } }

/* set-type: the masked line rise and the shutter, once the default and now one entrance of seven, dealt rarely. The
   headline's lines (.line > [data-line] style="--i:n", three at most, each fitting one line at every width) rise out
   of their own masks (the masked line helper above); the picture opens downward from its top edge; the sentence and
   the action rise 12px. Settled by 1.2s. Below the fold: chapter headings (data-lines, split by the "Lines that rise
   on scroll" script in motion.md) rise by line once. */
[data-entrance="set-type"] [data-lines] .line, [data-entrance="set-type"] [data-lines] .line > span { display: block; }
@media (prefers-reduced-motion: no-preference) {
  .js [data-entrance="set-type"] [data-e="headline"] [data-line] { animation: e-set-line 800ms var(--ease-expo) backwards; animation-delay: calc(100ms + var(--i, 0) * var(--stagger-line)); }
  .js [data-entrance="set-type"] [data-e="picture"] { animation: e-set-open 1000ms var(--ease-in-out) 150ms backwards; }
  .js [data-entrance="set-type"] [data-e="picture"] > :not(figcaption) { animation: e-set-settle 1100ms var(--ease-expo) 100ms backwards; }
  .js [data-entrance="set-type"] [data-e="lead"] { animation: e-set-small 500ms var(--ease-out) 420ms backwards; }
  .js [data-entrance="set-type"] [data-e="action"] { animation: e-set-small 500ms var(--ease-out) 480ms backwards; }
  .js [data-entrance="set-type"] .is-split:not(.is-in) .line > span { transform: translateY(calc(100% + 0.6em)); }
  .js [data-entrance="set-type"] .is-split.is-in .line > span { transition: transform 900ms var(--ease-expo) calc(var(--i, 0) * var(--stagger-line)); }
}
@keyframes e-set-line { from { transform: translateY(calc(100% + 0.6em)); } }
@keyframes e-set-open { from { clip-path: inset(0 0 100% 0); } to { clip-path: inset(0); } }
@keyframes e-set-settle { from { transform: scale(1.08); } }
@keyframes e-set-small { from { opacity: 0; transform: translateY(12px); } }

/* line-blocks: each headline line (.line > [data-line] style="--i:n", three at most, each fitting one line at every
   width) starts as a solid block of one palette colour, exactly as long as its words, and the blocks slide off toward
   the line ends in reading order; the words never move and the picture stays still. --block is the accent on a bold
   or playful page and var(--ink) on a serious one (the sheet says which). Below the fold: lists, indexes, tables and
   price rows (data-reveal="rows" on the list) step from 40% to full in reading order. */
[data-entrance="line-blocks"] { --block: var(--accent); }
[data-entrance="line-blocks"] [data-e="headline"] { isolation: isolate; }
[data-entrance="line-blocks"] [data-e="headline"] .line { display: block; }
[data-entrance="line-blocks"] [data-e="headline"] [data-line] { display: inline-block; position: relative; z-index: calc(10 - var(--i, 0)); }
@media (prefers-reduced-motion: no-preference) {
  .js [data-entrance="line-blocks"] [data-e="headline"] [data-line]::after { content: ""; position: absolute; inset: -0.22em -0.04em -0.28em; background: var(--block); transform-origin: 100% 50%; transform: scaleX(0); visibility: hidden; pointer-events: none; animation: e-lb-lift 600ms var(--ease-in-out) backwards; animation-delay: calc(80ms + var(--i, 0) * var(--stagger-line)); }
  .js [data-entrance="line-blocks"] :is([data-e="lead"], [data-e="action"]) { animation: e-lb-fade 500ms ease 600ms backwards; }
  .js [data-entrance="line-blocks"] [data-reveal="rows"] > * { opacity: 0.4; transition: opacity 500ms ease 240ms; }
  .js [data-entrance="line-blocks"] [data-reveal="rows"] > :nth-child(1) { transition-delay: 0ms; }
  .js [data-entrance="line-blocks"] [data-reveal="rows"] > :nth-child(2) { transition-delay: 60ms; }
  .js [data-entrance="line-blocks"] [data-reveal="rows"] > :nth-child(3) { transition-delay: 120ms; }
  .js [data-entrance="line-blocks"] [data-reveal="rows"] > :nth-child(4) { transition-delay: 180ms; }
  .js [data-entrance="line-blocks"] [data-reveal="rows"].is-in > * { opacity: 1; }
}
@keyframes e-lb-lift { from { visibility: visible; transform: none; } to { visibility: hidden; transform: scaleX(0); } }
@keyframes e-lb-fade { from { opacity: 0; } }
