/* ──────────────────────────────────────────────────────────────────────────
   Product pages.

   A product page is a single HTML file that links this, sets --product-rgb to
   its own color, and fills in the sections below. Nothing here knows which
   product it is showing, and that is the line: anything that could only be
   true of one product belongs in that page's own style block, not here.
   Signa's story sequence and the size of its name live there for that reason.

       .product-hero     icon, name, one line, the download, the facts
       .product-shot     one large, real screenshot
       .product-section  a labelled band: kicker on the left, content on the right
       .steps            numbered steps, each a row with a real picture
       .facts            a quiet list of specifics
       .product-download the download, once more, at the end

   The palette, type and chrome are the site's own, restated here because the
   other pages carry theirs inline and there is no shared sheet to import from.
   If the site's tokens change, change them here too.
   ────────────────────────────────────────────────────────────────────────── */

:root {
  --bg: #0e0d0b;
  --fg: #efe8dc;
  --muted: #8a8378;
  --line: rgb(239 232 220 / .14);
  --accent: #ff5a36;
  --sans: "Figtree", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
  --out: cubic-bezier(.16, 1, .3, 1);

  /* --fg's own channels. The site writes this color at several alphas – the
     hairline above is the same three numbers at .14 – and every one of those
     was a literal. Naming the channels means a rule can ask for the ink at
     whatever strength it needs without a fourth copy of "239 232 220". */
  --fg-rgb: 239 232 220;

  /*
   * The product's own color, as bare channels so it can take an alpha. A page
   * overrides --product-rgb; the default is the site's accent.
   *
   * It is deliberately NOT what focus rings use. --accent is the site's voice –
   * it is the same orange on every page, and a focus ring is the site talking
   * to the reader about their keyboard, not the product talking about itself.
   * So a page can be any color it likes and the focus ring stays where the
   * reader learned to expect it. On Signa the two sit a few degrees apart,
   * which looks like a mistake and is not one.
   */
  --product-rgb: 255 90 54;
  --product-accent: rgb(var(--product-rgb));
  /* Softer than body text, brighter than muted: for running copy. */
  --copy: #d9d2c6;

  color-scheme: dark;
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }

/* Same as every other page: arriving here from the menu is a navigation the
   browser may animate, and the footer below opts out of it so it stays put. */
@view-transition { navigation: auto; }

::view-transition-old(footer),
::view-transition-new(footer) { animation: none; }

html { background: var(--bg); scroll-behavior: smooth; }

body {
  margin: 0;
  min-height: 100svh;
  display: grid;
  grid-template-rows: auto 1fr auto;
  grid-template-columns: minmax(0, 1fr);
  padding-inline: clamp(16px, 4vw, 48px);
  background: var(--bg);
  color: var(--fg);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
}

/* The same film grain as the rest of the site, over everything. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 10;
  pointer-events: none;
  opacity: .05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ── site chrome: header, menu, footer. Identical to the other pages. ── */

.bar {
  position: relative;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: .5rem 1.5rem;
  padding-block: 1.25rem;
  font: 500 .72rem/1.4 var(--mono);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted);
}

header.bar { border-bottom: 1px solid var(--line); animation: fade 1s .1s var(--out) both; }
header.bar > span:first-child { margin-right: auto; }
header.bar .social + .social { margin-left: .35rem; }
/*
 * The footer joins the site's cross-page transition group.
 *
 * Every other page names its footer and holds it still, so navigating between
 * them leaves one fixed element on screen while the rest changes. A product
 * page that did not do this would be the one place the footer blinked, which
 * is more noticeable than the transition it was missing.
 */
footer.bar {
  border-top: 1px solid var(--line);
  view-transition-name: footer;
  /* No entrance, for the reason given on the other pages: a delayed fade held
     it at opacity 0 on arrival, so it read as disappearing and coming back. */
  animation: none;
}

.bar a { color: inherit; text-decoration: none; transition: color .2s; }
.bar a:hover { color: var(--fg); }

.social {
  display: inline-flex;
  padding: .4rem;
  margin: -.4rem;
  border-radius: 50%;
  color: var(--muted);
  background: transparent;
  transition: color .2s, background-color .2s;
}

.social .disc,
.social .cat { transition: opacity .45s var(--out); }
.social .disc { fill: var(--muted); opacity: 1; }
.social .cat { fill: #fff; opacity: 0; }
.social:hover .disc,
.social:focus-visible .disc { opacity: 0; }
.social:hover .cat,
.social:focus-visible .cat { opacity: 1; }
.social:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.social svg { width: 20px; height: 20px; clip-path: circle(49%); }

/* The menu is fixed on the short pages. A product page scrolls, and a menu
   pinned over a screenshot would sit on top of the thing being shown, so here
   it stays with the header and leaves with it. */
.menu {
  position: absolute;
  top: 4.6rem;
  right: clamp(16px, 4vw, 48px);
  z-index: 3;
  width: 13.5rem;
  font: 500 .72rem/1 var(--mono);
  letter-spacing: .12em;
  text-transform: uppercase;
  animation: fade 1s .6s var(--out) both;
}

.menu ul { margin: 0; padding: 0; list-style: none; }

/* Each level arrives as one thing when it is shown, which is why this is
   on the level and not on .menu ul. */
.menu [data-level] { animation: level-in .3s var(--out) both; }
.menu li + li { border-top: 1px solid var(--line); }

.menu button,
.menu a {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: .8em;
  width: 100%;
  padding: .95rem 0;
  border: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: color .2s;
}

.menu button:hover,
.menu a:hover,
.menu a[aria-current="page"] { color: var(--fg); }

.menu button:focus-visible,
.menu a:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }


.menu .chev { width: .85em; height: .85em; flex: none; }
.menu .ext { width: .8em; height: .8em; flex: none; opacity: .7; }
.menu [data-back] { justify-content: flex-end; color: var(--muted); }

.menu .pair { display: flex; align-items: center; justify-content: flex-end; }
.menu .pair a { width: auto; }

.menu .peek,
.menu .slash {
  max-width: 0;
  opacity: 0;
  overflow: hidden;
  white-space: nowrap;
  transition: max-width .4s var(--out), opacity .3s var(--out), margin .4s var(--out);
}

.menu .slash { padding: .95rem 0; color: var(--muted); }
.menu .pair:hover .peek,
.menu .pair:focus-within .peek { max-width: 8rem; opacity: 1; }
.menu .pair:hover .slash,
.menu .pair:focus-within .slash { max-width: 2rem; opacity: 1; margin: 0 .55em; }

.meta { display: inline-flex; align-items: center; gap: .6em; }
.meta .ico { width: 1.15em; height: 1.15em; flex: none; opacity: .85; }

/*
 * On a narrow screen the menu would sit over the product's name.
 *
 * It used to be deleted here, which made this the only page on the site where
 * the navigation is unreachable on a phone – the breadcrumb leads home, but
 * that is a round trip to reach a list every other page just shows you.
 *
 * resume/ has the same problem, being the other page that both scrolls and
 * carries this menu, and solves it by moving the content down instead:
 * `main { padding-top: 7.5rem }` below 60rem. Same answer here, so the two
 * scrolling pages behave the same way, and the menu stays where the rest of
 * the site has taught people to look for it.
 */
@media (max-width: 52rem) {
  main { padding-top: 7.5rem; }
}

/* ── the page ── */

main {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 72rem;
  margin-inline: auto;
}

/* Small uppercase label, in the product's color. The site's kicker. */
.kicker {
  margin: 0;
  font: 500 .72rem/1.4 var(--mono);
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--product-accent);
}

/* ── hero ── */

.product-hero {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding-block: clamp(3.5rem, 11vh, 7rem) clamp(3rem, 8vh, 5rem);
}

/* The product's own light, behind its icon. One still field: it does not
   follow the pointer or pulse. It is there so the icon sits in a room. */
.product-hero::before {
  content: "";
  position: absolute;
  left: 50%;
  top: clamp(1rem, 4vh, 3rem);
  z-index: -1;
  /*
   * Never wider than the space it sits in.
   *
   * This was 140vw, centred, which put a fifth of the viewport's width past
   * the right edge and gave the page real horizontal scroll: 72px at 320,
   * 152px at 720. The landing page can afford an oversized decoration because
   * its body is `overflow: hidden` – it is one fixed screen – and the same
   * line here would take the vertical scroll with it, which a product page
   * needs. Nor does the site clip with overflow-x anywhere; the way it avoids
   * this is by not building something that sticks out. Its own glow is a
   * gradient painted inside a `position: fixed; inset: 0` layer, which cannot
   * overflow by construction.
   *
   * So the box is capped at the width available. The gradient is `closest-side`
   * and so reshapes to whatever it is given: on a phone this is a smaller glow
   * rather than a clipped one.
   */
  width: min(64rem, 100%);
  aspect-ratio: 1.5;
  transform: translateX(-50%);
  pointer-events: none;
  background: radial-gradient(closest-side, rgb(var(--product-rgb) / .2), rgb(var(--product-rgb) / .07) 55%, rgb(var(--product-rgb) / 0));
  animation: fade 2.4s .2s var(--out) both;
}

.product-icon {
  width: clamp(9.5rem, 22vw, 15rem);
  height: auto;
  aspect-ratio: 1;
  /* The icon files carry their own shadow; this is the light under it. */
  filter: drop-shadow(0 1.6rem 2.6rem rgb(0 0 0 / .45));
  animation: icon-in 1.3s .15s var(--out) both;
}

/*
 * The product's name. Its SIZE is not set here.
 *
 * A name is as long as it is, and the clamp that suits "Signa" leaves
 * "Snippet Builder" wrapping badly or overflowing – at the floor this used to
 * carry, a 320px screen had room for about five letters. So the size is the
 * page's to choose, in its own style block, and what is shared is only the
 * things that are true of any name: the weight, the tracking, and the clip
 * that it rises out of.
 */
.product-name {
  margin: clamp(1rem, 3vh, 1.8rem) 0 0;
  font-weight: 600;
  line-height: .95;
  letter-spacing: -.035em;
  /* The clip is what the name rises out of. At this line height a descender
     hangs below the line box, so the box is padded to hold it and the padding
     taken back out of the layout: a name with a g keeps its tail. */
  overflow: hidden;
  padding-bottom: .22em;
  margin-bottom: -.22em;
}

.product-name span { display: block; animation: rise 1.1s .3s var(--out) both; }

.product-line {
  max-width: 22ch;
  margin: clamp(1rem, 2.6vh, 1.6rem) 0 0;
  font-size: clamp(1.45rem, 3.1vw, 2.4rem);
  font-weight: 500;
  line-height: 1.12;
  letter-spacing: -.02em;
  text-wrap: balance;
  animation: rise-in 1s .5s var(--out) both;
}

.product-sub {
  max-width: 44ch;
  margin: 1rem 0 0;
  font-size: clamp(1rem, 1.3vw, 1.12rem);
  line-height: 1.55;
  color: var(--muted);
  text-wrap: pretty;
  animation: rise-in 1s .6s var(--out) both;
}

.actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 1rem 1.6rem;
  margin-top: clamp(1.8rem, 4.5vh, 2.6rem);
  animation: rise-in 1s .72s var(--out) both;
}

/* The one filled control on the page. Paper on ink, as the name is. */
.button {
  display: inline-flex;
  align-items: center;
  gap: .7em;
  padding: .95em 1.6em;
  border-radius: 999px;
  background: var(--fg);
  color: var(--bg);
  font: 600 1rem/1 var(--sans);
  letter-spacing: -.005em;
  text-decoration: none;
  transition: transform .3s var(--out), box-shadow .5s var(--out), background-color .2s;
  box-shadow: 0 0 0 0 rgb(var(--product-rgb) / 0);
}

.button:hover { background: #fff; transform: translateY(-1px); box-shadow: 0 .9rem 2.6rem -.6rem rgb(var(--product-rgb) / .55); }
.button:active { transform: translateY(0); }
.button:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
.button svg { width: 1.05em; height: 1.05em; flex: none; }

/* A link that leaves the page, set the way the site sets its labels. */
.textlink {
  display: inline-flex;
  align-items: center;
  gap: .6em;
  padding: .5em 0;
  font: 500 .72rem/1 var(--mono);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--muted);
  text-decoration: none;
  transition: color .2s;
}

.textlink:hover { color: var(--fg); }
.textlink:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
.textlink svg { width: .85em; height: .85em; flex: none; opacity: .75; }

/* Version, requirement, and nothing else. */
.specline {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: .3rem 1.4rem;
  margin: 1.5rem 0 0;
  padding: 0;
  list-style: none;
  font: 500 .72rem/1.5 var(--mono);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted);
  animation: fade 1.2s .95s var(--out) both;
}

/* ── the screenshot ── */

.product-shot {
  margin: 0;
  padding-bottom: clamp(2rem, 8vh, 5rem);
  display: flex;
  justify-content: center;
}

/* Never wider than the capture itself: a screenshot scaled up stops looking real. */
.product-shot img {
  display: block;
  width: 100%;
  max-width: var(--shot-width, 64rem);
  height: auto;
}

/* ── sections ── */

.product-section {
  display: grid;
  grid-template-columns: minmax(0, 13rem) minmax(0, 1fr);
  gap: 1.5rem clamp(1.5rem, 5vw, 5rem);
  padding-block: clamp(4rem, 13vh, 8.5rem);
  border-top: 1px solid var(--line);
}

.product-section > .kicker { padding-top: .55em; }

.section-title {
  max-width: 18ch;
  margin: 0;
  font-size: clamp(1.9rem, 4.4vw, 3.4rem);
  font-weight: 600;
  line-height: 1.02;
  letter-spacing: -.03em;
  text-wrap: balance;
}

.section-copy {
  max-width: 52ch;
  margin: 1.4rem 0 0;
  font-size: clamp(1rem, 1.25vw, 1.12rem);
  line-height: 1.6;
  color: var(--copy);
  text-wrap: pretty;
}

.section-copy + .section-copy { margin-top: 1em; }

@media (max-width: 52rem) {
  .product-section { grid-template-columns: minmax(0, 1fr); }
}

/* ── steps ── */

/* One row per step: what to do on the left, what it looks like on the right.
   Rows rather than columns because the pictures are pieces of the real
   window, and a screenshot only looks real at the size it was taken. */
.steps {
  display: grid;
  gap: clamp(2.5rem, 7vh, 4.5rem);
  margin: clamp(2.4rem, 6vh, 4rem) 0 0;
  padding: 0;
  list-style: none;
  counter-reset: step;
}

.steps li {
  counter-increment: step;
  display: grid;
  grid-template-columns: minmax(0, 13rem) minmax(0, 1fr);
  align-items: center;
  gap: 1.5rem clamp(1.5rem, 4vw, 3.5rem);
}

/* Captured at 2x: the width and height attributes carry the size in points,
   and the picture never grows past it. */
.steps .picture { margin: 0; }

.steps .picture img {
  display: block;
  max-width: 100%;
  height: auto;
  border-radius: 1rem;
  box-shadow: 0 0 0 1px var(--line);
}

.steps h3 {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 600;
  letter-spacing: -.015em;
}

.steps h3::before {
  content: counter(step, decimal-leading-zero);
  display: block;
  margin-bottom: .6rem;
  font: 500 .72rem/1 var(--mono);
  letter-spacing: .12em;
  color: var(--muted);
}

.steps p { margin: .5rem 0 0; font-size: 1rem; line-height: 1.55; color: var(--copy); text-wrap: pretty; }

@media (max-width: 64rem) {
  .steps li { grid-template-columns: minmax(0, 1fr); }
}

/* ── facts ── */

.facts {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0 clamp(1.5rem, 3vw, 2.5rem);
  margin: clamp(2.2rem, 5vh, 3.4rem) 0 0;
}

/* Four across, or two by two. Never three and a leftover. */
@media (max-width: 64rem) {
  .facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.facts div { padding: 1.1rem 0; border-top: 1px solid var(--line); }

.facts dt {
  font: 500 .72rem/1.4 var(--mono);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--muted);
}

.facts dd { margin: .45rem 0 0; font-size: 1.05rem; line-height: 1.4; }

/* ── download ── */

.product-download {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding-block: clamp(4.5rem, 15vh, 9rem);
  border-top: 1px solid var(--line);
}

.product-download .product-icon { width: clamp(5rem, 9vw, 6.5rem); animation: none; }
.product-download .section-title { max-width: none; margin-top: 1.4rem; }
.product-download .actions,
.product-download .specline { animation: none; }
.product-download .links { display: flex; flex-wrap: wrap; justify-content: center; gap: .4rem 2rem; margin-top: 1.6rem; }

/*
 * A link inside running text.
 *
 * The first product pages never put one in a paragraph – everything that
 * linked was a .textlink or a .button, which carry their own styling – so a
 * link in prose fell through to the browser's own, and arrived purple and
 * underlined in the middle of a sentence.
 *
 * The treatment is the one the spec lines already use: the text keeps the
 * colour of the sentence it belongs to, and a rule under it says it can be
 * clicked. Colour alone is not enough here, because unlike a row of links
 * there is nothing else to say which words these are.
 */
.product-hero p a,
.product-section p a,
.product-download p a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid rgb(var(--fg-rgb) / .3);
  transition: color .2s, border-color .2s;
}

.product-hero p a:hover,
.product-section p a:hover,
.product-download p a:hover { color: var(--fg); border-bottom-color: var(--fg); }

.product-hero p a:focus-visible,
.product-section p a:focus-visible,
.product-download p a:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/*
 * Copy buttons on terminal blocks – assets/product/copy.js.
 *
 * The wrapper is a positioning context and nothing else, and it has to exist
 * because .term scrolls sideways: a button placed inside the block would slide
 * off with the command on a narrow screen. Which means the wrapper takes over
 * the block's sizing and .term fills it – including the cap, or a block sized
 * to its content escapes the column it sits in.
 *
 * All of it is built by script, so with no script, or no clipboard to write
 * to, there is no button and the text is still there to select.
 */
.term-wrap { position: relative; max-width: 100%; }

/* Width, because the wrapper is now the sized box. Padding, so a long command
   scrolls under its own right edge rather than under the label. */
.term-wrap > .term {
  width: 100%;
  padding-right: 5rem;
  cursor: pointer;
  transition: border-color .2s;
}

.term-wrap > .term:hover { border-color: rgb(var(--fg-rgb) / .22); }
.term-wrap > .term:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/*
 * The label, which is not a control.
 *
 * The block itself is the control now, so this only has to say so – which
 * means it can be a pseudo-element and can sit on the wrapper, where it does
 * not scroll sideways with the command the way anything inside .term would.
 * pointer-events: none so it never eats the click it is advertising.
 *
 * Hidden at rest, because a block that is entirely a button does not need a
 * button drawn on it; shown on hover and on focus, so the affordance is there
 * for a pointer and for a keyboard. On touch there is no hover and no need:
 * the tap copies, and the label appears to say it did.
 */
.term-wrap::after {
  content: "Copy";
  position: absolute;
  /* The block's own padding, less this label's, so it lands on the first
     line while the block's hit area stays the whole block. */
  top: calc(1.1rem - .5rem);
  right: calc(1.25rem - .5rem);
  padding: .5rem;
  /* The width of the longest word it can say, so nothing reflows. */
  min-width: 3.7rem;
  text-align: right;
  font: 500 .62rem/calc(.82rem * 1.75) var(--mono);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
  opacity: 0;
  transition: opacity .2s, color .2s;
  pointer-events: none;
}

.term-wrap:hover::after,
.term-wrap:focus-within::after { opacity: 1; }

/* The confirmation, in the product's own colour, and the one state that
   shows itself without being asked. */
.term-wrap[data-copied]::after {
  content: "Copied";
  opacity: 1;
  color: rgb(var(--product-rgb));
}

/* Refused: the script has selected the command, so the label says what to do
   with it rather than simply reporting failure. */
.term-wrap[data-failed]::after { content: "Press \2318 C"; opacity: 1; }

/* Visually hidden, and the page's live region. Unselectable, so a copy of a
   block never picks its text up twice. */
.sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  user-select: none;
}

/*
 * Commands that resolve out of noise – assets/product/resolve.js.
 *
 * While a command is churning it is noise, so it is dimmed to the muted
 * color and comes up to full as the last characters land. The transition
 * sits on the resting state and is switched off in the resolving one, so it
 * runs in one direction only: both ways round, the dim faded in over half a
 * second while the line was already churning – a dip and a recovery, where
 * what is wanted is noise that arrives dim and brightens as it becomes a
 * command.
 *
 * The class is the script's, so with no script a command is simply the
 * command, at full strength, from the first paint.
 */
[data-scramble] { transition: color .55s var(--out); }
.is-resolving [data-scramble] { color: var(--muted); transition: none; }

/*
 * Source blocks – code to be read, not a command to be run.
 *
 * Built like .term, because both are pictures of text in a monospace face,
 * and deliberately not .term: copy.js turns every .term into a copy button,
 * and a click on a passage of a language nobody has installed should not put
 * it on their clipboard. Here rather than on one page because /netsuite and
 * /mynt both show Mynt, and two pages should not disagree about how it looks.
 *
 * The colouring is four words and no more. Keywords take the product's
 * colour, types the ink, and strings and comments step back – enough to read
 * the shape of a line at a glance without turning the block into a palette.
 */
.code {
  margin: 0;
  padding: 1.1rem 1.25rem;
  border: 1px solid var(--line);
  border-radius: .75rem;
  background: rgb(0 0 0 / .32);
  overflow-x: auto;
  max-width: 100%;
  font: 500 .82rem/1.75 var(--mono);
  font-variant-ligatures: none;
  font-feature-settings: "liga" 0, "calt" 0;
  color: var(--copy);
  white-space: pre;
  tab-size: 4;
  -webkit-text-size-adjust: 100%;
}

.code .k { color: var(--product-accent); }
.code .t { color: var(--fg); }
.code .s { color: rgb(var(--fg-rgb) / .58); }
.code .c { color: var(--muted); }

/* ── motion ── */

/* Sections arrive as they are reached. Only when script is running: without
   it nothing is ever hidden, so the page is complete as plain HTML. */
.js [data-reveal] {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 1s var(--out), transform 1s var(--out);
}

.js [data-reveal].is-visible { opacity: 1; transform: none; }

@keyframes fade { from { opacity: 0; } }
@keyframes rise { from { transform: translateY(130%); } }
@keyframes rise-in { from { opacity: 0; transform: translateY(12px); } }
@keyframes icon-in { from { opacity: 0; transform: translateY(10px) scale(.96); } }
@keyframes level-in { from { opacity: 0; transform: translateX(10px); } }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-delay: 0s !important;
    transition-duration: 1ms !important;
    transition-delay: 0s !important;
  }

  .js [data-reveal] { opacity: 1; transform: none; }
}
