/* rishabh-792.github.io — hand-written, no framework, no build step.
   See docs/design.md.

   ── The one rule that keeps this consistent ──
   Every margin, padding, gap, and radius in this file comes from a token in
   the scale below. There are no ad-hoc length values in any spacing property.
   If a value is not on the scale, the scale is wrong, not the exception. */

@layer reset, tokens, base, layout, components, motion;

/* ═══════════════════════════════ reset ═══════════════════════════════ */

@layer reset {
  *, *::before, *::after { box-sizing: border-box; }

  html { -webkit-text-size-adjust: 100%; }

  body, h1, h2, h3, h4, p, ul, ol, li, dl, dd, dt, figure, blockquote {
    margin: 0;
    padding: 0;
  }

  ul, ol { list-style: none; }

  img, svg { display: block; max-width: 100%; }

  a { color: inherit; }

  button, input, select, textarea { font: inherit; }

  summary { cursor: pointer; }
}

/* ══════════════════════════════ tokens ══════════════════════════════ */

@layer tokens {
  :root {
    /* Spacing — a 4px base. The only source of spacing values. */
    --s-1: 0.25rem;   /*  4 */
    --s-2: 0.5rem;    /*  8 */
    --s-3: 0.75rem;   /* 12 */
    --s-4: 1rem;      /* 16 */
    --s-5: 1.5rem;    /* 24 */
    --s-6: 2rem;      /* 32 */
    --s-7: 3rem;      /* 48 */
    --s-8: 4rem;      /* 64 */
    --s-9: 6rem;      /* 96 */

    /* Type — size paired with its line-height, always used together. */
    --t-2xs: 0.6875rem;
    --t-xs:  0.75rem;
    --t-sm:  0.8125rem;
    --t-base:0.9375rem;
    --t-md:  1.0625rem;
    --t-lg:  1.25rem;
    --t-xl:  1.5rem;
    --t-2xl: 2rem;
    --t-3xl: clamp(2.5rem, 1.5rem + 4.5vw, 4.25rem);

    --lh-tight: 1.15;
    --lh-snug:  1.35;
    --lh-body:  1.6;
    --lh-loose: 1.7;

    /* Radii */
    --r-1: 3px;
    --r-2: 8px;
    --r-3: 14px;

    /* Measure and container. The container is wide so the page fills a
       desktop window; --measure keeps prose to a readable line length
       independently, so widening one does not hurt the other. */
    --wrap: 82rem;
    --measure: 62ch;
    --section-y: var(--s-9);

    /* Fonts */
    --f-display: "Bricolage Grotesque", system-ui, -apple-system, sans-serif;
    --f-text: "Bricolage Grotesque", system-ui, -apple-system, sans-serif;
    --f-serif: "Newsreader", ui-serif, Georgia, serif;
    --f-mono: "Commit Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

    /* Colour — light. One accent, with exactly one meaning: --accent marks
       something verified. It is never decorative, and never the sole carrier
       of meaning — every accented element is also labelled in words. */
    color-scheme: light dark;
    --paper:       oklch(98%   0.003 250);
    --surface:     oklch(100%  0     0);
    --surface-alt: oklch(96.2% 0.004 250);
    --ink:         oklch(22%   0.012 255);
    --ink-2:       oklch(44%   0.012 255);
    --ink-3:       oklch(51%   0.010 255);
    --rule:        oklch(90%   0.005 255);
    --rule-strong: oklch(82%   0.006 255);
    --accent:      oklch(46%   0.11  178);
    --accent-soft: oklch(95.5% 0.028 178);
    --shadow: 0 1px 2px oklch(22% 0.012 255 / 0.05),
              0 8px 24px oklch(22% 0.012 255 / 0.04);
  }

  @media (prefers-color-scheme: dark) {
    :root {
      --paper:       oklch(15%  0.010 255);
      --surface:     oklch(19%  0.011 255);
      --surface-alt: oklch(12.5% 0.010 255);
      --ink:         oklch(94%  0.004 255);
      --ink-2:       oklch(73%  0.008 255);
      --ink-3:       oklch(61%  0.008 255);
      --rule:        oklch(28%  0.010 255);
      --rule-strong: oklch(36%  0.010 255);
      --accent:      oklch(80%  0.12  178);
      --accent-soft: oklch(26%  0.045 178);
      --shadow: none;
    }
  }

  @media (width < 48rem) {
    :root { --section-y: var(--s-7); }
  }
}

/* ═══════════════════════════════ base ═══════════════════════════════ */

@layer base {
  body {
    font-family: var(--f-text);
    font-size: var(--t-base);
    line-height: var(--lh-body);
    font-weight: 400;
    color: var(--ink);
    background: var(--paper);
    text-wrap: pretty;
  }

  h1, h2, h3, h4 {
    font-family: var(--f-display);
    font-weight: 600;
    letter-spacing: -0.018em;
    line-height: var(--lh-tight);
    text-wrap: balance;
    color: var(--ink);
  }

  code {
    font-family: var(--f-mono);
    font-size: 0.92em;
    background: var(--surface-alt);
    border: 1px solid var(--rule);
    border-radius: var(--r-1);
    padding: 0 var(--s-1);
  }

  a { text-decoration-thickness: 1px; text-underline-offset: 0.16em; }

  :focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: var(--r-1);
  }

  .skip {
    position: absolute;
    inset-inline-start: -100vw;
    top: 0;
    z-index: 20;

    &:focus {
      position: fixed;
      inset-inline-start: var(--s-4);
      top: var(--s-4);
      padding: var(--s-2) var(--s-4);
      background: var(--surface);
      border: 1px solid var(--ink);
      border-radius: var(--r-1);
      text-decoration: none;
    }
  }
}

/* ══════════════════════════════ layout ══════════════════════════════ */

@layer layout {
  .wrap {
    width: 100%;
    max-width: var(--wrap);
    margin-inline: auto;
    padding-inline: var(--s-5);
  }

  @media (width < 48rem) {
    .wrap { padding-inline: var(--s-4); }
  }

  .section {
    padding-block: var(--section-y);
    border-block-start: 1px solid var(--rule);
  }

  /* The nav is sticky, so anything jumped to needs to clear it or the
     heading lands underneath. Covers the nav height plus a breath. */
  :target,
  [id] { scroll-margin-block-start: var(--s-8); }

  .section--alt { background: var(--surface-alt); }

  .section__head {
    display: flex;
    flex-direction: column;
    gap: var(--s-3);
    margin-block-end: var(--s-7);
    max-width: var(--measure);
  }

  .section__title {
    font-size: var(--t-2xl);
  }

  .section__note {
    color: var(--ink-2);
    font-size: var(--t-md);
    line-height: var(--lh-loose);
  }

  /* Exactly four tracks: education (1) + certifications (2) + honors (1).
     auto-fit produced five at this container width and left one empty, which
     pushed honors away from the group. */
  .cols {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--s-7) var(--s-6);
  }

  .col--wide { grid-column: span 2; }

  @media (width < 60rem) {
    .cols { grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr)); }
    .col--wide { grid-column: auto; }
  }
}

/* ════════════════════════════ components ════════════════════════════ */

@layer components {

  /* ── shared bits ─────────────────────────────────────────────────── */

  .eyebrow {
    font-family: var(--f-mono);
    font-size: var(--t-xs);
    line-height: var(--lh-snug);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--accent);
  }

  .col__title {
    font-family: var(--f-mono);
    font-size: var(--t-xs);
    line-height: var(--lh-snug);
    font-weight: 400;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ink-3);
    margin-block-end: var(--s-4);
  }

  .btn {
    display: inline-flex;
    align-items: center;
    gap: var(--s-2);
    padding: var(--s-3) var(--s-5);
    border: 1px solid var(--rule-strong);
    border-radius: var(--r-2);
    background: var(--surface);
    color: var(--ink);
    font-size: var(--t-base);
    font-weight: 500;
    text-decoration: none;
    transition: border-color 120ms ease, background 120ms ease;

    &:hover { border-color: var(--ink-3); }
  }

  .btn--solid {
    background: var(--ink);
    border-color: var(--ink);
    color: var(--paper);

    &:hover { background: var(--ink-2); border-color: var(--ink-2); }
  }

  .link-out {
    font-family: var(--f-mono);
    font-size: var(--t-sm);
    color: var(--accent);
    text-decoration: none;
    white-space: nowrap;

    &::after { content: " \2192"; }
    &:hover { text-decoration: underline; }
  }

  .chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-2);

    li {
      font-family: var(--f-mono);
      font-size: var(--t-2xs);
      line-height: var(--lh-snug);
      letter-spacing: 0.02em;
      color: var(--ink-2);
      background: var(--surface-alt);
      border: 1px solid var(--rule);
      border-radius: var(--r-1);
      padding: var(--s-1) var(--s-2);
    }
  }

  .chips--lg li {
    font-size: var(--t-xs);
    padding: var(--s-2) var(--s-3);
  }

  .tag {
    font-family: var(--f-mono);
    font-size: var(--t-2xs);
    letter-spacing: 0.02em;
    color: var(--ink-3);
    border: 1px solid var(--rule);
    border-radius: var(--r-1);
    padding: var(--s-1) var(--s-2);
  }

  .bullets {
    display: flex;
    flex-direction: column;
    gap: var(--s-2);
    max-width: var(--measure);

    li {
      position: relative;
      padding-inline-start: var(--s-5);
      color: var(--ink-2);
      line-height: var(--lh-body);

      &::before {
        content: "";
        position: absolute;
        inset-inline-start: var(--s-2);
        top: 0.6em;
        width: var(--s-2);
        height: 1px;
        background: var(--rule-strong);
      }
    }
  }

  /* ── nav ─────────────────────────────────────────────────────────── */

  .nav {
    position: sticky;
    top: 0;
    z-index: 10;
    background: color-mix(in oklab, var(--paper) 85%, transparent);
    backdrop-filter: blur(10px);
    border-block-end: 1px solid var(--rule);
  }

  .nav__inner {
    width: 100%;
    max-width: var(--wrap);
    margin-inline: auto;
    padding: var(--s-3) var(--s-5);
    display: flex;
    align-items: center;
    gap: var(--s-5);
  }

  .nav__mark {
    display: flex;
    align-items: center;
    gap: var(--s-3);
    text-decoration: none;
    margin-inline-end: auto;
  }

  .nav__initials {
    display: grid;
    place-items: center;
    width: var(--s-6);
    height: var(--s-6);
    border-radius: var(--r-1);
    background: var(--ink);
    color: var(--paper);
    font-family: var(--f-mono);
    font-size: var(--t-xs);
    letter-spacing: 0.02em;
  }

  .nav__name {
    font-family: var(--f-display);
    font-weight: 600;
    font-size: var(--t-base);
    letter-spacing: -0.01em;
  }

  .nav__links {
    display: flex;
    gap: var(--s-5);
    font-size: var(--t-base);

    a {
      color: var(--ink-2);
      text-decoration: none;
      &:hover { color: var(--ink); }
    }
  }

  .nav__cta { padding: var(--s-2) var(--s-4); }

  @media (width < 52rem) {
    .nav__links, .nav__name { display: none; }
    .nav__inner { padding-inline: var(--s-4); }
  }

  /* ── hero ────────────────────────────────────────────────────────── */

  .hero { padding-block: var(--s-9) var(--section-y); }

  .hero__grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--s-8);
    align-items: start;
    margin-block-end: var(--s-8);
  }

  .hero__body {
    display: flex;
    flex-direction: column;
    gap: var(--s-4);
  }

  .hero__name {
    font-size: var(--t-3xl);
    font-stretch: 92%;
    letter-spacing: -0.03em;
  }

  /* The tagline is the second thing read after the name, so it gets real
     size. Width is generous enough that it sets on one line on a desktop
     rather than breaking mid-thought; balance handles the narrow case. */
  .hero__thesis {
    font-family: var(--f-serif);
    font-size: var(--t-2xl);
    line-height: var(--lh-snug);
    letter-spacing: -0.01em;
    color: var(--ink);
    max-width: 46ch;
    text-wrap: balance;
  }

  /* Slightly wider than --measure: still a comfortable line length, but it
     closes the gap beside the portrait on a wide screen. */
  .hero__lede {
    font-size: var(--t-md);
    line-height: var(--lh-loose);
    color: var(--ink-2);
    max-width: 70ch;
  }

  .hero__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-3);
    margin-block-start: var(--s-2);
  }

  .hero__portrait img {
    width: 260px;
    height: 260px;
    border-radius: var(--r-3);
    border: 1px solid var(--rule);
    object-fit: cover;
  }

  @media (width < 48rem) {
    .hero { padding-block: var(--s-7) var(--section-y); }
    .hero__grid {
      grid-template-columns: minmax(0, 1fr);
      gap: var(--s-5);
      margin-block-end: var(--s-7);
    }
    .hero__portrait { order: -1; }
    .hero__portrait img { width: 104px; height: 104px; }
  }

  /* ── credibility strip ───────────────────────────────────────────── */

  .facts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
    gap: var(--s-4);
  }

  .fact {
    display: flex;
    flex-direction: column;
    gap: var(--s-1);
    padding: var(--s-4);
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: var(--r-2);
  }

  .fact__k {
    font-family: var(--f-mono);
    font-size: var(--t-2xs);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ink-3);
  }

  .fact__v {
    font-family: var(--f-display);
    font-weight: 600;
    font-size: var(--t-lg);
    line-height: var(--lh-tight);
    letter-spacing: -0.015em;
  }

  .fact__m {
    font-size: var(--t-xs);
    line-height: var(--lh-snug);
    color: var(--ink-2);
  }

  /* ── project card ────────────────────────────────────────────────── */

  .card {
    display: flex;
    flex-direction: column;
    gap: var(--s-5);
    padding: var(--s-6);
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: var(--r-3);
    box-shadow: var(--shadow);

    & + & { margin-block-start: var(--s-5); }
  }

  .card__head {
    display: flex;
    flex-wrap: wrap;
    align-items: start;
    justify-content: space-between;
    gap: var(--s-3);
  }

  .card__title {
    font-family: var(--f-mono);
    font-size: var(--t-lg);
    font-weight: 400;
    letter-spacing: -0.01em;
  }

  .card__domain {
    font-size: var(--t-sm);
    color: var(--ink-3);
    margin-block-start: var(--s-1);
  }

  .card__meta {
    display: flex;
    align-items: center;
    gap: var(--s-2);
  }

  .card__meta img { height: 20px; width: auto; }

  /* Description and numbers sit side by side on a wide card. Left alone, a
     62ch paragraph above full-width stat tiles left a large ragged gap on the
     right that read as unfinished. This also puts the figures next to the
     claim they support rather than under it. */
  .card__body {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 16rem;
    gap: var(--s-6);
    align-items: start;
  }

  .card__main {
    display: flex;
    flex-direction: column;
    gap: var(--s-5);
  }

  .card__hook {
    font-size: var(--t-md);
    line-height: var(--lh-loose);
    color: var(--ink-2);
    max-width: var(--measure);
  }

  @media (width < 56rem) {
    .card__body { grid-template-columns: minmax(0, 1fr); gap: var(--s-5); }
  }

  .card__foot {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-4);
    padding-block-start: var(--s-5);
    border-block-start: 1px solid var(--rule);
  }

  @media (width < 48rem) {
    .card { padding: var(--s-5); }
  }

  /* ── stat tiles ──────────────────────────────────────────────────────
     A single headline number each, so a tile is the right form and no
     chart is warranted. The values wear ink tokens, never the accent —
     the accent rule above each tile is the mark that carries the
     "verified" identity, so colour is never doing the reading's job. */

  /* Stacked inside a wide card body, side by side once it collapses. */
  .stats {
    display: grid;
    gap: var(--s-4);
  }

  @media (width < 56rem) {
    .stats { grid-template-columns: repeat(auto-fit, minmax(min(8.5rem, 100%), 1fr)); }
  }

  /* Stacked in the card's right column, an accent bar down the inline edge
     reads as a list of measurements. Rules across the top of each would read
     as table row separators instead. The orientation flips with the layout. */
  .stat {
    display: flex;
    flex-direction: column;
    gap: var(--s-1);
    padding-inline-start: var(--s-3);
    border-inline-start: 2px solid var(--accent);
  }

  @media (width < 56rem) {
    .stat {
      padding-inline-start: 0;
      padding-block-start: var(--s-3);
      border-inline-start: 0;
      border-block-start: 2px solid var(--accent);
    }
  }

  .stat__v {
    font-family: var(--f-mono);
    font-size: var(--t-xl);
    line-height: var(--lh-tight);
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.02em;
    color: var(--ink);
  }

  .stat__u {
    font-size: var(--t-base);
    color: var(--ink-3);
  }

  .stat__l {
    font-size: var(--t-xs);
    line-height: var(--lh-snug);
    color: var(--ink-2);
  }

  /* ── evidence rows ───────────────────────────────────────────────── */

  .evidence {
    display: flex;
    flex-direction: column;
    gap: var(--s-3);
  }

  .evidence__row {
    display: grid;
    grid-template-columns: 5.5rem minmax(0, 1fr);
    gap: var(--s-4);
    padding: var(--s-3) var(--s-4);
    border-radius: var(--r-2);
    border: 1px solid var(--rule);
    background: var(--surface-alt);
  }

  .evidence dt {
    font-family: var(--f-mono);
    font-size: var(--t-2xs);
    line-height: var(--lh-loose);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-3);
  }

  .evidence dd {
    font-size: var(--t-sm);
    line-height: var(--lh-body);
    color: var(--ink-2);
  }

  @media (width < 40rem) {
    .evidence__row { grid-template-columns: minmax(0, 1fr); gap: var(--s-1); }
  }

  /* ── roles ───────────────────────────────────────────────────────── */

  .roles {
    display: flex;
    flex-direction: column;
    gap: var(--s-7);
  }

  .role {
    display: grid;
    grid-template-columns: 11rem minmax(0, 1fr);
    gap: var(--s-6);
  }

  .role__when {
    display: flex;
    flex-direction: column;
    gap: var(--s-1);
    padding-block-start: var(--s-1);
  }

  .role__span {
    font-family: var(--f-mono);
    font-size: var(--t-sm);
    line-height: var(--lh-snug);
    font-variant-numeric: tabular-nums;
    color: var(--ink);
  }

  .role__dur {
    font-family: var(--f-mono);
    font-size: var(--t-2xs);
    line-height: var(--lh-snug);
    color: var(--ink-3);
  }

  .role__what {
    display: flex;
    flex-direction: column;
    gap: var(--s-3);
  }

  /* Title and org are one unit: they get their own tight gap rather than the
     row's, so no negative margin is needed to pull them together. */
  .role__id {
    display: flex;
    flex-direction: column;
    gap: var(--s-1);
  }

  .role__title { font-size: var(--t-lg); }

  .role__org {
    font-size: var(--t-base);
    line-height: var(--lh-snug);
    color: var(--ink-3);
  }

  .role__links {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-2) var(--s-5);
    font-family: var(--f-mono);
    font-size: var(--t-xs);

    a { color: var(--accent); text-decoration: none; }
    a:hover { text-decoration: underline; }
  }

  @media (width < 48rem) {
    .roles { gap: var(--s-6); }
    .role {
      grid-template-columns: minmax(0, 1fr);
      gap: var(--s-3);
    }
    .role__when { flex-direction: row; gap: var(--s-3); align-items: baseline; }
  }

  /* ── earlier roles ───────────────────────────────────────────────── */

  .more {
    margin-block-start: var(--s-7);
    padding-block-start: var(--s-5);
    border-block-start: 1px solid var(--rule);

    summary {
      font-family: var(--f-mono);
      font-size: var(--t-xs);
      letter-spacing: 0.06em;
      text-transform: uppercase;
      color: var(--ink-3);

      &:hover { color: var(--ink); }
    }
  }

  .minor {
    display: flex;
    flex-direction: column;
    gap: var(--s-2);
    margin-block-start: var(--s-4);
    font-size: var(--t-base);
    color: var(--ink-2);

    b { font-weight: 600; color: var(--ink); }
  }

  .minor__when {
    font-family: var(--f-mono);
    font-size: var(--t-2xs);
    color: var(--ink-3);
    margin-inline-start: var(--s-2);
  }

  /* ── listings ────────────────────────────────────────────────────── */

  .listing {
    display: flex;
    flex-direction: column;
    gap: var(--s-4);

    li {
      display: flex;
      flex-direction: column;
      gap: var(--s-1);
      padding-inline-start: var(--s-4);
      border-inline-start: 2px solid var(--rule);
    }
  }

  .listing__main {
    font-weight: 500;
    line-height: var(--lh-snug);
  }

  /* Certification titles link to their verification page. Every credential on
     this page can be checked, which is the same standard the project figures
     are held to. */
  a.listing__main {
    text-decoration-color: var(--rule-strong);

    &:hover { color: var(--accent); text-decoration-color: var(--accent); }
  }

  .listing__sub {
    font-size: var(--t-xs);
    line-height: var(--lh-snug);
    color: var(--ink-2);
  }

  .listing__when {
    font-family: var(--f-mono);
    font-size: var(--t-2xs);
    color: var(--ink-3);
  }

  .skills { margin-block-start: var(--s-8); }

  /* Five groups. A 14rem floor lands exactly five tracks at this container
     width, so they sit on one row instead of wrapping 4 + 1. */
  .skillset {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
    gap: var(--s-6) var(--s-5);
  }

  .skillgroup {
    display: flex;
    flex-direction: column;
    gap: var(--s-3);
  }

  .skillgroup__title {
    font-size: var(--t-base);
    line-height: var(--lh-snug);
    font-weight: 600;
    color: var(--ink);
  }

  /* ── closing CTA ─────────────────────────────────────────────────── */

  .section--cta { background: var(--surface-alt); }

  .cta {
    display: flex;
    flex-direction: column;
    gap: var(--s-4);
    align-items: start;
  }

  .cta__title {
    font-size: var(--t-2xl);
    max-width: 28ch;
  }

  .cta__note {
    font-size: var(--t-md);
    line-height: var(--lh-loose);
    color: var(--ink-2);
    max-width: var(--measure);
  }

  /* ── footer ──────────────────────────────────────────────────────── */

  .foot {
    padding-block: var(--s-6);
    border-block-start: 1px solid var(--rule);
  }

  .foot__inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-3);
    font-size: var(--t-xs);
    color: var(--ink-3);
  }
}

/* ══════════════════════════════ motion ══════════════════════════════
   One orchestrated moment: the project cards rise in sequence on load.
   Nothing else animates.

   Two things keep this from ever rendering a blank page, and both are
   load-bearing:
     1. The layer sits inside prefers-reduced-motion: no-preference, so a
        reduced-motion visitor never gets a hidden state at all.
     2. The hidden state keys off data-ready="pending", set by an inline
        blocking script. Nothing is hidden by default, so if that script
        never runs the content simply stays visible.

   An earlier version keyed off html:not([data-ready]), which left JS-off
   visitors with permanently invisible cards. That was a real bug caught in
   verification, not a hypothetical. */

@layer motion {
  @media (prefers-reduced-motion: no-preference) {
    html[data-ready="pending"] [data-reveal] { opacity: 0; }

    html[data-ready="true"] [data-reveal] {
      animation: rise 460ms cubic-bezier(0.2, 0, 0.2, 1) both;
      animation-delay: calc(var(--i, 0) * 80ms);
    }

    @keyframes rise {
      from { opacity: 0; transform: translateY(var(--s-2)); }
      to   { opacity: 1; transform: none; }
    }
  }
}
