/*
  CardJar's palette, from the same brand sheet the app and the share page use. The one
  rule that carries over from the apps: a barcode plate is pure white with pure black
  bars in light mode and dark alike, so the theming stops at the plate's edge. On this
  site the plates only appear inside screenshots, which is why the screens are never
  filtered, inverted or tinted.

  The page alternates between grounds rather than running one colour top to bottom.
  Every page opens on deep pine under a floating pine masthead; the body is cream, with
  white raised panels and pine bands; rows of notes are blocks of pine, mint and white;
  the last call to action is mint; the footer is the darkest pine there is. A single
  flat ground is what makes a long page read as a document instead of a product.
*/

/* A country flag emoji is a pair of Unicode regional indicator letters, and Windows
   ships no font with flag glyphs — Chrome and Edge there draw the letters instead, so a
   coverage page headed "🇦🇺 38 Australian loyalty cards" reads "AU 38 Australian loyalty
   cards". This supplies the missing glyphs for the twelve countries the catalogue covers.

   `unicode-range` is what makes it safe to put at the front of the body stack: the
   browser only consults this face for those codepoints, so it cannot touch a single
   letter of text. `block` rather than `swap` because the fallback here is the wrong
   thing rather than the right thing in the wrong typeface — better a beat of nothing
   than a flash of "AU". It is 2KB and same-origin, so that beat is not a real one.

   font/twemoji-country-flags-LICENCE.txt carries the attribution and how it was cut. */
@font-face {
  font-family: 'Twemoji Country Flags';
  font-style: normal;
  font-weight: 400;
  font-display: block;
  unicode-range: U+1F1E6-1F1FF;
  src: url(/font/twemoji-country-flags.39e7a60d.woff2) format('woff2');
}

/*
  What the page is drawn in for the moment before Poppins arrives: a local sans scaled
  and spaced to Poppins's own metrics, so the swap moves nothing. Without it the home
  page's three-line headline reflowed when the font landed and took the hero, its card
  wall and its buttons with it, which measured as a layout shift of over 1.0 on a phone.
*/
@font-face {
  font-family: 'Poppins Fallback';
  src: local('Arial'), local('Helvetica'), local('Liberation Sans');
  size-adjust: 112.16%;
  ascent-override: 93.62%;
  descent-override: 31.21%;
  line-gap-override: 8.92%;
}

@font-face {
  font-family: Poppins;
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/font/poppins-400.a2610771.woff2) format('woff2');
}
@font-face {
  font-family: Poppins;
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/font/poppins-600.63ae1960.woff2) format('woff2');
}
@font-face {
  font-family: Poppins;
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/font/poppins-700.425af348.woff2) format('woff2');
}

:root {
  --pine: #145C4A;
  --pine-deep: #0D3529;
  --cream: #F7F1E3;
  --cream-dim: #EDE7D8;
  --mint: #7FC9AC;
  --mint-hi: #A8E2C9;

  --ground: var(--cream-dim);
  --raised: #FFFFFF;
  --sunken: rgba(13, 53, 41, 0.045);
  --text: var(--pine-deep);
  --text-soft: rgba(13, 53, 41, 0.68);
  --text-faint: rgba(13, 53, 41, 0.5);
  --accent: var(--pine);
  --on-accent: var(--cream);
  --hairline: rgba(13, 53, 41, 0.1);
  --hairline-firm: rgba(13, 53, 41, 0.16);

  /* The pine band, and the tokens that flip inside it. */
  --band: var(--pine-deep);
  --band-text: var(--cream);
  --band-soft: rgba(247, 241, 227, 0.72);
  --band-faint: rgba(247, 241, 227, 0.5);
  --band-line: rgba(247, 241, 227, 0.16);
  --band-accent: var(--mint);

  --bezel: #101A17;
  --bezel-edge: rgba(247, 241, 227, 0.14);

  --pine-ink: #082219;

  /* The floating masthead, the language menu that drops out of it, and the cookie
     banner. Dark frosted glass rather than a lighter green: mid pine on the deep pine
     hero was two nearly matching greens, which read as a mistake rather than a choice.
     A darker glass is held apart from the hero by its hairline and a lit top edge, and
     stays a dark object over the cream further down. */
  --float: #08221A;
  --float-raised: #0F2D24;
  --float-edge: rgba(247, 241, 227, 0.14);
  --float-lit: rgba(247, 241, 227, 0.1);

  /* How far the opening band reaches up behind the masthead: its 12px gap and its
     60px capsule. A little taller under 840px, where the menu button is a 44px target. */
  --mast: 72px;

  /* The footer: the darkest ground on the page, so the page ends rather than fades. */
  --foot: var(--pine-ink);

  --lift: 0 1px 2px rgba(13, 53, 41, 0.05), 0 8px 18px -12px rgba(13, 53, 41, 0.28);
  --lift-high: 0 2px 4px rgba(13, 53, 41, 0.05), 0 30px 50px -28px rgba(13, 53, 41, 0.42);
  --lift-phone: 0 2px 5px rgba(13, 53, 41, 0.16), 0 36px 60px -32px rgba(13, 53, 41, 0.55);

  --measure: 34rem;
  --wide: 70rem;
  --radius: 20px;
}

/*
  Dark, twice over, with the same values: for a system set to dark unless the reader has
  chosen light in the footer, and for a reader who has chosen dark on a light system.
  `check_site.py` fails if the two ever differ. The choice is the `data-theme` attribute
  the head's one inline script sets before the first paint; Auto sets nothing.
*/
:root { color-scheme: light dark; }
:root[data-theme="light"] { color-scheme: light; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ground: #08120F;
    --raised: #101C18;
    --sunken: rgba(247, 241, 227, 0.05);
    --text: var(--cream);
    --text-soft: rgba(247, 241, 227, 0.72);
    --text-faint: rgba(247, 241, 227, 0.52);
    --accent: var(--mint);
    --on-accent: var(--pine-deep);
    --hairline: rgba(247, 241, 227, 0.13);
    --hairline-firm: rgba(247, 241, 227, 0.2);

    /* Pine on near-black has too little separation, so the band lifts instead. */
    --band: #12211C;
    --band-line: rgba(247, 241, 227, 0.13);

    --bezel: #1D2A26;

    /* A step off near-black, the way the band is, with the edge doing the rest. */
    --float: #0B1814;
    --float-raised: #13241F;
    --float-edge: rgba(247, 241, 227, 0.14);
    --foot: #040A08;

    --lift: 0 1px 2px rgba(0, 0, 0, 0.5), 0 8px 18px -12px rgba(0, 0, 0, 0.7);
    --lift-high: 0 2px 4px rgba(0, 0, 0, 0.5), 0 30px 50px -28px rgba(0, 0, 0, 0.9);
    --lift-phone: 0 2px 5px rgba(0, 0, 0, 0.6), 0 36px 60px -32px rgba(0, 0, 0, 0.95);
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --ground: #08120F;
  --raised: #101C18;
  --sunken: rgba(247, 241, 227, 0.05);
  --text: var(--cream);
  --text-soft: rgba(247, 241, 227, 0.72);
  --text-faint: rgba(247, 241, 227, 0.52);
  --accent: var(--mint);
  --on-accent: var(--pine-deep);
  --hairline: rgba(247, 241, 227, 0.13);
  --hairline-firm: rgba(247, 241, 227, 0.2);

  /* Pine on near-black has too little separation, so the band lifts instead. */
  --band: #12211C;
  --band-line: rgba(247, 241, 227, 0.13);

  --bezel: #1D2A26;

  /* A step off near-black, the way the band is, with the edge doing the rest. */
  --float: #0B1814;
  --float-raised: #13241F;
  --float-edge: rgba(247, 241, 227, 0.14);
  --foot: #040A08;

  --lift: 0 1px 2px rgba(0, 0, 0, 0.5), 0 8px 18px -12px rgba(0, 0, 0, 0.7);
  --lift-high: 0 2px 4px rgba(0, 0, 0, 0.5), 0 30px 50px -28px rgba(0, 0, 0, 0.9);
  --lift-phone: 0 2px 5px rgba(0, 0, 0, 0.6), 0 36px 60px -32px rgba(0, 0, 0, 0.95);
}

* { box-sizing: border-box; }

/*
  Every anchor on this site lands under the masthead without this. It is `position:
  sticky` at `top: 0`, so a browser scrolling `#install` or `#contact` to the top of the
  viewport puts it behind 70px of blurred header: the nav's own install link jumped to
  the buttons and then hid them, which is the worst version of the bug because the link
  looks like it did nothing at all.

  100px is the floating masthead's 12px gap and 60px capsule plus room to breathe; on
  the shorter mobile capsule that reads as a little more space above the target, which
  is the same answer a shade looser.

  It is `scroll-margin` on everything in the page and the footer rather than
  `scroll-padding` on the page itself, which is what it was first. Padding counts the
  top 100px as hidden for everything, the masthead's own controls included, so tapping
  the menu button or the language switcher focused something "under the header" and
  the browser scrolled the page up to reveal it, further on every tap. The margin
  covers every link target and every element keyboard focus can land on in the page,
  and nothing in the masthead, which never needs revealing.
*/
html { scroll-behavior: smooth; }
main *,
footer.site * { scroll-margin-top: 100px; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { transition: none !important; }
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--text);
  font: 16px/1.62 'Twemoji Country Flags', Poppins, 'Poppins Fallback', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

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

a { color: var(--accent); text-underline-offset: 3px; }

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

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--accent);
  color: var(--on-accent);
  padding: 12px 18px;
  border-radius: 0 0 12px 0;
  z-index: 10;
}
.skip:focus { left: 0; }

/* ---------------------------------------------------------------- shell */

.wrap { width: 100%; max-width: var(--wide); margin: 0 auto; padding-inline: 24px; }

/*
  The masthead floats: a pine capsule exactly as wide as the content under it, with the
  page showing through the gap above and either side as it scrolls past. The header
  itself is a transparent sticky strip that only exists to hold the gap, so it lets
  clicks through and the capsule takes them back.

  The capsule reaches past the content on either side where the page has room, 88px
  each way on a wide screen, so it reads as the page's own frame rather than one more
  block in the column under it. On a narrower screen it keeps 12px of page either side,
  half the content's gutter, so it is still the wider of the two.

  It carries the band's foreground tokens, the way `.band` does, so the nav, the menu
  and the language switcher recolour without modifiers of their own. It stays pine in
  both themes because a floating object has to be darker than whatever passes under
  it, and in dark mode that is near-black.
*/
.masthead {
  position: sticky;
  top: 0;
  z-index: 8;
  /* Always exactly the height the opening section reaches up behind. The phone menu
     opens downwards out of this box and over the page rather than making it taller:
     a taller masthead pushed the page down and showed a strip of cream above the pine. */
  height: var(--mast);
  padding-top: 12px;
  pointer-events: none;
}
.masthead .wrap {
  display: flex;
  align-items: center;
  /* The same 6px as between the links, so the language switcher sits as one more item
     in the row rather than a separate group. The wordmark keeps its distance with
     `margin-right: auto`. */
  gap: 6px;
  min-height: 60px;
  flex-wrap: wrap;
  width: calc(100% - 24px);
  max-width: calc(var(--wide) + 128px);
  padding: 7px 8px 7px 24px;
  border-radius: 30px;
  pointer-events: auto;
  /* A browser without color-mix() gets the capsule solid rather than see-through. */
  background: var(--float);
  background: color-mix(in srgb, var(--float) 84%, transparent);
  backdrop-filter: saturate(1.6) blur(24px);
  -webkit-backdrop-filter: saturate(1.6) blur(24px);
  box-shadow: inset 0 1px 0 var(--float-lit), 0 0 0 1px var(--float-edge),
    0 18px 40px -18px rgba(0, 0, 0, 0.6);

  --text: var(--band-text);
  /* A touch brighter than the band's own soft text, for the lighter green behind it. */
  --text-soft: rgba(247, 241, 227, 0.86);
  --text-faint: var(--band-faint);
  --accent: var(--band-accent);
  --on-accent: var(--pine-deep);
  --hairline: var(--band-line);
  --hairline-firm: rgba(247, 241, 227, 0.26);
  --sunken: rgba(247, 241, 227, 0.1);
  --raised: var(--float-raised);
  color: var(--text);
}
/* The mark's square is the capsule's own pine, so it gets a hairline to stand on. */
.masthead .brand .mark { box-shadow: 0 0 0 1px rgba(247, 241, 227, 0.2); }
.brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-weight: 700;
  font-size: 19.5px;
  letter-spacing: -0.5px;
  color: var(--text);
  text-decoration: none;
  margin-right: auto;
}
.brand .mark {
  width: 32px;
  height: 32px;
  flex: none;
  border-radius: 22.4%;
  box-shadow: var(--lift);
}
.brand em { font-style: normal; color: var(--accent); }

/* ---------------------------------------------------------------- the menu */

/*
  The menu is a checkbox and a label, so it opens without any script: the site serves
  one, the visit counter, and the menu does not depend on it. The input is moved out of
  sight rather than removed from the layout, because `display: none` would take its
  keyboard focus with it and the menu would only open for a mouse.
*/
.menu-toggle {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  opacity: 0;
  pointer-events: none;
}
.menu-button { display: none; }

.menu-bars {
  position: relative;
  width: 16px;
  height: 2px;
  border-radius: 2px;
  background: currentColor;
  flex: none;
}
.menu-bars::before,
.menu-bars::after {
  content: '';
  position: absolute;
  left: 0;
  width: 16px;
  height: 2px;
  border-radius: 2px;
  background: currentColor;
}
.menu-bars::before { top: -5px; }
.menu-bars::after { top: 5px; }

.nav { display: flex; align-items: center; gap: 2px 6px; flex-wrap: wrap; margin-left: auto; }
/* 16.5px is the size the stacked menu below 840px has always used, so this is the
   desktop nav catching up with it rather than a new number. Below that the nav is a
   menu and this rule is overridden anyway. */
.nav a {
  color: var(--text-soft);
  text-decoration: none;
  font-size: 16.5px;
  font-weight: 500;
  padding: 10px 16px;
  border-radius: 999px;
  transition: color 0.15s ease, background-color 0.15s ease;
}
.nav a:hover { color: var(--text); background: var(--sunken); }
.nav a[aria-current='page'] { color: var(--text); font-weight: 600; background: var(--sunken); }
/* Install is the one thing in the row a reader can act on, so it is a button, and the
   last thing in the row, after the language switcher, where a site's button usually is.
   Every item before it is 16px of padding either side of its text and 6px apart, so text
   sits 38px from text; Install shows its padding as a filled pill, so it takes 16px more
   beside it and its visible edge is 38px from the switcher's text too. */
.nav-install {
  color: var(--on-accent);
  text-decoration: none;
  font-size: 16.5px;
  font-weight: 600;
  padding: 10px 16px;
  margin-left: 16px;
  border-radius: 999px;
  background: var(--accent);
  transition: background-color 0.15s ease;
}
.nav-install:hover { background: var(--mint-hi, var(--accent)); color: var(--on-accent); text-decoration: none; }

main { display: block; }

section { padding: 76px 0; }
section + section { padding-top: 0; }

/* ---------------------------------------------------------------- the band */

/*
  Full bleed, and it brings its own foreground tokens with it so everything nested
  inside recolours without needing a modifier of its own.

  Every page opens on the same pine, under the floating masthead, so the opening section
  carries the band's tokens too. It is a `.wrap` on most pages rather than a full-width
  block, so its ground is a border image pushed out to the viewport's edges: painted
  outside the box, which never makes the page scroll sideways, and with nothing in the
  markup to change on twenty pages. It reaches up behind the masthead by the masthead's
  own height, so the capsule floats on pine rather than on a strip of cream.
*/
main > section:first-child {
  margin-top: calc(-1 * var(--mast));
  padding-top: calc(var(--mast) + 64px);
  padding-bottom: 80px;
  color: var(--band-text);
  border-image: linear-gradient(var(--band), var(--band)) fill 0 // 0 100vw;
}
main > section:first-child + section { padding-top: 76px; }
main > section:first-child .eyebrow { color: var(--accent); }
.band,
main > section:first-child {
  --text: var(--band-text);
  --text-soft: var(--band-soft);
  --text-faint: var(--band-faint);
  --accent: var(--band-accent);
  --on-accent: var(--pine-deep);
  --hairline: var(--band-line);
  --hairline-firm: rgba(247, 241, 227, 0.26);
  --sunken: rgba(247, 241, 227, 0.07);
  --raised: rgba(247, 241, 227, 0.06);
}
.band {
  background: var(--band);
  color: var(--band-text);
  padding: 84px 0;
}
section + .band { padding-top: 84px; }

/* The page's last word, centred, running straight into the footer. Mint rather than
   pine, so the one thing a reader can do is the brightest thing on the page and the
   footer under it reads as the end. Mint in dark mode too: it is the brand colour and
   the badges are black artwork, which is legible on it either way. */
.band.centre {
  text-align: center;
  background: var(--mint);
  color: var(--pine-ink);
  padding-block: 104px;

  --text: var(--pine-ink);
  --text-soft: rgba(8, 34, 25, 0.78);
  --text-faint: rgba(8, 34, 25, 0.62);
  --accent: var(--pine-deep);
  --on-accent: var(--cream);
  --hairline: rgba(8, 34, 25, 0.16);
  --hairline-firm: rgba(8, 34, 25, 0.32);
  --sunken: rgba(8, 34, 25, 0.06);
}
.band.centre h2 { font-size: clamp(38px, 7vw, 84px); line-height: 1.14; letter-spacing: -0.055em; }
.band.centre .measure,
.band.centre .footnote { margin-inline: auto; }
.band.centre .actions { justify-content: center; }
.band.centre .eyebrow { justify-content: center; }
.band + section { padding-top: 76px; }

/* ---------------------------------------------------------------- type */

h1, h2, h3 {
  letter-spacing: -0.032em;
  line-height: 1.1;
  margin: 0 0 18px;
  text-wrap: balance;
}
/* 1.1 to 1.14 on the big headings, because tight display type puts a descender on one line into
   an ascender on the next ("y" over "i", "j" over "k"); measured glyph against glyph. */
h1 { font-size: clamp(38px, 6.2vw, 66px); font-weight: 700; letter-spacing: -0.05em; line-height: 1.14; }
h2 { font-size: clamp(29px, 4.6vw, 50px); font-weight: 700; letter-spacing: -0.045em; line-height: 1.1; }
h3 { font-size: 20px; font-weight: 600; line-height: 1.28; letter-spacing: -0.02em; }

p, ul, ol { margin: 0 0 18px; text-wrap: pretty; }
li { margin-bottom: 8px; }

.lede { font-size: clamp(17px, 2.1vw, 20.5px); color: var(--text-soft); line-height: 1.52; }
.measure { max-width: var(--measure); }

.eyebrow {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin: 0 0 18px;
}
.eyebrow::before {
  content: '';
  width: 22px;
  height: 2px;
  border-radius: 2px;
  background: var(--accent);
  flex: none;
}
.eyebrow a { color: inherit; }

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

.hero {
  padding-top: 64px;
  position: relative;
  /* `clip` and not `hidden`. Both cut the card wall off at the hero's edge, but `hidden`
     makes the hero a scroll box, and the store buttons are inside it: the nav's Install
     link then scrolled the hero's own contents up past its top rather than scrolling
     the page, and the buttons it jumped to vanished under the masthead. `hidden` first
     for a browser older than `clip`. */
  overflow: hidden;
  overflow: clip;
}
/* A soft light behind the phones, so they sit in the page rather than on it. */
.hero::before {
  content: '';
  position: absolute;
  top: -180px;
  right: -140px;
  width: min(820px, 86vw);
  height: min(820px, 86vw);
  background: radial-gradient(circle closest-side, rgba(127, 201, 172, 0.16), transparent 72%);
  pointer-events: none;
  z-index: 0;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .hero::before { background: radial-gradient(circle, rgba(127, 201, 172, 0.13), transparent 66%); }
}
:root[data-theme="dark"] .hero::before { background: radial-gradient(circle, rgba(127, 201, 172, 0.13), transparent 66%); }
.hero > * { position: relative; z-index: 1; }

/*
  Three parts rather than two, so a phone screen can sit between the opening line and
  the detail underneath it. On a narrow screen the old two-column hero put the whole
  argument before any picture, which is a lot of prose to read on trust.
*/
.hero-grid {
  display: grid;
  gap: 34px;
  grid-template-columns: 1fr;
  align-items: center;
}
/*
  One column below 900px, in the order a reader on a phone needs: the sentence, the
  buttons, then the screenshots. The buttons used to sit under the three points, which
  put the only thing a reader can act on below two phone screenshots and three
  paragraphs — a whole screen of scrolling on a 390px phone before the App Store badge
  appeared at all. The screenshots are the evidence for the sentence above them and
  keep working underneath the buttons; the points are the detail, and detail comes
  after the offer.

  The desktop layout is unchanged: named areas put the call to action back under the
  points, in the left column beside the phones, which is where it has always been on a
  screen wide enough to show both at once.
*/
/* Stacked, the grid's own 34px row gap is the space above the buttons, so they do not
   also carry the top margin they need when they follow a paragraph directly. The
   sentence drops its trailing paragraph margin for the same reason: 18px from `p` plus
   34px of row gap is 52px of nothing between the sentence and the only thing on the
   screen a reader can act on, and neither number was chosen with the other in mind. */
.hero-cta .actions { margin-top: 0; }
.hero-copy > :last-child { margin-bottom: 0; }
@media (min-width: 900px) {
  .hero-grid {
    grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
    grid-template-areas:
      'copy phones'
      'tail phones'
      'cta  phones';
    align-items: start;
    gap: 8px 60px;
  }
  .hero-copy { grid-area: copy; }
  /* Back on, because two columns give this margin a different job: it is the space
     between the sentence and the three points, which the 8px row gap alone would not
     be. The 18px is the `p, ul, ol` margin it came from. */
  .hero-copy > :last-child { margin-bottom: 18px; }
  .hero-tail { grid-area: tail; }
  .hero-cta { grid-area: cta; }
  .hero .phone-duo { grid-area: phones; align-self: center; }
  /* 30px between the points and the buttons, as when they were one block: the last
     point's own 14px and the 8px row gap are the rest of it. Measured rather than
     reasoned — the buttons sit on the same pixel row as before this was split out. */
  .hero-cta .actions { margin-top: 8px; }
}

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

.hero-points { list-style: none; padding: 0; margin: 0; }
.hero-points li {
  position: relative;
  padding-left: 32px;
  color: var(--text-soft);
  margin-bottom: 14px;
}
.hero-points li::before {
  content: '';
  position: absolute;
  left: 3px;
  top: 0.62em;
  width: 9px;
  height: 9px;
  border-radius: 3px;
  background: var(--accent);
}
.hero-points strong { color: var(--text); font-weight: 600; }

/*
  The home page's opening: the headline over a wall of real cards, the jar screen
  hanging off the bottom edge, and the wall fading out behind the words so they never
  sit on a logo. Full width rather than a `.wrap`, because the wall runs to the edge.
*/
.hero.shuffle { padding-bottom: 0; isolation: isolate; }
/* The coverage pages open on the same wall, behind an ordinary split rather than the
   home page's grid, so the section keeps its own bottom padding. */
.hero.backdrop { padding-bottom: 80px; }
/* Their text column is wider than the home page's, so the wall stays behind further. */
.hero.backdrop::after {
  background:
    linear-gradient(0deg, var(--band), transparent 22%),
    linear-gradient(90deg, var(--band) 46%, rgba(13, 53, 41, 0.86) 58%, rgba(13, 53, 41, 0.2) 76%, rgba(13, 53, 41, 0.4));
  background:
    linear-gradient(0deg, var(--band), transparent 22%),
    linear-gradient(90deg,
      var(--band) 46%,
      color-mix(in srgb, var(--band) 86%, transparent) 58%,
      color-mix(in srgb, var(--band) 20%, transparent) 76%,
      color-mix(in srgb, var(--band) 40%, transparent));
}
.hero.shuffle::before { display: none; }
.hero.shuffle::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  /* Without color-mix(), the light theme's pine at the same steps: close in dark mode,
     and the words stay on a solid ground either way, which is the part that matters. */
  background:
    linear-gradient(0deg, var(--band), transparent 22%),
    linear-gradient(90deg, var(--band) 30%, rgba(13, 53, 41, 0.86) 46%, rgba(13, 53, 41, 0.16) 70%, rgba(13, 53, 41, 0.36));
  background:
    linear-gradient(0deg, var(--band), transparent 22%),
    linear-gradient(90deg,
      var(--band) 30%,
      color-mix(in srgb, var(--band) 86%, transparent) 46%,
      color-mix(in srgb, var(--band) 16%, transparent) 70%,
      color-mix(in srgb, var(--band) 36%, transparent));
}
.shuffle-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.46fr);
  grid-template-areas:
    'copy      phone'
    'cta       phone'
    'countries phone';
  align-items: start;
  column-gap: 56px;
  padding-bottom: 72px;
}
.shuffle .hero-copy { grid-area: copy; }
.shuffle .hero-cta { grid-area: cta; }
.shuffle .hero-cta .actions { margin-top: 30px; }
.shuffle-grid h1 {
  font-size: clamp(50px, 6.8vw, 96px);
  /* Room for a descender over an ascender: "jar" sits on "Works", and at 0.92 the "j"
     and the "k" touched. 1.1 is the least that clears every pair in all eight languages
     at every width, measured glyph against glyph; at 1.0 "p" still met "f" in German. */
  line-height: 1.1;
  letter-spacing: -0.06em;
  margin-bottom: 26px;
  max-width: none;
}
/* For the translations whose lines run long ("Une application.", "Incluso sin
   conexión."): a step down, so the three lines stay three on a desktop. */
.shuffle-grid h1.long { font-size: clamp(42px, 5.4vw, 76px); }
.shuffle-grid h1 .count { color: var(--accent); font-variant-numeric: tabular-nums; }
.shuffle .lede { max-width: 31rem; }
.hero-phone {
  grid-area: phone;
  align-self: end;
  justify-self: end;
  width: 100%;
  max-width: 300px;
  margin-bottom: -150px;
  transform: rotate(5deg);
}
.hero-countries {
  grid-area: countries;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  max-width: 38rem;
  margin: 26px 0 0;
  font-size: 13.5px;
  color: var(--text-faint);
}
.hero-countries a,
.country-chips a {
  color: var(--text);
  font-weight: 500;
  text-decoration: none;
  padding: 5px 12px;
  border: 1px solid var(--hairline-firm);
  border-radius: 999px;
  background: var(--sunken);
}
.hero-countries a:hover,
.country-chips a:hover { border-color: var(--accent); color: var(--accent); }
/* The twelve countries, one chip each, beside the link to every retailer rather than in
   the hero, where they were three rows of pills between the headline and the store
   buttons. Each is the only link the home page has to that country's list. */
.country-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 22px 0 0;
  font-size: 13.5px;
}

/*
  Sized to cover the right of the hero on any screen. The cards grow with the viewport
  so six columns reach the edge of a wide monitor, and the columns are centred on the
  hero and taller than it, so their drift never shows an end. The drift is slow enough
  to sit behind the words and fast enough to be seen moving: about ten pixels a second,
  each column the opposite way to its neighbours.
*/
.hero > .wall {
  --card: clamp(190px, 12.5vw, 270px);
  --gap: clamp(16px, 1.2vw, 24px);
  position: absolute;
  /* In pixels from the top rather than a share of the hero's height: anything that
     changes that height (a font arriving, a line wrapping) would otherwise move every
     card on the wall, and the wall is most of the screen. */
  top: -420px;
  height: 2000px;
  left: 34%;
  right: -10%;
  z-index: 0;
  display: flex;
  align-items: center;
  gap: var(--gap);
  transform: rotate(-14deg);
  pointer-events: none;
  contain: layout paint;
}
.wall-col {
  display: flex;
  flex-direction: column;
  gap: var(--gap);
  animation: drift 28s ease-in-out infinite alternate;
}
.wall-col:nth-child(even) { animation-direction: alternate-reverse; animation-duration: 34s; }
.wall-col:nth-child(3n) { animation-duration: 31s; }
@keyframes drift {
  from { transform: translateY(9%); }
  to { transform: translateY(-9%); }
}
/* The app's tile shape. Each is a `<use>` of its face in the wall's sprite, and the
   faint outline is what shows until the page has loaded and that file arrives. */
.wall-card {
  flex: none;
  display: block;
  width: var(--card);
  aspect-ratio: 150 / 106;
  border-radius: 14px;
  overflow: hidden;
  background: rgba(247, 241, 227, 0.06);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16), 0 18px 30px -18px rgba(0, 0, 0, 0.7);
}
.wall-card svg { display: block; width: 100%; height: 100%; opacity: 0; transition: opacity 0.6s ease; }
/* The faces arrive after the page has loaded (see `wall_columns` in build.py), and fade in
   over the outlines rather than popping. */
.wall.is-drawn .wall-card svg { opacity: 1; }

@media (max-width: 899px) {
  .shuffle-grid {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: 'copy' 'cta' 'countries' 'phone';
    padding-bottom: 0;
  }
  .hero-phone { justify-self: center; max-width: 230px; margin: 44px 0 -120px; transform: rotate(4deg); }
  .hero > .wall { top: 420px; left: -45%; right: -45%; height: 1400px; opacity: 0.6; }
  .hero.shuffle::after {
    background: linear-gradient(180deg, var(--band) 52%, rgba(13, 53, 41, 0.5) 82%, var(--band));
    background: linear-gradient(180deg, var(--band) 52%, color-mix(in srgb, var(--band) 50%, transparent) 82%, var(--band));
  }
}

/* A running line of scheme names under the hero. Slow enough to read. */
.ticker {
  overflow: hidden;
  white-space: nowrap;
  background: var(--mint);
  color: var(--pine-ink);
  border-block: 2px solid var(--pine-ink);
  position: relative;
  z-index: 1;
  /* Its own layout: the track is thousands of pixels of names, and nothing outside it
     depends on how they lay out. */
  contain: layout paint;
}
.ticker-track { display: inline-flex; padding-block: 14px; animation: ticker 220s linear infinite; }
.ticker span { font-weight: 700; font-size: 18px; letter-spacing: -0.01em; }
/* Drawn rather than typed: the brand font has no star, so a glyph here sent every one
   of eighty separators to the system's font fallback, which on a slow phone was most of
   the page's layout time before it could first paint. */
.ticker span::after {
  content: '';
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-inline: 24px;
  vertical-align: 0.2em;
  background: currentColor;
  opacity: 0.45;
  transform: rotate(45deg);
}
@keyframes ticker { to { transform: translateX(-50%); } }

@media (prefers-reduced-motion: reduce) {
  .wall-col, .ticker-track { animation: none; }
}

/* The barcode formats, as a row of tags. */
.facts { display: flex; flex-wrap: wrap; gap: 8px; list-style: none; padding: 0; margin: 24px 0 0; }
.facts li {
  margin: 0;
  font-size: 13.5px;
  font-weight: 500;
  padding: 6px 13px;
  border-radius: 999px;
  border: 1px solid var(--hairline-firm);
}

/* ---------------------------------------------------------------- stats */

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 4px 32px;
  list-style: none;
  padding: 0;
  margin: 0;
}
.stat-figure {
  display: block;
  font-size: clamp(38px, 5vw, 52px);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.045em;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.stat-label {
  display: block;
  margin-top: 10px;
  font-size: 14.5px;
  line-height: 1.4;
  color: var(--text-soft);
}
.stats li { margin: 0; padding: 22px 0 0; border-top: 1px solid var(--hairline); }
.stats { margin-block: 6px 30px; }

/* ---------------------------------------------------------------- phones */

/*
  A bezel rather than a border. The screenshots are edge to edge with no device
  around them, so without one they read as flat pictures pasted onto the page.
*/
/*
  The same phone is drawn at about 150px wide in the hero pair on a narrow screen and
  at 300px in the split sections, so the corner radius has to be a proportion of the
  width rather than a fixed number. At 29px it was a fifth of the small one and cut
  real interface out of every corner: these are full-bleed captures with the jar's
  title and its cards hard against the edge, so there is nothing there to spare.

  Percentages, and the two axes given separately, because a single percentage draws an
  elliptical corner on a tall box. Every screenshot is 720x1561, so one ratio holds for
  all of them: the vertical figure is the horizontal one times that aspect. This is
  also why `container-type` is not used here. It collapsed the hero pair to nothing,
  since containment tells the grid to size these boxes without looking at what is in
  them.
*/
.phone { display: block; margin: 0; }
/* The bezel wraps the picture, not the figure: a caption inside the device body
   reads as part of the screenshot. */
.phone picture {
  display: block;
  padding: 2.4%;
  border-radius: 9.1% / 4.3%;
  background: var(--bezel);
  box-shadow: var(--lift-phone), inset 0 0 0 1px var(--bezel-edge);
}
.phone img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 7% / 3.2%;
  background: var(--cream-dim);
}
.phone figcaption,
.watch figcaption {
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--text-faint);
  padding: 16px 4px 0;
  text-align: center;
  text-wrap: pretty;
}

/* ---------------------------------------------------------------- watches */

/*
  A round display, drawn round. The renders arrive on the square canvas Play asks for,
  with black outside the circle and no bezel drawn on, so the roundness here is the
  frame's job: a ring rather than the rounded rectangle a phone gets, and the image
  clipped to the same circle so the canvas corners never show.

  Percentages again, and one is enough this time because the box is square.
*/
.watch { display: block; margin: 0; }
.watch picture {
  display: block;
  padding: 3.4%;
  border-radius: 50%;
  background: var(--bezel);
  box-shadow: var(--lift-phone), inset 0 0 0 1px var(--bezel-edge);
}
.watch img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 50%;
  background: var(--bezel);
}

/* Two watches read as a pair of screens; three read as a spec sheet. */
.watch-duo {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 20px;
  max-width: 420px;
  margin-inline: auto;
  align-items: start;
}

.phone-duo {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 20px;
  max-width: 460px;
  align-items: start;
}
.hero .phone-duo { margin-inline: auto; }
/* Staggered, because two phones at the same height read as a spec sheet. */
@media (min-width: 620px) {
  .phone-duo > :first-child { margin-top: 34px; }
  .phone-duo > :last-child { margin-bottom: 34px; }
}

.gallery {
  display: grid;
  gap: 20px;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (min-width: 700px) {
  .gallery { gap: 30px; grid-template-columns: repeat(auto-fit, minmax(215px, 1fr)); }
}
.gallery .phone { max-width: 320px; }
/* The home page's strip of screens, set down like cards on a table rather than in a
   ruled grid. Small angles, alternating, and only from 700px where the strip is a row. */
@media (min-width: 700px) {
  .gallery.tilt .phone:nth-child(odd) picture { transform: rotate(-2.5deg); }
  .gallery.tilt .phone:nth-child(even) picture { transform: rotate(2.5deg); }
  .gallery.tilt .phone:nth-child(even) { transform: translateY(18px); }
  .gallery.tilt figcaption { padding-top: 24px; }
}
.gallery .watch { max-width: 260px; margin-inline: auto; }

/* ---------------------------------------------------------------- panels */

.panel {
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: 26px;
  padding: clamp(26px, 4vw, 44px);
  box-shadow: var(--lift-high);
}
.band .panel { box-shadow: none; }

.cols {
  display: grid;
  gap: 20px;
  grid-template-columns: repeat(auto-fit, minmax(258px, 1fr));
}

/* Four cards fall three and one at the auto-fit width, a lone card with a hole beside
   it. Two by two, and four across once there is room for four. */
@media (min-width: 560px) {
  .cols:has(> :nth-child(4):last-child) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1000px) {
  .cols:has(> :nth-child(4):last-child) { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
.card-note {
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: 26px;
  box-shadow: var(--lift);
}
/*
  A row of notes is a row of colour blocks: pine, mint, then the white panel, so each
  one reads as its own object and the row reads as three claims rather than one grid.
  Each block carries the tokens for its own ground, the way the band does. In a pine
  band the pine block would vanish, so there it takes the band's raised tint instead.
*/
.cols > .card-note {
  border: 0;
  border-radius: 26px;
  padding: 30px;
  box-shadow: none;
}
.cols > .card-note:nth-child(3n+1) {
  background: var(--band);
  color: var(--band-text);
  --text: var(--band-text);
  --text-soft: var(--band-soft);
  --accent: var(--band-accent);
  --hairline: var(--band-line);
  --sunken: rgba(247, 241, 227, 0.07);
}
.cols > .card-note:nth-child(3n+2) {
  background: var(--mint);
  color: var(--pine-ink);
  --text: var(--pine-ink);
  --text-soft: rgba(8, 34, 25, 0.8);
  --accent: var(--pine-deep);
  --hairline: rgba(8, 34, 25, 0.16);
  --sunken: rgba(8, 34, 25, 0.07);
}
.cols > .card-note:nth-child(3n) { box-shadow: 0 0 0 1px var(--hairline); }
.band .cols > .card-note:nth-child(3n+1),
main > section:first-child .cols > .card-note:nth-child(3n+1) { background: var(--raised); }
.card-note h3 { color: var(--text); }
.card-note code { color: inherit; }

/* The home page's three: a figure as big as the heading, so each block makes its one
   point before it is read. */
.blocks > .card-note { display: flex; flex-direction: column; min-height: 360px; container-type: inline-size; }
/* Sized against the block's own width, so the word always fits the block it is in:
   `long` is for the translations whose word runs past eight letters ("téléphones",
   "telefoons"), which the ordinary size would push out of the side of a third-width
   block. `break-word` is the last resort, never the plan. */
.card-note .big {
  font-size: min(80px, 22cqw);
  overflow-wrap: break-word;
  font-weight: 700;
  line-height: 0.88;
  letter-spacing: -0.06em;
  color: var(--text);
  margin: 0 0 auto;
  padding-bottom: 36px;
}
.card-note .big.long { font-size: min(80px, 15.5cqw); }
.card-note h3 { margin-bottom: 10px; }
.card-note p { color: var(--text-soft); margin: 0; }
.card-note p + p { margin-top: 12px; }

.split {
  display: grid;
  gap: 42px;
  grid-template-columns: 1fr;
  align-items: center;
}
@media (min-width: 860px) {
  .split { grid-template-columns: minmax(0, 1fr) minmax(0, 0.78fr); gap: 64px; }
  .split.flip > :first-child { order: 2; }
}
.split .phone { max-width: 296px; margin-inline: auto; }
/*
  A screenshot beside a page's opening, on the pages whose opening is words alone. Those
  headings are held to `--measure`, so on a wide screen the right half of the first
  screen was empty page: the coverage pages, the localised retailer lists, and the two
  comparison pages read as a column of text with nothing beside it.
  Wide screens only. Below 860px the split is one column, the shot would land under the
  buttons and push the thing the page is about (the retailer list, the table) a whole
  phone's height further down, and there is no empty column for it to fill. `lazy` in the
  markup is what keeps a phone that never shows it from fetching it, and `layout()` in
  build.py preloads it behind this same 860px query so a wide screen does not wait for
  layout before asking for it. Change the breakpoint in both places or neither.
*/
.lead-shot { display: none; }
@media (min-width: 860px) {
  .lead-shot { display: block; }
}
.lead-shot .phone-duo { margin-inline: auto; }
.split .watch { max-width: 244px; margin-inline: auto; }
/* A watch is a third of a phone's height, so left at `align-items: start` the hero's
   pair sits at the top of a column of empty page. */
.hero-grid > .watch,
.hero-grid > .watch-duo { align-self: center; }
.hero-grid > .watch { max-width: 268px; margin-inline: auto; }

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

.actions { display: flex; flex-wrap: wrap; gap: 12px; margin: 30px 0 0; }

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 15px 26px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 600;
  font-size: 16px;
  letter-spacing: -0.01em;
  text-decoration: none;
  border: 1px solid transparent;
  box-shadow: var(--lift);
  transition: transform 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease;
}
.button:hover { transform: translateY(-1px); box-shadow: var(--lift-high); }
.button:active { transform: translateY(0); }

.button.ghost {
  background: transparent;
  color: var(--text);
  border-color: var(--hairline-firm);
  box-shadow: none;
}
.button.ghost:hover { border-color: var(--accent); color: var(--accent); box-shadow: none; }

/* Store badges.
   Apple's and Google's artwork, at a size the guidelines both allow. Two numbers do the
   work and neither is arbitrary. `--badge-height` is above Apple's 40px on-screen
   minimum with room to spare. `--badge-play-height` is equal to it while the two badges
   share one canvas, as the pair in `static/badges` does; swap in Google's own file,
   whose artwork carries its own clear space and so looks smaller at an identical
   height, and raising this corrects the optics while satisfying their rule that the
   Play badge be the same size or larger than the ones beside it. It may only ever go up.

   The gap is computed rather than typed, so it stays the clear space both guidelines
   ask for when either height changes: a quarter of the badge height, taken from the
   Play one because that is the taller of the two by the rule above, and clear space
   measured against the shorter badge would be too little for the taller. */
:root { --badge-height: 48px; --badge-play-height: 48px; }

.actions:has(.badge) { gap: calc(var(--badge-play-height) / 4); align-items: center; }

.badge {
  display: inline-flex;
  border-radius: 8px;
  text-decoration: none;
  transition: transform 0.15s ease;
}
.badge:hover { transform: translateY(-1px); }
.badge:active { transform: translateY(0); }
.badge:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* Height drives the size and the width follows the artwork's own proportions, because
   fixing both is how a badge gets stretched, which is the one thing neither company
   allows. */
.badge img { height: var(--badge-height); width: auto; display: block; }
.badge.play img { height: var(--badge-play-height); }

.button[aria-disabled='true'] {
  background: var(--sunken);
  border-color: var(--hairline);
  color: var(--text-faint);
  cursor: default;
  pointer-events: none;
  box-shadow: none;
}

.status-note {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  margin: 0;
  padding: 8px 15px 8px 12px;
  border-radius: 999px;
  border: 1px solid var(--hairline-firm);
  background: var(--sunken);
  color: var(--text-soft);
  font-size: 14px;
  font-weight: 500;
}
.status-note::before {
  content: '';
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  flex: none;
  box-shadow: 0 0 0 3px rgba(20, 92, 74, 0.22);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
}

.footnote {
  color: var(--text-faint);
  font-size: 14px;
  margin: 16px 0 0;
  max-width: 46rem;
  text-wrap: pretty;
}

/* ---------------------------------------------------------------- tables */

.scroller {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin: 0 0 18px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  background: var(--raised);
  box-shadow: var(--lift);
}

table {
  border-collapse: collapse;
  width: 100%;
  min-width: 640px;
  font-size: 15px;
}
/*
  The caption is inside the scroller, because a <caption> can only live inside its
  table. So it takes the cells' own padding and a hairline under it, and reads as the
  panel's first row. With `padding: 0 0 14px` it started hard against the panel's
  rounded top-left corner, where the border cropped the top of the sentence and the
  header row below it looked like it had lost its heading.
*/
caption {
  caption-side: top;
  text-align: left;
  color: var(--text-faint);
  font-size: 14px;
  padding: 15px 18px;
  border-bottom: 1px solid var(--hairline);
}
th, td {
  text-align: left;
  padding: 15px 18px;
  border-bottom: 1px solid var(--hairline);
  vertical-align: top;
}
tbody tr:last-child th, tbody tr:last-child td { border-bottom: 0; }
thead th {
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
  font-weight: 600;
  background: var(--sunken);
  border-bottom-color: var(--hairline-firm);
}
tbody th { font-weight: 600; width: 28%; color: var(--text); }
td.yes { color: var(--accent); font-weight: 600; }

/* Our own column, marked once and read down. */
.table-us {
  background: rgba(20, 92, 74, 0.09);
  background: color-mix(in srgb, var(--accent) 9%, transparent);
  box-shadow: inset 3px 0 0 var(--accent);
}
thead .table-us { color: var(--accent); }

/* ---------------------------------------------------------------- retailers */

/* Twelve countries, so the column counts are the ones twelve divides by: two on a
   phone, three on a tablet, four on a wide screen, and never a short last row. The grid
   runs the full width of the page. It used to stop at 62rem inside a 70rem wrap, which
   left a strip of empty page down the right of every row; and below 880px it was one
   card per row on a phone, twelve screens of flags with most of each card empty. */
.countries {
  display: grid;
  gap: 12px;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  list-style: none;
  padding: 0;
  margin: 0;
}
.countries li { margin: 0; }
@media (min-width: 620px) {
  .countries { gap: 16px; grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (min-width: 880px) {
  .countries { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
.country-link {
  display: block;
  padding: 24px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  background: var(--raised);
  box-shadow: var(--lift);
  text-decoration: none;
  color: var(--text);
  height: 100%;
  transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}
.country-link:hover {
  transform: translateY(-2px);
  box-shadow: var(--lift-high);
  border-color: var(--accent);
}
.country-link .flag {
  font-size: 26px;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  border-radius: 14px;
  background: var(--sunken);
  margin-bottom: 16px;
}
.country-link strong { display: block; font-size: 19px; letter-spacing: -0.025em; }
/* The country's best-known schemes, as a strip of their own colours under its name. */
.country-link .swatches { display: flex; gap: 4px; margin-top: 14px; }
.country-link .swatches i { flex: 1; height: 10px; border-radius: 4px; }
.country-link span { color: var(--text-faint); font-size: 14.5px; }
@media (max-width: 619px) {
  .country-link { padding: 16px; }
  .country-link .flag { width: 40px; height: 40px; font-size: 22px; border-radius: 12px; margin-bottom: 12px; }
  .country-link strong { font-size: 16.5px; line-height: 1.25; }
  .country-link span { font-size: 13.5px; }
}

.retailers {
  display: grid;
  gap: 10px;
  grid-template-columns: repeat(auto-fill, minmax(172px, 1fr));
  list-style: none;
  padding: 0;
  margin: 0 0 20px;
}
.retailer {
  position: relative;
  border-radius: 15px;
  padding: 14px 15px;
  font-size: 14.5px;
  font-weight: 600;
  line-height: 1.32;
  letter-spacing: -0.01em;
  /* The shape of the app's own tile, 150x106dp, so the grid reads as a jar of cards
     rather than as a list of coloured boxes. The ratio is a preferred height and not a
     cap: a grid item's automatic minimum is its content, so "British Garden Centres
     Family Card" pushes its row taller rather than being clipped by the `overflow:
     hidden` below, and every chip in that row grows with it. `min-height` is what a
     browser without `aspect-ratio` falls back to, and is set nearer a card's shape than
     the 84px it used to be for the same reason. */
  aspect-ratio: 150 / 106;
  min-height: 96px;
  /* So the mark can be sized against the chip's own width. `inline-size` and not `size`:
     size containment would stop the content growing the chip, which is what lets a long
     name push its row taller instead of being clipped. */
  container-type: inline-size;
  margin: 0;
  display: flex;
  align-items: flex-end;
  overflow-wrap: anywhere;
  overflow: hidden;
  box-shadow: var(--lift);
}
/* Flat brand colours look printed without one soft highlight across the top. The
   fade completes at 58% and the name sits at the bottom, so the text is always over
   the untouched colour: the catalogue checks `onColour` at 4.5:1 against `colour`,
   and lightening the ground under the text would quietly spend that margin. */
/* The retailer's mark, where the catalogue carries one. A height rather than a width,
   so a stacked monogram and a nine-letter wordmark set to the same cap height read as
   the same size; the viewBox is already cropped to the mark's ink, so this is the mark's
   own height and not its file's padding. `auto` width lets a wide wordmark take the room
   it needs, and `max-width` stops the widest of them touching the chip's edges. */
/* The height comes from the build, per mark: every mark is drawn to the ink area of a
   square MARK_REFERENCE_PX tall, which is what stops a wordmark carrying five times the
   ink of a roundel in the same row. A fixed height here is what it used to do, and this
   rule keeps one as the fallback for a mark whose aspect the build could not measure. */
.retailer-mark {
  display: block;
  width: auto;
  max-width: 100%;
  margin-bottom: auto;
  /* For a browser with no container units: the flat height every mark used to get.
     It loses the per-mark normalisation, which is a fair trade for one declaration. */
  height: 21px;
  /* The app's own proportion, so a mark is the same fraction of a chip as it is of a
     tile: its mark box is 38dp tall in a 150dp-wide tile, and the chip is the same
     150:106 shape. Container units rather than pixels because the chip is fluid — the
     grid resolves it anywhere from 172px to whatever a wide viewport gives — so a fixed
     length is the wrong size at every width but one. `--mark-scale` comes from the
     build: it is the only part that depends on the shape of the mark.

     `max-width` above is the cap on the other axis, and it does bind: a chip's content is
     82.6cqw at the narrowest column the grid resolves to, which a mark past about 10.6:1
     overruns. Six do — Victoria's Secret at 13.2:1 wants 92cqw, Brocard 90, the
     Container Store and Barnes & Noble 88, New Look 86, Rituals 83. They are drawn as wide
     as the chip allows and no wider, keeping their aspect, because the replaced-element
     rules scale the height down with the clamped width rather than letterboxing. That is
     the same thing the app's own box does to those marks, so they are under the target
     area on both surfaces and by the same reasoning. */
  height: calc(25.33cqw * var(--mark-scale, 1));
}
.retailer:has(.retailer-mark) { flex-direction: column; align-items: flex-start; gap: 10px; }
/* The name stays on the chip under its mark. It is what somebody searching for
   "waitrose loyalty card" is looking for, and a logo is not text to a search engine. */
.retailer:has(.retailer-mark) > span { margin-top: auto; }

.retailer::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(160deg, rgba(255, 255, 255, 0.16), transparent 58%);
  pointer-events: none;
}
.retailer > * { position: relative; }

/* A scheme with a page of its own. The whole chip is the link, and the name keeps the
   chip's own colour rather than the site's link colour.

   Both halves of that are the same reason. A chip is painted in the retailer's brand
   colour, and the only contrast the catalogue guarantees is `onColour` against `colour`,
   checked at 4.5:1. The accent green is checked against the page, not against Booths
   blue, so link-coloured underlined words inside a chip are unreadable at exactly the
   chips whose brand is darkest. Inheriting keeps the guarantee that was actually made.

   That leaves the link with no visible affordance, so the target becomes the whole tile
   instead of the words: the chip already reads as one object, and a card-sized hit area
   is the honest shape of it on a phone. `position: static` on the span is what allows
   the stretch. `.retailer > *` above makes every child a containing block, which would
   otherwise clip the anchor's `::after` to the width of the name. The `::before` sheen
   is already `pointer-events: none`, so it does not sit between the reader and this. */
.retailer > span:has(> a) { position: static; }
.retailer a { color: inherit; text-decoration: none; }
.retailer a::after { content: ''; position: absolute; inset: 0; }
/* The affordance the underline used to carry, drawn on the whole chip and in the chip's
   own terms: a hairline in the name's colour, which is the one colour already checked
   against this background. Keyboard focus draws the page's own ring around the tile
   rather than around the two or three words inside it. */
.retailer:has(a):hover { box-shadow: var(--lift), 0 0 0 2px currentColor inset; }
.retailer:has(a:focus-visible) { outline: 3px solid var(--accent); outline-offset: 3px; }
/* The ring belongs to the chip, so the anchor inside it does not draw a second one. */
.retailer a:focus-visible { outline: none; }

/* ---------------------------------------------------------------- faq */

.faq { border-top: 1px solid var(--hairline); padding-top: 56px; }
.faq details {
  border-bottom: 1px solid var(--hairline);
}
.faq summary {
  cursor: pointer;
  padding: 22px 0;
  font-weight: 600;
  font-size: 17.5px;
  letter-spacing: -0.02em;
  list-style: none;
  display: flex;
  justify-content: space-between;
  gap: 24px;
  align-items: center;
  transition: color 0.15s ease;
}
.faq summary:hover { color: var(--accent); }
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: '';
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1px solid var(--hairline-firm);
  background:
    linear-gradient(var(--accent), var(--accent)) center / 10px 2px no-repeat,
    linear-gradient(var(--accent), var(--accent)) center / 2px 10px no-repeat;
  transition: transform 0.2s ease, border-color 0.15s ease;
}
.faq details[open] summary::after {
  transform: rotate(135deg);
  background:
    linear-gradient(var(--accent), var(--accent)) center / 10px 2px no-repeat;
  border-color: var(--accent);
}
.faq details > :not(summary) { color: var(--text-soft); max-width: var(--measure); }
.faq details > p:last-child { padding-bottom: 12px; }

/* ---------------------------------------------------------------- prose */

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

/*
  A long document: its sections listed down the left and held there while it scrolls,
  and the text beside them in a wider column than a marketing page's, about 75
  characters, which a policy read end to end can carry. One column below 960px, with
  the list dropped: on a phone it would be a screen of links before the first sentence.
*/
.doc { display: grid; gap: 72px; }
.doc .prose { max-width: 46rem; }
.toc { display: none; }
@media (min-width: 960px) {
  .doc { grid-template-columns: 15rem minmax(0, 1fr); }
  .toc {
    display: block;
    position: sticky;
    top: calc(var(--mast) + 32px);
    align-self: start;
    max-height: calc(100vh - var(--mast) - 64px);
    overflow-y: auto;
  }
}
.toc ol { list-style: none; padding: 0; margin: 0; border-left: 2px solid var(--hairline); }
.toc li { margin: 0; }
.toc a {
  display: block;
  padding: 6px 0 6px 16px;
  margin-left: -2px;
  border-left: 2px solid transparent;
  color: var(--text-soft);
  font-size: 14.5px;
  line-height: 1.35;
  text-decoration: none;
}
.toc a:hover { color: var(--text); border-left-color: var(--accent); }
.prose h2[id] { scroll-margin-top: calc(var(--mast) + 24px); }

/* The privacy page's opening figure, in the pine beside the headline. */
.tally {
  justify-self: end;
  width: 100%;
  max-width: 340px;
  padding: 32px;
  border-radius: 26px;
  background: var(--raised);
  border: 1px solid var(--hairline);
}
.tally-figure {
  font-size: clamp(96px, 12vw, 148px);
  font-weight: 700;
  line-height: 0.85;
  letter-spacing: -0.06em;
  color: var(--accent);
  margin: 0 0 14px;
}
.tally-label { font-size: 17px; font-weight: 600; color: var(--text); margin: 0 0 22px; }
.tally ul { list-style: none; padding: 18px 0 0; margin: 0; border-top: 1px solid var(--hairline); }
.tally li { margin: 0 0 8px; color: var(--text-soft); padding-left: 22px; position: relative; }
.tally li::before {
  content: '';
  position: absolute;
  left: 2px;
  top: 0.6em;
  width: 10px;
  height: 2px;
  border-radius: 2px;
  background: var(--accent);
}
@media (max-width: 859px) { .tally { justify-self: start; } }
.prose h2 { font-size: clamp(22px, 3vw, 28px); margin-top: 48px; }
.prose h2:first-child { margin-top: 0; }
.prose h3 { margin-top: 32px; }
.prose p, .prose li { color: var(--text-soft); }
.prose strong { color: var(--text); }
.prose code {
  font-family: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.86em;
  background: var(--sunken);
  border: 1px solid var(--hairline);
  padding: 1px 6px;
  border-radius: 6px;
  overflow-wrap: anywhere;
}

.callout {
  border-left: 3px solid var(--accent);
  background: var(--sunken);
  border-radius: 0 var(--radius) var(--radius) 0;
  padding: 22px 26px;
  margin: 30px 0;
  color: var(--text-soft);
  max-width: 46rem;
}
.callout p:last-child { margin-bottom: 0; }

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

footer.site {
  margin-top: 0;
  padding: 64px 0 72px;
  background: var(--foot);
  color: var(--text-faint);
  font-size: 14.5px;

  --text: var(--band-text);
  --text-soft: var(--band-soft);
  --text-faint: var(--band-faint);
  --accent: var(--band-accent);
  --hairline: var(--band-line);
}
footer.site .footer-brand .mark { box-shadow: 0 0 0 1px rgba(247, 241, 227, 0.2); }
.band + footer.site { border-top: 0; margin-top: 0; }
footer.site .wrap {
  display: grid;
  gap: 32px;
  grid-template-columns: repeat(auto-fit, minmax(185px, 1fr));
}
footer.site h2 {
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 14px;
  font-weight: 600;
}
footer.site ul { list-style: none; padding: 0; margin: 0; }
footer.site li { margin-bottom: 10px; }
footer.site a {
  color: var(--text-soft);
  text-decoration: none;
  transition: color 0.15s ease;
}
footer.site a:hover { color: var(--accent); }
footer.site .colophon { color: var(--text-faint); line-height: 1.6; max-width: 20rem; }
footer.site .footer-brand { margin-bottom: 14px; }
footer.site .licence { margin-top: 18px; font-size: 13px; }

/*
  The publisher's line: who owns the site, where the company is registered and where it
  can be written to. A second `wrap` under the column grid rather than a fourth column,
  because a registered office does not fit a 185px track and the statement is about the
  site rather than about any one topic in it. The rule above it is the same hairline the
  footer is separated from the page with: the small print is a footer to the footer.
*/
footer.site .smallprint {
  display: block;
  margin-top: 40px;
  padding-top: 24px;
  border-top: 1px solid var(--hairline);
  font-size: 13px;
  line-height: 1.7;
  color: var(--text-faint);
}
footer.site .smallprint p { margin: 0; }
footer.site .smallprint a { color: var(--text-soft); }
/*
  The language switcher, in the masthead beside the nav. A reader who cannot read the
  page will not scroll to the bottom of it looking for the way out.

  It is a disclosure rather than a row of eight names, which is what it was first and what
  made it unaffordable up here: seven of those names are noise to any given visitor by
  definition. `details` needs no script, and the menu button beside it is a checkbox for
  the same reason.

  `span` for the current language rather than a link to the page you are already on,
  which is why the two selectors differ in weight rather than only in colour.
*/
.masthead .languages { position: relative; }
.masthead .languages details { display: block; }

/* Drawn as one more item in the row rather than a control of its own: no outline, the
   links' padding and pill, and the same soft fill on hover and while it is open. */
.masthead .languages summary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 10px 16px;
  border-radius: 999px;
  cursor: pointer;
  color: var(--text-soft);
  font-weight: 500;
  font-size: 16.5px;
  line-height: 1.62;
  transition: color 0.15s ease, background-color 0.15s ease;
  /* Both are needed: the standard property, and Safari's own marker pseudo-element. */
  list-style: none;
}
.masthead .languages summary::-webkit-details-marker { display: none; }
.masthead .languages summary::after {
  content: "";
  width: 6px;
  height: 6px;
  margin-left: 1px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
}
.masthead .languages details[open] summary::after { transform: translateY(1px) rotate(225deg); }
.masthead .languages summary:hover,
.masthead .languages details[open] summary { color: var(--text); background: var(--sunken); }

/* The pill is the flag alone, at every width. The names belong in the list, where a
   reader is choosing between them; on the closed control the flag says "language" on its
   own and the word beside it was only ever saying which one you are already reading.

   Off the screen rather than removed, so the control still announces itself as "English"
   to a screen reader instead of as an unlabelled button. */
.masthead .languages summary > span:not(.flag) {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.masthead .languages ul {
  position: absolute;
  top: 100%;
  right: 0;
  margin: 8px 0 0;
  padding: 6px;
  list-style: none;
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: 14px;
  box-shadow: 0 12px 32px rgb(13 53 41 / 0.16);
  min-width: 186px;
  z-index: 20;
}
.masthead .languages li { margin: 0; }
.masthead .languages li > * {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 11px;
  border-radius: 9px;
  white-space: nowrap;
}
.masthead .languages li a { color: var(--text-soft); text-decoration: none; }
.masthead .languages li a:hover { background: var(--sunken); color: var(--text); text-decoration: none; }
.masthead .languages [aria-current="page"] { color: var(--text); font-weight: 600; }

/* ---------------------------------------------------------------- small screens */

@media (max-width: 839px) {
  /* The nav folds into a menu below this width. It is three items and the language
     switcher in every language, and the widest of the eight, Ukrainian, measured at 797px
     of capsule; the capsule is the screen less 24px, so 840 is where every language still
     fits on one row, and nothing between here and there is ever laid out in two.

     Re-measure this if the nav gains an item, rather than assuming it still holds. */
  .masthead .wrap { min-height: 0; gap: 12px; padding-block: 7px; }
  /* 12px of page beside the capsule and 12px inside it: the wordmark on the content edge. */
  .masthead .wrap { padding-left: 12px; }
  :root { --mast: 73px; }
  /* Folded into the menu with the links, and last in it, under them: the panel ends on
     the button the way the wide row does. */
  .nav-install { display: none; order: 4; }
  .menu-toggle:checked ~ .nav-install {
    display: block;
    width: 100%;
    margin: 0 0 3px;
    padding: 13px 12px;
    border-radius: 999px;
    text-align: center;
  }
  /* The menu and the language list open over each other. analytics.js closes the list
     when the menu opens; this hides it in the moment before that, and for good without. */
  .menu-toggle:checked ~ .languages details[open] > ul { display: none; }

  /* Three lines and no word. "Menu" is the word in Polish, French, Italian and Dutch as
     well as English, so five of the eight languages read as though the button had been
     missed by the translation even though it had not; the other three carried a word for
     something every phone user already recognises by its shape. The bars sit beside the
     flag pill, which is the control that does need its label.

     Sized rather than padded, because the label below is taken out of the flow and the
     bars are 2px tall: without a height the button would collapse to a hairline. 36px
     matches the switcher beside it. */
  .menu-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: 999px;
    color: var(--text);
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
  }
  /* Kept for a screen reader, which gets a button called "Menü" rather than one called
     nothing, and off the screen for everybody else. */
  .menu-button > span:not(.menu-bars) {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .menu-toggle:focus-visible + .menu-button {
    outline: 3px solid var(--accent);
    outline-offset: 3px;
  }
  .menu-toggle:checked + .menu-button {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--on-accent);
  }
  .menu-toggle:checked + .menu-button .menu-bars { background: transparent; }
  .menu-toggle:checked + .menu-button .menu-bars::before { top: 0; transform: rotate(45deg); }
  .menu-toggle:checked + .menu-button .menu-bars::after { top: 0; transform: rotate(-45deg); }

  .nav { display: none; order: 3; }
  /* Menu furthest right, because that is the control most taps go to and the thumb gets
     there first; the switcher sits just inside it. Both are ordered explicitly so they
     keep those places when the menu opens — the nav then becomes a full-width stack, and
     without an order of its own it would come between them. */
  .masthead .languages { order: 1; margin-left: auto; }
  .menu-button { order: 2; }
  .masthead .wrap { column-gap: 10px; }
  .menu-toggle:checked ~ .nav {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    width: 100%;
    gap: 2px;
    padding: 6px 0 10px;
    border-top: 1px solid var(--hairline);
  }
  .nav a { font-size: 16.5px; padding: 13px 12px; border-radius: 11px; }
  /* Open, the capsule is a tall panel over whatever is below it, cream included, and
     glass that size turns grey. A browser without :has() keeps the glass. */
  .masthead .wrap:has(.menu-toggle:checked) { background: var(--float); }
}

@media (max-width: 640px) {
  section { padding: 56px 0; }
  .band { padding: 64px 0; }
  section + .band { padding-top: 64px; }
  .band + section { padding-top: 56px; }
  .hero { padding-top: 40px; }
  .faq { padding-top: 40px; }

  .callout { padding: 18px 20px; }
  th, td, caption { padding: 13px 14px; }
}

/* Retailer chip colours are in the generated /retailers.css, one class per retailer.
   They cannot be inline `style` attributes: the site's own Content-Security-Policy
   forbids inline styles, and the whole point of a chip is its colour. */

/* ---------------------------------------------------------------- cookie consent */

/*
  The analytics question, drawn by `/analytics.js` and only on a visitor's first page.
  A card in the corner rather than a wall across the page: everything underneath keeps
  working while it is open, because nothing on this site waits on the answer. The two
  answers are the same size and the same weight, so no is exactly as easy as yes.
  Pine in both themes, like the masthead it sits under.
*/
.consent {
  position: fixed;
  z-index: 20;
  left: max(12px, env(safe-area-inset-left, 0px));
  right: max(12px, env(safe-area-inset-right, 0px));
  bottom: calc(12px + env(safe-area-inset-bottom, 0px));
  max-width: 440px;
  padding: 20px 20px 18px;
  border-radius: 22px;
  background: var(--float);
  color: var(--band-text);
  box-shadow: inset 0 1px 0 var(--float-lit), 0 0 0 1px var(--float-edge),
    0 24px 50px -20px rgba(0, 0, 0, 0.6);
  font-size: 14.5px;
  line-height: 1.5;
}
.consent p { margin: 0 0 16px; color: var(--band-soft); }
.consent a { color: var(--band-text); }
.consent-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
/* Bottom right on a wide screen, where it covers the hero's decoration rather than the
   store buttons, which sit bottom left and are the one thing the page most wants used. */
@media (min-width: 900px) { .consent { left: auto; margin-inline: 0; } }
.consent-answer {
  font: inherit;
  font-weight: 600;
  padding: 11px 16px;
  border-radius: 999px;
  border: 1px solid var(--band-line);
  background: rgba(247, 241, 227, 0.08);
  color: var(--band-text);
  cursor: pointer;
}
.consent-answer:hover { background: var(--mint); border-color: var(--mint); color: var(--pine-ink); }
.consent-answer:focus-visible { outline: 3px solid var(--mint); outline-offset: 3px; }

/* The footer's way back to the question: a button that reads as the links beside it. */
.cookie-settings {
  font: inherit;
  color: var(--text-soft);
  background: none;
  border: 0;
  padding: 0;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.cookie-settings:hover { color: var(--accent); }

/* The footer's Auto, Light and Dark: three small segments in one pill, the chosen one
   filled. Shown by /analytics.js, which is what makes it work. The footer is dark in
   both themes, so the pill is drawn in cream over it rather than in the page's lines. */
.theme-switch {
  display: inline-flex;
  gap: 2px;
  margin-top: 12px;
  padding: 3px;
  border: 1px solid rgba(247, 241, 227, 0.28);
  border-radius: 999px;
}
.theme-switch[hidden] { display: none; }
/* On a wide screen it sits on the right of the small print, level with its first line,
   rather than under the address: the left column is the publisher, the right is a
   setting. On a phone it stays underneath, where there is no right to put it on. */
@media (min-width: 840px) {
  footer.site .smallprint {
    display: grid;
    grid-template-columns: 1fr auto;
    /* No row gap: it is a `.wrap` too, and the footer's grid gap between its columns
       would otherwise open up between these lines. */
    gap: 0 32px;
  }
  footer.site .smallprint > p { grid-column: 1; }
  footer.site .smallprint > .theme-switch {
    grid-column: 2;
    grid-row: 1 / span 4;
    align-self: start;
    /* Centred on the first line rather than hanging from its top. */
    margin-top: -10px;
  }
}
.theme-switch button {
  font: inherit;
  font-weight: 500;
  color: var(--text-soft);
  background: none;
  border: 0;
  padding: 4px 13px;
  border-radius: 999px;
  cursor: pointer;
}
.theme-switch button:hover { color: var(--text); }
.theme-switch button[aria-pressed="true"] { color: var(--text); background: rgba(247, 241, 227, 0.16); }
.theme-switch button:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
