/* codebldr.com, second life.
   The 2016 template it grew out of is gone: no Bootstrap grid, no jQuery, no Font Awesome,
   no masonry. What is left is one stylesheet and one small script.

   The palette is the one already in the logo — the orange is #F79C14, taken off logo.png —
   sat on ink instead of the old grey page. Space Grotesk carries the headings, JetBrains Mono
   the small labels, because a studio that writes software may as well look like it. */

:root {
  --ink: #0D0F13;
  --ink-2: #12151B;
  --ink-3: #171B23;
  --paper: #F3F4F6;
  --muted: rgba(243, 244, 246, 0.64);
  /* 0.52 rather than the 0.40 this started at: the mono labels are 12–13px, and at 0.40 they
     came out around 3.5:1 on the ink, which is under the 4.5:1 small text has to clear. */
  --faint: rgba(243, 244, 246, 0.52);
  --line: rgba(243, 244, 246, 0.10);
  --line-soft: rgba(243, 244, 246, 0.06);
  --glass: rgba(243, 244, 246, 0.04);

  --orange: #F79C14;
  --orange-hi: #FFB53F;
  --orange-dim: #C97D0C;
  --on-orange: #17130A;

  --page: 1160px;
  --r-sm: 10px;
  --r-md: 16px;
  --r-lg: 24px;
  --r-full: 999px;

  --space-1: 8px;
  --space-2: 16px;
  --space-3: 24px;
  --space-4: 40px;
  --space-5: 64px;
  --space-6: 96px;

  --sans: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  --display: "Space Grotesk", var(--sans);
  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
}

* { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* The bar is sticky, so an anchor that lands flush with the top lands under it. */
  scroll-padding-top: 88px;
}

body {
  margin: 0;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--display);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.025em;
}

p, ul, ol { margin: 0; }
ul.plain { list-style: none; padding: 0; }

/* height:auto is not optional: the width/height attributes on an img are a presentational
   hint that sets a real height, and an image being narrowed by max-width would otherwise
   keep its attribute height and stretch. */
img { max-width: 100%; height: auto; display: block; }

a { color: inherit; }

:focus-visible {
  outline: 2px solid var(--orange);
  outline-offset: 3px;
  border-radius: 4px;
}

::selection { background: var(--orange); color: var(--on-orange); }

.wrap {
  max-width: var(--page);
  margin: 0 auto;
  padding-inline: var(--space-3);
}

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 60;
  padding: 12px 18px;
  background: var(--orange);
  color: var(--on-orange);
  font-weight: 600;
  border-radius: 0 0 var(--r-sm) 0;
}
.skip:focus { left: 0; }

.eyebrow {
  font-family: var(--mono);
  font-size: 0.78rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--orange);
}

/* ---------- nav ---------- */

.nav {
  position: sticky;
  top: 0;
  z-index: 30;
  border-bottom: 1px solid transparent;
  transition: background 200ms ease, border-color 200ms ease;
}

.nav.is-stuck {
  background: rgba(13, 15, 19, 0.82);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom-color: var(--line);
}

.nav-inner {
  position: relative;
  max-width: var(--page);
  margin: 0 auto;
  padding: 14px var(--space-3);
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

/* The helmet and the word are two files cut out of the supplied lockup, which is why the gap
   between them is set here instead of being baked into a picture. In the artwork it comes to
   about 12px at this size, and that reads tight.

   38px is a compromise between two things pulling opposite ways: under about 36 the </> inside
   the helmet closes up, and over about 40 the logo starts shouting over the nav links beside
   it, which sit at 15px. Both halves keep the artwork's full height, so cutting them apart
   cannot shift one against the other. */
.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 18px;
  margin-right: auto;
  text-decoration: none;
}

.wordmark img { height: 38px; width: auto; }

.nav-links {
  display: flex;
  gap: var(--space-3);
  font-size: 0.94rem;
}

.nav-links a {
  color: var(--muted);
  text-decoration: none;
  padding: 6px 0;
  transition: color 160ms ease;
}

.nav-links a:hover { color: var(--paper); }

/* ---------- buttons ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 48px;
  padding: 12px 22px;
  border: 1px solid transparent;
  border-radius: var(--r-full);
  font: inherit;
  font-weight: 600;
  font-size: 0.98rem;
  text-decoration: none;
  cursor: pointer;
  transition: background 180ms ease, border-color 180ms ease, color 180ms ease, transform 180ms ease;
}

.btn:active { transform: translateY(1px); }

.btn-primary { background: var(--orange); color: var(--on-orange); }
.btn-primary:hover { background: var(--orange-hi); }

.btn-ghost { border-color: var(--line); color: var(--paper); background: var(--glass); }
.btn-ghost:hover { border-color: rgba(243, 244, 246, 0.28); background: rgba(243, 244, 246, 0.08); }

.btn svg { width: 18px; height: 18px; flex: none; }

.nav .btn { min-height: 42px; padding: 8px 18px; }

/* ---------- menu button and drawer ---------- */

.nav-toggle {
  display: none;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--glass);
  color: var(--paper);
  cursor: pointer;
}

.nav-toggle svg { width: 22px; height: 22px; margin: auto; }
.nav-toggle .cross { display: none; }
.nav.is-open .nav-toggle .bars { display: none; }
.nav.is-open .nav-toggle .cross { display: block; }

@media (max-width: 900px) {
  .nav-toggle { display: flex; }
  .nav > .nav-inner > .btn { display: none; }

  .nav-links {
    position: absolute;
    top: calc(100% + 8px);
    right: var(--space-3);
    left: var(--space-3);
    flex-direction: column;
    gap: 0;
    padding: var(--space-1);
    background: var(--ink-2);
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5);
    display: none;
  }

  .nav.is-open .nav-links { display: flex; }

  .nav-links a {
    padding: 13px 14px;
    border-radius: var(--r-sm);
    color: var(--paper);
  }

  .nav-links a:hover { background: var(--glass); }
}

/* ---------- hero ---------- */

.hero {
  position: relative;
  padding: clamp(72px, 12vw, 140px) 0 var(--space-5);
  text-align: center;
  overflow: hidden;
}

/* Two layers, both decorative and both out of the accessibility tree: a faint grid that gives
   the ink some texture, and a warm bloom behind the heading. */
.hero::before,
.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.hero::before {
  background-image:
    linear-gradient(var(--line-soft) 1px, transparent 1px),
    linear-gradient(90deg, var(--line-soft) 1px, transparent 1px);
  background-size: 64px 64px;
  mask-image: radial-gradient(ellipse 80% 60% at 50% 30%, #000 20%, transparent 75%);
  -webkit-mask-image: radial-gradient(ellipse 80% 60% at 50% 30%, #000 20%, transparent 75%);
}

.hero::after {
  background: radial-gradient(ellipse 46% 38% at 50% 8%, rgba(247, 156, 20, 0.18), transparent 70%);
}

.hero .wrap { position: relative; z-index: 1; }

/* A project page's hero carries a name rather than an argument, so it is shorter and its
   heading does not need the width the home page's does. */
.hero-project { padding-bottom: var(--space-4); }
.hero-project h1 { max-width: 20ch; font-size: clamp(2.4rem, 6vw, 3.8rem); }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 7px 16px 7px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-full);
  background: var(--glass);
  font-family: var(--mono);
  font-size: 0.76rem;
  letter-spacing: 0.06em;
  color: var(--muted);
}

.chip .dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--orange);
  box-shadow: 0 0 0 4px rgba(247, 156, 20, 0.16);
}

.hero h1 {
  margin: var(--space-3) auto var(--space-2);
  max-width: 15ch;
  font-size: clamp(2.5rem, 7vw, 4.4rem);
  font-weight: 700;
}

.hero h1 em { font-style: normal; color: var(--orange); }

.lede {
  margin: 0 auto;
  max-width: 60ch;
  font-size: clamp(1.05rem, 2.2vw, 1.22rem);
  color: var(--muted);
}

.cta-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2);
  margin-top: var(--space-4);
}

/* The strip of things we actually work with. Wraps rather than scrolls: a marquee that a
   phone cannot stop is a worse answer than two tidy lines. */
.stack-strip {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px var(--space-2);
  margin-top: var(--space-5);
  padding-top: var(--space-3);
  border-top: 1px solid var(--line-soft);
  font-family: var(--mono);
  font-size: 0.8rem;
  color: var(--faint);
}

.stack-strip span { white-space: nowrap; }

/* ---------- section furniture ---------- */

section { position: relative; }

.section {
  padding: clamp(64px, 9vw, 104px) 0;
  border-top: 1px solid var(--line-soft);
}

.section-head { max-width: 62ch; margin-bottom: var(--space-5); }

.section-head h2 {
  margin: var(--space-2) 0 var(--space-2);
  font-size: clamp(1.9rem, 4vw, 2.7rem);
}

.section-head p { color: var(--muted); }

/* ---------- services ---------- */

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--space-3);
}

.card {
  padding: var(--space-4) var(--space-3) var(--space-3);
  background: linear-gradient(180deg, var(--ink-2), var(--ink-2) 60%, rgba(18, 21, 27, 0.4));
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  transition: border-color 200ms ease, transform 200ms ease;
}

.card:hover { border-color: rgba(247, 156, 20, 0.4); transform: translateY(-3px); }

.card .icon {
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  margin-bottom: var(--space-3);
  border-radius: 12px;
  background: rgba(247, 156, 20, 0.12);
  color: var(--orange);
}

.card .icon svg { width: 22px; height: 22px; }

.card h3 { font-size: 1.16rem; margin-bottom: 10px; }
.card p { color: var(--muted); font-size: 0.97rem; }

/* The feature lists on the CodeNews page. A dash rather than a bullet, because a tick would
   promise a comparison table that does not exist. */
.feature-list {
  list-style: none;
  padding: 0;
  display: grid;
  gap: 8px;
  color: var(--muted);
  font-size: 0.95rem;
}

.feature-list li { padding-left: 18px; position: relative; }

.feature-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.72em;
  width: 9px;
  height: 1px;
  background: var(--orange);
}

/* ---------- work ---------- */

.project {
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  gap: clamp(var(--space-4), 6vw, var(--space-6));
  align-items: center;
  padding: var(--space-5) 0;
  border-top: 1px dashed var(--line);
}

.project:first-of-type { border-top: 0; padding-top: 0; }
.project.flip .project-media { order: -1; }

.project h3 {
  margin: var(--space-2) 0 var(--space-2);
  font-size: clamp(1.6rem, 3.4vw, 2.1rem);
}

.project p { color: var(--muted); }
.project p + p { margin-top: var(--space-2); }

.tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: var(--space-3) 0;
  list-style: none;
  padding: 0;
}

.tags li {
  padding: 5px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-full);
  font-family: var(--mono);
  font-size: 0.74rem;
  color: var(--faint);
}

.project-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

.project-links.centered { justify-content: center; margin-top: var(--space-5); }

/* A phone-shaped frame around a real screenshot. Fixed aspect so the space is reserved before
   the image lands and nothing below it jumps. */
.phones {
  display: flex;
  justify-content: center;
  gap: var(--space-3);
}

.phone {
  width: min(232px, 42vw);
  padding: 9px;
  background: #05070A;
  border: 1px solid rgba(243, 244, 246, 0.16);
  border-radius: 32px;
  box-shadow: 0 30px 70px rgba(0, 0, 0, 0.55);
}

.phone img {
  border-radius: 24px;
  aspect-ratio: 1264 / 2660;
  object-fit: cover;
  background: var(--ink-3);
}

.phone:nth-child(2) { margin-top: var(--space-4); }

/* A browser-shaped frame, same idea, for anything that is a site rather than an app. */
.browser {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--ink-2);
  box-shadow: 0 30px 70px rgba(0, 0, 0, 0.45);
}

.browser-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 11px 14px;
  border-bottom: 1px solid var(--line);
  background: var(--ink-3);
}

.browser-bar i {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: rgba(243, 244, 246, 0.18);
}

.browser-bar .url {
  margin-left: 10px;
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--faint);
  font-style: normal;
}

.browser img { aspect-ratio: 16 / 10; object-fit: cover; object-position: top center; }

/* A phone-width capture inside the same browser frame: taller than it is wide, and it should
   not be cropped to a letterbox the way a desktop shot is. */
.browser img.shot-tall {
  aspect-ratio: 560 / 1100;
  max-height: 620px;
  object-fit: contain;
  margin: 0 auto;
  background: #fff;
}

.media-note {
  margin-top: var(--space-2);
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--faint);
  text-align: center;
}

/* ---------- process ---------- */

.steps {
  counter-reset: step;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: var(--space-3);
  list-style: none;
  padding: 0;
}

.steps li {
  position: relative;
  padding: var(--space-4) var(--space-3) var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--ink-2);
}

.steps li::before {
  counter-increment: step;
  content: "0" counter(step);
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  font-family: var(--mono);
  font-size: 0.82rem;
  color: var(--orange);
}

.steps h3 { font-size: 1.1rem; margin-bottom: 8px; }
.steps p { color: var(--muted); font-size: 0.96rem; }

/* ---------- contact ---------- */

.contact { background: var(--ink-2); border-top: 1px solid var(--line); }

.contact h2 {
  margin: var(--space-2) 0;
  font-size: clamp(1.9rem, 4vw, 2.7rem);
}

.contact-lede { color: var(--muted); max-width: 52ch; }

.contact-grid {
  display: grid;
  grid-template-columns: 1.15fr 0.85fr;
  gap: clamp(var(--space-4), 6vw, var(--space-6));
  align-items: start;
}

.form { margin-top: var(--space-4); }

.field { margin-bottom: var(--space-3); }

.field label {
  display: block;
  margin-bottom: 8px;
  font-size: 0.86rem;
  font-weight: 500;
  color: var(--muted);
}

.field input,
.field textarea {
  width: 100%;
  min-height: 50px;
  padding: 13px 16px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--ink);
  color: var(--paper);
  font: inherit;
  font-size: 1rem;
  transition: border-color 160ms ease, background 160ms ease;
}

.field textarea { min-height: 148px; resize: vertical; }

.field input::placeholder,
.field textarea::placeholder { color: rgba(243, 244, 246, 0.5); }

.field input:hover,
.field textarea:hover { border-color: rgba(243, 244, 246, 0.2); }

.field input:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--orange);
  background: rgba(247, 156, 20, 0.04);
}

.field-note { margin-top: var(--space-2); font-size: 0.84rem; color: var(--faint); }

/* The form's own message, printed above the fields. Colour is never the only signal: each
   one carries an icon and words. */
.notice {
  display: flex;
  gap: 12px;
  padding: 14px 16px;
  margin-bottom: var(--space-3);
  border-radius: var(--r-sm);
  border: 1px solid;
  font-size: 0.95rem;
}

.notice svg { width: 20px; height: 20px; flex: none; margin-top: 2px; }
.notice ul { padding-left: 18px; }

.notice-ok { border-color: rgba(52, 199, 129, 0.45); background: rgba(52, 199, 129, 0.09); color: #8FE7C0; }
.notice-bad { border-color: rgba(239, 68, 68, 0.45); background: rgba(239, 68, 68, 0.09); color: #FCA5A5; }

.contact-aside {
  padding: var(--space-4) var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--ink);
}

.contact-aside h3 { font-size: 1.05rem; margin-bottom: var(--space-2); }
.contact-aside h3 + p { color: var(--muted); font-size: 0.96rem; }
.contact-aside .block + .block { margin-top: var(--space-4); }

.detail-list { list-style: none; padding: 0; display: grid; gap: 12px; }

.detail-list li {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 0.96rem;
  color: var(--muted);
}

.detail-list svg { width: 18px; height: 18px; color: var(--orange); flex: none; }
.detail-list a { color: var(--paper); text-decoration: none; border-bottom: 1px solid var(--line); }
.detail-list a:hover { border-bottom-color: var(--orange); }

/* ---------- footer ---------- */

.site-footer {
  padding: var(--space-5) 0 var(--space-4);
  border-top: 1px solid var(--line);
  font-size: 0.94rem;
}

.footer-top {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-4);
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--line-soft);
}

.footer-brand { max-width: 38ch; }
.footer-brand p { margin-top: var(--space-2); color: var(--muted); }

.footer-links { display: grid; gap: var(--space-3); justify-items: start; }

.wordmark.sm { gap: 14px; }
.wordmark.sm img { height: 32px; }

.footer-nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  color: var(--muted);
}

.footer-nav a { text-decoration: none; }
.footer-nav a:hover { color: var(--paper); }

.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding-top: var(--space-3);
  color: var(--faint);
}

.socials { display: flex; gap: 10px; }

.socials a {
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  color: var(--muted);
  transition: color 160ms ease, border-color 160ms ease;
}

.socials a:hover { color: var(--orange); border-color: rgba(247, 156, 20, 0.45); }
.socials svg { width: 18px; height: 18px; }

/* The other language. Its own line rather than a flag: a flag is a country, not a language. */
.lang-switch {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--muted);
  text-decoration: none;
  padding: 6px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-full);
}

.lang-switch:hover { color: var(--paper); border-color: rgba(243, 244, 246, 0.28); }
.lang-switch svg { width: 15px; height: 15px; }

/* ---------- documents ---------- */

/* The privacy page and anything else that is mostly words. Narrow measure, because a line of
   text stops being readable somewhere past 75 characters. */
.prose { max-width: 68ch; }
.prose h2 { font-size: 1.4rem; margin: var(--space-5) 0 var(--space-2); }
.prose h2:first-child { margin-top: 0; }
.prose p { color: var(--muted); }
.prose p + p { margin-top: var(--space-2); }
.prose a { color: var(--orange); }

/* ---------- cookie bar ---------- */

/* A strip along the bottom rather than a sheet over the middle: it asks one question and has no
   business standing between somebody and what they came to read. */
.consent {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  background: rgba(18, 21, 27, 0.94);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-top: 1px solid var(--line);
}

.consent-inner {
  max-width: var(--page);
  margin: 0 auto;
  padding: 14px var(--space-3);
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.consent-copy {
  font-size: 0.88rem;
  color: var(--muted);
  margin-right: auto;
}

.consent-copy a { color: var(--orange); margin-left: 6px; }

.consent-buttons { display: flex; gap: 10px; flex: none; }

/* Both answers are the same size and sit side by side. A refusal that is harder to find than an
   acceptance is not a refusal anybody has been offered. */
.consent-buttons .btn { min-height: 40px; padding: 8px 18px; font-size: 0.9rem; }

.footer-small {
  display: inline;
  margin-left: 14px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.footer-small:hover { color: var(--paper); }

@media (max-width: 700px) {
  .consent-inner { flex-direction: column; align-items: stretch; gap: var(--space-2); }
  .consent-buttons .btn { flex: 1; }
}

/* ---------- responsive ---------- */

@media (max-width: 900px) {
  .project,
  .contact-grid {
    grid-template-columns: 1fr;
  }

  .project.flip .project-media { order: 0; }
  .phone:nth-child(2) { margin-top: 0; }
}

@media (max-width: 560px) {
  body { font-size: 16px; }
  /* The logo is wide for its height, and on a phone it shares the bar with the menu button.
     32 is as low as it goes before the helmet's detail closes up. */
  .wordmark { gap: 14px; }
  .wordmark img { height: 32px; }
  .phones { gap: var(--space-2); }
  .footer-top { flex-direction: column; }
}

/* ---------- motion ---------- */

/* Set by the script on anything it is going to bring in, so the page as delivered has nothing
   hidden in it: with JavaScript off, or with the observer unsupported, every section is
   already in its final state. */
.js .reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 620ms cubic-bezier(0.22, 1, 0.36, 1) var(--reveal-delay, 0ms),
              transform 620ms cubic-bezier(0.22, 1, 0.36, 1) var(--reveal-delay, 0ms);
}

.js .reveal.is-in { opacity: 1; transform: none; }

/* The hero animates from the stylesheet rather than the observer: waiting for the script left
   the first painted frame empty. */
@media (prefers-reduced-motion: no-preference) {
  .hero .chip,
  .hero h1,
  .hero .lede,
  .hero .cta-row,
  .hero .stack-strip {
    animation: rise 700ms cubic-bezier(0.22, 1, 0.36, 1) backwards;
  }

  .hero h1 { animation-delay: 70ms; }
  .hero .lede { animation-delay: 140ms; }
  .hero .cta-row { animation-delay: 210ms; }
  .hero .stack-strip { animation-delay: 280ms; }
}

@keyframes rise {
  from { opacity: 0; transform: translateY(20px); }
  to { opacity: 1; transform: none; }
}

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

  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  .card:hover { transform: none; }
}
