/* 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(214 155 43 / 52%);
  --banner-halo: rgb(214 155 43 / 26%);

  /* 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 / 42%);
    --banner-halo: rgb(224 169 100 / 20%);
  }
}

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

/*
  `hidden` MEANS HIDDEN, WHATEVER ELSE A RULE SAYS.

  THE FAULT THIS FIXES (R244.1), which Javier found by clicking it: the
  banner's close button did nothing. The handler ran, `banner.hidden = true`
  was set -- and nothing moved, because the browser's own
  `[hidden] { display: none }` comes from the user-agent stylesheet and
  `.banner { display: grid }` is an author rule, which beats it. Every
  element here that sets `display` had quietly opted out of `hidden`.

  It cost twice. The close button is also `display: grid`, so it was on
  screen before `nav.js` ever unhid it -- which means the "a close button
  that does nothing is worse than none" design was itself not working: for
  anyone with scripts off there has been a visible X that does nothing at
  all.

  `!important` is right here and almost nowhere else: this is not a
  preference to be outranked, it is what the attribute MEANS.
*/
[hidden] {
  display: none !important;
}

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.
*/
/*
  R243.1, from Javier's mock-up: an inset card with a gold glow all round it,
  rather than a ribbon welded to the top of the page.

  WHY THESE OVERRIDES RATHER THAN EDITING THE RULES ABOVE. The unroll, the
  delay, the grid-row animation and the reduced-motion guards are all still
  exactly right -- what changed is the surface. Keeping the mechanism and
  replacing the skin means the one thing that is genuinely hard here (opening
  to a height nobody measured, with no JavaScript) is not touched.
*/
.banner {
  display: grid;
  grid-template-rows: 1fr;
  overflow: hidden;
  border-top: 3px solid var(--banner-rule);
  border-bottom: 1px solid var(--hairline);
  background: none;
  border: 0;
}

@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. */
/* ------------------------------------------- the banner as a glowing card */
.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: 1.6rem var(--gutter);
  padding: var(--space-lg) 3.25rem var(--space-lg) var(--space-lg);
  border-radius: 22px;
  background: linear-gradient(
    to bottom, var(--banner-top), var(--banner-bottom));
  border: 2px solid var(--banner-rule);
  box-shadow:
    0 0 0 1px rgb(255 255 255 / 55%) inset,
    0 0 12px var(--banner-glow),
    0 0 34px var(--banner-glow),
    0 0 72px var(--banner-halo);
}

/* 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.
*/
/* "FREE, ALWAYS" as a pill, which is how the mock-up sets it and how the
   app's own badge already looks. */
.banner-tag {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
  display: inline-block;
  margin: 0 0 var(--space-md);
  padding: 0.3rem 0.85rem;
  border-radius: 999px;
  background: var(--accent-soft);
  border: 1px solid var(--banner-rule);
}

.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);
}

/*
  The two headings of the banner, which are one rule on purpose (R244.3):
  Javier asked for them to be the same size, and sharing a declaration is
  what makes that a fact rather than two numbers that agree today.

  THE TYPE IS SET IN ONE PLACE, further down with the rest of the card. It
  used to be set here AND there, seven hundred lines apart, and the request
  to make it smaller landed on this copy while the other one went on
  winning -- so the heading stayed at 2.45rem while the stylesheet said
  1.65. `css.test.ts` now refuses a selector that has two top-level rules.
*/
.banner-free,
.banner-hire-title {
  margin: 0;
  font-weight: 700;
  color: var(--text-strong);
  letter-spacing: -0.01em;
  /* Smaller again, at Javier's word, and bold -- the two headings share
     this one declaration, so they cannot be made the same size twice. */
  font-size: clamp(1.05rem, 0.92rem + 0.5vw, 1.3rem);
  line-height: 1.25;
  /* 42ch is one character more than the longer of the two sentences needs,
     so neither is forced to break where the other does not. */
  max-width: 42ch;
  text-wrap: balance;
}

.banner-thanks {
  margin: 0.45rem 0 0;
  color: var(--text-body);
  font-size: clamp(0.98rem, 0.9rem + 0.35vw, 1.15rem);
  max-width: 52ch;
  margin-top: 0.7rem;
}

.banner-hire-line {
  margin: 0.7rem 0 0;
  color: var(--text-body);
  /* The same size as `.banner-thanks` on the left (R244.6), and from the
     same clamp rather than a number that happens to match it today. */
  font-size: clamp(0.98rem, 0.9rem + 0.35vw, 1.15rem);
  max-width: 52ch;
}

/* The button and its small print sit on one line once there is room. */
.banner-act {
  margin-top: 1.25rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem 1rem;
}

/*
  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);
}

/* The close control, as a round button in the corner of the card. */
.banner-close {
  position: absolute;
  width: 2.25rem;
  height: 2.25rem;
  display: grid;
  place-items: center;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--text-muted);
  border: 1px solid transparent;
  border-radius: 999px;
  cursor: pointer;
  top: calc(1.6rem + 0.6rem);
  inset-inline-end: calc(var(--gutter) + 0.6rem);
  background: rgb(0 0 0 / 5%);
  border-color: var(--hairline);
}

.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;
  }
}



@media (min-width: 52rem) {
  .banner-inner {
    margin: 1.9rem auto;
    /*
      WIDER THAN THE PAGE COLUMN, AND DELIBERATELY SO.

      Everything else on the site lives in a 64rem measure, because that is
      how wide prose should be. This is not prose -- it is a card with a
      headline and a button, and at 64rem inside a 1600px window it read as
      a small notice floating in the middle of nothing. The mock-up has it
      nearly edge to edge, which is what gives the type room to be large.
    */
    max-width: 84rem;
    width: calc(100% - 2 * var(--gutter));
    padding: 2.25rem 4rem 2.25rem 2.5rem;
    gap: var(--space-md) 3rem;
    grid-template-columns: 1.45fr 1fr;
  }
}

/*
  THE GLOW BREATHES, SLOWLY, AND THEN STOPS.

  Six cycles at eight seconds, matching the button above it, and then it
  settles. A border that pulses forever is a browser tab somebody closes.
*/
@media (prefers-reduced-motion: no-preference) {
  .banner-inner {
    animation: banner-glow 8s ease-in-out var(--banner-delay) 6;
  }
}

@keyframes banner-glow {
  0%, 100% {
    box-shadow:
      0 0 0 1px rgb(255 255 255 / 55%) inset,
      0 0 12px var(--banner-glow),
      0 0 34px var(--banner-glow),
      0 0 72px var(--banner-halo);
  }
  50% {
    box-shadow:
      0 0 0 1px rgb(255 255 255 / 55%) inset,
      0 0 18px var(--banner-glow),
      0 0 52px var(--banner-glow),
      0 0 104px var(--banner-halo);
  }
}







/*
  UNDER THE BUTTON, NOT BESIDE IT.

  It was three short lines crammed into the space left over next to the
  button. It carries a link now, and a sentence with somewhere to go in it
  deserves a line of its own.
*/
.banner-assurance {
  margin: 0.75rem 0 0;
  font-size: 0.88rem;
  color: var(--text-muted);
  /* Wide enough for the sentence and its link on one line; at 44ch it broke
     between "see" and "what is coming", which splits a link in half. */
  max-width: 60ch;
}

.banner-assurance a {
  color: var(--accent);
  font-weight: 600;
}

/* The second offer is a panel INSIDE the card, as in the mock-up -- which is
   what stops it reading as a second column of the same paragraph. */
@media (min-width: 52rem) {
  .banner-hire {
    border-inline-start: 0;
    padding: var(--space-lg);
    border-radius: 16px;
    /*
      It hugs its content rather than stretching: stretched, it left a hand's
      width of empty panel below the links, which reads as something that
      failed to load. And it starts below the close button's row, so the X
      sits in the card's corner rather than on top of the panel.
    */
    align-self: start;
    margin-top: 0;
    background: rgb(0 0 0 / 3.5%);
    border: 1px solid var(--hairline);
  }
}

@media (prefers-color-scheme: dark) {
  .banner-hire {
    background: rgb(255 255 255 / 4%);
  }
}



@media (min-width: 52rem) {
  /*
    IN THE CARD'S OWN CORNER.

    This was `calc((100% - min(100%, var(--page))) / 2 + 0.75rem)`, which
    computed an inset from the VIEWPORT back to the page column -- correct
    when the banner was a full-width ribbon and meaningless once the card
    became a centred box that is its own positioning context. The arithmetic
    landed the X on top of the panel beside it.
  */
  .banner-close {
    top: calc(1.9rem + 0.55rem);
    inset-inline-end: 0.9rem;
  }
}

/* ------------------------------------------------------- the store badges */

/*
  APPLE'S BADGE AT APPLE'S PROPORTIONS (R247.7).

  Their guidelines set a minimum height and require clear space around it, so
  the badge is sized by HEIGHT and lets its own aspect ratio decide the width
  -- a badge squeezed to match a button's width is a badge used wrongly.

  The Android slot is the same height and the same corner radius so the two
  sit as a pair, but it is a span and looks like one: dashed, quiet, clearly
  not a thing to press. The day there is a Play listing it becomes the real
  badge and this rule stops applying to it.
*/
.store-badge {
  display: inline-flex;
  align-items: center;
  height: 48px;
  border-radius: 8px;
}

.store-badge-img {
  display: block;
  height: 48px;
  width: auto;
}

/* The badge that is artwork and not yet a link: full colour, so the site
   looks finished, with the cursor saying it is not pressable. */
.store-badge.waiting {
  cursor: default;
}

/* The row they sit in: badges are images, so they need their own alignment
   rather than the button row's. */
.store-row {
  align-items: center;
  gap: 0.75rem 1rem;
}

/* ------------------------------------------- the hire block's own heading */

/*
  THE SAME SIZE AS "Make a donation" (R247.4).

  Javier asked for it explicitly, and the reason is that the two are the same
  kind of thing: a section heading on the same page. The giving block's `h2`
  takes the page's default, so this one must simply not be given a different
  one -- which it was, through `.business h2` further up. Setting them from
  one rule is how they stay equal.
*/
.give > h2,
.business > h2,
.church > h2,
.give-cta > h2 {
  font-size: 1.45rem;
  line-height: 1.25;
  margin-top: 0;
}

/*
  THE SECOND GIVING BUTTON IN THE BANNER (R247.1).

  Quieter than the first, because they are a pair and not a competition: the
  monthly gift is what actually keeps the thing running, so it is the gold
  one, and the one-off sits beside it as the easier yes rather than beneath
  it as an afterthought.
*/
.btn.give-quiet {
  background: transparent;
  border-color: var(--banner-rule);
  color: var(--accent);
  font-weight: 650;
}

.btn.give-quiet:hover {
  background: rgb(255 255 255 / 55%);
}
