/* The whole of the site's appearance. One file, no imports, no web fonts.
 *
 * NO FONT LINK, DELIBERATELY. A Google Fonts stylesheet is a second origin on
 * the critical path, a third-party request on behalf of a visitor who did not
 * agree to it, and a blank paragraph for anyone whose network blocks it. The
 * system stack is already on the device, renders instantly, and is the typeface
 * the reader's own operating system chose for prose.
 *
 * NO FRAMEWORK, for the same reason the build has no bundler: this stylesheet
 * has to still work in two years with nobody looking at it.
 *
 * COLOUR IS DECLARED TWICE AND ONLY ONCE PER PROPERTY. The tokens below carry
 * light values; the `prefers-color-scheme: dark` block redefines the tokens and
 * nothing else. Writing dark-mode rules per component is how half a site ends
 * up dark and the other half white -- and the half that is wrong is always the
 * half nobody on the team uses.
 */

:root {
  color-scheme: light dark;

  --ink: #1b1a17;
  --ink-soft: #4a4740;
  --ink-faint: #6d685e;
  --paper: #fbfaf7;
  --paper-raised: #ffffff;
  --rule: #e2ddd2;
  --accent: #8a5a1c;
  --accent-ink: #ffffff;
  --accent-soft: #f3ece0;
  --focus: #1456b8;

  --measure: 34rem;
  --page: 64rem;
  --gutter: 1rem;
  --radius: 0.75rem;

  /* Aliases, because components written later reached for these names and
     a rename would leave the same trap set for the next one. The colour
     three are redefined in the dark block below with the tokens they
     alias; the space and radius four are not colours and do not change. */
  --hairline: var(--rule);
  --text-strong: var(--ink);
  --text-body: var(--ink-soft);
  --text-muted: var(--ink-faint);

  --space-sm: 0.5rem;
  --space-md: 1rem;
  --space-lg: 1.5rem;
  --space-xl: 2rem;
  --radius-md: 0.75rem;

  /* The banner's own surface: a warm gradient under a gold rule, and a
     gold button with a little depth in it. Declared as tokens so the dark
     block can answer them without a second copy of the component. */
  --banner-top: #fffdf8;
  --banner-bottom: #f6e8d0;
  --banner-rule: #c9962f;
  --banner-btn-top: #a86a1b;
  --banner-btn-bottom: #7c4f15;
  --banner-glow: rgb(168 106 27 / 34%);

  /* How long the banner waits before it unrolls. `splash.js` raises this to
     just past the film's length, and only when it has decided to play one
     (R241.1). The default is for every visit that sees no film. */
  --banner-delay: 0.35s;

  /* The breakpoint the navigation becomes a drawer below. Declared once so
     `nav.js` and the media query cannot disagree; `css.test.ts` checks that
     the number here is the number in both. */
  --nav-drawer-max: 52rem;
}

@media (prefers-color-scheme: dark) {
  :root {
    --ink: #f2efe8;
    --ink-soft: #c9c3b6;
    --ink-faint: #9b958a;
    --paper: #161513;
    --paper-raised: #201e1b;
    --rule: #332f2a;
    --accent: #e0a964;
    --accent-ink: #1b1a17;
    --accent-soft: #2a2520;
    --focus: #8ab4ff;

    /* The aliases follow their tokens. Declared again rather than left to
       inherit, because `var()` in a custom property resolves where it is
       USED, not where it is written -- and leaving them out is the way half
       a site ends up dark and the other half white. */
    --hairline: var(--rule);
    --text-strong: var(--ink);
    --text-body: var(--ink-soft);
    --text-muted: var(--ink-faint);

    --banner-top: #241f17;
    --banner-bottom: #191511;
    --banner-rule: #e0a964;
    --banner-btn-top: #e8b877;
    --banner-btn-bottom: #c48f45;
    --banner-glow: rgb(224 169 100 / 30%);
  }
}

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

html {
  /* Smooth scrolling is a preference, not a default: it makes some readers
   * motion-sick, and the media query is the only honest way to offer it. */
  scroll-behavior: auto;
}

@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
    Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
  font-size: clamp(1rem, 0.96rem + 0.2vw, 1.0625rem);
  line-height: 1.6;
  -webkit-text-size-adjust: 100%;
}

h1,
h2,
h3 {
  line-height: 1.2;
  /* `balance` keeps a two-line headline from leaving one word on line two.
   * Unsupported browsers ignore it, which is the whole appeal. */
  text-wrap: balance;
  margin: 0 0 0.6em;
}

h1 {
  font-size: clamp(1.9rem, 1.4rem + 2.4vw, 3rem);
  letter-spacing: -0.02em;
}

h2 {
  font-size: clamp(1.35rem, 1.2rem + 0.8vw, 1.75rem);
}

h3 {
  font-size: 1.15rem;
}

p {
  margin: 0 0 1em;
  max-width: var(--measure);
  text-wrap: pretty;
}

a {
  color: var(--accent);
  text-decoration-thickness: 0.08em;
  text-underline-offset: 0.18em;
}

a:hover {
  text-decoration-thickness: 0.14em;
}

/* One focus ring, everywhere, and it is never removed. A keyboard visitor who
 * cannot see where they are is a visitor who cannot use the site at all. */
:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
  border-radius: 2px;
}

img,
iframe {
  max-width: 100%;
}

/* Visible only to a screen reader. Not `display: none`, which hides it from
 * one too; not `opacity: 0`, which leaves it taking up space and focusable in
 * the wrong place. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The skip link: off screen until focused, then the first thing on the page.
 * It is first in the document so a keyboard visitor reaches it before the
 * navigation they are trying to skip. */
.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 10;
  background: var(--accent);
  color: var(--accent-ink);
  padding: 0.6rem 1rem;
  border-radius: 0 0 var(--radius) 0;
}

.skip:focus {
  left: 0;
}

/* ---------- masthead ---------- */

.masthead {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1.25rem;
  max-width: var(--page);
  margin: 0 auto;
  padding: 1rem var(--gutter);
  border-bottom: 1px solid var(--rule);
}

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-weight: 650;
  font-size: 1.05rem;
  color: var(--ink);
  text-decoration: none;
}

.wordmark-icon {
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 0.5rem;
}

.main-nav ul {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.main-nav a {
  color: var(--ink-soft);
  text-decoration: none;
  padding: 0.2rem 0;
  border-bottom: 2px solid transparent;
}

.main-nav a:hover {
  color: var(--ink);
  border-bottom-color: var(--rule);
}

.main-nav a[aria-current="page"] {
  color: var(--ink);
  border-bottom-color: var(--accent);
}

.langs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 0.75rem;
  margin-inline-start: auto;
  font-size: 0.9rem;
}

.langs a {
  color: var(--ink-faint);
  text-decoration: none;
}

.langs a:hover {
  color: var(--ink);
  text-decoration: underline;
}

.langs a[aria-current="true"] {
  color: var(--ink);
  font-weight: 600;
}

/* ---------- the page itself ---------- */

main {
  max-width: var(--page);
  margin: 0 auto;
  padding: 2.5rem var(--gutter) 3rem;
  display: flex;
  flex-direction: column;
  gap: 3rem;
}

section {
  margin: 0;
}

.narrow,
.prose {
  max-width: var(--measure);
}

.lede {
  font-size: 1.15rem;
  color: var(--ink-soft);
}

.label {
  font-size: 0.8rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 0.4rem;
}

.fine {
  font-size: 0.85rem;
  color: var(--ink-faint);
}

/* The hero picture's rules are with the full-bleed banner, further down
   (R241.4). It used to be a 320px phone-shaped thumbnail in the text column;
   those rules lived here and went on winning on max-width after the banner
   was built, rendering a 1280px still at 360. Deleted rather than overridden:
   a rule that is only ever beaten is a rule someone will one day un-beat. */

.badge {
  display: inline-block;
  background: var(--accent-soft);
  color: var(--accent);
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 0.2rem 0.75rem;
  font-size: 0.85rem;
  font-weight: 600;
  margin: 0 0 1rem;
}

.hero h1 {
  max-width: 24ch;
}

.hero-aside {
  margin-top: 0.75rem;
  font-size: 0.95rem;
}

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

.buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin: 1.25rem 0;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 44px of target, which is the smallest thing a thumb hits reliably. */
  min-height: 2.75rem;
  padding: 0.6rem 1.15rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper-raised);
  color: var(--ink);
  font-weight: 600;
  text-decoration: none;
  text-align: center;
}

.btn:hover {
  border-color: var(--ink-faint);
}

.btn.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

.btn.quiet {
  background: transparent;
  font-weight: 500;
}

/* The Android button, which is not a link because there is nowhere to go.
 * It is drawn as plainly unavailable rather than as a button that fails. */
.btn.disabled {
  background: transparent;
  color: var(--ink-faint);
  border-style: dashed;
  font-weight: 500;
  cursor: not-allowed;
}

/* ---------- blocks ---------- */

.promise,
.give,
.business,
.support-claim,
.who,
.steps-section,
.cards-section {
  background: var(--paper-raised);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 1.5rem;
}

@media (min-width: 40rem) {
  .promise,
  .give,
  .business,
  .support-claim,
  .who,
  .steps-section,
  .cards-section {
    padding: 2rem;
  }
}

.ticks,
.expertise {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
  display: grid;
  gap: 0.6rem;
}

.ticks li,
.expertise li {
  position: relative;
  padding-inline-start: 1.6rem;
  max-width: var(--measure);
}

.ticks li::before,
.expertise li::before {
  /* A mark drawn in CSS rather than an emoji: an emoji is announced aloud by
   * a screen reader ("check mark button") in front of every line. */
  content: "";
  position: absolute;
  inset-inline-start: 0.25rem;
  top: 0.62em;
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 50%;
  background: var(--accent);
}

.pledge {
  border-inline-start: 3px solid var(--accent);
  padding-inline-start: 1rem;
  font-size: 1.1rem;
  color: var(--ink);
  margin-bottom: 0;
}

.give h3 {
  margin-top: 1.75rem;
}

/* ---------- the how-to steps ---------- */

.steps {
  list-style: none;
  counter-reset: step;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 2rem;
}

.step {
  counter-increment: step;
  display: grid;
  gap: 1rem;
}

.step-words {
  position: relative;
  padding-inline-start: 2.75rem;
}

.step-words h3::before {
  content: counter(step);
  position: absolute;
  inset-inline-start: 0;
  top: 0;
  width: 2rem;
  height: 2rem;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent);
  border: 1px solid var(--rule);
  font-size: 0.95rem;
  font-weight: 700;
}

.media {
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--accent-soft);
}

.media .shot {
  display: block;
  width: 100%;
  height: auto;
}

.media.video {
  /* A phone-shaped frame would crop a widescreen clip and a widescreen frame
   * letterboxes a portrait one; 16/9 is what YouTube serves by default and the
   * player handles the rest. */
  aspect-ratio: 16 / 9;
}

.media.video iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

@media (min-width: 48rem) {
  /* Two columns only where there is room for both. Below this the image sits
   * under its words, which is the order they are read in. */
  .step:has(.media) {
    grid-template-columns: 1fr 1fr;
    align-items: start;
    gap: 1.5rem;
  }
}

/* ---------- cards ---------- */

.cards {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 1.5rem;
}

@media (min-width: 48rem) {
  .cards {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.card h2 {
  font-size: 1.3rem;
}

.card p:last-child {
  margin-bottom: 0;
}

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

.footer {
  max-width: var(--page);
  margin: 0 auto;
  padding: 2rem var(--gutter) 3rem;
  border-top: 1px solid var(--rule);
  color: var(--ink-soft);
  font-size: 0.95rem;
}

.footer p {
  max-width: 48rem;
  margin-bottom: 0.6rem;
}

/* ---------------------------------------------------------------- splash */

/*
  The launch film, over a page that is already there.

  `position: fixed` rather than absolute so it covers the viewport regardless
  of scroll position, and the navy is the app's own, so a film that does not
  match the window's aspect ratio is letterboxed into the artwork's own colour
  rather than into a bar.

  `object-fit: contain`, deliberately. `cover` would fill the window, and on a
  laptop it would fill it by cropping the title and tagline straight off a
  portrait cut -- the two things the film exists to say.
*/
.splash-overlay {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #001433;
  opacity: 1;
  transition: opacity 520ms ease-out;
}

.splash-overlay.is-going {
  opacity: 0;
  /* Stops it swallowing a click during the fade, when it is already
     half-invisible and the page beneath looks ready to use. */
  pointer-events: none;
}

.splash-overlay video {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

/*
  Belt and braces for reduced motion.

  splash.js returns before building anything when the preference is set, so
  this should never apply -- but a stylesheet is the one place the rule can be
  honoured even if the script changes, is cached stale, or is edited by
  someone who did not read it.
*/
@media (prefers-reduced-motion: reduce) {
  .splash-overlay { display: none; }
}

/* ------------------------------------------------------------- languages */

/*
  One card per language, the endonym leading.

  The language's own name is the heading because that is what a reader
  scanning for their own is looking for -- someone who reads Korean is looking
  for 한국어, not for the word "Korean". The English gloss is underneath for a
  visitor scanning in English, and the translation is the fact that makes the
  claim real rather than a flag on a list.
*/
.languages {
  list-style: none;
  padding: 0;
  margin: 0 0 var(--space-xl);
  display: grid;
  gap: var(--space-md);
  grid-template-columns: 1fr;
}

@media (min-width: 560px) {
  .languages { grid-template-columns: 1fr 1fr; }
}

@media (min-width: 900px) {
  .languages { grid-template-columns: 1fr 1fr 1fr; }
}

.language {
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  padding: var(--space-md);
}

.language-name {
  margin: 0;
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--text-strong);
}

.language-gloss {
  margin: 2px 0 0;
  font-size: 0.85rem;
  color: var(--text-muted);
}

.language-texts {
  margin: var(--space-sm) 0 0;
  font-size: 0.9rem;
  color: var(--text-body);
}

/* ------------------------------------------------------------------ banner */

/*
  THE STRIP THAT UNROLLS AT THE TOP OF EVERY PAGE (R241.1).

  Javier: "a banner that deploys (at the very top of the site)".

  IT UNROLLS WITH NO JAVASCRIPT. grid-template-rows: 0fr -> 1fr is the one way
  to animate to a height nobody has measured: the row is the content's own
  height at the end and zero at the start, and the browser interpolates
  between them. A max-height of 1000px is the old trick and it is wrong -- the
  "open" speed then depends on how much slack was guessed, so a short banner
  snaps and a long one crawls.

  animation-fill-mode: both holds the first frame through the delay, which is
  what makes the delay a delay rather than a flash of the open state. And
  because this is an animation rather than a transition, nothing has to toggle
  a class: the markup arrives, the animation runs, and a visitor with scripts
  off sees exactly what everyone else sees.

  The delay is a custom property so splash.js can raise it in the single
  moment it decides to play a film, and nothing else has to know. See the
  comment there.
*/
.banner {
  display: grid;
  grid-template-rows: 1fr;
  overflow: hidden;
  /*
    A warm gradient rather than a flat fill, with a gold hairline along the
    very top edge of the page. The rule is what makes it read as a ribbon
    laid over the site instead of a block of coloured background that
    happens to be first.
  */
  background: linear-gradient(
    to bottom, var(--banner-top), var(--banner-bottom));
  border-top: 3px solid var(--banner-rule);
  border-bottom: 1px solid var(--hairline);
}

@media (prefers-reduced-motion: no-preference) {
  .banner {
    animation: banner-unroll 620ms cubic-bezier(0.22, 0.61, 0.36, 1)
      var(--banner-delay) both;
  }
}

@keyframes banner-unroll {
  from { grid-template-rows: 0fr; }
  to { grid-template-rows: 1fr; }
}

/* The grid row's child is what must clip, or the content spills out of a row
   that is still zero high. */
.banner-inner {
  min-height: 0;
  position: relative;
  display: grid;
  gap: var(--space-md) var(--space-lg);
  grid-template-columns: 1fr;
  align-items: start;
  max-width: var(--page);
  margin: 0 auto;
  /* Room at the end for the close button, which is positioned over the
     corner and must not land on top of a word. */
  padding: var(--space-md) 3rem var(--space-md) var(--gutter);
}

/* Side by side once there is room for two columns of prose. Giving is on the
   leading side because it is the one most people act on; hiring us is the
   larger ask and sits beside it rather than under it. */
@media (min-width: 52rem) {
  .banner-inner {
    grid-template-columns: 1.1fr 1fr;
    align-items: center;
  }
}

/*
  THE EYEBROWS.

  Two words in gold over each half, which is what tells a reader at a glance
  that there are two offers here and not one long paragraph. The second one
  is quieter than the first, because hiring us is the larger ask and should
  not compete with the gift.
*/
.banner-tag {
  margin: 0 0 0.3rem;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
}

.banner-hire-tag {
  margin: 0 0 0.3rem;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.banner-free {
  margin: 0;
  font-weight: 700;
  color: var(--text-strong);
  font-size: clamp(1.1rem, 1rem + 0.5vw, 1.3rem);
  line-height: 1.25;
  letter-spacing: -0.01em;
  max-width: 24ch;
}

.banner-thanks {
  margin: 0.45rem 0 0;
  color: var(--text-body);
  font-size: 0.95rem;
  max-width: 48ch;
}

.banner-hire-line {
  margin: 0;
  color: var(--text-body);
  font-size: 0.9rem;
  max-width: 44ch;
}

.banner-act {
  margin-top: 0.9rem;
}

/*
  THE GIVING BUTTON, WHICH IS THE POINT OF THE BANNER.

  Gold rather than the site's flat brown, with a gradient, a soft glow and an
  arrow that slides on hover. It is the one element here allowed to look like
  an invitation; everything around it is quiet so that it can be.

  Its own class rather than `.btn.primary`, because the primary button
  elsewhere on the site should NOT start glowing -- a donate button that
  looks identical to "Download on the App Store" is a banner that has not
  said anything.
*/
.btn.give {
  background: linear-gradient(
    to bottom, var(--banner-btn-top), var(--banner-btn-bottom));
  border-color: transparent;
  color: var(--accent-ink);
  font-weight: 700;
  font-size: 1rem;
  padding: 0.7rem 1.35rem;
  box-shadow: 0 2px 10px var(--banner-glow);
}

.btn.give:hover {
  filter: brightness(1.06);
  box-shadow: 0 4px 16px var(--banner-glow);
}

.btn-arrow {
  display: inline-block;
  margin-inline-start: 0.5rem;
}

@media (prefers-reduced-motion: no-preference) {
  .btn-arrow {
    transition: transform 180ms ease-out;
  }

  .btn.give:hover .btn-arrow {
    transform: translateX(3px);
  }
}

.banner-hire {
  border-top: 1px solid var(--hairline);
  padding-top: var(--space-md);
}

.banner-dot {
  color: var(--text-muted);
  padding: 0 0.15rem;
}

@media (min-width: 52rem) {
  .banner-hire {
    border-top: 0;
    /* A gold rule rather than a grey one: it ties the two halves to the
       same ribbon instead of looking like a table border. */
    border-inline-start: 2px solid var(--accent-soft);
    padding-top: 0.2rem;
    padding-inline-start: var(--space-lg);
  }
}

.banner-hire-act {
  margin: 0.55rem 0 0;
  font-size: 0.9rem;
  font-weight: 600;
}

.banner-hire-act a {
  color: var(--accent);
}

.banner-close {
  position: absolute;
  top: 0.5rem;
  inset-inline-end: 0.5rem;
  width: 2.25rem;
  height: 2.25rem;
  display: grid;
  place-items: center;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--text-muted);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 999px;
  cursor: pointer;
}

.banner-close:hover {
  color: var(--text-strong);
  border-color: var(--hairline);
}

/*
  THE BUTTON THAT MOVES (R241.1).

  Javier: "Show the donation button with an animation that shows as a donate
  now".

  A BREATH, NOT A FLASH. A slow scale and a widening ring on a four-second
  cycle -- the rhythm of something alive rather than something demanding. A
  donation button that blinks reads as an advertisement, and the paragraph
  directly above it is arguing that this is not one.

  IT STOPS AFTER SIX CYCLES. A count rather than "infinite", because a thing
  that moves forever stops being noticed and starts being a reason to leave,
  and a reader who scrolls back up to find it would rather read it than watch
  it.

  AND IT DOES NOT MOVE AT ALL under reduced motion: the whole rule is inside
  the media query, so there is no animation to override.
*/
@media (prefers-reduced-motion: no-preference) {
  .btn.pulse {
    animation: donate-breathe 4s ease-in-out 6;
    animation-delay: calc(var(--banner-delay) + 700ms);
  }
}

@keyframes donate-breathe {
  0%, 100% {
    transform: scale(1);
    box-shadow: 0 0 0 0 rgb(138 90 28 / 42%);
  }
  12% {
    transform: scale(1.045);
    box-shadow: 0 0 0 10px rgb(138 90 28 / 0%);
  }
  24% {
    transform: scale(1);
    box-shadow: 0 0 0 0 rgb(138 90 28 / 0%);
  }
}

/* ------------------------------------------------- the navigation, two ways */

/*
  A ROW ON A WIDE SCREEN, A DRAWER ON A NARROW ONE (R241.5).

  ONE LIST, TWO SHAPES, AND THE SWITCH IS A MEDIA QUERY -- so it happens as
  the window is dragged, at the moment it crosses, with no listener and no
  second copy of the links to fall out of step. That is the whole of "make it
  dynamic so it should change according to the width of the browser".

  EVERYTHING BELOW IS UNDER [data-nav="drawer"], which only nav.js sets. With
  no script the list is an ordinary wrapped row at every width: smaller than a
  drawer, and present. The drawer is the enhancement, never the only way in.
*/
.nav-toggle {
  display: none;
  align-items: center;
  gap: 0.5rem;
  margin-inline-start: auto;
  padding: 0.45rem 0.8rem;
  font: inherit;
  font-weight: 600;
  color: var(--text-strong);
  background: transparent;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  cursor: pointer;
}

.nav-bars,
.nav-bars::before,
.nav-bars::after {
  display: block;
  width: 1.1rem;
  height: 2px;
  background: currentcolor;
  border-radius: 2px;
}

.nav-bars {
  position: relative;
}

.nav-bars::before,
.nav-bars::after {
  content: "";
  position: absolute;
  inset-inline-start: 0;
}

.nav-bars::before { top: -6px; }
.nav-bars::after { top: 6px; }

@media (max-width: 51.999rem) {
  [data-nav="drawer"] .nav-toggle {
    display: inline-flex;
  }

  /*
    The panel. position: fixed so it covers the viewport whatever the scroll
    position, and it slides from the END edge -- the right in English and the
    left in Arabic, because these are logical properties. A translation of
    this site should not have its menu fly in from the wrong side of the
    world.
  */
  [data-nav="drawer"] .main-nav ul {
    position: fixed;
    z-index: 60;
    top: 0;
    bottom: 0;
    inset-inline-end: 0;
    width: min(20rem, 82vw);
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: 4.5rem var(--gutter) var(--gutter);
    background: var(--paper-raised);
    border-inline-start: 1px solid var(--rule);
    box-shadow: -12px 0 40px rgb(0 0 0 / 22%);
    transform: translateX(100%);
    visibility: hidden;
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  /* translateX is not aware of writing direction the way the inset
     properties are, so the right-to-left case is named. */
  [dir="rtl"][data-nav="drawer"] .main-nav ul {
    transform: translateX(-100%);
  }

  [data-nav="drawer"][data-nav-open] .main-nav ul {
    transform: translateX(0);
    visibility: visible;
    transition-delay: 0s;
  }

  [data-nav="drawer"] .main-nav li {
    border-bottom: 1px solid var(--hairline);
  }

  [data-nav="drawer"] .main-nav a {
    display: block;
    padding: 0.85rem 0.2rem;
    font-size: 1.05rem;
    border-bottom: 0;
  }

  /*
    THE FIRST ITEM IS THE DONATION PAGE, dressed as the thing it is.
    NAV_ORDER puts it first in the markup, so :first-child is a CONSEQUENCE of
    that decision rather than a second copy of it -- reorder the list and this
    follows rather than arguing with it.
  */
  [data-nav="drawer"] .main-nav li:first-child a {
    color: var(--accent);
    font-weight: 700;
  }

  /* The page behind must not scroll under an open drawer. */
  [data-nav-open],
  [data-nav-open] body {
    overflow: hidden;
  }

  /* A scrim, so it is plain that the page behind is not the thing in use. */
  [data-nav="drawer"][data-nav-open] .main-nav::after {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 50;
    background: rgb(0 0 0 / 38%);
  }
}

@media (max-width: 51.999rem) and (prefers-reduced-motion: no-preference) {
  [data-nav="drawer"] .main-nav ul {
    transition: transform 280ms cubic-bezier(0.22, 0.61, 0.36, 1),
      visibility 0s linear 280ms;
  }
}

/* On a wide screen there is no toggle and the list is a row, which the base
   rules already say. The donation link still leads. */
@media (min-width: 52rem) {
  .main-nav ul li:first-child a {
    color: var(--accent);
    font-weight: 650;
  }
}

/* --------------------------------------------------- the hero, full bleed */

/*
  THE PICTURE RUNS THE FULL WIDTH AND THE MENU SITS OVER IT (R241.4, R241.5).

  Javier: "I'd like to see it over the picture (???)". The question marks are
  his, and they are fair -- the obvious reading lays a row of links across the
  artwork's own title, which both stills carry in their top third.

  So THE MASTHEAD OVERLAYS THE PICTURE'S FIELD, NOT ITS SUBJECT. The hero is a
  band of the app's own navy -- #001433, the colour splash.js letterboxes the
  film into -- and the picture sits inside it, pushed down by the height of
  the masthead. The menu floats at the top of one continuous dark banner that
  the artwork is part of. It reads as over the picture, and covers none of it.

  ONLY ON THE HOME PAGE, keyed on data-page, which the document already
  carries. Every other page has nothing at the top for a transparent masthead
  to be transparent against, and white links on white paper is the kind of
  thing that ships.
*/
html[data-page="index"] .masthead {
  position: absolute;
  z-index: 20;
  inset-inline: 0;
  border-bottom: 0;
  background: linear-gradient(to bottom, rgb(0 10 28 / 72%), rgb(0 10 28 / 0%));

  /*
    FULL WIDTH, WITH THE CONTENT STILL IN THE COLUMN.

    The scrim has to reach both edges of the picture or it is a floating band
    two thirds of the way across the window -- which is what it was, because
    the masthead's own `max-width: var(--page)` was still in force. So the
    element spans the viewport and the PADDING does the centring: the gutter,
    or half the difference between the window and the column, whichever is
    larger.
  */
  max-width: none;
  padding-inline: max(var(--gutter), calc(50% - var(--page) / 2));
}

html[data-page="index"] .masthead .wordmark,
html[data-page="index"] .masthead .main-nav a,
html[data-page="index"] .masthead .nav-toggle {
  color: #f6f2e9;
}

html[data-page="index"] .masthead .langs a {
  color: rgb(246 242 233 / 78%);
}

html[data-page="index"] .masthead .langs a[aria-current="true"] {
  color: #f6f2e9;
}

html[data-page="index"] .masthead .nav-toggle {
  border-color: rgb(246 242 233 / 45%);
}

html[data-page="index"] .masthead .main-nav a[aria-current="page"] {
  border-bottom-color: #e0a964;
}

/* The drawer is a solid panel, so its links are ink on paper again -- even on
   the one page whose masthead is white on navy. */
@media (max-width: 51.999rem) {
  html[data-page="index"][data-nav="drawer"] .main-nav ul a {
    color: var(--ink-soft);
  }

  html[data-page="index"][data-nav="drawer"] .main-nav ul li:first-child a {
    color: var(--accent);
  }
}

.hero-banner {
  position: relative;
  background: #001433;
  padding: 0;
  margin: 0;
  /* Full bleed out of main's centred column, with no horizontal scrollbar:
     the two margins pull it back out to the viewport edges. */
  width: 100vw;
  max-width: 100vw;
  margin-inline-start: calc(50% - 50vw);
  margin-inline-end: calc(50% - 50vw);
}

/*
  The picture, whole, below the masthead rather than behind it.

  object-fit: contain and a height cap, for the reason the old hero had one: a
  9:16 still at full viewport width is taller than any laptop screen, and the
  first thing a visitor would meet is a picture with no claim attached to it.
*/
.hero-splash {
  display: block;
  margin: 0 auto;
}

/*
  NEVER UPSCALED, AND LETTERBOXED INTO THE APP'S OWN NAVY WHEN THE WINDOW IS
  WIDER THAN THE FILE.

  The widescreen still is 1280x720 and the portrait one 720x1280, both
  straight off the films. Stretching either past its natural width is visible
  as softness on exactly the image the whole page is built around -- so the
  cap is the file's own width, and a wider window gets more navy on each side.
  That is also what `splash.js` does with the film it just played, so the fade
  lands on the same composition in the same frame.

  `max-height` as well, for the reason the old thumbnail had one: a 9:16 still
  at full width is taller than any laptop screen, and a visitor would meet a
  picture with no claim attached to it.
*/
.hero-splash img {
  display: block;
  width: 100%;
  max-width: 1280px;
  height: auto;
  max-height: 84vh;
  object-fit: contain;
  margin: 0 auto;
  /* Clear of the masthead floating above it, so the menu sits over the navy
     field rather than over the artwork's own title. */
  padding-top: 4.25rem;
}

/* The portrait still is a phone screen. Shown at its natural 720 it is 1280
   tall, which is a scroll and a half before a word of the page -- so it is
   capped nearer a phone's apparent size and the navy carries the rest. */
@media (orientation: portrait) {
  .hero-splash img { max-width: 480px; }
}

.hero-say {
  padding-top: var(--space-xl);
}

/* -------------------------------------------------------- the how-to clips */

/*
  A recording of the app, at the proportions it was recorded in.

  No fixed height: the clips come off a 1080x2400 device, and a box sized for
  16:9 would letterbox every one of them between two black bars. The height
  follows the file's own dimensions once metadata arrives; min-height holds
  the space until then so the page does not jump as six of them resolve.
*/
.media.clip video {
  display: block;
  width: 100%;
  max-width: 300px;
  min-height: 160px;
  height: auto;
  border-radius: 14px;
  border: 1px solid var(--hairline);
  background: #000;
  margin: 0 auto;
}

/*
  THE PICTURE STARTS AT THE TOP, SO THE MENU IS OVER IT.

  `main` has 2.5rem of padding above its first child, and the masthead is out
  of flow -- so the navy band began BELOW the menu and the menu's dark scrim
  sat on 2.5rem of cream paper, reading as a grey strip between the banner
  and the picture. The menu was over the gap, not over the artwork.

  Only the home page, and only its top: the gap below the hero is what keeps
  the first heading off the picture, so the rest of the padding stays.
*/
html[data-page="index"] main {
  padding-top: 0;
}

/* ------------------------------------- the blocks that had no rules at all */

/*
  SPONSOR, CHURCH AND THE GIVING CALL-TO-ACTION.

  All three shipped with a class and no stylesheet entry, so they rendered as
  loose paragraphs between cards -- which is why the donate page read as two
  panels with stray text around them rather than as a page. `css.test.ts`
  found them by comparing every class the build emits against every class
  this file mentions, which is the check that was missing when
  `.visually-hidden` was shipping a heading that should have been invisible.

  The sponsor block is the one that is deliberately NOT a card: it is the
  publisher speaking, directly on the page, and boxing it would make it look
  like an advertisement for the thing it is giving away.
*/
.sponsor {
  border-inline-start: 3px solid var(--accent);
  padding-inline-start: var(--space-lg);
}

.sponsor .lede {
  margin-bottom: 0;
}

.church,
.give-cta {
  background: var(--paper-raised);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 1.5rem;
}

@media (min-width: 40rem) {
  .church,
  .give-cta {
    padding: 2rem;
  }
}

/* The call to give is the one that should feel like an invitation, so it
   takes the banner's warmth rather than the plain panel. */
.give-cta {
  background: linear-gradient(
    to bottom, var(--banner-top), var(--banner-bottom));
  border-color: var(--banner-rule);
}

.give-cta h2 {
  margin-top: 0;
}

/* --------------------------------------------- small structural helpers */

/* A grid child that holds prose must be allowed to be narrower than its
   content, or a long word pushes the whole column wide. */
.banner-say {
  min-width: 0;
}

/* The company's link inside the banner, which is a link and should look
   like one against the warm background. */
.banner-org {
  color: var(--accent);
  font-weight: 600;
}

/* The word beside the hamburger. It goes at the very narrowest widths,
   where the three bars are unambiguous and the gutter is not. */
.nav-word {
  font-size: 0.95rem;
}

@media (max-width: 23rem) {
  .nav-word {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}
