/* ───────────────────────────────────────────────────────────────────────────
   Loyollo, laid out the way squareup.com lays itself out.

   One stylesheet for both pages, which is the point: the business page and the
   customer page are different arguments to different people, but they are one
   brand and they share every token, every button and every band of spacing.

   Tokens are lifted from web/tap/main.css rather than re-picked, so this is the
   real brand in a borrowed structure and not a new brand: --ink, the house
   gradient, --accent, the radii, Bricolage Grotesque over Archivo. Light only,
   which is main.css's own stated decision for the brand site.

   What is borrowed from Square is the SHAPE: a centred hero carrying one short
   line and two buttons, a full-bleed band of colour tiles under it, segment
   tabs that filter the page, a numbered goal-to-solution table, a row of
   oversized numerals, and a great deal of air between all of it.
   ─────────────────────────────────────────────────────────────────────────── */

:root {
  color-scheme: light;

  --ink:       #120D0B;
  --ink-dim:   rgba(18, 13, 11, 0.64);
  --ink-faint: rgba(18, 13, 11, 0.40);
  --line:      rgba(18, 13, 11, 0.12);
  --paper:     #FFFFFF;
  --shell:     #F7F4F2;

  --accent:    #A86BFF;
  --accent-b:  #539AD9;

  /* The dashboard's one data colour. Darker than the app's #00D592, which is
     drawn on near-black; this is drawn on white and needs the contrast. */
  --chart-up:  #19A974;

  /* The card colours, straight off the wallet. */
  --c-purple:  #A86BFF;
  --c-yellow:  #FFE24A;
  --c-blue:    #8ECAFF;
  --c-pink:    #FCD3FF;

  /* The house gradient, at the strength the pricing band settled on. It was
     written out in four places at two different strengths; one token means the
     next tweak lands everywhere instead of in whichever block got edited. */
  --house: linear-gradient(150deg, #FFFFFF 0%, #FDEDFF 52%, #D2E8FF 100%);

  --display: "Bricolage Grotesque", Archivo, "Helvetica Neue", Helvetica, Arial, sans-serif;
  --body:    Archivo, "Helvetica Neue", Helvetica, Arial, sans-serif;

  --r-lg: 28px;
  --r-md: 20px;
  --r-sm: 14px;
  /* The column grows with the window from 1180px up to 1480px: a 1180px
     column on a 2,000px screen read as a strip down the middle. */
  --wrap: clamp(1180px, 74vw, 1480px);

  /* Square's defining measurement is the gap between sections, not the
     typography. It is big, and it never varies down the page. */
  --band: clamp(64px, 7.5vw, 112px);
}

* { box-sizing: border-box; }

/* ── page skeleton ─────────────────────────────────────────────────────────
   :root carries the phone's safe-area insets so a full-bleed page still keeps
   its content clear of the system bars; the sticky nav is offset by the same
   amount. */
:root { padding-top: env(safe-area-inset-top, 0px); padding-bottom: env(safe-area-inset-bottom, 0px); }
[hidden] { display: none !important; }

/* Loyollo's own line in the ink band, set as a heading rather than a quote:
   the same measure the quote had. */
.inkband .said { max-width: 30ch; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--body);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; display: block; }
a { color: inherit; }

h1, h2, h3 {
  font-family: var(--display);
  font-weight: 800;
  letter-spacing: -0.032em;
  line-height: 1.04;
  text-wrap: balance;
  margin: 0;
}

.wrap { max-width: var(--wrap); margin-inline: auto; padding-inline: 20px; }
.band { padding-block: var(--band); }

/* In-page links glide rather than jump, and land below the sticky nav
   instead of tucking the heading under it. No glide for anyone who has asked
   their device for less motion. */
html { scroll-behavior: smooth; scroll-padding-top: 84px; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

/* ── nav ───────────────────────────────────────────────────────────────────
   Square keeps this very light: a wordmark, a handful of words, and the only
   filled button above the fold. */
.nav {
  position: sticky; top: env(safe-area-inset-top, 0px); z-index: 40;
  background: rgba(255, 255, 255, 0.86);
  backdrop-filter: saturate(150%) blur(12px);
  border-bottom: 1px solid var(--line);
}
.nav-in {
  max-width: var(--wrap); margin-inline: auto;
  padding: 0 20px; height: 64px;
  display: flex; align-items: center; gap: 28px;
}
.mark { width: 112px; flex: none; }
.mark img { width: 100%; }
.nav-links { display: flex; gap: 24px; font-size: 14.5px; font-weight: 500; }
.nav-links a { color: var(--ink-dim); text-decoration: none; }
.nav-links a:hover { color: var(--ink); }
.nav-links a[aria-current="page"] { color: var(--ink); font-weight: 600; }
.nav-links a { white-space: nowrap; }
/* Four links plus Sign in, the button and the toggle only just fit between
   the burger breakpoint and a laptop, so the gap tightens there. */
@media (min-width: 921px) and (max-width: 1080px) {
  .nav-links { gap: 18px; }
}
.nav-right { margin-left: auto; display: flex; align-items: center; gap: 16px; }
.nav-right .plain { font-size: 14.5px; font-weight: 500; color: var(--ink-dim); text-decoration: none; }
.nav-right .plain:hover { color: var(--ink); }

/* The language switch: a segmented toggle. One soft track, both languages
   always visible (a dropdown would hide that the Welsh exists), and a white
   thumb sitting behind the current one. The thumb is the track's ::before,
   so it can slide: :has() puts it on whichever side carries aria-current,
   and on click the menu script adds .flip to send it across before the
   page changes. */
.lang {
  position: relative; display: inline-grid; grid-template-columns: 1fr 1fr;
  padding: 3px; border-radius: 999px; background: rgba(18, 13, 11, 0.07);
  font-size: 12.5px; font-weight: 700; letter-spacing: 0.04em;
}
.lang::before {
  content: ""; position: absolute; top: 3px; bottom: 3px; left: 3px; width: calc(50% - 3px);
  border-radius: 999px; background: var(--paper);
  box-shadow: 0 1px 3px rgba(18, 13, 11, 0.16), 0 0 0 0.5px rgba(18, 13, 11, 0.06);
  transition: transform 0.24s cubic-bezier(0.3, 0.7, 0.2, 1);
}
.lang:has(> :last-child[aria-current])::before { transform: translateX(100%); }
.lang.flip::before { transform: translateX(100%); }
.lang:has(> :last-child[aria-current]).flip::before { transform: none; }
.lang > * {
  position: relative; z-index: 1; display: inline-flex; align-items: center; justify-content: center;
  min-width: 40px; min-height: 32px; padding: 0 10px; line-height: 1;
  color: var(--ink-faint); text-decoration: none; border-radius: 999px;
  transition: color 0.2s ease;
}
.lang [aria-current] { color: var(--ink); }
.lang a:hover, .lang a:focus-visible { color: var(--ink); }
.lang.flip [aria-current] { color: var(--ink-faint); }
.lang.flip a { color: var(--ink); }
/* abbr carries the full language name for a screen reader; browsers draw it
   with a dotted underline by default. */
.lang abbr { text-decoration: none; cursor: inherit; }
@media (prefers-reduced-motion: reduce) { .lang::before { transition: none; } }

.btn {
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--body); font-size: 15px; font-weight: 600;
  padding: 11px 22px; border-radius: 999px;
  border: 1px solid transparent; text-decoration: none; cursor: pointer;
  white-space: nowrap;
}
.btn.ink { background: var(--ink); color: #fff; }
.btn.ink:hover { background: #2a211d; }
.btn.ghost { background: transparent; color: var(--ink); border-color: var(--line); }
.btn.ghost:hover { border-color: var(--ink-dim); }
.btn.soon { color: var(--ink-faint); border-color: var(--line); cursor: default; }
/* The nav's button and the language pill have to be the same height. Left to
   padding and line-height they came out 39.7 and 32.5, so both are pinned to
   one value instead. */
.btn.sm { padding: 0 17px; font-size: 14px; min-height: 38px; }

/* ── hero ──────────────────────────────────────────────────────────────────
   Centred, one short line, two buttons, nothing else competing. */
.hero-wrap { position: relative; overflow: hidden; }
/* padding-block, not the shorthand: the hero is also a .wrap, and a shorthand
   with 0 at the sides took the wrap's gutter away, so on a phone the lead ran
   to the very edge of the screen. */
.hero { position: relative; z-index: 1; text-align: center;
        padding-block: clamp(52px, 7vw, 96px) clamp(36px, 4vw, 56px); }
.hero h1 { font-size: clamp(38px, 6.2vw, 74px); max-width: 15ch; margin-inline: auto; }
.hero .lead {
  font-size: clamp(17px, 1.6vw, 19px); color: var(--ink-dim);
  max-width: 50ch; margin: 22px auto 0;
}
.hero .lead strong { color: var(--ink); font-weight: 700; }
.hero .cta { display: flex; gap: 12px; justify-content: center; margin-top: 30px; flex-wrap: wrap; }
.trust {
  margin-top: clamp(40px, 5vw, 64px);
  font-family: var(--display); font-weight: 700; letter-spacing: -0.03em;
  font-size: clamp(22px, 2.6vw, 30px); line-height: 1.15;
  max-width: 30ch; margin-inline: auto;
}

/* No size override any more: both heroes are set at the same scale, and the
   customer sentence takes the extra line that costs. The one thing that has to
   hold is that EVERY trade in the cycle breaks the same way — if one word
   pushed the headline onto a fourth line, the whole page below it would jump
   once a second. The longest in the list is "florist", and "Your florist" is
   still well inside this measure. */
/* Wide enough for "Your coffee loyalty card." on one line, so the headline
   is two lines on a desktop — the <br> sets the break, this lets it land. */
.hero.cust h1 { max-width: 24ch; }
.trust span { color: var(--ink-faint); }

/* ── the stamp field ───────────────────────────────────────────────────────
   Stamps scattered down both margins of the hero, blurred back so they read as
   texture rather than as content. Every one is a real stamp a venue can choose,
   which is the only reason they belong here: the drawing IS the product.

   Each is placed by four custom properties rather than by a nth-child table,
   so the scatter is legible in the markup and a stamp can be nudged without
   touching CSS. Blur and opacity move together — the further back something
   is, the softer AND the fainter it gets, which is the one rule that stops a
   scatter like this reading as a mistake.

   The field is decorative and aria-hidden. Below 1100px there are no margins
   left to scatter into, so it goes entirely rather than crowding the headline. */
.field { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 0; }
.sk {
  position: absolute;
  left: var(--x); top: var(--y);
  width: var(--s);
  transform: rotate(var(--r));
  filter: blur(var(--b));
  opacity: var(--o);
}
/* The scatter lives in the margins either side of the centred hero, so it only
   has to go when there are no margins left. 1100px was set by eye and was far
   too cautious — checked at 1000 and 820 the stamps still sit clear of the
   text column, and the page in a side panel or a half-screen window was losing
   the one piece of the hero that makes it feel like Loyollo. 720px is where the
   headline actually starts to reach them. */
/* On a phone there are no margins to scatter into, so each field keeps just
   five of its stamps, smaller and moved to the very edges — half off the
   screen, alternating left and right down the section — so they frame the
   words instead of sitting behind them. The five carry their own phone
   positions (--mx, --my) in the markup; the rest stand down. */
@media (max-width: 720px) {
  .field .sk:not([style*="--mx"]) { display: none; }
  /* --mx is the side: 0 left, 1 right. Either way about 40% of the stamp
     hangs off the screen, so most of it stays in the gutter. */
  .field .sk {
    --w: calc(var(--s) * 0.88);
    width: var(--w); top: var(--my);
    left: calc((100% - var(--w) * 0.6) * var(--mx) - var(--w) * 0.4 * (1 - var(--mx)));
  }
}

/* ── the cycling trade, in the headline ────────────────────────────────────
   "Your coffee loyalty card" is true of one shop. Rotating the word through
   the trades is the whole pitch in one line: this is not a coffee app.

   The span carries an explicit, animated width measured in the real font, so
   the rest of the sentence does not jump when the word changes. It stays ink —
   main.css is explicit that headlines are ink and that this was a decision, so
   the motion is the emphasis and the colour is not. */
.swap {
  display: inline-block;
  vertical-align: baseline;
  text-align: left;
  /* No overflow here, deliberately. An inline-block whose overflow is anything
     but visible takes its BOTTOM MARGIN EDGE as its baseline instead of its
     text's, so the word floats above the line it is sitting in — which is
     exactly what `overflow: hidden` did here. There is nothing to clip anyway:
     the outgoing word is at opacity 0 before it has travelled far enough to
     need it. */
  transition: width 0.34s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.swap i {
  display: inline-block; font-style: normal;
  transition: opacity 0.24s ease, transform 0.24s ease;
}
.swap.out i { opacity: 0; transform: translateY(-0.3em); }
@media (prefers-reduced-motion: reduce) {
  .swap { transition: none; }
  .swap i { transition: none; }
}

/* ── the colour-tile band ──────────────────────────────────────────────────
   Square's signature move: full-bleed, edge to edge, one flat colour behind
   each image and no gutters at all between them. */
.tiles { display: grid; grid-template-columns: repeat(4, 1fr); }
.tile { aspect-ratio: 3 / 4; display: grid; place-items: center; overflow: hidden; }
.tile img { width: 100%; height: 100%; object-fit: cover; }
/* The row has to be bounded, or `max-height: 100%` below resolves against a
   track the image has already stretched — a percentage against an auto track
   that the percentage was meant to constrain. minmax(0, 1fr) gives the item a
   definite area to be capped by. */
.tile.pad { padding: 26px; grid-template-rows: minmax(0, 1fr); grid-template-columns: minmax(0, 1fr); }
/* Fit, don't fill. A phone render is far taller than the 3:4 tile, so sizing
   it by width pushed its foot past the bottom edge and overflow clipped it.
   Capping both axes lets the tile's own place-items centre whatever shape
   arrives — a tall screenshot or a wide stack of cards. */
.tile.pad img {
  max-width: 100%; max-height: 100%; width: auto; height: auto;
  border-radius: 10px;
  /* drop-shadow rather than box-shadow, for the reason spelled out under
     .tile.tilt img below: these renders carry transparent corners, and a box
     shadow is painted around the element's RECTANGLE, which prints four pale
     shoulders where the corners see through. */
  filter: drop-shadow(0 18px 38px rgba(18, 13, 11, 0.20));
}
/* The house gradient at full strength, as a tile ground. */
.t-house {
  background: var(--house);
}
/* Tilted hard and anchored off the bottom-right corner, so the device leaves
   the frame on two edges instead of floating inside it with air all round —
   that difference is a sticker versus a photograph. Absolutely positioned
   rather than centred, because `place-items` has no way to say "hang this
   corner outside the box"; the tile's own overflow does the cutting.

   The render carries real transparency around the device, so the gradient runs
   right up to the phone's edge and no second rectangle appears behind it. */
.tile.tilt { padding: 0; position: relative; }
.tile.tilt img {
  position: absolute; top: 13%; left: 15%;
  width: 96%; height: auto; max-width: none; max-height: none;
  transform: rotate(-16deg); transform-origin: 0 0;

  /* drop-shadow, not box-shadow. An outer box-shadow is painted only OUTSIDE
     the element's rectangle, so on a PNG with transparent corners it darkens
     the ground around the bounding box while leaving the see-through corners
     clean — which draws a hard-edged pale rectangle exactly the size of the
     image. filter: drop-shadow follows the alpha, so the shadow is the phone's
     shape and nothing marks where the file ends. */
  box-shadow: none;
  filter: drop-shadow(0 16px 30px rgba(18, 13, 11, 0.3));
}

.t-purple { background: var(--c-purple); }
.t-yellow { background: var(--c-yellow); }
.t-ink    { background: var(--ink); }
.t-blue   { background: var(--c-blue); }

/* ── three across ──────────────────────────────────────────────────────────
   The business page runs the band at three. At a third of the width each tile
   is wider than the quarter it replaces, so it goes square rather than 3:4 —
   the band keeps the height it had at four instead of growing by a third. */
.tiles.three { grid-template-columns: repeat(3, 1fr); }
/* A bento at every width now: the page gutter either side, a gap between
   the tiles and rounded corners, rather than three images butted edge to
   edge. Tighter on a phone (below). */
.tiles { gap: 14px; padding-inline: 20px; }
.tile { border-radius: var(--r-lg); }
.tiles.three .tile { aspect-ratio: 1 / 1; }
/* The tilted phone is sized off the tile's width, so a wider tile drew it
   bigger and cropped the screen to its header. Pulled back so the card stack
   — the colourful part, and the point of the picture — makes it into frame. */
.tiles.three .tile.tilt img { width: 74%; top: 11%; left: 19%; }
/* Business page: the band runs a little taller (4:5) on a desktop, so the
   barista photo can show the till and counter at the foot without cropping
   her hair at the top. The portrait photo is anchored to its bottom edge; on
   a phone, where the tile is square again, it sits just below centre. */
.tiles.three.tall .t-till img { object-position: 50% 52%; }
@media (min-width: 921px) {
  .tiles.three.tall .tile { aspect-ratio: 4 / 5; }
  .tiles.three.tall .t-till img { object-position: 50% 100%; }
  .tiles.three.tall .t-dash img { object-position: 30% 50%; }
}
/* Customer page: the pocket photo, framed on the phone. */
/* Two tiles (the towns page): a photograph of a busy street beside the
   numbers a scheme reports. Wider than tall, so the pair isn't a wall. */
.tiles.two { grid-template-columns: repeat(2, 1fr); }
.tiles.two .tile { aspect-ratio: 5 / 4; }
.tiles.two .t-dash img { object-position: 30% 50%; }
@media (max-width: 720px) {
  .tiles.two { grid-template-columns: minmax(0, 1fr); }
  .tiles.two .tile { aspect-ratio: 4 / 3; }
}
.tile.t-pocket img { object-position: 57% 50%; }
/* Customer page: the phone square-on and centred, big, running off the foot
   of the tile. Sized by height so the cut lands inside the orange card at any
   tile size: 95% of the tile is left below the 5% top margin, and the
   render is sized so the tile's foot falls across the pink MOG card. */
.tile.pad.upright { padding: 0; position: relative; }
.tile.pad.upright img {
  position: absolute; top: 5%; left: 50%; transform: translateX(-50%);
  height: 132%; width: auto; max-width: none; max-height: none;
}
/* Mirrored: the phone comes in off the LEFT edge, tilted the other way, so
   it leans in towards the barista beside it rather than away from her. */
.tiles.three .tile.tilt.from-left img {
  left: auto; right: 9%; top: 18%; transform: rotate(16deg); transform-origin: 100% 0;
}
@media (max-width: 920px) { .tiles.three .tile.tilt.from-left img { top: 26%; } }

/* ── goal → solution ───────────────────────────────────────────────────────
   Two columns with a rule between the rows. Left is the operator's words,
   right is the product's. */
/* The heading sits in a column 30ch wide, which left a large hole to its right.
   The stamps fill it: one from each kind of place, which is the "for every
   business" point made without a line saying so. (These used to change with a
   row of trade tabs above; the tabs are gone and the section opens here, so it
   carries no top margin of its own — the band's padding is the gap.) */
.seg-row {
  /* Not 1fr for the text. A comfortable measure caps the paragraph at about
     64 characters whatever the column is, so a 1fr column just put 300px of
     white between the words and the stamps. Capping the column and grouping
     the row to the left keeps the two as one object. */
  display: grid; grid-template-columns: minmax(0, 600px) minmax(0, 1fr);
  gap: clamp(28px, 4vw, 56px); align-items: center;
}
/* Not `30ch`. A ch on this element is a character of the PARENT's 16px body
   font, so 30ch came out at about 240px and broke the 44px heading over four
   lines in the middle of an empty row. Font-relative widths have to sit on the
   element whose font they mean. */
/* 560/16ch broke the heading over three lines and left the row reading as two
   unrelated things at opposite ends of a white gap. The column is 720 now and
   the type is allowed to fill it. */
.goal-head { max-width: 600px; }
.goal-head h2 { font-size: clamp(28px, 3.6vw, 44px); max-width: 20ch; }
.goal-head p { color: var(--ink-dim); margin-top: 14px; max-width: 64ch; }

/* A stamp card filling up, beside the heading: one venue's card, eight
   slots, a stamp landing every beat until the reward shows, then round again.
   It is the first row of the list below ("a count they can watch filling up")
   shown rather than said. A second card tucked behind gives it the wallet's
   stacked look. */
.seg-art { position: relative; justify-self: center; width: min(100%, 360px); padding: 18px 0 26px; }
.fc-behind {
  position: absolute; inset: 30px -18px 8px 22px; border-radius: 26px;
  background: var(--house); transform: rotate(5deg);
  box-shadow: 0 18px 40px rgba(18, 13, 11, 0.12), inset 0 0 0 1px rgba(18, 13, 11, 0.08);
}
.fillcard {
  position: relative; border-radius: 26px; padding: 20px 22px 18px;
  background: linear-gradient(140deg, #B37DFF 0%, #8F4FF0 100%); color: #fff;
  transform: rotate(-3deg);
  /* Its own layer: the stamps animate inside a tilted card, and without one
     Safari repaints the whole tilted card, shadows and all, on every frame. */
  will-change: transform; backface-visibility: hidden;
  box-shadow: 0 22px 50px rgba(64, 20, 130, 0.28), inset 0 1px 0 rgba(255, 255, 255, 0.25);
}
.fc-top { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.fc-name { font-family: var(--display); font-weight: 800; font-size: 21px; letter-spacing: -0.03em; }
/* The count as the app draws it: the number large, "/8" smaller and fainter,
   and the venue's own stamp beside it. "Ready" in place of the number once
   the card is full. */
.fc-count { display: inline-flex; align-items: center; gap: 8px; flex: none; }
.fc-num, .fc-done {
  font-family: var(--display); font-weight: 800; font-size: 26px; letter-spacing: -0.03em;
  line-height: 1; font-variant-numeric: tabular-nums;
}
.fc-num i { font-style: normal; }
.fc-num small { font-size: 15px; opacity: 0.6; letter-spacing: 0; }
.fc-done { display: none; font-size: 22px; }
.fillcard.full .fc-num { display: none; }
.fillcard.full .fc-done { display: inline; }
.fc-count img { width: 34px; height: 34px; transform: rotate(-8deg);
  filter: drop-shadow(0 2px 3px rgba(40, 10, 90, 0.3)); }
.fc-slots { display: grid; grid-template-columns: repeat(4, 1fr); gap: 18px 12px; margin: 18px 0 12px; }
/* An empty place is the stamp's own silhouette, faint and upright, as the
   app draws it, rather than a dashed ring. */
.fc-slot {
  position: relative; aspect-ratio: 1;
  display: grid; place-items: center;
}
.fc-slot::before {
  content: ""; position: absolute; inset: 2%;
  -webkit-mask: url(/img/sk-bean.png) center / contain no-repeat;
  mask: url(/img/sk-bean.png) center / contain no-repeat;
  background: rgba(255, 255, 255, 0.16);
}
/* A filled place is the sticker itself, with the date it was collected on a
   tag tucked under its foot. Its resting state is set here and the landing is
   an animation with no fill, so nothing holds a stamp mid-pop when the card
   goes round again. */
.fc-slot img {
  position: relative; width: 96%; opacity: 0; transform: rotate(var(--t));
  filter: drop-shadow(0 3px 5px rgba(40, 10, 90, 0.35));
}
.fc-slot.on img { opacity: 1; animation: fc-pop 0.5s cubic-bezier(0.2, 0.9, 0.3, 1.3); }
/* Going round again: everything collected fades out together. */
.fillcard.fade .fc-slot.on img, .fillcard.fade .fc-slot.on .fc-date {
  opacity: 0; animation: none; transition: opacity 0.4s ease;
}
.fc-date {
  position: absolute; left: 50%; bottom: -7px; z-index: 1;
  background: #fff; color: var(--ink); border-radius: 999px; padding: 3px 5px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 8.5px; font-weight: 600; letter-spacing: 0.02em; line-height: 1; white-space: nowrap;
  box-shadow: 0 1px 3px rgba(18, 13, 11, 0.25);
  opacity: 0; transform: translate(-50%, 4px) rotate(calc(var(--t) * -0.4));
  transition: opacity 0.25s ease, transform 0.3s ease;
}
.fc-slot.on .fc-date { opacity: 1; transform: translate(-50%, 0) rotate(calc(var(--t) * -0.4)); transition-delay: 0.18s; }
@keyframes fc-pop {
  0%   { transform: scale(1.6) rotate(var(--t)); opacity: 0; }
  60%  { transform: scale(0.9) rotate(var(--t)); opacity: 1; }
  100% { transform: scale(1) rotate(var(--t)); }
}
@media (prefers-reduced-motion: reduce) {
  .fc-slot.on img { animation: none; transform: rotate(var(--t)); }
}
@media (max-width: 920px) {
  .seg-row { grid-template-columns: minmax(0, 1fr); gap: 8px; }
  /* Centred, and as wide as the column allows less room for the tilt and
     the card tucked behind it. */
  .seg-art { order: -1; justify-self: center; width: min(100% - 36px, 420px); margin: 0 auto 18px; }
}
@media (min-width: 1600px) {
  .seg-art { width: 400px; }
  .fc-name { font-size: 24px; }
}
.goals { margin-top: 38px; border-top: 1px solid var(--line); }
.goal {
  display: grid; grid-template-columns: 44px minmax(0, 1fr) minmax(0, 1.25fr);
  gap: 20px; align-items: start; padding: 24px 0; border-bottom: 1px solid var(--line);
}
/* Grey, not the accent. These are ordinals — they mark the row, they are not
   the point of it — and in purple they pulled the eye down the left edge
   ahead of the words they number. */
.goal .n { font-family: var(--display); font-weight: 800; font-size: 17px;
           color: var(--ink-faint); padding-top: 1px; font-variant-numeric: tabular-nums; }
.goal .g { font-weight: 600; font-size: 17px; letter-spacing: -0.01em; }
.goal .s { color: var(--ink-dim); font-size: 15.5px; }

/* ── steps ─────────────────────────────────────────────────────────────────── */
.steps { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; margin-top: 40px; }
.step { background: var(--house); border-radius: var(--r-lg); padding: 28px 26px 30px; }
/* One gradient across the row rather than the same one three times: each
   step is a window onto a strip as wide as all three and the gaps, pink at
   the first and blue by the last. Stacked, the strip runs down instead. */
.steps { --steps-gap: 16px; }
.step {
  background-image: linear-gradient(100deg, #FDEDFF 0%, #F1E9FF 50%, #D8E8FF 100%);
  background-size: calc(300% + 2 * var(--steps-gap)) 100%;
}
.step:nth-child(1) { background-position: 0% 0; }
.step:nth-child(2) { background-position: 50% 0; }
.step:nth-child(3) { background-position: 100% 0; }
.step .n {
  display: inline-grid; place-items: center; width: 30px; height: 30px;
  border-radius: 999px; background: var(--ink); color: #fff;
  font-family: var(--display); font-weight: 800; font-size: 14px;
}
.step h3 { font-size: 19px; margin-top: 18px; }
.step p { color: var(--ink-dim); font-size: 15px; margin: 10px 0 0; }

/* ── stats ─────────────────────────────────────────────────────────────────── */
.statband { background: var(--shell); }
.stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 28px; }
/* The month band: five figures across the full width, the stamp scatter in
   the right half behind them, and every colour mixed against white remixed
   on black. */
.inkband.month, #forward, .inkband.lowspot, #funding { position: relative; overflow: hidden; }
.inkband.month > :not(.field), #forward > :not(.field), .inkband.lowspot > :not(.field), #funding > :not(.field) { position: relative; z-index: 1; }
.inkband .stats.five { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 30px 24px; margin-top: 40px; }
/* Heading and sentence run the full width, to match the row of figures
   between them rather than sitting in a column of their own. */
.inkband.month h2, .inkband.month .wrap > p { max-width: none; }
.inkband.month .wrap > p { margin-top: 36px; }
.inkband .stat .cap { color: rgba(255, 255, 255, 0.60); }
.inkband .statnote { color: rgba(255, 255, 255, 0.38); }

.stat .fig {
  font-family: var(--display); font-weight: 800; letter-spacing: -0.04em;
  font-size: clamp(38px, 5vw, 58px); line-height: 1; font-variant-numeric: tabular-nums;
}
.stat .cap { margin-top: 10px; color: var(--ink-dim); font-size: 14.5px; text-wrap: balance; }
.statnote { margin-top: 34px; color: var(--ink-faint); font-size: 13.5px; max-width: 62ch; }

/* ── split features ────────────────────────────────────────────────────────── */
.split { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
         gap: clamp(28px, 4vw, 64px); align-items: center; }
.split.flip .copy { order: 2; }
.split h2 { font-size: clamp(26px, 3.2vw, 40px); }
.split p { color: var(--ink-dim); margin-top: 16px; max-width: 46ch; }
.split .shot {
  border-radius: var(--r-lg); overflow: hidden;
  background: var(--house);
  padding: clamp(18px, 2.6vw, 34px);
}
.split .shot img { border-radius: var(--r-sm); width: 100%; }

/* The privacy section's shield is a line drawing with nothing in it — no
   screen, no faces, no detail to go and look at — so it does not need the
   400px the tap demonstration earns. Smaller, and the sentence beside it gets
   the width instead: at 46ch the paragraph was stopping well short of its own
   column and leaving a gap that read as a mistake rather than as margin. */
#data .spot { max-width: 250px; }
/* The shield stays its own size; the column either side of it is filled
   with the three things the paragraph promises, as little cards from the
   app — the six-digit code, a count with no names on it, and the delete
   button. The outer cards sit on the column's edges, so the group spans the
   same width as the "Run a business?" box below. */
#data .secure-art { position: relative; width: 100%; min-height: 320px; display: grid; place-items: center; }
#data .secure-art .spot { width: 100%; }
.sa-chip {
  position: absolute; z-index: 2; background: #fff; color: var(--ink);
  border-radius: 16px; padding: 12px 16px;
  box-shadow: 0 12px 30px rgba(18, 13, 11, 0.12), 0 0 0 1px rgba(18, 13, 11, 0.07);
  display: flex; flex-direction: column; gap: 3px;
  animation: sa-float 6s ease-in-out infinite;
}
.sa-chip span { font-size: 12px; color: var(--ink-dim); }
.sa-code { left: 0; top: 20%; rotate: -4deg; }
.sa-code b {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 26px; font-weight: 700; letter-spacing: 0.14em; font-variant-numeric: tabular-nums;
}
.sa-count { right: 0; top: 10%; rotate: 3deg; animation-delay: -2s; }
.sa-count b { font-family: var(--display); font-weight: 800; font-size: 30px; letter-spacing: -0.03em; line-height: 1; }
.sa-count em {
  align-self: flex-start; margin-top: 4px; font-style: normal; font-size: 11px; font-weight: 700;
  background: var(--shell); border-radius: 999px; padding: 3px 8px; letter-spacing: 0.02em;
}
.sa-del {
  right: 6%; bottom: 10%; rotate: -2deg; animation-delay: -4s;
  flex-direction: row; align-items: center; gap: 8px; border-radius: 999px; padding: 10px 16px 10px 12px;
}
.sa-del svg { width: 18px; height: 18px; fill: none; stroke: var(--ink); stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.sa-del span { font-size: 14px; font-weight: 700; color: var(--ink); }
/* A settings switch, flicking on every few seconds: sharing is off unless
   the customer says otherwise. */
.sa-share {
  left: 7%; bottom: 8%; rotate: 3deg; animation-delay: -3s;
  flex-direction: row; align-items: center; gap: 12px; padding: 12px 14px 12px 16px;
}
.sa-share span { font-size: 14px; font-weight: 700; color: var(--ink); }
.sa-toggle {
  position: relative; flex: none; width: 44px; height: 26px; border-radius: 999px;
  background: var(--ink); animation: sa-track 6s ease-in-out infinite;
}
.sa-toggle::after {
  content: ""; position: absolute; top: 3px; left: 3px; width: 20px; height: 20px;
  border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(18, 13, 11, 0.3);
  transform: translateX(18px); animation: sa-knob 6s ease-in-out infinite;
}
@keyframes sa-track { 0%, 12% { background: rgba(18, 13, 11, 0.16); } 20%, 100% { background: var(--ink); } }
@keyframes sa-knob  { 0%, 12% { transform: translateX(0); } 20%, 100% { transform: translateX(18px); } }
@keyframes sa-float { 0%, 100% { translate: 0 0; } 50% { translate: 0 -8px; } }
@media (prefers-reduced-motion: reduce) { .sa-chip, .sa-toggle, .sa-toggle::after { animation: none; } }
@media (max-width: 560px) {
  #data .secure-art { min-height: 280px; }
  #data .secure-art .spot { max-width: 210px; }
  .sa-chip { padding: 9px 12px; border-radius: 13px; }
  .sa-chip span { font-size: 11px; }
  .sa-code b { font-size: 19px; }
  .sa-count b { font-size: 22px; }
  .sa-del span { font-size: 12px; }
  .sa-share { left: 2%; bottom: 4%; }
  .sa-share span { font-size: 12px; }
}
#data .copy p { max-width: 58ch; }
/* The towns page's version: a scheme total top left, the venue count top
   right, the months as bars bottom left, and the report bottom right. */
#data .secure-art:has(.sa-total) { min-height: 420px; }
.sa-total { left: 0; top: 6%; rotate: -4deg; animation-delay: -1s; }
.secure-art:has(.sa-total) .sa-count { top: 4%; }
.secure-art:has(.sa-total) .sa-del { bottom: 8%; right: 2%; }
.sa-total b { font-family: var(--display); font-weight: 800; font-size: 30px; letter-spacing: -0.03em; line-height: 1; }
.sa-total em {
  align-self: flex-start; margin-top: 4px; font-style: normal; font-size: 11px; font-weight: 700;
  color: #0E7A4F; background: #E3F5EC; border-radius: 999px; padding: 3px 8px;
}
.sa-bars { left: 5%; bottom: 6%; rotate: 3deg; animation-delay: -3s; gap: 8px; }
.sa-bars i { display: flex; align-items: flex-end; gap: 5px; height: 44px; }
.sa-bars s {
  display: block; width: 14px; height: calc(var(--v) * 100%); border-radius: 4px 4px 2px 2px;
  background: rgba(18, 13, 11, 0.18); transform-origin: bottom;
  animation: sa-grow 6s ease-in-out infinite;
}
.sa-bars s:last-child { background: var(--ink); }
.sa-bars s:nth-child(2) { animation-delay: 0.08s; } .sa-bars s:nth-child(3) { animation-delay: 0.16s; }
.sa-bars s:nth-child(4) { animation-delay: 0.24s; } .sa-bars s:nth-child(5) { animation-delay: 0.32s; }
.sa-bars s:nth-child(6) { animation-delay: 0.4s; }
@keyframes sa-grow { 0%, 6% { scale: 1 0.15; } 22%, 100% { scale: 1 1; } }
@media (prefers-reduced-motion: reduce) { .sa-bars s { animation: none; } }
@media (max-width: 560px) {
  .sa-total b { font-size: 22px; }
  #data .secure-art:has(.sa-total) { min-height: 400px; }
  #data .secure-art:has(.sa-total) .spot { max-width: 170px; }
  .sa-total { top: 0; }
  .secure-art:has(.sa-total) .sa-count { top: 2%; }
  .sa-bars { left: 2%; bottom: 2%; }
  .secure-art:has(.sa-total) .sa-del { bottom: 4%; }
  .sa-bars i { height: 32px; gap: 4px; }
  .sa-bars s { width: 10px; }
}

/* A shot on ink rather than on the house gradient, cropped shorter than the
   photograph is. `object-position: top` is what makes the cut happen at the
   FOOT of the picture — the wallet runs off the bottom edge and the top of the
   phone stays whole, which is the right way round: a screen clipped at the top
   reads as a mistake, one clipped at the bottom reads as a crop. */
/* White rather than near-black. One user, so the modifier is renamed with it
   instead of leaving a rule called .ink that paints white. */
.split .shot.paper { background: var(--paper); }
/* The same edge as the bento panel above it: a white panel on a pale band
   needs its own line and shadow, or it reads as a hole in the gradient. The
   ring is an ::after for the reason given on .bento — the picture inside
   would paint over an inset shadow. */
.split .shot.paper { position: relative; box-shadow: 0 22px 50px rgba(18, 13, 11, 0.13); }
.split .shot.paper::after {
  content: ""; position: absolute; inset: 0; z-index: 2; pointer-events: none;
  border-radius: inherit; box-shadow: inset 0 0 0 1px rgba(18, 13, 11, 0.13);
}
/* No frame at all: the photograph is its own picture and the gradient inset
   was putting a second border round something that already had edges. */
.split .shot.bare { background: none; padding: 0; }
.split .shot.bare img { border-radius: var(--r-lg); }

/* Flush to the foot: inset on three sides, nothing at the bottom, and the
   picture cropped shorter than it is so its lower edge IS the container's.
   `object-position: top` again puts the cut at the bottom — the card's head
   stays whole and it runs out of the panel, which is what a card in a wallet
   does. The panel's own rounded corners clip the image, so the image needs no
   radius of its own down there. */
.split .shot.flush { padding-bottom: 0; }
.split .shot.flush img {
  aspect-ratio: 6 / 5; object-fit: cover; object-position: top;
  border-radius: var(--r-sm) var(--r-sm) 0 0;
}
/* Cropped from the middle, not the top: the phone sits dead centre in the
   photograph, and a top-anchored crop pushed it low in the frame. */
.split .shot.short img { aspect-ratio: 3 / 2; object-fit: cover; object-position: center; }

/* ── the offer card ────────────────────────────────────────────────────────
   A venue card drawn in markup: Seren Taxis, a made-up firm. Every size is in
   container units (cqw), so the card scales as one picture at any column
   width, the way the screenshot it replaced did. The offer pulses — a ring
   breathing out from its edge and the price chip beating — because the point
   of the section is that it is live on their phone the moment it is typed. */
/* The container is the panel, not the card: cqw on an element resolves
   against its nearest container ANCESTOR, so the card's own padding needs one
   above it. */
.split .shot.vshot { container-type: inline-size; overflow: visible; }
.vcard {
  position: relative; overflow: hidden;
  border-radius: 7cqw; padding-bottom: 7cqw;
  box-shadow: 0 24px 50px rgba(120, 90, 0, 0.18), inset 0 1px 0 rgba(255, 255, 255, 0.6);
  background: linear-gradient(165deg, #FFEA5C 0%, #FFDD2E 60%, #FFD21A 100%);
  color: var(--ink); padding-inline: 6cqw;
}
/* No ticker: the app dropped the "HAPPY HOUR" run across the top of a card
   when the offer bar under the name started saying it. */
.vc-head { display: flex; align-items: center; gap: 2.4cqw; margin-top: 6cqw; }
.vc-logo { display: flex; align-items: center; gap: 1.6cqw; line-height: 0.86; }
.vc-logo svg { width: 7.2cqw; height: 7.2cqw; fill: var(--ink); }
.vc-logo span { display: flex; flex-direction: column; }
.vc-logo b { font-family: var(--display); font-weight: 800; font-size: 7.6cqw; letter-spacing: -0.04em; }
.vc-logo small {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 2.3cqw; font-weight: 700; letter-spacing: 0.62em; margin-top: 0.8cqw; color: var(--ink);
}
.vc-vip {
  background: var(--ink); color: #FFE14A; border-radius: 999px;
  font-size: 2.6cqw; font-weight: 800; letter-spacing: 0.06em;
  writing-mode: vertical-rl; transform: rotate(180deg); padding: 2cqw 1.3cqw;
}
.vc-loc {
  margin-left: auto; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 3.2cqw; color: rgba(18, 13, 11, 0.72);
}
/* The count as the app sets it now: the stamps big, "/8" quieter, and the
   card's own stamp beside it, where a progress ring used to be. */
.vc-count {
  margin-left: auto; display: flex; align-items: baseline; gap: 0.4cqw;
  font-family: var(--display); font-weight: 800; font-size: 4.2cqw; color: rgba(18, 13, 11, 0.5);
}
.vc-count b { font-size: 7cqw; color: var(--ink); letter-spacing: -0.03em; }
.vcard .vc-count img { width: 8.4cqw; border-radius: 0; align-self: center; margin-left: 1.6cqw; filter: drop-shadow(0 0.5cqw 0.8cqw rgba(90, 60, 0, 0.3)); }

.vc-title { margin-top: 4.4cqw; display: flex; flex-direction: column; }
.vc-title b { font-family: var(--display); font-weight: 800; font-size: 5.6cqw; letter-spacing: -0.03em; }
.vc-title span { font-size: 3.6cqw; color: rgba(18, 13, 11, 0.68); margin-top: 0.6cqw; }

.vc-offer {
  margin-top: 4cqw; display: flex; align-items: center; gap: 2.6cqw;
  padding: 3.4cqw 3.4cqw 3.4cqw 3.8cqw; border-radius: 3.6cqw;
  background: rgba(255, 255, 255, 0.38); border: 1px solid rgba(18, 13, 11, 0.22);
  animation: vc-pulse 2.4s ease-out infinite;
}
.vc-offer > div { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.vc-offer b { font-family: var(--display); font-weight: 800; font-size: 4.4cqw; letter-spacing: -0.025em; }
.vc-offer span { font-size: 3.2cqw; color: rgba(18, 13, 11, 0.68); margin-top: 0.6cqw; }
.vc-offer .vc-price {
  margin: 0; flex: none; background: var(--ink); color: #FFE14A; border-radius: 999px;
  font-family: var(--display); font-weight: 800; font-size: 4.6cqw; letter-spacing: -0.02em;
  padding: 1.6cqw 3.2cqw; animation: vc-beat 2.4s ease-in-out infinite;
}
.vc-chev { width: 4cqw; height: 4cqw; fill: none; stroke: rgba(18, 13, 11, 0.6); stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round; }
@keyframes vc-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(18, 13, 11, 0.32), 0 0 0 0 rgba(255, 255, 255, 0); background-color: rgba(255, 255, 255, 0.38); }
  18%  { background-color: rgba(255, 255, 255, 0.62); }
  70%  { box-shadow: 0 0 0 3cqw rgba(18, 13, 11, 0), 0 0 6cqw 0 rgba(255, 255, 255, 0.45); background-color: rgba(255, 255, 255, 0.38); }
  100% { box-shadow: 0 0 0 0 rgba(18, 13, 11, 0), 0 0 0 0 rgba(255, 255, 255, 0); }
}
@keyframes vc-beat {
  0%, 100% { transform: scale(1); }
  12% { transform: scale(1.12); }
  24% { transform: scale(0.98); }
  36% { transform: scale(1.05); }
}

.vc-stamps { display: grid; grid-template-columns: repeat(4, 1fr); gap: 7cqw 6cqw; margin-top: 4.6cqw; }
/* An empty slot is the stamp's own shadow in the card's ink, upright, as the
   app draws one. */
.vc-stamp.ghost {
  aspect-ratio: 1; background: var(--ink); opacity: 0.12;
  -webkit-mask: url(/img/sk-taxi.png) center / contain no-repeat;
  mask: url(/img/sk-taxi.png) center / contain no-repeat;
}
.vc-left { margin: 13cqw 0 0; font-size: 3.8cqw; font-weight: 650; color: rgba(18, 13, 11, 0.8); }
.vc-stamp { position: relative; display: block; }
.vc-stamp img { width: 100%; display: block; transform: rotate(var(--t)); filter: drop-shadow(0 0.8cqw 1.2cqw rgba(90, 60, 0, 0.3)); }
.vc-stamp b {
  position: absolute; left: 50%; bottom: -3.4cqw; transform: translateX(-50%) rotate(calc(var(--t) * -0.5));
  background: var(--ink); color: #fff; border-radius: 999px; padding: 0.8cqw 1.4cqw;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 2.3cqw; font-weight: 700; letter-spacing: 0.02em; line-height: 1; white-space: nowrap;
  box-shadow: 0 0.3cqw 0.8cqw rgba(0, 0, 40, 0.3);
}
@media (prefers-reduced-motion: reduce) {
  .vc-offer, .vc-offer .vc-price { animation: none; }
}

/* ── the bento board ───────────────────────────────────────────────────────
   The dashboard rebuilt as markup instead of a screenshot. Two reasons it is
   worth the lines: the figures stay sharp at any width and on any screen, and
   a wrong number is a text edit rather than a re-export.

   The bleed is the whole trick. The panel has a fixed aspect and clips, and
   the grid is pinned to NEGATIVE insets inside it, so the outer ring of tiles
   runs under the panel's edges and is cropped by its own rounded corners. The
   board therefore reads as a window onto a larger dashboard rather than as
   eight boxes that happen to fit.

   The bleed is in pixels rather than per cent for one reason: every tile that
   touches an edge adds the same length back as padding on that side, so what
   the panel crops is the tile's own margin and its corner, never a figure. A
   percentage bleed cannot be compensated that way, and the first draft of this
   cut the 3 off 361.

   Four columns and four rows, named rather than numbered: at this size the
   areas are the design, and a column span written as a number is a puzzle the
   next person has to solve twice. */
.split .shot.bento {
  position: relative; padding: 0; aspect-ratio: 9 / 8;
  /* The board is white tiles on a white page, and with the outer ring of them
     running to the edge there was nothing to say where the panel stopped —
     the gradient only survives in the gaps. So the panel draws its own edge:
     a hairline ring over the tiles, and a soft shadow under the whole thing.
     The ring is an ::after rather than an inset shadow because an inset
     shadow paints under the children, and the children are what cover it. */
  box-shadow: 0 22px 50px rgba(18, 13, 11, 0.13);
}
.split .shot.bento::after {
  content: ""; position: absolute; inset: 0; z-index: 2; pointer-events: none;
  border-radius: inherit; box-shadow: inset 0 0 0 1px rgba(18, 13, 11, 0.13);
}

.bento-grid {
  --bleed: clamp(14px, 2.1vw, 28px);
  position: absolute;
  inset: calc(var(--bleed) * -1) calc(var(--bleed) * -1) calc(var(--bleed) * -1.15);
  display: grid; gap: clamp(8px, 1.15vw, 15px);
  grid-template-columns: repeat(4, 1fr);
  /* Row 1 and row 4 carry a caption, a figure, a sub-line and a chart; rows 2
     and 3 carry three of those at most, and the tall tile spanning them has
     slack either way. Weighting them evenly is what forced the panel square. */
  grid-template-rows: 1.12fr 0.77fr 0.77fr 0.98fr;
  grid-template-areas:
    "stamps  stamps  back    back"
    "where   where   busy    busy"
    "where   where   drift   drift"
    "newcus  newcus  happy   rewards";
}

/* Put back, as padding, exactly what the panel takes off each outer tile.
   Written as .bt.a-* rather than .a-*: .bt sets padding as a shorthand, and at
   equal specificity whichever rule comes last wins — which is how the first
   version of this cut the 3 off 361 despite the compensation being here. */
.bt.a-stamps, .bt.a-where, .bt.a-new { padding-left: calc(clamp(10px, 1.5vw, 18px) + var(--bleed)); }
.bt.a-back, .bt.a-busy, .bt.a-drift, .bt.a-rewards { padding-right: calc(clamp(10px, 1.5vw, 18px) + var(--bleed)); }
.bt.a-stamps, .bt.a-back { padding-top: calc(clamp(10px, 1.5vw, 18px) + var(--bleed)); }
/* The bottom row is the one edge where padding is the wrong tool: pushing the
   content up off the crop left the sparkline four pixels tall. These tiles
   align to the top instead and let their own foot run under the panel edge —
   a chart that carries on past the fold is the effect we are after anyway. */
.bt.a-new, .bt.a-rewards, .bt.a-happy { justify-content: flex-start; }
.a-stamps  { grid-area: stamps; }
.a-back    { grid-area: back; }
.a-where   { grid-area: where; }
.a-busy    { grid-area: busy; }
.a-drift   { grid-area: drift; }
.a-new     { grid-area: newcus; }
.a-rewards { grid-area: rewards; }
.a-happy   { grid-area: happy; }

.bt {
  position: relative; overflow: hidden; min-width: 0;
  background: var(--paper); border-radius: clamp(12px, 1.4vw, 18px);
  padding: clamp(10px, 1.5vw, 18px);
  display: flex; flex-direction: column; justify-content: center; gap: 3px;
  box-shadow: 0 5px 14px rgba(18, 13, 11, 0.055);
}
.bt-cap {
  flex: none; line-height: 1.2;
  font-size: clamp(8.5px, 0.78vw, 10px); font-weight: 600;
  letter-spacing: 0.15em; text-transform: uppercase; color: var(--ink-faint);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.bt-fig {
  font-family: var(--display); font-weight: 800; letter-spacing: -0.03em;
  flex: none;
  font-size: clamp(21px, 2.2vw, 30px); line-height: 1.08; margin-top: 2px;
}
.bt-fig.sm { font-size: clamp(16px, 1.7vw, 23px); }
.bt-sub { flex: none; font-size: clamp(9.5px, 0.85vw, 11.5px); color: var(--ink-dim); line-height: 1.35; }
/* The one place a colour carries meaning on this board, so it is the only
   place a second hue is spent. */
.bt-sub.up { color: var(--chart-up); }

/* Sparklines fill whatever is left under the caption: the tile sets the
   height, the viewBox is stretched to it, and the stroke is drawn without
   scaling so it stays the same weight in a tall tile and a short one. */
.bt-spark { flex: 1 1 0; min-height: 16px; width: 100%; margin-top: 4px; }
.bt-spark path {
  fill: none; stroke: var(--chart-up); stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
.bt-spark .area { fill: rgba(25, 169, 116, 0.12); stroke: none; }

/* A ring and its reading, side by side, so the tile works at half width. */
.bt-ring { display: flex; align-items: center; gap: clamp(10px, 1.3vw, 16px); margin-top: 4px; min-width: 0; }
.bt-ring > div { min-width: 0; }
.bt-donut {
  flex: none; width: clamp(44px, 5vw, 66px); aspect-ratio: 1; border-radius: 50%;
  background: conic-gradient(var(--chart-up) calc(var(--p) * 1%), rgba(18, 13, 11, 0.10) 0);
  position: relative;
}
.bt-donut::before {
  content: ""; position: absolute; inset: 22%; border-radius: 50%; background: var(--paper);
}

.bt-rows { list-style: none; margin: 5px 0 0; padding: 0; display: grid; gap: clamp(4px, 0.75vw, 11px); flex: 1 1 0; align-content: center; min-height: 0; }
.bt-rows li { display: grid; grid-template-columns: 4.6em 1fr auto; align-items: center; gap: 10px; }
.bt-rows span { font-size: clamp(10px, 0.9vw, 12.5px); color: var(--ink-dim); }
.bt-rows i { height: 7px; border-radius: 999px; background: var(--chart-up); width: var(--w); }
.bt-rows b { font-family: var(--display); font-weight: 700; font-size: clamp(11px, 1vw, 14px); }

.bt-bars { display: flex; align-items: flex-end; gap: clamp(3px, 0.5vw, 6px); flex: 1 1 0; min-height: 20px; margin-top: 4px; }
.bt-bars i { flex: 1; height: var(--h); border-radius: 2px; background: rgba(25, 169, 116, 0.26); }
.bt-bars i.on { background: var(--chart-up); }

/* Below the split's own stacking point the panel is the full column, so four
   columns of tile become two and the tall tiles give up their second row. The
   side bleed goes to nothing here: on a 350px panel it would eat a third of a
   tile's text, and the compensation map above is written for the wide layout's
   edges, not this one. Top and bottom still bleed, which is where the effect
   reads anyway. */
@media (max-width: 760px) {
  .split .shot.bento { aspect-ratio: 2 / 3; }
  .bento-grid {
    inset: calc(var(--bleed) * -1) 0 calc(var(--bleed) * -1.15);
    grid-template-columns: repeat(2, 1fr);
    grid-template-rows: repeat(5, 1fr);
    grid-template-areas:
      "stamps  stamps"
      "back    back"
      "where   where"
      "busy    drift"
      "newcus  rewards";
  }
  .bt.a-stamps, .bt.a-where, .bt.a-new,
  .bt.a-back, .bt.a-busy, .bt.a-drift, .bt.a-rewards {
    padding-left: clamp(10px, 1.5vw, 18px); padding-right: clamp(10px, 1.5vw, 18px);
  }
  .bt.a-back { padding-top: clamp(10px, 1.5vw, 18px); }
  /* Only the first tile meets the top edge here, and at 99px it has no three
     pixels to spare for the compensation — so it keeps the clearance and the
     sparkline gives up the difference. */
  .bt.a-stamps .bt-spark { min-height: 12px; }
  .bt.a-new, .bt.a-rewards { justify-content: flex-start; }
  .a-happy { display: none; }
}
.inline-link {
  display: inline-block; margin-top: 20px; font-weight: 600; font-size: 15.5px;
  color: var(--ink); text-decoration: none;
  border-bottom: 2px solid var(--ink); padding-bottom: 2px;
}

/* ── ink band ──────────────────────────────────────────────────────────────── */
.inkband { background: var(--ink); color: #fff; }
.inkband .inner { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
                  gap: clamp(28px, 5vw, 72px); align-items: center; }
.inkband h2, .inkband blockquote {
  font-family: var(--display); font-weight: 700; letter-spacing: -0.03em;
  font-size: clamp(24px, 3.2vw, 38px); line-height: 1.16; max-width: 20ch; margin: 0;
}
.inkband p { margin-top: 24px; color: rgba(255, 255, 255, 0.64); font-size: 15.5px; max-width: 44ch; }

/* The quote runs wider than a heading would. 20ch suits a headline, which is
   read in one glance; this is a sentence somebody said, and at that measure it
   was breaking over three short lines with a third of its column unused. */
/* Wide enough that the first line — broken by hand after "till." — fits on
   one line rather than wrapping before the break lands. */
.inkband blockquote { max-width: 30ch; }
/* And the spot grows with it. The artwork was cropped to its own ink, so at
   the old 400px it now draws the scene larger than before — this is the rest
   of the slack in the column, not a second enlargement. */
.inkband .spot { max-width: 370px; }
/* The scene is a diagonal — phone low and left, card high and right — so its
   crop box centres on the diagonal rather than on the phone, which is what the
   eye reads as the subject. With the waves gone and the canvas trimmed to the
   remaining ink, the phone's centre sits 3.3% of the canvas below the box
   centre, so the text drops by that share of the spot's capped height
   (370 x 546/592 x 0.033 = 11px) to come level with it. Only while the band is
   two columns, where the spot is always at its cap. */
@media (min-width: 921px) {
  .inkband.lowspot .inner > :first-child { transform: translateY(11px); }
}
.inkband .shot { border-radius: var(--r-lg); overflow: hidden; }

/* ── the project deck ──────────────────────────────────────────────────────
   Four cards, fanned, that turn over: a photograph of a local project on the
   front, and on the back the kind of card the pound came off — a coffee shop,
   a bakery, a barber, a taxi. The section's whole argument in one object, and
   shorter than the sentence it would otherwise take.

   Built in two layers on purpose. The OUTER .deck-card carries the fan — the
   step up, the slight growth to the right, and the entrance — and the INNER
   .deck-flip carries the rotation. One element doing both means the entrance
   transform and the flip transform overwrite each other, and the card either
   arrives flat or never turns. */
.deck {
  /* Where in the cycle a card has finished turning and is showing its stamp.
     Read by the script that fires the confetti, so the two cannot drift: the
     keyframes below and this number are the only places the timing lives. */
  --landed: 0.44;
  position: relative; display: flex; justify-content: center; align-items: center;
  /* Enough depth for the turn to read as a turn rather than a squash. */
  perspective: 1400px;
  /* Room for the up-down offsets, which are transform and so invisible to
     layout — without this the raised cards are clipped by the band. */
  padding-block: 26px;
}
.deck-card {
  position: relative; flex: none;
  /* Sized so the row spans its column exactly, like the image above it.
     The arithmetic is not the obvious one: the negative margins pull the
     laid-out row IN, but each end card's own box still hangs a margin's worth
     beyond it, so what you see is the row plus two margins rather than the
     row. 29.9% at m = 3% is the pair that measures full-width across every
     breakpoint — arrived at on the page rather than on paper, because flex
     rounds the overhang in a way the arithmetic does not predict. Move one
     number and the other has to move with it. */
  width: 29.9%; margin-inline: -3%;
  aspect-ratio: 3 / 4;
  z-index: var(--i);
  opacity: 0; transform: translateY(44px) scale(0.9);
  transition: opacity .55s ease, transform .7s cubic-bezier(0.22, 1, 0.36, 1);
  transition-delay: calc(var(--i) * 95ms);
}
/* Up, down, up, down. All one size: a growth ramp left-to-right also insets
   the small end, so the row stops lining up with anything above it. */
.deck-card:nth-child(odd)  { --y: -20px; }
.deck-card:nth-child(even) { --y: 20px; }
.deck.in .deck-card { opacity: 1; transform: translateY(var(--y)); }
.deck-flip { position: absolute; inset: 0; transform-style: preserve-3d; }
/* Only once it has arrived, or the first card turns while the fourth is still
   on its way in. */
/* The first and third arrive already turned over, so the row is never four of
   the same thing: two photographs and two stamps, whichever way round the
   cycle happens to be. Set statically as well as by the negative delay below,
   or they show their photograph for the length of the entrance and then snap. */
.deck-card:nth-child(odd) .deck-flip { transform: rotateY(180deg); }
.deck.in .deck-flip {
  animation: deck-flip 11s cubic-bezier(0.65, 0, 0.35, 1) infinite;
  /* A full second between cards, not a tenth: at a short stagger four cards
     turning read as one event with a wobble. At this spacing it is a wave —
     each card turns, is read, and the next goes. */
  animation-delay: calc(1.1s + var(--i) * 1s);
}
/* A negative delay starts the animation part-way in rather than late. -6s and
   -8s land both of these inside the stretch where the back is held (44%-82%
   of 11s, so 4.8s to 9s), which is what "already flipped" has to mean for an
   animation that never stops. */
.deck.in .deck-card:nth-child(odd) .deck-flip {
  animation-delay: calc(-6s - var(--i) * 1s);
}
/* Ends on 360 rather than back at 0: a return to 0 spins the card backwards
   through the photograph it just showed, which reads as a rewind. */
@keyframes deck-flip {
  0%, 32%   { transform: rotateY(0deg); }
  44%, 82%  { transform: rotateY(180deg); }
  94%, 100% { transform: rotateY(360deg); }
}
.deck-face {
  position: absolute; inset: 0; border-radius: 20px; overflow: hidden;
  display: grid; place-items: center;
  backface-visibility: hidden; -webkit-backface-visibility: hidden;
  box-shadow: 0 20px 44px rgba(0, 0, 0, 0.42);
}
.deck-face.front img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* A zero-size anchor at the middle of the card for the paper to leave from.
   On .deck-card rather than on the face, for two reasons: the faces clip
   (border-radius and overflow), so a piece would be cut off at the edge it is
   trying to fly past, and the back face is turned away for half the cycle,
   which would take the confetti with it. */
.deck-card .confetti {
  position: absolute; left: 50%; top: 50%;
  width: 0; height: 0; pointer-events: none; z-index: 3;
}
.deck-card .confetti i { position: absolute; left: 0; top: 0; display: block; border-radius: 1.5px; }
.deck-face.back { transform: rotateY(180deg); background: var(--tint); }
/* One stamp, centred, at the size it is on a real card. */
.deck-face.back img { width: 44%; height: auto; display: block; }

/* The pale set. Tints of the brand colours rather than the colours themselves:
   these are backs of cards on a near-black band, and at full strength four of
   them beside each other fight both the headline and each other. */
:root {
  --t-yellow: #F7EFC4;
  --t-blue:   #D8E5FB;
  --t-white:  #FFFFFF;
  --t-lilac:  #EEDDF8;
}

/* Somebody who has asked not to be moved around gets the four photographs,
   arrived and still. The backs are the same idea said twice, so losing them
   costs nothing. */
@media (prefers-reduced-motion: reduce) {
  .deck-card { opacity: 1; transition: none; }
  .deck-card, .deck.in .deck-card { transform: translateY(var(--y)); }
  .deck.in .deck-flip, .deck.in .deck-card:nth-child(odd) .deck-flip { animation: none; }
}

/* ── "built for all kinds of" ──────────────────────────────────────────────── */
.kinds { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(24px, 3vw, 44px); }
.kind h3 { font-size: 19px; }
.kind ul { list-style: none; margin: 16px 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
.kind li {
  display: inline-flex; align-items: center; gap: 7px;
  border: 1px solid var(--line); border-radius: 999px;
  padding: 6px 13px 6px 7px; font-size: 14px; color: var(--ink-dim);
}
.kind li img { width: 22px; height: 22px; }
/* A count in place of the icon (the towns page’s window kit). */
.kind li:has(> .qty) { padding-left: 13px; }
.kind .qty { color: var(--ink); font-weight: 700; font-variant-numeric: tabular-nums; }

/* ── pricing ───────────────────────────────────────────────────────────────── */
.plans { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; margin-top: 40px; }
.plan {
  border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 28px 26px 26px; background: var(--paper);
  display: flex; flex-direction: column;
}
.plan.best { border-color: var(--ink); position: relative; }
.plan .tag {
  position: absolute; top: -11px; left: 26px;
  background: var(--ink); color: #fff; font-size: 11.5px; font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase; padding: 4px 11px; border-radius: 999px;
}
.plan .tier { font-family: var(--display); font-weight: 800; font-size: 19px; letter-spacing: -0.02em; }
.plan .fee { font-family: var(--display); font-weight: 800; letter-spacing: -0.04em;
             font-size: 46px; line-height: 1; margin-top: 12px; }
.plan .per { color: var(--ink-faint); font-size: 14px; margin-top: 6px; }
.plan .what { color: var(--ink-dim); font-size: 15px; margin: 18px 0 24px; flex: 1; }
.plan .btn { width: 100%; }
.foot-note { margin-top: 22px; color: var(--ink-faint); font-size: 13.5px; }

/* ── faq ───────────────────────────────────────────────────────────────────── */
/* The FAQ band carries the hero's drifting scatter in its margins. The
   questions are an 820px column, so the stamps are placed in the outer ~9% of
   each side and the field goes entirely below 1240px — the width at which
   that outer strip starts to reach the column. Behind an answer they would be
   noise in the one place people are reading carefully. */
.faqband { position: relative; overflow: hidden; }
.faqband > :not(.field) { position: relative; z-index: 1; }
/* Between a phone and a wide desktop the questions' column leaves only a
   narrow margin, so the FAQ scatter switches to its phone arrangement: the
   five marked stamps, at the very edges, part off the screen. */
@media (min-width: 721px) and (max-width: 1240px) {
  .faqband .field .sk:not([style*="--mx"]) { display: none; }
  .faqband .field .sk {
    --w: calc(var(--s) * 0.88);
    width: var(--w); top: var(--my);
    left: calc((100% - var(--w) * 0.6) * var(--mx) - var(--w) * 0.4 * (1 - var(--mx)));
  }
}

.faq { max-width: 820px; margin-inline: auto; margin-top: 36px; }
.faq details { border-bottom: 1px solid var(--line); }
.faq summary {
  list-style: none; cursor: pointer; padding: 20px 36px 20px 0; position: relative;
  font-family: var(--display); font-weight: 700; font-size: 18px; letter-spacing: -0.02em;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: ""; position: absolute; right: 6px; top: 50%;
  width: 10px; height: 10px; margin-top: -6px;
  border-right: 2px solid var(--ink-faint); border-bottom: 2px solid var(--ink-faint);
  transform: rotate(45deg); transition: transform 0.18s ease;
}
.faq details[open] summary::after { transform: rotate(-135deg); margin-top: -2px; }
.faq .a { color: var(--ink-dim); font-size: 15.5px; padding: 0 40px 22px 0; }

/* ── cross-link, closer, footer ────────────────────────────────────────────── */
.cross {
  border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 26px 28px; display: flex; gap: 20px; align-items: center;
  flex-wrap: wrap; justify-content: space-between;
  /* A scatter of stamps behind it: relative so the scatter has something to
     be absolute against, and clipped so the stamps end at the panel's own
     rounded corners rather than running into the page. */
  position: relative; overflow: hidden;
  background: var(--paper);
}

/* Every scatter drifts, and visibly. A one-pixel breath is the kind of motion
   that is only ever noticed as a rendering fault, so these move far enough to
   read as deliberate: up and down about their own height, with a sway on top
   of the angle each one already sits at.
 *
 * That base angle is why the keyframes carry rotate(var(--r)) rather than
 * translating alone — transform is one property, so animating it at all
 * throws away the rotation the scatter was composed with, and every stamp
 * would snap upright on the first frame.
 *
 * will-change because each of these is a blurred bitmap: promoted, the blur is
 * rendered once into a layer and only the transform moves. Without it the
 * browser re-blurs on every frame, which is the one way a decorative drift can
 * cost real money. */
.sk {
  animation: sk-drift var(--dur, 5.5s) ease-in-out infinite alternate;
  animation-delay: var(--d, 0s);
  will-change: transform;
}

/* The hero's stamps carry no timings of their own — twelve of them, and
   writing four custom properties onto each in the markup would bury the
   composition under bookkeeping. Seven variations cycled by position is
   enough that no two neighbours are ever in step, and the panel scatter
   further down keeps its own inline values because inline wins. */
.field .sk:nth-child(7n + 1) { --dur: 6.6s;  --d: -1.1s; --lift: 14px; --sway: 7deg; }
.field .sk:nth-child(7n + 2) { --dur: 4.9s;  --d: -3.4s; --lift: 10px; --sway: -10deg; }
.field .sk:nth-child(7n + 3) { --dur: 7.8s;  --d: -0.5s; --lift: 17px; --sway: 9deg; }
.field .sk:nth-child(7n + 4) { --dur: 5.4s;  --d: -2.2s; --lift: 12px; --sway: -7deg; }
.field .sk:nth-child(7n + 5) { --dur: 8.6s;  --d: -4.1s; --lift: 18px; --sway: 6deg; }
.field .sk:nth-child(7n + 6) { --dur: 6.1s;  --d: -1.7s; --lift: 11px; --sway: -11deg; }
.field .sk:nth-child(7n + 7) { --dur: 7.2s;  --d: -2.9s; --lift: 15px; --sway: 8deg; }
@keyframes sk-drift {
  from { transform: translateY(var(--lift, 9px))
                    rotate(var(--r))
                    scale(0.96); }
  to   { transform: translateY(calc(var(--lift, 9px) * -1))
                    rotate(calc(var(--r) + var(--sway, 9deg)))
                    scale(1.04); }
}
@media (prefers-reduced-motion: reduce) {
  .sk { animation: none; }
}
/* Everything that is not the scatter sits above it. */
.cross > :not(.field) { position: relative; z-index: 1; }
/* Solid white rather than the ghost's transparent fill, which over a gradient
   would tint the button and lose it against the panel. */
.btn.ghost.solid { background: var(--paper); border-color: var(--line); }
.btn.ghost.solid:hover { border-color: var(--ink-dim); }
.cross b { font-family: var(--display); font-weight: 800; letter-spacing: -0.02em; font-size: 18px; }
.cross p { margin: 6px 0 0; color: var(--ink-dim); font-size: 15px; }

/* The house gradient mixed about four fifths of the way to white. The full
   strength version earns its place once, on the closing band; behind a section
   you are meant to read and compare numbers in, it has to be a wash — enough
   to lift the white plan cards off the page, not enough to tint them. */
.soft {
  background: var(--house);
}

.closer {
  background: var(--house);
  text-align: center;
}
.closer h2 { font-size: clamp(30px, 4.4vw, 54px); max-width: 22ch; margin-inline: auto; text-wrap: balance; }
.closer p { color: var(--ink-dim); margin: 18px auto 0; max-width: 64ch; }
.keycards { max-width: 620px; margin: clamp(6px, 1.2vw, 14px) auto 0; }
.keycards img { width: 100%; height: auto; display: block; }

/* A heading that is meant to be read as one breath, so it is kept on one line.
   The clamp underneath it has to come down far enough that the line still fits
   a 390px phone — nowrap without that is a heading that runs off the side of
   the page rather than one that wraps. */
.one-line { white-space: nowrap; max-width: none; }

/* ── the wallet ───────────────────────────────────────────────────────────
   The graphic whole, with no panel round it, running off the bottom of the
   section. The closer drops its bottom padding so the section's edge and the
   artwork's edge are the same line — the MOG card is cut by the band itself
   rather than by a box drawn inside it. */
.closer.flush { padding-bottom: 0; }
.wallet { max-width: 460px; margin: clamp(34px, 4.5vw, 54px) auto 0; }
.wallet img { width: 100%; display: block; }
/* The hand-held version is a near-square cut-out rather than a flat graphic, so
   it wants more width to read at the same apparent phone size, and it wants a
   shadow — but drop-shadow and not box-shadow. An outer box-shadow is painted
   only outside the border box, which on a transparent PNG means a hard-edged
   rectangle of shade around the artwork and daylight through its corners.
   filter: drop-shadow follows the alpha, so the shadow is the hand's. */
/* Wider than the flat wallet graphic, and nudged left. The photograph's own
   composition puts the phone in its right-hand two thirds with the forearm
   sweeping in from the bottom left, so centring the FILE leaves the phone
   sitting off to one side of a centred headline and a lake of empty gradient
   beside it. Centring the PHONE instead means the forearm runs out of the
   bottom-left corner, which is what it is doing in the photograph anyway. */
/* The October photograph is the whole hand, uncut: taller than the last one
   and with the phone further right, so it is nudged further to bring the
   phone under the headline. The forearm then runs out past the column's left
   edge — on a phone, out past the screen's — and the section clips it there
   rather than letting the page scroll sideways to show the rest of an arm. */
.closer.flush { overflow-x: clip; }
.wallet.hand { max-width: 600px; margin-top: 30px; }
.wallet.hand img { transform: translateX(-22%); }
@media (max-width: 640px) { .wallet.hand img { transform: translateX(-14%); } }

.foot { border-top: 1px solid var(--line); padding-block: 56px 40px; font-size: 14.5px; }
.foot-cols { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 28px; }
.foot h4 { font-size: 13px; letter-spacing: 0.1em; text-transform: uppercase;
           color: var(--ink-faint); margin: 0 0 14px; font-weight: 700; font-family: var(--body); }
.foot ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 9px; }
.foot a { color: var(--ink-dim); text-decoration: none; }
.foot a:hover { color: var(--ink); }
/* The mark signs the page off the way it opens it in the nav — same file, ink
   rather than anything coloured. It sits on the small print, and a logo that
   competes with the line of legal text beside it is a logo in the wrong place:
   small, dark, and level with the sentence. */
.foot-mark { width: 78px; height: auto; flex: none; }
.foot-legal {
  margin-top: 44px; padding-top: 24px; border-top: 1px solid var(--line);
  color: var(--ink-faint); font-size: 13px;
  display: flex; gap: 20px; flex-wrap: wrap; align-items: center;
}

/* ── phone menu ─────────────────────────────────────────────────────────────
   Below 920px the section links and the cross-link to the other audience
   leave the bar; the round button opens them as a panel under it. */
.nav-burger {
  display: none; flex: none; width: 38px; height: 38px; padding: 0;
  border-radius: 999px; border: 1px solid var(--line); background: var(--paper);
  flex-direction: column; align-items: center; justify-content: center; gap: 4px; cursor: pointer;
}
.nav-burger i {
  display: block; width: 16px; height: 1.8px; border-radius: 2px; background: var(--ink);
  transition: transform 0.25s ease, opacity 0.2s ease;
}
.nav.open .nav-burger i:nth-child(1) { transform: translateY(5.8px) rotate(45deg); }
.nav.open .nav-burger i:nth-child(2) { opacity: 0; }
.nav.open .nav-burger i:nth-child(3) { transform: translateY(-5.8px) rotate(-45deg); }
.nav-menu {
  display: none; position: absolute; left: 0; right: 0; top: 100%;
  background: var(--paper); border-bottom: 1px solid var(--line);
  box-shadow: 0 18px 30px rgba(18, 13, 11, 0.08);
  padding: 6px 20px 14px;
}
.nav-menu a:not(.btn) {
  display: block; padding: 15px 0; border-bottom: 1px solid var(--line);
  font-size: 17px; font-weight: 600; color: var(--ink); text-decoration: none;
}
.nav-menu a:last-child { border-bottom: 0; }
/* Below 920px the bar's main button lives here, at the foot of the menu,
   rather than in the bar beside it. */
.nav-menu .menu-cta { display: flex; margin-top: 14px; color: #fff; border-bottom: 0; padding: 14px 22px; }
/* The narrowest phones: the bar holds the mark, the main button, the
   language pill and the menu button, so each gives a little back. */
@media (max-width: 400px) {
  .mark { width: 80px; }
  .nav-in { gap: 10px; padding-inline: 14px; }
  .nav-right { gap: 7px; }
  .nav-burger { width: 36px; height: 36px; }
}

@media (max-width: 920px) {
  .nav-burger { display: inline-flex; }
  .nav-right .btn.sm { display: none; }
  /* Slides down and fades in, the links following a beat behind each other;
     reverses on close. visibility (not display) so the close can animate. */
  .nav-menu {
    display: block; visibility: hidden; opacity: 0; transform: translateY(-10px);
    pointer-events: none;
    transition: opacity 0.22s ease, transform 0.28s cubic-bezier(0.2, 0.8, 0.2, 1), visibility 0s linear 0.28s;
  }
  .nav.open .nav-menu {
    visibility: visible; opacity: 1; transform: none; pointer-events: auto;
    transition: opacity 0.22s ease, transform 0.28s cubic-bezier(0.2, 0.8, 0.2, 1), visibility 0s;
  }
  .nav-menu > * { opacity: 0; transform: translateY(-6px); transition: opacity 0.2s ease, transform 0.25s ease; }
  .nav.open .nav-menu > * { opacity: 1; transform: none; }
  .nav.open .nav-menu > :nth-child(2) { transition-delay: 0.03s; }
  .nav.open .nav-menu > :nth-child(3) { transition-delay: 0.06s; }
  .nav.open .nav-menu > :nth-child(4) { transition-delay: 0.09s; }
  .nav.open .nav-menu > :nth-child(5) { transition-delay: 0.12s; }
  .nav.open .nav-menu > :nth-child(6) { transition-delay: 0.15s; }
  .nav.open .nav-menu > :nth-child(7) { transition-delay: 0.18s; }
}
@media (max-width: 920px) and (prefers-reduced-motion: reduce) {
  .nav-menu, .nav-menu > * { transition: none !important; transform: none !important; }
}

/* ── narrow ────────────────────────────────────────────────────────────────── */
/* The header at phone width. The language switch stays — on a bilingual site
   it is the last thing to drop — so the cross-link goes instead, which the
   footer carries anyway, and the wordmark and gaps give back the rest. */
@media (max-width: 760px) {
  .nav-right .plain { display: none; }
}
@media (max-width: 480px) {
  .mark { width: 92px; }
  .nav-in { gap: 14px; padding-inline: 16px; }
  .nav-right { gap: 10px; }
  .lang { font-size: 12.5px; }
}

@media (max-width: 920px) {
  .nav-links { display: none; }
  .tiles { grid-template-columns: repeat(2, 1fr); }
  /* Three into two leaves an orphan. The pocket shot is the one landscape
     photograph, so it takes the whole second row at its own shape. */
  .tiles.three { grid-template-columns: repeat(2, 1fr); }
  .tiles.three .tile:last-child { grid-column: 1 / -1; aspect-ratio: 3 / 2; }
  .tiles { gap: 10px; padding-inline: 16px; }
  .tile { border-radius: 22px; }
  .stats { grid-template-columns: repeat(2, 1fr); gap: 24px; }
  .inkband .stats.five { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .split, .inkband .inner { grid-template-columns: minmax(0, 1fr); }
  .split.flip .copy { order: 0; }
  .steps, .kinds, .plans { grid-template-columns: minmax(0, 1fr); }
  .step {
    background-image: linear-gradient(170deg, #FDEDFF 0%, #F1E9FF 50%, #D8E8FF 100%);
    background-size: 100% calc(300% + 2 * var(--steps-gap));
  }
  .step:nth-child(1) { background-position: 0 0%; }
  .step:nth-child(2) { background-position: 0 50%; }
  .step:nth-child(3) { background-position: 0 100%; }
  .foot-cols { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 32px 24px; }
}
@media (max-width: 620px) {
  .inkband .stats.five { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .inkband .stats.five .stat:last-child { grid-column: 1 / -1; }
  .goal { grid-template-columns: 30px minmax(0, 1fr); row-gap: 8px; }
  .goal .s { grid-column: 2; }
}

/* ── the wave ──────────────────────────────────────────────────────────────
   A swell travelling left to right through "Supercharged.", one letter at a
   time. It replaces the colour sweep that used to cross the word: main.css is
   explicit that headlines are ink, and a gradient — even a moving one — put a
   permanent second colour in the largest thing on the page.

   Each letter is its own inline-block so `transform` can scale it without
   reflowing the line; a font-size change here would shove every letter after
   it sideways on every frame. The origin sits near the baseline so letters
   grow UPWARDS rather than straddling it, which is what keeps the line of
   type looking level while the wave goes through.

   The stagger is 55ms and the swell itself occupies the first quarter of the
   cycle, so the wave crosses the word and then the word sits still for about
   two seconds before the next one. Without that pause it reads as a wobble.
*/
.wave i {
  display: inline-block;
  font-style: normal;
  transform-origin: 50% 86%;
  animation: wave 3s cubic-bezier(0.33, 0, 0.3, 1) infinite;
  animation-delay: calc(var(--i) * 55ms);
}
@keyframes wave {
  0%, 24%, 100% { transform: scale(1); }
  11%           { transform: scale(1.17); }
}
@media (prefers-reduced-motion: reduce) {
  .wave i { animation: none; }
}
}

/* ── the bilingual pair ────────────────────────────────────────────────────
   Two screenshots of the SAME screen, side by side. The claim "it is in Welsh
   too" is one a lot of software makes and very few mean; showing the identical
   screen twice is the only version of it that cannot be fudged. */
.pair {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(16px, 2.5vw, 32px);
  max-width: 760px; margin-top: clamp(32px, 4vw, 48px);
}
.pair figure { margin: 0; }
.pair img {
  width: 100%; border-radius: var(--r-md);
  border: 1px solid var(--line);
}
.pair figcaption {
  margin-top: 12px; font-size: 12px; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-faint);
}

/* A portrait screenshot in a landscape slot would be scaled to the width and
   run off the bottom of the section. Cap it by height instead and centre it. */
.shot.tall { display: grid; place-items: center; }
.shot.tall img { width: auto; max-height: 560px; }

/* ── the Cymraeg row ───────────────────────────────────────────────────────
   Copy on the left, Owain Glyndwr on the right, and the proof — the same
   screen in both languages — underneath them both. */
/* Set in a box of its own — a soft grey panel with big corners — so the
   language point reads as a feature in its own right rather than one more
   row. */
.cymraeg {
  display: grid; grid-template-columns: minmax(0, 1fr) 360px;
  gap: clamp(28px, 4vw, 56px); align-items: center;
  background: #EEF5FF; border-radius: clamp(24px, 3vw, 40px);
  padding: clamp(32px, 5.5vw, 88px);
}
/* The panel is its own frame, and the questions below are another white
   band, so the two bands' padding stacked into a double gap. The panel's band
   drops its foot; the questions' own top padding is the space between. */
.cy-band { padding-bottom: 0; }
.glyn { position: relative; justify-self: center; width: 100%; }
.glyn img { width: 100%; height: auto; }
/* The Lottie is laid over the still rather than swapped for it: the still
   holds the box's height, so nothing on the page moves when the animation
   arrives. If the fetch fails, .playing never lands and the still stays. */
.glyn .spot-play, .spot .spot-play { position: absolute; inset: 0; }
.glyn .spot-play svg, .spot .spot-play svg { width: 100% !important; height: 100% !important; }
.glyn.playing > img, .spot.playing > img { visibility: hidden; }

/* A spot on the ink band. The artwork is black line on transparent, so one
   invert turns it white — the filter leaves alpha alone, so the transparent
   ground stays transparent and nothing has to be re-exported. */
.spot { position: relative; width: 100%; max-width: 400px; justify-self: center; }
.spot img { width: 100%; height: auto; }
/* The invert sits on the artwork, not on the container. On the container it
   would also invert anything else inside — the confetti below is in here, and
   a purple bit of paper came out green. */
.spot.invert > img, .spot.invert > .spot-play { filter: invert(1); }

/* Confetti, fired when the tap lands. A zero-size anchor at the point where
   the card meets the phone; each piece is positioned from there, so moving the
   burst is two numbers. */
.spot .confetti {
  position: absolute; left: 56%; top: 40%;
  width: 0; height: 0; pointer-events: none; z-index: 2;
}
.spot .confetti i { position: absolute; left: 0; top: 0; display: block; border-radius: 1.5px; }

@media (max-width: 920px) {
  .cymraeg { grid-template-columns: minmax(0, 1fr); }
  .glyn { max-width: 320px; }
}

/* The Cymraeg strip: edge to edge and slim. Owain rides along the top of a
   ticker of the app's own Welsh, drifting past, so the point that every word
   is in Welsh is shown rather than only said. */
.cy-strip { background: #EEF5FF; overflow: hidden; padding-top: clamp(48px, 6vw, 96px); }
.cy-strip .cymraeg {
  background: none; border-radius: 0; padding: 0 20px;
  grid-template-columns: minmax(0, 1fr) clamp(220px, 19vw, 290px); align-items: end;
  /* Over the ground, and his hooves on its line: the line is 28px down the
     ground strip, so the row ends 28px into it. */
  position: relative; z-index: 1; margin-bottom: -28px;
}
.cy-strip .cymraeg > div:first-child { align-self: center; padding-bottom: clamp(24px, 3vw, 40px); max-width: 70ch; }
/* The road: a line with the odd tuft and stone on it, edge to edge, behind
   Owain, rolling right at the words' pace (see the script in the page). */
.cy-ground {
  height: 40px; position: relative; z-index: 0; opacity: 0.4;
  /* One tile, line and all: a gentle rise and fall built from waves that
     each fit the 720px tile a whole number of times, so the line meets
     itself at the seam and runs edge to edge unbroken. The line stops under
     each stone and the stone's own outline carries it over. */
  background: url(/img/cy-ground.svg) repeat-x 0 0 / 720px 40px;
  animation: cy-ground 18s linear infinite;
}
@keyframes cy-ground { to { background-position: 720px 0; } }
/* Hooves a touch into the line rather than perched on it. */
.cy-strip .glyn { transform: translateY(10px); }
/* The strip's own blue, under Owain's own ground line. His horse walks on
   the spot facing left and the words roll right beneath him, so the pair of
   them read as him riding along. */
.cy-ticker { background: #EEF5FF; overflow: hidden; padding-block: 22px 36px; }
.cy-track {
  display: flex; align-items: center; gap: 18px; width: max-content;
  animation: cy-roll 60s linear infinite;
}
.cy-track span { font-family: var(--display); font-weight: 700; font-size: 19px; letter-spacing: -0.01em; color: var(--ink); white-space: nowrap; }
.cy-track img { width: 26px; height: 26px; object-fit: contain; flex: none; }
@keyframes cy-roll { from { transform: translateX(calc(-50% - 9px)); } to { transform: translateX(0); } }
@media (prefers-reduced-motion: reduce) { .cy-track, .cy-ground { animation: none; } }
@media (max-width: 920px) {
  .cy-strip .cymraeg { grid-template-columns: minmax(0, 1fr); }
  .cy-strip .cymraeg > div:first-child { padding-bottom: 0; }
  .cy-strip .glyn { max-width: 230px; }
  .cy-track span { font-size: 16px; }
}


/* ── section headings ──────────────────────────────────────────────────────
   One scale for the plain section headings, which used to carry their size in
   a style attribute each — and a size in a style attribute is one the
   large-screen rules below cannot reach. */
.sec-h { font-size: clamp(26px, 3.3vw, 42px); }
.sec-h-tight { font-size: clamp(22px, 4.6vw, 42px); }

/* ── large screens ─────────────────────────────────────────────────────────
   Past 1600px the column has already widened (see --wrap); the type follows,
   so a section is not a small paragraph adrift in a big panel. Every size
   here is what the clamp above it stops at, taken one step further. */
@media (min-width: 1600px) {
  body { font-size: 17.5px; }
  .nav-links a, .nav-right .plain { font-size: 15.5px; }
  .hero h1 { font-size: 90px; }
  .hero .lead { font-size: 21px; }
  .trust { font-size: 34px; }
  .sec-h, .sec-h-tight, .split h2, .goal-head h2, .why-panel h2 { font-size: 52px; }
  .inkband h2, .inkband blockquote { font-size: 46px; }
  .closer h2 { font-size: 64px; }
  .split p, .inkband p, .closer p, .goal-head p { font-size: 18.5px; }
  .stat .fig { font-size: 70px; }
  .stat .cap { font-size: 16px; }
  .goal .g { font-size: 19px; }
  .goal .s { font-size: 17px; }
  .step h3 { font-size: 22px; }
  .step p { font-size: 16.5px; }
  .faq { max-width: 920px; }
  .faq summary { font-size: 19px; }
  .faq .a { font-size: 17px; }
  .bt-cap { font-size: 11px; }
  .bt-fig { font-size: 36px; }
  .bt-fig.sm { font-size: 27px; }
  .bt-sub { font-size: 13px; }
  .bt-rows span { font-size: 14px; }
  .bt-rows b { font-size: 16px; }
  .keycards { max-width: 740px; }
  .inkband .spot { max-width: 440px; }
  /* the nudge that levels the text with the phone is 3.3% of the spot's
     height, so it grows with the spot: 440 x 546/592 x 0.033 */
  .inkband.lowspot .inner > :first-child { transform: translateY(13px); }
}

/* ══ the why band ════════════════════════════════════════════════════════════
   The short version of why Loyollo exists, on both main pages: a heading and
   a link on the left, three proof points on the right, on the house gradient.
   The long version is its own page (/why). */
.why-panel {
  display: grid; grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.6fr);
  gap: clamp(28px, 4vw, 64px); align-items: center;
  /* Straight on the page now, no panel: the band gives it its room. */
}
.why-panel .chip, .page-hero .chip {
  display: inline-block; font-size: 12px; font-weight: 700; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ink-faint);
}
.why-panel h2 { font-size: clamp(26px, 3.3vw, 42px); margin-top: 10px; text-wrap: balance; }
.why-panel .inline-link { margin-top: 22px; }
.why-points { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(18px, 2.4vw, 32px); }
.why-points img { width: 56px; height: 56px; display: block; margin-left: -6px; }
.why-points h3 { font-size: 18px; margin-top: 14px; }
.why-points p { color: var(--ink-dim); font-size: 15px; margin: 8px 0 0; }
@media (max-width: 920px) {
  .why-panel { grid-template-columns: minmax(0, 1fr); }
  .why-points { grid-template-columns: minmax(0, 1fr); gap: 22px; }
  .why-points > div { display: grid; grid-template-columns: 46px minmax(0, 1fr); column-gap: 16px; }
  .why-points img { grid-row: span 2; }
  .why-points h3 { margin-top: 2px; }
}

/* ══ places near you (customer page) ═════════════════════════════════════════
   A drawn map rather than a screenshot: a few streets, stamps standing in for
   the places that collect, and a pulsing dot for where you are. Generic on
   purpose — no real venue is named until real venues are live. */
.near-map {
  position: relative; aspect-ratio: 5 / 4; border-radius: var(--r-lg); overflow: hidden;
  background: #F3EEFB; box-shadow: inset 0 0 0 1px var(--line);
}
.near-map svg.roads { position: absolute; inset: 0; width: 100%; height: 100%; }
.near-map .pin {
  position: absolute; left: var(--x); top: var(--y); transform: translate(-50%, -100%);
  display: flex; align-items: center; gap: 7px; padding: 6px 12px 6px 6px;
  background: #fff; border-radius: 999px; white-space: nowrap;
  font-size: 13px; font-weight: 600; color: var(--ink);
  box-shadow: 0 10px 24px rgba(18, 13, 11, 0.14);
  animation: pin-bob 5s ease-in-out infinite; animation-delay: var(--d, 0s);
}
.near-map .pin::after {
  content: ""; position: absolute; left: 50%; bottom: -6px; width: 12px; height: 12px;
  background: #fff; transform: translateX(-50%) rotate(45deg); border-radius: 2px; z-index: -1;
}
.near-map .pin img { width: 28px; height: 28px; }
.near-map .you {
  position: absolute; left: 46%; top: 58%; width: 18px; height: 18px; margin: -9px 0 0 -9px;
  border-radius: 50%; background: var(--accent-b); border: 3px solid #fff;
  box-shadow: 0 0 0 0 rgba(83, 154, 217, 0.5); animation: you-pulse 2.2s ease-out infinite;
}
.near-map .pin:has(.pin-offer) { z-index: 2; }
/* An offer hangs off a few pins as a little tag in that place's colour,
   the same deals as the offer circles in the wallet. */
.near-map .pin-offer {
  position: absolute; left: 50%; top: 100%; transform: translate(-50%, 10px) rotate(-3deg);
  font: 800 11px/1 var(--display); font-style: normal; white-space: nowrap;
  padding: 5px 8px; border-radius: 999px; box-shadow: 0 4px 10px rgba(18, 13, 11, 0.18);
}
.near-map .pin-offer.purple { background: #7B45D6; color: #fff; }
.near-map .pin-offer.orange { background: #FF6A2B; color: #fff; }
.near-map .pin-offer.teal   { background: #2FD6C4; color: var(--ink); }
.near-map .pin-offer.yellow { background: #FFEF1F; color: var(--ink); }
@keyframes pin-bob { 0%, 100% { translate: 0 0; } 50% { translate: 0 -5px; } }
@keyframes you-pulse { to { box-shadow: 0 0 0 22px rgba(83, 154, 217, 0); } }
@media (prefers-reduced-motion: reduce) { .near-map .pin, .near-map .you { animation: none; } }
@media (max-width: 560px) { .near-map .pin { font-size: 10.5px; gap: 4px; padding: 3px 8px 3px 3px; } .near-map .pin img { width: 18px; height: 18px; } .near-map .pin-offer { font-size: 9px; padding: 4px 6px; transform: translate(-50%, 7px) rotate(-3deg); } }

/* ══ the extra pages: why, and towns & councils ══════════════════════════════
   Built from the same parts as the two main pages: a centred hero, bands,
   splits, the step cards and the ink band. */
.page-hero { text-align: center; padding-block: clamp(52px, 7vw, 96px) clamp(28px, 4vw, 48px); }
.page-hero h1 { font-size: clamp(40px, 6.4vw, 84px); line-height: 0.98; letter-spacing: -0.045em; margin: 14px auto 0; max-width: 16ch; text-wrap: balance; }
.page-hero .lead { color: var(--ink-dim); font-size: clamp(17px, 1.7vw, 20px); max-width: 52ch; margin: 22px auto 0; }
.page-hero .cta { margin-top: 30px; display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }
.beliefs { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; margin-top: 40px; }
.belief {
  position: relative; overflow: hidden; isolation: isolate;
  background: #fff; border: 1px solid rgba(18, 13, 11, 0.07);
  border-radius: var(--r-lg); padding: clamp(24px, 3vw, 36px);
}
/* Corner swirl: the Loyollo pink, lilac and blue blurred into one soft swirl, tucked
   into a different corner of each card so the four don't repeat. Kept faint:
   a hint of colour on white, not a wash. */
.beliefs { grid-auto-rows: 1fr; }
.belief > * { position: relative; z-index: 1; }
.belief::before {
  content: ""; position: absolute; z-index: 0; pointer-events: none;
  width: 320px; height: 320px; border-radius: 50%;
  background: conic-gradient(from var(--swirl, 0deg), #FCD3FF, #E2C6FF, #B9D9FF, #E2C6FF, #FCD3FF);
  filter: blur(36px); opacity: 0.3;
}
.belief:nth-child(1)::before { --swirl: 0deg;   right: -120px; top: -140px; }
.belief:nth-child(2)::before { --swirl: 90deg;  left: -120px;  bottom: -160px; }
.belief:nth-child(3)::before { --swirl: 180deg; right: -120px; bottom: -160px; }
.belief:nth-child(4)::before { --swirl: 270deg; left: -140px;  top: -140px; }
.belief img { width: 56px; height: 56px; display: block; margin-left: -6px; }
.belief h3 { font-size: clamp(19px, 1.8vw, 22px); margin-top: 16px; }
.belief p { color: var(--ink-dim); font-size: 15.5px; margin: 10px 0 0; }
.paths { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; margin-top: 36px; }
.path {
  display: block; text-decoration: none; color: var(--ink); border-radius: var(--r-lg);
  padding: clamp(22px, 2.6vw, 32px); box-shadow: inset 0 0 0 1px var(--line);
  transition: box-shadow 0.2s ease, transform 0.2s ease;
}
.path:hover { box-shadow: inset 0 0 0 1px var(--ink-dim); transform: translateY(-2px); }
.path b { font-family: var(--display); font-weight: 800; font-size: 20px; letter-spacing: -0.02em; display: block; }
.path span { color: var(--ink-dim); font-size: 15px; display: block; margin-top: 8px; }
.path i { font-style: normal; display: inline-block; margin-top: 14px; font-weight: 600; font-size: 14.5px; }
.ladder { list-style: none; margin: 22px 0 0; padding: 0; display: grid; gap: 10px; }
.ladder li {
  display: grid; grid-template-columns: 96px minmax(0, 1fr); gap: 14px; align-items: baseline;
  padding: 14px 18px; background: var(--house); border-radius: 14px;
}
.ladder b { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 13px; color: rgba(18, 13, 11, 0.62); font-weight: 600; }
.ladder span { color: var(--ink); font-size: 15px; }
.story p { color: var(--ink-dim); font-size: clamp(16px, 1.5vw, 18px); max-width: 60ch; }
.story p + p { margin-top: 16px; }
.draft-note { display: inline-block; margin-top: 18px; font-size: 13px; color: var(--ink-faint); }
@media (max-width: 920px) {
  .beliefs, .paths { grid-template-columns: minmax(0, 1fr); }
  .ladder li { grid-template-columns: minmax(0, 1fr); gap: 4px; }
}
.story-grid { display: grid; grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr); gap: clamp(24px, 4vw, 64px); align-items: start; }
.story-grid h2 { margin: 0; }
.story-img { display: block; width: 100%; height: auto; margin-top: clamp(28px, 4vw, 48px); border-radius: var(--r-lg); }
@media (max-width: 920px) { .story-grid { grid-template-columns: minmax(0, 1fr); } }
/* A photograph rather than the drawn street: cropped wide on a desktop, a
   little taller on a phone so the shopfronts stay readable. */
.story-img.photo { aspect-ratio: 16 / 8; object-fit: cover; object-position: 50% 62%; }
@media (max-width: 720px) { .story-img.photo { aspect-ratio: 4 / 3; } }
/* Where the stamp card's column reaches the page edge, the card behind it
   stays inside rather than poking past the gutter. */
@media (max-width: 1100px) { .fc-behind { right: 0; } }

/* ══ M-SParc on Anglesey (/why) ═════════════════════════════════════════════
   Ynys Môn from OpenStreetMap's coastline, drawn in the ink band's white
   line. When it scrolls in, the coast draws itself, a pin drops on Gaerwen
   and keeps pulsing, and the M-SParc label fades up beside it. */
.mon { width: 100%; max-width: 600px; justify-self: center; margin-block: calc(var(--band) * -0.45); }
/* On a desktop the map grows past its column to the right, so Gwynedd runs
   off the band's right and bottom edges rather than ending in mid-air. */
@media (min-width: 1400px) {
  .mon { width: 128%; max-width: 760px; justify-self: start; }
}
@media (max-width: 920px) { .mon { margin-block: 0; } }
/* Stamps drifting inside the island, clipped to its coast and blurred back
   so they read as texture under the pin rather than as places. */
.mon-sk {
  filter: blur(3px); opacity: 0.42;
  transform-box: fill-box; transform-origin: center;
  animation: mon-sk 7s ease-in-out var(--d, 0s) infinite alternate;
}
.mon-sk:nth-child(3n) { filter: blur(5px); opacity: 0.3; animation-duration: 9s; }
.mon-sk:nth-child(3n+1) { filter: blur(1.5px); opacity: 0.5; animation-duration: 6s; }
@keyframes mon-sk {
  from { transform: translate(0, 6px) rotate(var(--r)); }
  to   { transform: translate(4px, -8px) rotate(calc(var(--r) + 8deg)); }
}
@media (prefers-reduced-motion: reduce) { .mon-sk { animation: none; transform: rotate(var(--r)); } }
.mon-map { display: block; width: 100%; height: auto; overflow: visible; }
.mon-fill { fill: rgba(255, 255, 255, 0.05); }
.mon-coast {
  fill: none; stroke: #fff; stroke-width: 3; stroke-linejoin: round; stroke-linecap: round;
  stroke-dasharray: 1; stroke-dashoffset: 1;
}
.mon-drop path { fill: #fff; }
.mon-drop circle { fill: var(--ink); }
.mon-drop { opacity: 0; transform: translateY(-40px); }
.mon-ring { fill: none; stroke: #fff; stroke-width: 2; opacity: 0; }
.mon-label { opacity: 0; }
.mon-label rect { fill: #fff; }
.mon-label text { font-family: var(--display); font-weight: 800; font-size: 17px; letter-spacing: -0.02em; fill: var(--ink); text-anchor: middle; }
.mon.on .mon-coast { animation: mon-draw 2.4s cubic-bezier(0.5, 0, 0.3, 1) forwards; }
.mon.on .mon-drop { animation: mon-drop 0.6s cubic-bezier(0.3, 1.4, 0.5, 1) 1.9s forwards; }
.mon.on .mon-ring { animation: mon-ring 2.4s ease-out 2.4s infinite; }
.mon.on .mon-ring.r2 { animation-delay: 3.6s; }
.mon.on .mon-label { animation: mon-fade 0.5s ease 2.5s forwards; }
@keyframes mon-draw { to { stroke-dashoffset: 0; } }
@keyframes mon-drop { to { opacity: 1; transform: none; } }
@keyframes mon-ring { 0% { opacity: 0.8; transform: scale(0.6); } 100% { opacity: 0; transform: scale(5); } }
@keyframes mon-fade { to { opacity: 1; } }
.mon-ring { transform-box: fill-box; transform-origin: center; }
@media (prefers-reduced-motion: reduce) {
  .mon .mon-coast { stroke-dashoffset: 0; animation: none; }
  .mon .mon-drop, .mon .mon-label { opacity: 1; transform: none; animation: none; }
  .mon .mon-ring { animation: none; }
}
.inkband .mon-credit { margin: 10px 0 0; font-size: 11px; color: rgba(255, 255, 255, 0.4); text-align: left; max-width: none; }
/* Ar y Lôn: a second, smaller pin across the Menai on Bangor High Street,
   joined to Gaerwen by a dashed line that keeps moving. */
.mon-link { fill: none; stroke: rgba(255, 255, 255, 0.55); stroke-width: 2; stroke-dasharray: 5 7; stroke-linecap: round; opacity: 0; }
.mon.on .mon-link { animation: mon-fade 0.5s ease 2.8s forwards, mon-march 1.4s linear 2.8s infinite; }
@keyframes mon-march { to { stroke-dashoffset: -24; } }
.mon.on .p2 .mon-drop { animation-delay: 3.1s; }
.mon.on .p2 .mon-ring { animation-delay: 3.6s; }
.mon.on .mon-label.l2 { animation-delay: 3.4s; }
.mon-label.l2 text { font-size: 15px; }
@media (prefers-reduced-motion: reduce) { .mon .mon-link { opacity: 1; animation: none; } }
.story-fig { margin: clamp(28px, 4vw, 48px) 0 0; }
.story-fig .story-img { margin-top: 0; }
.story-fig figcaption { margin-top: 12px; font-size: 14px; color: var(--ink-faint); }
/* The mainland across the Menai, so the Ar y Lôn pin plainly sits on the
   Bangor side: a faint shape, not a second coastline to read. */
.mon-main { fill: rgba(255, 255, 255, 0.04); stroke: rgba(255, 255, 255, 0.5); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
/* Gwynedd runs on past the map, so the band clips it at its right and
   bottom edges. */
.mon-band { position: relative; overflow: hidden; overflow: clip; }
.inkband p a { color: #fff; text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }

/* ══ the high street, scrolling (towns page closer) ═════════════════════════
   tools/high-street-long.py draws one seamless tile of twenty shopfronts
   (4000×580). The track carries three copies and slides left by exactly one
   tile, so the loop has no seam and no shop comes round again on screen.
   Welsh fascias on the Welsh page.

   Above the roofs, stamps pop up in speech bubbles over the shop they were
   collected at. Their places are in the tile's own units (--cx along, --b up
   from the pavement), scaled by --h, so they ride the track with the
   buildings. With reduced motion nothing moves and every bubble just shows. */
.street {
  --h: clamp(300px, 34vw, 520px);
  --u: calc(var(--h) / 580);               /* one unit of the drawing */
  position: relative; overflow: hidden;
  height: calc(var(--h) * 1.24); margin-top: clamp(16px, 2vw, 32px);
  opacity: 0.9;
}
.street-track {
  position: absolute; left: 0; bottom: 0;
  height: var(--h); width: calc(var(--u) * 12000);
  background: url(/spots/high-street-long.svg) repeat-x 0 100% / auto 100%;
  animation: street 350s linear infinite;
  will-change: transform;
}
.street.cy .street-track { background-image: url(/spots/high-street-long-cy.svg); }
@keyframes street { to { transform: translateX(calc(var(--u) * -4000)); } }

.bub {
  position: absolute; z-index: 1;
  left: calc(var(--u) * var(--cx)); bottom: calc(var(--u) * var(--b));
  width: calc(var(--u) * 116); aspect-ratio: 1;
  translate: -50% 0; rotate: var(--r);
  transform-origin: 50% 112%;
  display: grid; place-items: center;
  background: #fff; border-radius: 50%;
  filter: drop-shadow(0 calc(var(--u) * 6) calc(var(--u) * 10) rgba(40, 30, 70, 0.16))
          drop-shadow(0 1px 2px rgba(40, 30, 70, 0.10));
  animation: bub 12s cubic-bezier(0.2, 0.9, 0.3, 1.25) infinite;
  animation-delay: var(--d);
}
/* The tail: a white square turned 45°, half tucked under the circle. The
   shadow is a drop-shadow on the bubble, so it follows the tail too. */
.bub::after {
  content: ""; position: absolute; left: 50%; bottom: 0;
  width: 18%; aspect-ratio: 1;
  background: #fff; border-radius: 0 0 3px 0;
  transform: translate(-50%, 45%) rotate(45deg);
}
.bub img { width: 76%; position: relative; z-index: 1; }
@keyframes bub {
  0%, 52%, 100% { scale: 0; opacity: 0; }
  58%           { scale: 1; opacity: 1; }
  88%           { scale: 1; opacity: 1; }
  93%           { scale: 0.2; opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .street-track, .bub { animation: none; }
}


/* ══ ready for the window (towns page) ══════════════════════════════════════
   The real print: both A4 posters behind the glass, this page's language in
   front, and the round sticker stuck on the glass over them. Two soft
   reflections cross the pane between the posters and the sticker, which is
   what makes it read as a window rather than a pinboard. */
.mat-grid {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
  gap: clamp(32px, 5vw, 72px); align-items: center;
}
@media (max-width: 860px) { .mat-grid { grid-template-columns: minmax(0, 1fr); } }
.window {
  position: relative; margin: 0; aspect-ratio: 6 / 5; max-width: 100%;
  border-radius: var(--r-lg); overflow: hidden; isolation: isolate;
  background: linear-gradient(160deg, #F4F6FA 0%, #E4EAF2 100%);
  box-shadow: inset 0 0 0 1px rgba(18, 13, 11, 0.06);
}
.window::after {
  content: ""; position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background: linear-gradient(115deg,
    transparent 22%, rgba(255, 255, 255, 0.42) 27%, transparent 33%,
    transparent 58%, rgba(255, 255, 255, 0.28) 61%, transparent 66%);
}
.w-poster {
  position: absolute; width: 37%; height: auto; border-radius: 4px;
  box-shadow: 0 14px 30px rgba(40, 30, 70, 0.18), 0 2px 6px rgba(40, 30, 70, 0.10);
}
.w-poster.back  { left: 12%; top: 13%; rotate: -5deg; }
.w-poster.front { left: 39%; top: 9%;  rotate: 3deg; z-index: 1; }
.w-sticker {
  position: absolute; z-index: 3; right: 5%; bottom: 7%; width: 30%; height: auto;
  rotate: -9deg;
  filter: drop-shadow(0 6px 10px rgba(18, 13, 11, 0.25));
  transition: scale 0.45s cubic-bezier(0.2, 0.9, 0.3, 1.3), opacity 0.25s ease;
}
.window.wait:not(.on) .w-sticker { opacity: 0; scale: 1.35; }
@media (max-width: 520px) {
  .window { aspect-ratio: 1; }
  .w-poster { width: 44%; }
  .w-poster.back { left: 6%; top: 12%; }
  .w-poster.front { left: 40%; top: 8%; }
  .w-sticker { width: 36%; right: 3%; bottom: 5%; }
}


/* ══ the plain pages: privacy and terms ═════════════════════════════════════
   One reading column, the brand's type, and nothing else on the page asking
   for attention. The markup is the live pages' own, so these rules dress it
   rather than restructure it. */
.prose { padding-block: clamp(44px, 7vw, 88px) var(--band); }
/* The same width as every other page. On a wide screen each section's
   heading sits in a column of its own and stays in view while its text
   scrolls past, so the reading column keeps a comfortable measure. */
@media (min-width: 1081px) {
  .prose > section:not(.prose-head) {
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
    column-gap: clamp(40px, 5vw, 88px); align-items: start;
  }
  .prose > section:not(.prose-head) > h2 {
    grid-column: 1; grid-row: 1 / span 60; margin: 0;
    position: sticky; top: 104px; text-wrap: balance;
  }
  .prose > section:not(.prose-head) > :not(h2) { grid-column: 2; max-width: 74ch; }
  .prose > section:not(.prose-head) > h2 + * { margin-top: 0; }
}
.prose-head { padding-bottom: clamp(24px, 4vw, 40px); border-bottom: 1px solid var(--line); margin-bottom: clamp(28px, 4vw, 44px); }
.chip {
  display: inline-block; font-size: 12px; font-weight: 700; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ink-faint);
}
.prose h1 { font-size: clamp(32px, 4.8vw, 54px); margin-top: 12px; }
.prose .lead { font-size: clamp(17px, 1.6vw, 19px); color: var(--ink-dim); margin: 18px 0 0; max-width: 58ch; }
.prose .small { font-size: 13.5px; color: var(--ink-faint); margin: 12px 0 0; }
.prose section + section { margin-top: clamp(32px, 4.5vw, 48px); }
.prose h2 { font-size: clamp(22px, 2.4vw, 28px); margin-bottom: 12px; }
.prose h3 { font-size: 17px; letter-spacing: -0.015em; margin: 24px 0 6px; }
.prose p, .prose li { color: var(--ink-dim); font-size: 16px; line-height: 1.68; }
.prose p { margin: 0 0 14px; }
.prose ul { margin: 0 0 14px; padding-left: 20px; display: grid; gap: 8px; }
.prose b { color: var(--ink); font-weight: 600; }
.prose a { color: var(--ink); text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }
.prose .panel { background: var(--house); border-radius: var(--r-md); padding: clamp(18px, 2.4vw, 26px) clamp(20px, 2.8vw, 30px); }
.prose .panel ul, .prose .panel p:last-child { margin-bottom: 0; }
/* The two halves of the terms page: a label that starts each part, with room
   above it so the switch from the customer part to the venue part is seen. */
.prose .part {
  margin: clamp(44px, 6vw, 72px) 0 clamp(18px, 2.4vw, 26px); padding-top: 18px;
  border-top: 1px solid var(--line);
  font-size: 12.5px; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink-faint); scroll-margin-top: 84px;
}
.prose .prose-head + .part { margin-top: 0; padding-top: 0; border-top: 0; }

/* ══ the tap page ═══════════════════════════════════════════════════════════
   What a customer sees when they tap a key card without the app. The hero is
   the customer page's own, with the tap drawn above the headline. */
.tap-hero { padding-top: clamp(24px, 4vw, 48px); }
.tap-hero .chip { margin-bottom: 8px; }
.tap-anim { position: relative; width: min(240px, 58vw); aspect-ratio: 1; margin: 0 auto 6px; }
.tap-anim img, .tap-anim-play { width: 100%; height: 100%; display: block; }
.tap-anim-play { position: absolute; inset: 0; }
.tap-anim-play svg { width: 100% !important; height: 100% !important; }
.tap-hero h1 { font-size: clamp(36px, 5.6vw, 64px); }
.tap-note { color: var(--ink-faint); font-size: 14px; margin: 16px auto 0; max-width: 46ch; }
.tap .faq h2 { font-size: clamp(26px, 3.4vw, 40px); text-align: center; margin-bottom: 8px; }

/* ══ unsubscribe ════════════════════════════════════════════════════════════ */
.unsub { padding-block: clamp(56px, 9vw, 120px); }
.unsub h1 { font-size: clamp(34px, 5vw, 58px); margin-top: 10px; }
.unsub-panel {
  max-width: 520px; margin: 30px auto 0; padding: 26px 28px; text-align: left;
  background: var(--house); border-radius: var(--r-md);
}
.unsub-panel h3 { font-size: 20px; letter-spacing: -0.02em; }
.unsub-panel p { color: var(--ink-dim); margin: 8px 0 0; }
.unsub-panel .cta { justify-content: flex-start; margin-top: 18px; }

/* ── October: live offers, the passport, and the small things ─────────────
   Real app screens, each drawn as a phone: the screenshot with its corners
   rounded to the display's own and a titanium-ish edge round it, so a flat
   PNG reads as a device without a mock-up frame to keep in step with the app.
   Two to a picture, one behind and turned away, the way they would lie on a
   table. Sized off the column, so the pair scales as one. */
.phones {
  position: relative; width: 100%; max-width: 500px; margin-inline: auto;
  aspect-ratio: 1 / 1.12;
}
.phone {
  position: absolute; width: 47%; height: auto; display: block;
  border-radius: 9.5% / 4.4%;
  box-shadow: 0 0 0 5px #1E1916, 0 0 0 6.5px #8A817A, 0 28px 60px rgba(18, 13, 11, 0.35);
}
.phone.back { left: 5%; top: 7%; transform: rotate(-9deg); }
.phone.front { right: 5%; top: 2%; transform: rotate(4deg); }
.inkband .phone { box-shadow: 0 0 0 5px #2A2421, 0 0 0 6.5px #8A817A, 0 30px 70px rgba(0, 0, 0, 0.6); }

/* Tap, flick, claim: the three moves, as numbered chips. */
.howsteps {
  list-style: none; margin: 28px 0 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: 10px;
}
.howsteps li {
  display: inline-flex; align-items: center; gap: 9px;
  padding: 7px 14px 7px 7px; border-radius: 999px;
  background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.14);
  font-size: 14.5px; font-weight: 600; color: #fff;
}
.howsteps span {
  width: 24px; height: 24px; border-radius: 50%; display: grid; place-items: center;
  background: var(--house); color: var(--ink); font-size: 12.5px; font-weight: 800;
}

/* The passport's badges, each with the sticker the app puts on the town. */
.badges { list-style: none; margin: 26px 0 0; padding: 0; display: grid; gap: 14px; max-width: 46ch; }
.badges li { display: flex; align-items: center; gap: 14px; }
.badges img { width: 46px; height: 46px; flex: none; object-fit: contain; filter: drop-shadow(0 4px 6px rgba(18, 13, 11, 0.15)); }
.badges span { display: flex; flex-direction: column; color: var(--ink-dim); font-size: 14.5px; line-height: 1.4; }
.badges b { color: var(--ink); font-size: 15.5px; }

/* The small things: two tall tiles holding a real screen, running off their
   foot, either side of four short ones that are a mark and a sentence. */
.perks {
  margin-top: 44px; display: grid; gap: 14px;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  grid-template-rows: repeat(2, minmax(250px, auto));
  grid-template-areas: "r a b y" "r c d y";
}
.perk.t-receipt { grid-area: r; }
.perk.t-year { grid-area: y; }
.perk.p1 { grid-area: a; } .perk.p2 { grid-area: b; } .perk.p3 { grid-area: c; } .perk.p4 { grid-area: d; }
.perk {
  border-radius: var(--r-lg); padding: 26px 24px; min-width: 0;
  background: #E9F0FD;
  display: flex; flex-direction: column; gap: 8px;
}
/* The four small ones black, like the ticket, with the marks in the
   Loyollo gradient: the year tile is the one light box in the set. */
.perk.p1, .perk.p2, .perk.p3, .perk.p4 { background: var(--ink); color: #fff; }
.perk.p1 p, .perk.p2 p, .perk.p3 p, .perk.p4 p { color: rgba(255, 255, 255, 0.72); }
.perk h3 { margin: 0; font-family: var(--display); font-size: 21px; letter-spacing: -0.02em; line-height: 1.15; }
.perk p { margin: 0; color: var(--ink-dim); font-size: 14.5px; line-height: 1.5; }
.perk.tall { overflow: hidden; padding-bottom: 0; border: 0; }
.perk.t-receipt { background: var(--ink); color: #fff; }
.perk.t-year { background: #FBEEFD; color: var(--ink); }
.perk.tall.t-year p { color: var(--ink-dim); }
.perk.tall p { color: rgba(255, 255, 255, 0.72); }
/* The screen's box takes whatever height the row leaves and no more: its
   image is absolute, so a 2:1 screenshot cannot make the tile taller. */
.perk-shot { position: relative; flex: 1; min-height: 300px; margin-top: 14px; }
.perk-shot img {
  position: absolute; left: 50%; top: 8px; transform: translateX(-50%);
  width: 82%; max-width: 280px; border-radius: 9.5% / 4.4%;
  box-shadow: 0 0 0 5px #1E1916, 0 0 0 6.5px rgba(255, 255, 255, 0.35), 0 24px 50px rgba(0, 0, 0, 0.4);
}
.perk-mark {
  align-self: flex-start; margin-bottom: 8px;
  height: 64px; display: flex; align-items: center;
  font-family: var(--display); font-weight: 800; font-size: 56px; line-height: 1;
  letter-spacing: -0.03em;
}
/* The value itself, large, in the Loyollo colours rather than in a circle.
   64px tall, the same as the stickers, so every tile's title lines up. */
.perk-mark {
  background: linear-gradient(110deg, #FFFFFF 0%, #FCD3FF 38%, #C9A6FF 62%, #8ECAFF 100%);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
}

/* Shiny stamps: the app's holographic foil (HoloSheen in StampResultView),
   done in CSS. The stamp goes grey, Loyollo's pink, white, blue and green run
   across it in diagonal bands, overlaid and then washed on lightly so the
   white sticker edge takes the colour too, and a band of light crosses it.
   All clipped to the stamp's outline. It drifts on its own: no tilt here. */
.shiny {
  --foil: linear-gradient(135deg, #FF6FD8, #FFFFFF, #6FB6FF, #5FF0B0, #FF6FD8,
                                  #FFFFFF, #6FB6FF, #5FF0B0, #FF6FD8);
  position: relative; width: 64px; margin-bottom: 8px; display: block;
  transform: rotate(-8deg); isolation: isolate;
  filter: drop-shadow(0 6px 8px rgba(18, 13, 11, 0.18));
}
.shiny img { width: 100%; display: block; filter: grayscale(1) contrast(1.15); }
.shiny b, .shiny i {
  position: absolute; inset: 0;
  -webkit-mask: url(/img/sk-bean.png) center / 100% 100% no-repeat;
  mask: url(/img/sk-bean.png) center / 100% 100% no-repeat;
}
.shiny b {
  background: var(--foil); background-size: 200% 200%;
  mix-blend-mode: overlay; animation: foil 12s linear infinite;
}
.shiny i {
  background:
    linear-gradient(135deg, transparent 38%, rgba(255, 255, 255, 0.6) 50%, transparent 62%) no-repeat,
    var(--foil);
  background-size: 300% 300%, 200% 200%;
  opacity: 0.55; animation: glint 3.6s ease-in-out infinite, foil 12s linear infinite;
}
@keyframes foil { from { background-position: 0% 0%; } to { background-position: 100% 100%; } }
@keyframes glint {
  0%, 20% { background-position: 100% 100%, 0% 0%; }
  80%, 100% { background-position: 0% 0%, 100% 100%; }
}
@media (prefers-reduced-motion: reduce) { .shiny b, .shiny i { animation: none; } }

/* The year tile has the wallet's drift behind it. */
.perk.t-year { position: relative; }
.perk.t-year > :not(.field) { position: relative; z-index: 1; }

@media (max-width: 1080px) {
  .perks { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: none;
           grid-template-areas: "r y" "a b" "c d"; }
  .perk.tall { min-height: 520px; }
}
@media (max-width: 640px) {
  .perks { grid-template-columns: minmax(0, 1fr); grid-template-areas: "r" "a" "b" "y" "c" "d"; }
  .perk.tall { min-height: 480px; }
  .phones { max-width: 380px; }
}

/* The offers band: the stickers drifting behind it as they do behind pay it
   forward, and the two phones standing on the band's floor. The section drops
   its bottom padding so the phones' foot is the band's edge; the words keep
   theirs, and a tall picture runs out of the top of the band rather than
   making it taller. */
#offers { position: relative; overflow: hidden; padding-bottom: 0; }
#offers > :not(.field) { position: relative; z-index: 1; }
#offers .inner { align-items: stretch; }
#offers .inner > :first-child { align-self: center; padding-bottom: var(--band); }
/* In the flow, not absolute: the band grows to fit the whole picture, with
   room above it, rather than the picture running out of the top. */
.offer-phones {
  display: flex; justify-content: center; align-items: flex-end;
  padding-top: clamp(40px, 5vw, 72px);
}
.offer-phones img {
  display: block; width: min(100%, 560px); height: auto;
  filter: drop-shadow(0 30px 60px rgba(0, 0, 0, 0.5));
}
@media (max-width: 920px) {
  #offers .inner > :first-child { padding-bottom: 0; }
  .offer-phones { padding-top: 8px; }
  .offer-phones img { width: min(100%, 480px); }
}

/* The passport band: on the house wash, the two phones level with the
   words and the badge list beside them. */
#passport { position: relative; overflow: hidden; }
.pass-phones { display: flex; justify-content: center; align-items: center; }
/* The shadow is in the picture rather than a CSS filter, which left a pale
   box the size of the image in some browsers. The picture is the phones plus
   room for that shadow, so it is drawn wider than the phones and pulled back
   by the margins: the phones themselves sit at min(100%, 480px). */
.pass-phones img {
  display: block; max-width: none; height: auto;
  width: min(131.11%, 629.3px);
  margin: max(-10%, -48px) max(-15.56%, -74.7px) max(-22.22%, -106.7px);
}
@media (max-width: 920px) {
  .pass-phones { padding-top: 8px; }
  .pass-phones img {
    width: min(120.62%, 550.7px);
    margin: max(-9.2%, -42px) max(-14.31%, -65.3px) max(-20.44%, -93.3px);
  }
}

/* ══ business: it brings them back for you ═════════════════════════════════
   Two tiles, each with a small drawing of the thing at work: the reminder
   email Loyollo sends, and a card in the app during a double-stamps hour. */
.back-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; margin-top: 40px; }
.back-card {
  border-radius: var(--r-lg); padding: clamp(24px, 3vw, 40px); min-width: 0;
  display: flex; flex-direction: column; gap: 10px;
}
/* The cheese on the pizza slice, pale: warm, and the orange card and the
   pizzas drifting behind the section both sit well on it. */
#back { position: relative; overflow: hidden; }
#back > :not(.field) { position: relative; z-index: 1; }
.back-card.b-pink, .back-card.b-blue { background: #FFF1CF; color: var(--ink); }
.back-card h3 { margin: 22px 0 0; font-family: var(--display); font-size: clamp(22px, 2vw, 26px); letter-spacing: -0.02em; line-height: 1.15; }
.back-card p { margin: 0; color: var(--ink-dim); font-size: 15.5px; line-height: 1.6; max-width: 56ch; }
.back-card p.back-small { font-size: 14px; color: var(--ink-faint); }
.back-mock {
  min-height: 210px; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 14px;
}
.mail, .bcard {
  width: min(100%, 360px); border-radius: 20px; padding: 18px 20px;
  box-shadow: 0 18px 40px rgba(150, 90, 20, 0.16), 0 2px 6px rgba(18, 13, 11, 0.06);
}
.mail { background: #fff; color: var(--ink); display: flex; flex-direction: column; gap: 4px; transform: rotate(-1.5deg); }
.mail-top { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; font-size: 13px; }
.mail-top small { margin-left: auto; color: var(--ink-faint); font-size: 12px; }
.mail-logo { width: 28px; height: 28px; border-radius: 7px; }
.mail-word { height: 15px; width: auto; }
.mail-subj { font-family: var(--display); font-size: 19px; letter-spacing: -0.02em; line-height: 1.2; }
.mail-line { color: var(--ink-dim); font-size: 13.5px; }
.mail-stamps { display: flex; gap: 8px; margin-top: 12px; }
.mail-stamps img, .mail-stamps i { width: 40px; height: 40px; flex: none; }
.mail-stamps img { object-fit: contain; }
.mail-stamps i { border-radius: 50%; border: 1.5px dashed rgba(18, 13, 11, 0.25); }
.bcard { background: #E8572A; color: #fff; display: flex; flex-direction: column; gap: 16px; transform: rotate(1.5deg); }
.bcard-head { display: flex; align-items: center; gap: 10px; }
.bcard-logo { width: 40px; height: 40px; border-radius: 12px; background: #fff; display: grid; place-items: center; flex: none; }
.bcard-logo img { width: 28px; }
.bcard-name { display: flex; flex-direction: column; line-height: 1.2; min-width: 0; }
.bcard-name b { font-family: var(--display); font-size: 18px; letter-spacing: -0.02em; }
.bcard-name small { font-size: 12.5px; opacity: 0.75; }
.bcard-brand { height: 46px; width: auto; display: block; margin-bottom: 6px; }
.bcard-count { margin-left: auto; font-size: 14px; opacity: 0.85; }
.bcard-count b { font-family: var(--display); font-size: 26px; letter-spacing: -0.02em; }
.bonus-pill {
  align-self: flex-start; display: inline-flex; align-items: center; gap: 8px;
  background: #fff; color: var(--ink); border-radius: 999px; padding: 5px 14px 5px 5px;
  font-size: 13.5px; font-weight: 600; max-width: 100%;
}
.bonus-pill b { background: var(--ink); color: #fff; border-radius: 999px; padding: 3px 9px; font-family: var(--display); font-size: 13px; }
.bonus-slot {
  font-size: 13px; font-weight: 600; color: var(--ink-dim);
  background: rgba(255, 255, 255, 0.75); border-radius: 999px; padding: 6px 14px;
}

/* What customers get on top: the customer page's black tiles, four in a row. */
.ontop { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; margin-top: 40px; }
.perk.dark { background: #fff; color: var(--ink); box-shadow: 0 10px 30px rgba(60, 20, 90, 0.06); }
.perk.dark p { color: var(--ink-dim); }
/* On white the pale gradient would vanish, so the stronger end of the
   Loyollo colours: pink, purple, blue. */
.ontop .perk-mark { background-image: linear-gradient(110deg, #FF6FD8 0%, #A86BFF 50%, #539AD9 100%); }
/* VIP as a black pill with white letters. The same
   72px slot as the other marks, so the titles still line up. */
.ontop .perk-mark.vip {
  height: 44px; margin: 10px 0 18px; padding: 0 18px; border-radius: 999px;
  background: var(--ink); -webkit-text-fill-color: #fff; color: #fff;
  font-size: 20px; letter-spacing: 0.08em;
}

.perk-pp { width: 64px; margin-bottom: 8px; display: block; transform: rotate(6deg); }
.perk-pp img { width: 100%; display: block; }
/* Three places fanned out, like stamps dropped on a passport page. */
.perk-pp.trio { position: relative; width: 150px; height: 64px; transform: none; }
.perk-pp.trio img { position: absolute; top: 4px; width: 58px; filter: drop-shadow(0 4px 6px rgba(18, 13, 11, 0.12)); }
.perk-pp.trio img:nth-child(1) { left: 0;    transform: rotate(-9deg); }
.perk-pp.trio img:nth-child(2) { left: 44px; transform: rotate(4deg); top: 0; z-index: 1; }
.perk-pp.trio img:nth-child(3) { left: 88px; transform: rotate(11deg); }
@media (max-width: 1080px) { .ontop { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 920px) { .back-grid { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 640px) {
  .ontop { grid-template-columns: minmax(0, 1fr); }
  .back-mock { min-height: 0; padding-block: 8px; }
  .mail-stamps { gap: 6px; }
  .mail-stamps img, .mail-stamps i { width: 32px; height: 32px; }
}

/* A few of the stamps people collect, stuck down above "Every place you go":
   each a little crooked and overlapping the last, like stickers on a card. */
.sk-row { display: flex; margin-bottom: 22px; }
.sk-row img { width: 64px; height: 64px; display: block; filter: drop-shadow(0 4px 6px rgba(18, 13, 11, 0.12)); }
.sk-row img + img { margin-left: -10px; }
.sk-row img:nth-child(1) { transform: rotate(-8deg); }
.sk-row img:nth-child(2) { transform: rotate(6deg) translateY(4px); }
.sk-row img:nth-child(3) { transform: rotate(-4deg) translateY(-2px); }
.sk-row img:nth-child(4) { transform: rotate(9deg) translateY(3px); }
@media (max-width: 640px) { .sk-row img { width: 52px; height: 52px; } }

/* "Simply tap" on the home page: Apple-style grey tiles, each with a small
   picture of the step above its words: the usual, the tap, the card filling. */
.steps.art .step {
  background: #F5F5F7; background-image: none; border-radius: 30px;
  padding: 30px 30px 32px; display: flex; flex-direction: column;
}
.steps.art .step-art { display: grid; place-items: center; margin-bottom: 18px; }
.steps.art .k { font-size: 13px; font-weight: 700; color: var(--ink-dim); }
.steps.art .step h3 { margin-top: 6px; }
.steps.art .step-art { height: 210px; }

/* 1: the usual, a takeaway cup with the stamp on its sleeve, still steaming. */
.sa-cup { width: 112px; height: auto; overflow: visible; filter: drop-shadow(0 14px 18px rgba(18, 13, 11, 0.12)); }
.sa-steam path { animation: sa-steam 3.2s ease-in-out infinite; opacity: 0; }
.sa-steam path:nth-child(2) { animation-delay: 0.9s; }
.sa-steam path:nth-child(3) { animation-delay: 1.8s; }
@keyframes sa-steam {
  0% { opacity: 0; transform: translateY(8px); }
  35% { opacity: 0.9; }
  100% { opacity: 0; transform: translateY(-10px); }
}

/* 2: the tap. A phone showing the wallet, and the key card coming in to meet
   it; the waves pulse at the moment they touch. */
.sa-tap { position: relative; width: 240px; height: 200px; }
.sa-phone {
  position: absolute; left: 34px; top: 4px; width: 98px; height: 192px; border-radius: 22px;
  background: var(--ink); transform: rotate(-8deg); box-shadow: 0 14px 24px rgba(18, 13, 11, 0.16);
}
.sa-screen {
  position: absolute; inset: 5px; border-radius: 17px; overflow: hidden; background: var(--ink);
  display: flex; flex-direction: column; padding: 16px 7px 0;
}
.sa-word { width: 38px; height: auto; filter: invert(1); margin: 0 0 8px 2px; }
.sa-card {
  display: flex; align-items: center; justify-content: space-between; flex: none;
  height: 26px; margin-top: -8px; padding: 0 6px 6px; border-radius: 7px;
  background: var(--c); color: var(--t); font: 800 8px/1 var(--display); letter-spacing: -0.01em;
}
.sa-card:first-of-type { margin-top: 0; }
.sa-card img { width: 12px; height: 12px; }
.sa-card.big { flex: 1 0 34px; height: auto; align-items: flex-start; padding-top: 6px; border-radius: 7px 7px 0 0; }
.sa-key {
  position: absolute; z-index: 1; right: 2px; top: 58px; width: 106px; height: 66px; border-radius: 12px;
  background: #fff; color: var(--ink); transform: rotate(10deg);
  box-shadow: 0 12px 26px rgba(18, 13, 11, 0.14);
  animation: sa-key 4.4s cubic-bezier(.45, 0, .25, 1) infinite;
}
.sa-key svg { position: absolute; left: 12px; top: calc(50% - 13px); width: 26px; height: 26px; animation: sa-waves 4.4s ease-in-out infinite; }
@keyframes sa-key {
  0%, 12% { transform: translateX(14px) rotate(14deg); }
  30%, 50% { transform: translateX(-46px) translateY(6px) rotate(8deg); }
  68%, 100% { transform: translateX(14px) rotate(14deg); }
}
@keyframes sa-waves {
  0%, 28% { opacity: 0.35; }
  33% { opacity: 1; transform: scale(1.15); }
  40% { opacity: 0.6; transform: scale(1); }
  46% { opacity: 1; transform: scale(1.1); }
  56%, 100% { opacity: 0.35; transform: scale(1); }
}

/* The moment they touch, the phone shows what the app does: the venue's
   colour, the stamp spinning in, the count. It holds while the card stays,
   and stays a moment after the card pulls away so it can be read, then the
   wallet comes back. One 4.4s loop for card, waves and screen. */
.sa-stamped {
  position: absolute; inset: 5px; border-radius: 17px; overflow: hidden;
  background: linear-gradient(170deg, #8A55E0, #6C3BC9); color: #fff;
  display: flex; flex-direction: column; align-items: flex-start; padding: 16px 9px 10px;
  opacity: 0; animation: sa-screen 4.4s ease-in-out infinite;
}
.sa-stamped .sa-word { opacity: 0.6; margin-bottom: 14px; }
.sa-bean { width: 50px; height: 50px; filter: drop-shadow(0 5px 6px rgba(0, 0, 0, 0.25)); animation: sa-bean 4.4s ease-out infinite; }
.sa-stamped b { font: 800 13px/1.05 var(--display); letter-spacing: -0.03em; margin-top: 12px; }
.sa-stamped i { font: 700 8px/1 ui-monospace, SFMono-Regular, Menlo, monospace; font-style: normal; margin-top: 5px; }
.sa-done { margin-top: auto; align-self: stretch; height: 16px; border-radius: 99px; background: #fff; }
@keyframes sa-screen {
  0%, 31% { opacity: 0; }
  36%, 88% { opacity: 1; }
  95%, 100% { opacity: 0; }
}
@keyframes sa-bean {
  0%, 33% { transform: rotate(-140deg) scale(0.6); }
  43% { transform: rotate(8deg) scale(1.08); }
  49%, 100% { transform: rotate(0) scale(1); }
}

/* 3: the card filling. Six empty places; the stamps land one at a time,
   the full card holds for a beat, then it clears and starts again. Each
   place has its own keyframes so they all clear together. */
.sa-grid { display: grid; grid-template-columns: repeat(3, 52px); gap: 10px; }
.sa-grid > span { position: relative; width: 52px; height: 52px; display: block; }
.sa-grid > span::before {
  content: ""; position: absolute; inset: 0; background: rgba(18, 13, 11, 0.08);
  -webkit-mask: url(/img/sk-bean.png) center/contain no-repeat; mask: url(/img/sk-bean.png) center/contain no-repeat;
}
.sa-grid > span img { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; animation: 6s ease-out infinite; }
.sa-grid > span:nth-child(1) img { animation-name: sa-land1; }
@keyframes sa-land1 {
  0%, 5.0% { opacity: 0; transform: scale(1.6) rotate(-18deg); }
  8.5% { opacity: 1; transform: scale(0.92) rotate(-1deg); }
  11.5%, 84% { opacity: 1; transform: scale(1) rotate(-6deg); }
  92%, 100% { opacity: 0; transform: scale(1) rotate(-6deg); }
}
.sa-grid > span:nth-child(2) img { animation-name: sa-land2; }
@keyframes sa-land2 {
  0%, 14.2% { opacity: 0; transform: scale(1.6) rotate(-7deg); }
  17.7% { opacity: 1; transform: scale(0.92) rotate(10deg); }
  20.7%, 84% { opacity: 1; transform: scale(1) rotate(5deg); }
  92%, 100% { opacity: 0; transform: scale(1) rotate(5deg); }
}
.sa-grid > span:nth-child(3) img { animation-name: sa-land3; }
@keyframes sa-land3 {
  0%, 23.3% { opacity: 0; transform: scale(1.6) rotate(-15deg); }
  26.8% { opacity: 1; transform: scale(0.92) rotate(2deg); }
  29.8%, 84% { opacity: 1; transform: scale(1) rotate(-3deg); }
  92%, 100% { opacity: 0; transform: scale(1) rotate(-3deg); }
}
.sa-grid > span:nth-child(4) img { animation-name: sa-land4; }
@keyframes sa-land4 {
  0%, 32.5% { opacity: 0; transform: scale(1.6) rotate(-5deg); }
  36.0% { opacity: 1; transform: scale(0.92) rotate(12deg); }
  39.0%, 84% { opacity: 1; transform: scale(1) rotate(7deg); }
  92%, 100% { opacity: 0; transform: scale(1) rotate(7deg); }
}
.sa-grid > span:nth-child(5) img { animation-name: sa-land5; }
@keyframes sa-land5 {
  0%, 41.7% { opacity: 0; transform: scale(1.6) rotate(-17deg); }
  45.2% { opacity: 1; transform: scale(0.92) rotate(0deg); }
  48.2%, 84% { opacity: 1; transform: scale(1) rotate(-5deg); }
  92%, 100% { opacity: 0; transform: scale(1) rotate(-5deg); }
}
.sa-grid > span:nth-child(6) img { animation-name: sa-land6; }
@keyframes sa-land6 {
  0%, 50.8% { opacity: 0; transform: scale(1.6) rotate(-8deg); }
  54.3% { opacity: 1; transform: scale(0.92) rotate(9deg); }
  57.3%, 84% { opacity: 1; transform: scale(1) rotate(4deg); }
  92%, 100% { opacity: 0; transform: scale(1) rotate(4deg); }
}
@media (prefers-reduced-motion: reduce) {
  .sa-steam path, .sa-key, .sa-key svg, .sa-stamped, .sa-bean { animation: none; }
  .sa-steam path { opacity: 0.6; }
  .sa-grid > span img { animation: none; opacity: 1; }
}
@media (max-width: 920px) { .steps.art .step-art { height: 200px; } }
/* The passport's three town stamps are the subject of the section, so they
   are drawn larger than the stickers above "Every place you go". */
#passport .sk-row img { width: 92px; height: 92px; object-fit: contain; }
#passport .sk-row img + img { margin-left: -14px; }
@media (max-width: 640px) { #passport .sk-row img { width: 72px; height: 72px; } #passport .sk-row img + img { margin-left: -10px; } }
