/*
  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 three grounds rather than running one colour top to
  bottom: the cream page, a white raised panel, and a deep pine band. 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');
}

@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;

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

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

@media (prefers-color-scheme: dark) {
  :root {
    --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;

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

  90px is the desktop masthead's 70px plus room to breathe; on the 65px mobile one that
  reads as 25px of space above the target, which is the same answer a shade looser.
  `scroll-padding` on the scrolling element rather than `scroll-margin` on each target,
  because the header belongs to the page and not to whatever somebody links to next.
*/
html {
  scroll-behavior: smooth;
  scroll-padding-top: 90px;
}
@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, -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; }

.masthead {
  position: sticky;
  top: 0;
  z-index: 8;
  background: color-mix(in srgb, var(--ground) 86%, transparent);
  backdrop-filter: saturate(1.5) blur(14px);
  -webkit-backdrop-filter: saturate(1.5) blur(14px);
  border-bottom: 1px solid var(--hairline);
}
.masthead .wrap {
  display: flex;
  align-items: center;
  gap: 18px;
  min-height: 68px;
  flex-wrap: wrap;
  padding-block: 11px;
}
.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 */

/*
  No JavaScript anywhere on this site, and the Content-Security-Policy would not run
  any, so the menu is a checkbox and a label. 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 1024px has always used, so this is the
   desktop nav catching up with it rather than a new number. It fits: the full nav is
   754px wide at this size and the narrowest width that still shows it is 1024px, which
   leaves 93px spare beside the wordmark and the language switcher. 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 14px;
  border-radius: 10px;
  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); }

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.
*/
.band {
  background: var(--band);
  color: var(--band-text);
  padding: 84px 0;

  --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);
}
section + .band { padding-top: 84px; }

/* The page's last word, centred, running straight into the footer. */
.band.centre { text-align: center; }
.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;
}
h1 { font-size: clamp(34px, 5.6vw, 56px); font-weight: 700; letter-spacing: -0.04em; }
h2 { font-size: clamp(26px, 3.8vw, 38px); font-weight: 700; }
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;
  overflow: hidden;
}
/* 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(20, 92, 74, 0.17), transparent 72%);
  pointer-events: none;
  z-index: 0;
}
@media (prefers-color-scheme: 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; }

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

.card-note {
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: 26px;
  box-shadow: var(--lift);
}
.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; }
.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: 13px;
  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 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: color-mix(in srgb, var(--accent) 9%, transparent);
  box-shadow: inset 3px 0 0 var(--accent);
}
thead .table-us { color: var(--accent); }

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

/* Capped at four on a wide screen, because twelve countries fill three rows of
   four. Four and three is a shape. */
.countries {
  display: grid;
  gap: 16px;
  grid-template-columns: repeat(auto-fit, minmax(232px, 1fr));
  max-width: 62rem;
  list-style: none;
  padding: 0;
  margin: 0;
}
.countries li { margin: 0; }
@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; }
.country-link span { color: var(--text-faint); font-size: 14.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); }
.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 {
  border-top: 1px solid var(--hairline);
  margin-top: 8px;
  padding: 56px 0 64px;
  color: var(--text-faint);
  font-size: 14.5px;
}
.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; }

.masthead .languages summary {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 7px 13px;
  border: 1px solid var(--hairline-firm);
  border-radius: 999px;
  cursor: pointer;
  color: var(--text);
  font-weight: 600;
  font-size: 0.92rem;
  /* 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 { border-color: var(--pine); }

/* 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: 1023px) {
  /* The nav folds into a menu below this width. The number was 820 when the nav was five
     links and nothing sat beside it; it is six links and a language switcher now, and
     measured against the real masthead those need 1003px before the switcher drops onto
     a line of its own. Rounding up to 1023 means a 1024-wide screen keeps the full nav
     with room to spare, and nothing between here and there is ever laid out in two rows.

     Re-measure this if the nav gains an item, rather than assuming it still holds. */
  .masthead .wrap { min-height: 0; gap: 12px; padding-block: 12px; }

  /* 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: 42px;
    height: 36px;
    border: 1px solid var(--hairline-firm);
    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; }
}

@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. */
