/* HappyDance Books - happydancebooks.com
   Palette is happydance-brand-guidelines.md section 4 (source of truth).
   Interactive colours are contrast-checked: coral fails WCAG AA for normal-size
   text (3.02:1 on white), so coral is a display/accent colour and navy
   (5.73:1 with white) carries buttons and links. */

/* ---------- fonts ---------- */
@font-face {
  font-family: "Fredoka One";
  src: url("/fonts/fredoka-one-400.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Hanken Grotesk";
  src: url("/fonts/hanken-500.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Hanken Grotesk";
  src: url("/fonts/hanken-600.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Hanken Grotesk";
  src: url("/fonts/hanken-700.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
}

/* ---------- tokens ---------- */
:root {
  color-scheme: light;

  --ink: #1F1D1B;
  --paper: #FFFFFF;
  --muted: #6B645C;          /* 5.83:1 on paper, 5.32:1 on wash */
  --aqua-wash: #EAF7F9;
  --sea-band: #BFE8EF;
  --ocean: #3D9DBE;
  --whale-teal: #8FD4E0;
  --coral: #F2695C;
  --navy: #1F6E8C;
  --navy-deep: #185874;
  --sunshine: #FFD166;
  --sand: #F7E8C3;
  --go-green: #2FA84F;
  --rule: #D4E9EE;

  --font-display: "Fredoka One", "Trebuchet MS", system-ui, sans-serif;
  --font-body: "Hanken Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;

  --step--1: clamp(0.88rem, 0.85rem + 0.15vw, 0.95rem);
  --step-0:  clamp(1.05rem, 1rem + 0.25vw, 1.18rem);
  /* Reading scale, ~1.2-1.3 per step. These are the DEFAULT because most of the
     site is prose -- the research write-up, About, Privacy. A page title at
     2.2x body reads as a publisher; at 3.2x it reads as a billboard.
     Every size is reachable by a real component: if a step is only hit by
     accident, the scale is lying about the hierarchy. */
  --step-1:  clamp(1.2rem, 1.12rem + 0.38vw, 1.5rem);      /* card title, lede */
  --step-2:  clamp(1.3rem, 1.18rem + 0.55vw, 1.6875rem);   /* sub-headings     */
  --step-3:  clamp(1.55rem, 1.35rem + 0.95vw, 2rem);       /* section headings */
  --step-4:  clamp(1.9rem, 1.55rem + 1.6vw, 2.625rem);     /* page title       */

  /* The one deliberate exception. A landing hero is allowed to be loud; a
     reading page is not. Used only by .hero h1. */
  --step-display: clamp(2.25rem, 1.7rem + 2.6vw, 3.75rem);

  --gutter: clamp(1.15rem, 4vw, 2.5rem);
  --stack: clamp(3.75rem, 9vw, 7rem);
  --radius-sm: 14px;
  --radius: 22px;
  --radius-lg: 30px;

  /* Layered elevation. Depth reads as real when a tight contact shadow sits
     under a wide ambient one, rather than a single soft blur. */
  --shadow-1:
    0 1px 2px rgba(31, 29, 27, .04),
    0 3px 10px -4px rgba(31, 110, 140, .10);
  --shadow-2:
    0 1px 2px rgba(31, 29, 27, .05),
    0 8px 20px -8px rgba(31, 110, 140, .18),
    0 24px 48px -28px rgba(31, 110, 140, .30);
  --shadow-3:
    0 2px 4px rgba(31, 29, 27, .06),
    0 14px 32px -10px rgba(31, 110, 140, .24),
    0 40px 72px -36px rgba(31, 110, 140, .40);
  --shadow: var(--shadow-2);
  --shadow-lift: var(--shadow-3);

  --hairline: rgba(31, 110, 140, .14);
  --glass: rgba(255, 255, 255, .72);
  --ease-out: cubic-bezier(.22, .61, .36, 1);
  --measure: 66ch;
}

/* ---------- base ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-weight: 500;
  font-size: var(--step-0);
  line-height: 1.65;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4 { font-family: var(--font-display); font-weight: 400; line-height: 1.12; margin: 0 0 .5em; }
h1 { font-size: var(--step-4); letter-spacing: -.01em; color: var(--navy); }
h2 { font-size: var(--step-3); color: var(--navy); }
h3 { font-size: var(--step-2); }
h4 { font-size: var(--step-1); font-family: var(--font-body); font-weight: 700; line-height: 1.3; }
p { margin: 0 0 1.15em; }
p:last-child { margin-bottom: 0; }

a { color: var(--navy); text-decoration-thickness: .08em; text-underline-offset: .18em; }
a:hover { color: var(--navy-deep); }

img, svg { max-width: 100%; height: auto; }
ul, ol { margin: 0 0 1.15em; padding-left: 1.3em; }
li { margin-bottom: .4em; }
strong { font-weight: 700; }
hr { border: 0; border-top: 2px solid var(--rule); margin: 2.5rem 0; }

:where(a, button, input, textarea, select, summary):focus-visible {
  outline: 3px solid var(--navy);
  outline-offset: 2px;
  border-radius: 4px;
}

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--navy); color: #fff; padding: .7rem 1.1rem;
  border-radius: 0 0 var(--radius) 0; font-weight: 700;
}
.skip-link:focus { left: 0; color: #fff; }

.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---------- layout ---------- */
.wrap { width: min(100% - (var(--gutter) * 2), 1120px); margin-inline: auto; }
.wrap--narrow { width: min(100% - (var(--gutter) * 2), 760px); margin-inline: auto; }
.section { padding-block: var(--stack); }
.section--wash {
  background: linear-gradient(180deg, #F4FBFC 0%, var(--aqua-wash) 55%, #F4FBFC 100%);
}
.section--sand { background: var(--sand); }
.section--tight { padding-block: clamp(2rem, 4vw, 3rem); }
.prose { max-width: var(--measure); }
.lede { font-size: var(--step-1); }

/* ---------- header ---------- */
/* Frosted, translucent header: content scrolls under it and stays legible
   through the blur. The @supports fallback keeps it opaque where backdrop-filter
   is unavailable, since translucency without blur is unreadable. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--glass);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid var(--hairline);
  padding-block: .8rem;
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .site-header { background: var(--paper); }
}
.site-header__inner { display: flex; flex-wrap: wrap; align-items: center; gap: .9rem 1.6rem; }
.brand { display: inline-flex; align-items: center; text-decoration: none; flex: 0 0 auto; }
.brand img { width: clamp(148px, 20vw, 188px); display: block; }
.site-nav { margin-left: auto; }
.site-nav ul { display: flex; flex-wrap: wrap; gap: .35rem 1.4rem; list-style: none; margin: 0; padding: 0; }
.site-nav li { margin: 0; }
.site-nav a {
  display: inline-block; padding: .35rem .1rem;
  font-weight: 600; font-size: var(--step--1); letter-spacing: .01em;
  color: var(--ink); text-decoration: none; border-bottom: 3px solid transparent;
}
.site-nav a:hover { color: var(--navy); border-bottom-color: var(--whale-teal); }
.site-nav a[aria-current="page"] { color: var(--navy); border-bottom-color: var(--coral); }

/* ---------- buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .55rem;
  padding: .85rem 1.6rem; border-radius: 999px; border: 1px solid transparent;
  font-family: var(--font-body); font-weight: 700; font-size: var(--step-0);
  line-height: 1.2; text-decoration: none; cursor: pointer;
  transition: transform .22s var(--ease-out), box-shadow .22s var(--ease-out),
              background-color .22s var(--ease-out);
}
.btn--primary {
  background: linear-gradient(180deg, #2A7E9E 0%, var(--navy) 100%);
  color: #fff;
  box-shadow: var(--shadow-1), inset 0 1px 0 rgba(255, 255, 255, .16);
}
.btn--primary:hover {
  background: linear-gradient(180deg, var(--navy) 0%, var(--navy-deep) 100%);
  color: #fff; transform: translateY(-2px); box-shadow: var(--shadow-2);
}
.btn--primary:active { transform: translateY(0); box-shadow: var(--shadow-1); }
.btn--secondary {
  background: rgba(255, 255, 255, .78);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  color: var(--navy); border-color: rgba(31, 110, 140, .35);
  box-shadow: var(--shadow-1);
}
.btn--secondary:hover {
  background: #fff; color: var(--navy); transform: translateY(-2px);
  border-color: var(--navy); box-shadow: var(--shadow-2);
}
.btn--secondary:active { transform: translateY(0); }
.btn[aria-disabled="true"], .btn:disabled {
  background: var(--sea-band); color: var(--ink); box-shadow: none;
  cursor: default; pointer-events: none; border-color: transparent;
}
.btn-row { display: flex; flex-wrap: wrap; gap: .85rem; align-items: center; }

/* ---------- decorative wave ---------- */
.wave { display: block; width: 100%; height: clamp(22px, 3.5vw, 40px); color: var(--ocean); }
.wave svg { display: block; width: 100%; height: 100%; }

/* ---------- hero ---------- */
/* Layered light rather than a flat fill: two wide, very low-opacity brand glows
   over a soft vertical wash. Reads as depth without becoming a gradient. */
.hero {
  background:
    radial-gradient(85% 110% at 88% -15%, rgba(143, 212, 224, .60) 0%, rgba(143, 212, 224, 0) 62%),
    radial-gradient(60% 85% at 2% 5%, rgba(242, 105, 92, .10) 0%, rgba(242, 105, 92, 0) 58%),
    linear-gradient(180deg, #F4FBFC 0%, var(--aqua-wash) 100%);
  padding-block: clamp(3.25rem, 7.5vw, 6rem);
  overflow: hidden;
}
.hero__grid { display: grid; gap: clamp(2rem, 5vw, 4rem); align-items: center; }
@media (min-width: 860px) { .hero__grid { grid-template-columns: 1.05fr .95fr; } }
.hero h1 {
  font-size: var(--step-display);
  color: var(--navy); margin-bottom: .32em; letter-spacing: -.02em;
}
.hero__lede { font-size: var(--step-1); color: var(--ink); max-width: 34ch; }
.hero__figure { margin: 0; justify-self: center; }
.hero__figure img {
  width: min(100%, 390px);
  border-radius: 10px;
  box-shadow: var(--shadow-3);
  border: 1px solid rgba(31, 110, 140, .10);
}
.eyebrow {
  display: inline-block; font-weight: 700; font-size: var(--step--1);
  letter-spacing: .11em; text-transform: uppercase; color: var(--navy); margin-bottom: .9rem;
}
.section-intro { max-width: 60ch; margin-bottom: clamp(1.8rem, 4vw, 2.8rem); }
.section-intro h2 { color: var(--navy); }

/* ---------- cards ---------- */
.card-grid { display: grid; gap: clamp(1.2rem, 3vw, 2rem); }
.card-grid--3 { grid-template-columns: repeat(auto-fit, minmax(255px, 1fr)); }
.card-grid--wide { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.mt-lg { margin-top: 2rem; }
.card {
  background: linear-gradient(180deg, #fff 0%, #FCFEFE 100%);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  padding: clamp(1.5rem, 3.2vw, 2.25rem);
  box-shadow: var(--shadow-1);
  transition: transform .28s var(--ease-out), box-shadow .28s var(--ease-out);
}
.card:hover { box-shadow: var(--shadow-2); }
/* Component-scoped sizes. These do not depend on the surrounding container,
   so a heading cannot change size just by moving between pages. */
.card h3 { color: var(--navy); font-size: var(--step-1); }
.card p:last-child { margin-bottom: 0; }
.card--flat {
  background: linear-gradient(180deg, rgba(234,247,249,.95) 0%, rgba(234,247,249,.6) 100%);
  border-color: rgba(31, 110, 140, .10);
}

/* ---------- book cards ---------- */
.book-list { display: grid; gap: clamp(1.6rem, 4vw, 2.6rem); list-style: none; margin: 0; padding: 0; }
.book-list li { margin: 0; }
.book-card {
  display: grid; gap: clamp(1.4rem, 3.2vw, 2.4rem); align-items: start;
  background: linear-gradient(180deg, #fff 0%, #FCFEFE 100%);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg); padding: clamp(1.5rem, 3.2vw, 2.25rem);
  box-shadow: var(--shadow-1);
  transition: transform .28s var(--ease-out), box-shadow .28s var(--ease-out);
}
.book-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-2); }
.book-card--bare:hover { transform: none; box-shadow: none; }
@media (min-width: 680px) { .book-card { grid-template-columns: 210px 1fr; } }
.book-card--bare { border: 0; padding: 0; }
.book-card__cover img {
  width: 100%; border-radius: 10px; box-shadow: var(--shadow-2);
  border: 1px solid rgba(31, 110, 140, .10);
}
.book-card__body h2, .book-card__body h3 { margin-bottom: .2em; color: var(--navy); }
.book-card__title { font-size: var(--step-2); }
.book-card__body h2 a, .book-card__body h3 a { text-decoration: none; color: inherit; }
.book-card__body h2 a:hover, .book-card__body h3 a:hover { text-decoration: underline; }
.book-card__sub {
  font-family: var(--font-body); font-weight: 700; color: var(--navy);
  font-size: var(--step-1); margin-bottom: .5em;
}
.book-meta { font-size: var(--step--1); color: var(--muted); margin-bottom: 1em; }

.badge {
  display: inline-block; padding: .3rem .85rem; border-radius: 999px;
  font-weight: 700; font-size: var(--step--1); line-height: 1.35;
  box-shadow: inset 0 0 0 1px rgba(31, 29, 27, .06);
}
.badge--ages { background: var(--sunshine); color: var(--ink); }
.badge--soon { background: var(--sea-band); color: var(--ink); }
.badge--live { background: var(--go-green); color: #fff; }
.badge-row { display: flex; flex-wrap: wrap; gap: .5rem; margin-bottom: 1rem; }

/* ---------- sample pages ---------- */
.samples { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1.2rem; list-style: none; margin: 0; padding: 0; }
.samples li { margin: 0; }
.samples figure { margin: 0; }
.samples img {
  width: 100%; background: #fff; display: block;
  border: 1px solid var(--hairline); border-radius: var(--radius-sm);
  box-shadow: var(--shadow-1);
  transition: transform .28s var(--ease-out), box-shadow .28s var(--ease-out);
}
.samples img:hover { transform: translateY(-3px); box-shadow: var(--shadow-2); }
.samples figcaption { font-size: var(--step--1); color: var(--muted); margin-top: .5rem; }

/* ---------- research prose ---------- */
.prose h2 { color: var(--navy); margin-top: 2.2em; }
.prose h2:first-child { margin-top: 0; }
/* no font-size here: h3 is h3 wherever it lands */
.prose h3 { color: var(--ink); margin-top: 1.9em; }
.prose ul { padding-left: 1.2em; }

.grade {
  display: inline-block; font-family: var(--font-body); font-weight: 700;
  font-size: .72em; letter-spacing: .08em; text-transform: uppercase;
  padding: .18rem .6rem; border-radius: 6px; margin-right: .5rem;
}
.grade--strong { background: var(--go-green); color: #fff; }
.grade--moderate { background: var(--sunshine); color: var(--ink); }
.grade--weak { background: var(--sand); color: var(--ink); }
.grade--not-supported { background: var(--coral); color: var(--ink); }

.callout {
  background: linear-gradient(180deg, rgba(234,247,249,.92) 0%, rgba(234,247,249,.62) 100%);
  border: 1px solid rgba(61, 157, 190, .22);
  border-left: 5px solid var(--ocean);
  border-radius: var(--radius-sm);
  padding: clamp(1.2rem, 3vw, 1.7rem) clamp(1.3rem, 3vw, 1.9rem);
  margin: 2em 0;
  box-shadow: var(--shadow-1);
}
.callout--warn {
  background: linear-gradient(180deg, rgba(253,239,237,.95) 0%, rgba(253,239,237,.65) 100%);
  border-color: rgba(242, 105, 92, .28); border-left-color: var(--coral);
}
.callout h3, .callout h4 { margin-top: 0; font-size: var(--step-1); }
.callout p:last-child { margin-bottom: 0; }

.pull-claim {
  font-family: var(--font-display); font-size: var(--step-2); line-height: 1.3;
  color: var(--navy); margin: 1.6em 0;
  padding: clamp(1.1rem, 2.5vw, 1.5rem) clamp(1.2rem, 3vw, 1.7rem);
  border: 0; border-left: 5px solid var(--sunshine);
  border-radius: var(--radius-sm);
  background: linear-gradient(180deg, rgba(255,209,102,.16) 0%, rgba(255,209,102,.05) 100%);
}

.source-note { font-size: var(--step--1); color: var(--muted); }
.source-list { list-style: none; padding: 0; }
.source-list li { padding: .7rem 0; border-bottom: 1px solid var(--rule); font-size: var(--step--1); line-height: 1.55; }
.source-list li:last-child { border-bottom: 0; }
.source-list cite { font-style: italic; }

.claim-table { width: 100%; border-collapse: collapse; margin: 1.6em 0; font-size: var(--step--1); }
.claim-table th, .claim-table td { text-align: left; padding: .8rem .7rem; border-bottom: 1px solid var(--rule); vertical-align: top; }
.claim-table th { font-weight: 700; color: var(--navy); }
.claim-table tbody th { font-weight: 500; color: var(--ink); }
.table-scroll { overflow-x: auto; }

/* ---------- form ---------- */
.form-grid { display: grid; gap: 1.25rem; }
/* align-items:end keeps the inputs on a row level with each other even when one
   field carries a hint line and its neighbour does not */
@media (min-width: 640px) { .form-grid--2 { grid-template-columns: 1fr 1fr; align-items: end; } }
.field label { display: block; font-weight: 700; margin-bottom: .35rem; }
.field .hint { display: block; font-weight: 500; font-size: var(--step--1); color: var(--muted); margin-bottom: .45rem; }
.field input, .field select, .field textarea {
  width: 100%; padding: .8rem 1rem; font: inherit; font-size: var(--step-0);
  color: var(--ink); background: rgba(255, 255, 255, .9);
  border: 1px solid #9FB7BF; border-radius: var(--radius-sm);
  box-shadow: inset 0 1px 2px rgba(31, 29, 27, .04);
  transition: border-color .2s var(--ease-out), box-shadow .2s var(--ease-out);
}
.field input:focus, .field select:focus, .field textarea:focus {
  border-color: var(--navy); background: #fff;
  box-shadow: 0 0 0 4px rgba(31, 110, 140, .12);
}
.field textarea { min-height: 120px; resize: vertical; }
.field--check { display: flex; gap: .7rem; align-items: flex-start; }
.field--check input {
  width: 1.25rem; height: 1.25rem; margin-top: .28rem;
  flex: 0 0 auto; accent-color: var(--navy);
}
.field--check label { font-weight: 500; }
.req { color: #A8321F; font-weight: 700; }
.form-status { margin-top: 1.2rem; padding: 1rem 1.2rem; border-radius: var(--radius); font-weight: 600; }
.form-status[data-state="ok"] { background: #E7F6EC; border: 2px solid var(--go-green); }
.form-status[data-state="error"] { background: #FDEFED; border: 2px solid var(--coral); }
.form-status[hidden] { display: none; }
.honey { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* ---------- notify ("tell me when it's out") ---------- */
.notify h2 { margin-bottom: .35em; }
.notify > p { max-width: 54ch; }
.notify-form { margin-top: 1.6rem; }
.notify-row { display: grid; gap: .9rem; align-items: end; margin-bottom: 1rem; }
@media (min-width: 560px) {
  .notify-row { grid-template-columns: minmax(0, 1fr) auto; }
}
.notify-row .btn { white-space: nowrap; }
.notify .field--check label { font-size: var(--step--1); }

/* ---------- footer ---------- */
.site-footer {
  background: linear-gradient(180deg, #22758F 0%, var(--navy) 45%, var(--navy-deep) 100%);
  color: #fff;
  padding-block: clamp(3rem, 6vw, 4.5rem);
  border-top: 1px solid rgba(255, 255, 255, .10);
}
.site-footer h2, .site-footer h3 { color: #fff; }   /* headings default to navy */
.site-footer a { color: #fff; }
.site-footer a:hover { color: var(--sunshine); }
.site-footer__grid { display: grid; gap: 1.8rem; }
@media (min-width: 720px) { .site-footer__grid { grid-template-columns: 1.4fr 1fr; align-items: start; } }
.site-footer img { width: 168px; }
/* The footer mark is the one-colour ink wordmark flipped to white. This lives
   here rather than in a style attribute because the site's CSP sets
   style-src 'self', which blocks inline styles outright. */
.site-footer__mark { filter: brightness(0) invert(1); }
.site-footer .tagline { margin-top: 1rem; max-width: 34ch; color: #EAF7F9; }
.footer-nav h2 { font-size: var(--step-1); margin-bottom: .6em; }
.footer-nav ul { list-style: none; padding: 0; margin: 0; columns: 2; column-gap: 1.6rem; }
.footer-nav li { margin-bottom: .5rem; break-inside: avoid; }
.footer-fine { margin-top: 2rem; padding-top: 1.4rem; border-top: 1px solid rgba(255,255,255,.28); font-size: var(--step--1); color: #E4F2F6; }
.footer-fine p { margin-bottom: .5em; }

/* ---------- scroll-driven reveals ---------- */
/* Elements rise and fade as they enter the viewport, the way apple.com does it.
   This is pure CSS (animation-timeline: view()) -- no JavaScript, which matters
   because the CSP is script-src 'self' and this must never become a dependency
   for content being visible.

   Three guards, all deliberate:
   - @supports: browsers without scroll-driven animations never get the opening
     opacity:0 state, so content is simply always visible there.
   - prefers-reduced-motion: motion-sensitive users get the static page.
   - print: paper has no scroll position; force everything visible. */
@keyframes hd-rise {
  from { opacity: 0; transform: translateY(26px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes hd-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Reveals are driven by IntersectionObserver in site.js, not by
   animation-timeline: view().

   The CSS scroll-timeline version was written first and is genuinely nicer --
   no JavaScript at all. It was removed because it could not be verified here:
   the ViewTimeline attaches correctly but its progress never advances under
   headless Chrome (compositor-driven timelines do not tick with virtual time),
   and the failure mode if it is wrong in a real browser is permanently
   invisible content. A verifiable effect beats an elegant unverifiable one.
   IntersectionObserver also covers Safari and Firefox, which do not ship
   scroll-driven animations. */
.reveal-js .section-intro,
.reveal-js .card,
.reveal-js .book-card,
.reveal-js .samples li,
.reveal-js .callout,
.reveal-js .pull-claim,
.reveal-js .table-scroll,
.reveal-js .prose > h2,
.reveal-js .form-grid > * {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity .6s var(--ease-out), transform .6s var(--ease-out);
}
.reveal-js .is-revealed {
  opacity: 1;
  transform: translateY(0);
}

/* Failsafe, and the reason this design is safe to ship: a pure-CSS animation
   that forces every revealable element visible 2s after load, needing no
   scroll event, no observer and no frame callback. In normal use JavaScript
   reveals things long before this fires and it is never seen. If the reveal
   logic breaks in some browser this project cannot test, the worst case is a
   two-second delay -- not content that is invisible forever. */
@keyframes hd-unhide { to { opacity: 1; transform: none; } }
.reveal-js .section-intro,
.reveal-js .card,
.reveal-js .book-card,
.reveal-js .samples li,
.reveal-js .callout,
.reveal-js .pull-claim,
.reveal-js .table-scroll,
.reveal-js .prose > h2,
.reveal-js .form-grid > * {
  animation: hd-unhide .01s linear 2s forwards;
}
@media (prefers-reduced-motion: reduce) {
  .reveal-js .section-intro, .reveal-js .card, .reveal-js .book-card,
  .reveal-js .samples li, .reveal-js .callout, .reveal-js .pull-claim,
  .reveal-js .table-scroll, .reveal-js .prose > h2, .reveal-js .form-grid > * {
    opacity: 1; transform: none; transition: none;
  }
}

@media print {
  .reveal-js .section-intro, .reveal-js .card, .reveal-js .book-card,
  .reveal-js .samples li, .reveal-js .callout, .reveal-js .pull-claim,
  .reveal-js .table-scroll, .reveal-js .prose > h2, .reveal-js .form-grid > * {
    opacity: 1 !important; transform: none !important;
  }
  .section-intro, .card, .book-card, .samples li, .callout, .pull-claim,
  .table-scroll, .prose > h2, .form-grid > *, .hero__figure img, .wave {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}
