/* ==== tokens.css ==== */
@charset "UTF-8";

/* ==========================================================================
   MKB — design system
   ==========================================================================

   One stylesheet, on purpose. No build step exists on the server, and a single
   file means a single render-blocking request instead of a chain of @imports.

   Layer order is declared once, up front. Because layers win over source order,
   a component can be written anywhere in the file and still beat base styles
   without a specificity fight. The only !important in this file is the
   reduced-motion block at the very bottom, which is meant to beat everything.

   Colours are not invented. They were measured out of the brand book itself
   (docs/MKB Guidline.pdf) by counting the fills it actually uses:
     #1B7CC0 ×129   #FFC71C ×93   #EAF5FD ×31   #353535 ×10   #0A3D62 ×4
   ========================================================================== */

@layer reset, tokens, base, layout, components, utilities;

/* ==========================================================================
   Satoshi — the brand typeface, self-hosted (see assets/fonts/README.md)
   ========================================================================== */

@font-face {
	font-family: "Satoshi";
	src: url("../fonts/satoshi-300.woff2") format("woff2");
	font-weight: 300;
	font-style: normal;
	font-display: swap;
}
@font-face {
	font-family: "Satoshi";
	src: url("../fonts/satoshi-400.woff2") format("woff2");
	font-weight: 400;
	font-style: normal;
	font-display: swap;
}
@font-face {
	font-family: "Satoshi";
	src: url("../fonts/satoshi-500.woff2") format("woff2");
	font-weight: 500;
	font-style: normal;
	font-display: swap;
}
@font-face {
	font-family: "Satoshi";
	src: url("../fonts/satoshi-700.woff2") format("woff2");
	font-weight: 700;
	font-style: normal;
	font-display: swap;
}
@font-face {
	font-family: "Satoshi";
	src: url("../fonts/satoshi-900.woff2") format("woff2");
	font-weight: 900;
	font-style: normal;
	font-display: swap;
}

/* ==========================================================================
   TOKENS
   ========================================================================== */

@layer tokens {
	:root {
		/* --- Brand ------------------------------------------------------- */
		/* These are the brand book's exact values. Use them for large fills,
		   graphics and artwork, where WCAG only asks for 3:1. */
		--mkb-blue: #1b7cc0;
		--mkb-blue-bright: #2a8fd6;
		--mkb-navy: #0a3d62;
		--mkb-yellow: #ffc71c;
		--mkb-charcoal: #353535;
		--mkb-tint: #eaf5fd; /* pale blue, straight from the brand book */

		/* Supporting accents. Each one means something — coral is urgency,
		   green is a gain. They are never decoration. */
		--mkb-coral: #e8573d;
		--mkb-green: #2eaa6f;

		/* --- Accessible counterparts -------------------------------------
		   Measured, not guessed. White text on the exact brand blue scores
		   4.48:1 — it misses AA (4.5) by a hair, and the same is true of coral
		   and green, which are much further off. These are the darkest-by-the-
		   minimum variants that carry white text legally. The blue differs from
		   the brand value by one step per channel and is indistinguishable side
		   by side; coral and green shift more, so they are reserved for small
		   badge fills where white text sits on top. */
		--blue-aa: #1974b4; /* white 5.00 · page 4.74 · tint 4.52 · white-on-it 5.00 */
		--blue-aa-dark: #16649b; /* hover/pressed — filled controls darken, never lighten */
		--blue-on-dark: #5fabe0; /* link text on navy: 4.52 */
		--coral-aa: #dd381a; /* 4.51:1 under white text */
		--green-aa: #248658; /* 4.54:1 under white text */

		/* --- Neutrals ---------------------------------------------------- */
		/* Kept slightly cool so MKB blue sits naturally on them rather than
		   looking pasted on top of a warm grey. */
		--gray-50: #f7f9fb;
		--gray-100: #eef2f7;
		--gray-200: #e0e7ef;
		--gray-300: #c8d3e0;
		--gray-400: #9aa8b8;
		--gray-500: #61707f; /* darkened from #6D7C8D so muted text clears AA */
		--gray-600: #4d5a68;
		--gray-700: #37424e;
		--gray-800: #232c36;
		--gray-900: #141a21;

		/* --- Semantic roles ---------------------------------------------- */
		/* Components reference these, never the raw brand values above. That is
		   what makes a future dark theme a change in one place. */
		--surface-page: var(--gray-50);
		--surface: #fff;
		--surface-sunken: var(--gray-100);
		--surface-tint: var(--mkb-tint);
		--surface-inverse: var(--mkb-navy);

		--text-strong: var(--gray-900);
		--text: var(--gray-700);
		--text-muted: var(--gray-500);
		--text-inverse: #fff;
		--text-on-accent: #fff;

		--accent: var(--blue-aa);
		--accent-hover: var(--blue-aa-dark);
		--accent-soft: var(--mkb-tint);
		--accent-on-dark: var(--blue-on-dark); /* link/text colour on navy surfaces */
		--reward: var(--mkb-yellow); /* always pairs with dark ink, never white */
		--offer: var(--coral-aa);
		--positive: var(--green-aa);

		/* Hairlines exist, but they are a last resort. Depth in this design
		   comes from fill and shadow — a 1px grey box around everything is
		   exactly the generic look we are avoiding. */
		--line: rgb(200 211 224 / 55%);
		--line-strong: var(--gray-300);

		/* On navy. Tinted from the surface hue rather than grey — grey text on a
		   coloured ground always reads as dirty. Both clear AA on #0A3D62:
		   7.58 and 5.53. */
		--footer-text: #c3d6e6;
		--footer-muted: #9bb9d2;

		/* On the blue app panel (--accent). Same reasoning, different ground.
		   Measured on #1974B4: 4.85 and 4.62. My first attempt at these was
		   eyeballed and both values actually failed at 4.31 and 3.80. */
		--on-accent-text: #f8fcfe;
		--on-accent-muted: #eff7fd;

		/* --- Typography --------------------------------------------------- */
		--font: "Satoshi", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
			"Helvetica Neue", Arial, sans-serif;

		/* Fluid, so nothing needs a breakpoint just to change size. */
		--fs-display: clamp(2.25rem, 1.2rem + 5.4vw, 6rem);
		--fs-h1: clamp(2rem, 1.35rem + 2.9vw, 3.5rem);
		--fs-h2: clamp(1.5rem, 1.18rem + 1.45vw, 2.25rem);
		--fs-h3: clamp(1.15rem, 1.03rem + 0.55vw, 1.5rem);
		--fs-lead: clamp(1.0625rem, 1rem + 0.4vw, 1.3125rem);
		--fs-body: 1rem;
		--fs-sm: 0.875rem;
		--fs-xs: 0.75rem;

		--lh-display: 0.96;
		--lh-tight: 1.12;
		--lh-snug: 1.35;
		--lh-body: 1.6;

		/* Big type needs negative tracking or it reads loose and amateur. */
		--ls-display: -0.035em;
		--ls-heading: -0.02em;
		--ls-label: 0.02em;

		/* --- Space -------------------------------------------------------- */
		--space-3xs: 0.25rem;
		--space-2xs: 0.5rem;
		--space-xs: 0.75rem;
		--space-sm: 1rem;
		--space-md: 1.5rem;
		--space-lg: 2rem;
		--space-xl: 3rem;
		--space-2xl: 4rem;
		--space-3xl: 6rem;

		/* Vertical rhythm between page sections, fluid so small screens are not
		   mostly empty space. */
		--section-y: clamp(3.5rem, 2rem + 5.5vw, 7.5rem);

		/* --- Layout ------------------------------------------------------- */
		--content: 1240px;
		--content-wide: 1440px;
		--content-narrow: 720px;
		--gutter: clamp(1.25rem, 0.7rem + 2.2vw, 3rem);
		--nav-h: 56px;

		/* --- Shape -------------------------------------------------------- */
		/* Rounded, not bubbly. Small radius for controls, generous for tiles,
		   very generous for sheets. */
		--r-xs: 8px;
		--r-sm: 12px;
		--r-md: 18px;
		--r-lg: 24px;
		--r-xl: 32px;
		--r-2xl: 40px;
		--r-pill: 999px;

		/* --- Elevation ---------------------------------------------------- */
		/* Layered and cool-tinted. A single flat shadow always looks synthetic;
		   two offsets read as real light. Never glossy. */
		--shadow-xs: 0 1px 2px rgb(20 34 51 / 4%);
		--shadow-sm: 0 1px 2px rgb(20 34 51 / 4%), 0 3px 8px rgb(20 34 51 / 4%);
		--shadow-md: 0 2px 4px rgb(20 34 51 / 4%), 0 10px 24px rgb(20 34 51 / 7%);
		--shadow-lg: 0 4px 10px rgb(20 34 51 / 5%), 0 20px 48px rgb(20 34 51 / 11%);
		--shadow-nav: 0 1px 2px rgb(20 34 51 / 5%), 0 8px 28px rgb(20 34 51 / 10%);

		/* ------------------------------------------------------------------
		   NAV LOGO BADGE — drop shadow
		   ------------------------------------------------------------------
		   The MKB tile in the top-left sits proud of the navigation bar. This
		   is the shadow that lifts it.

		   To soften it:  reduce the two percentages.
		   To lift it more: increase the second offset (currently 7px) and blur.
		   To switch it off entirely, replace the whole value with:  none
		   ------------------------------------------------------------------ */
		/* ------------------------------------------------------------------
		   NAV LOGO BADGE — size
		   ------------------------------------------------------------------
		   The logo sits OVER the bar, not inside it, so the bar's height does
		   not limit it. Make this bigger and the tile grows, overhanging the
		   bar further; the bar reserves room for it automatically.

		   The same corner radius is shared by the icon and the white navigation
		   bar, so the two surfaces use one consistent silhouette.
		   ------------------------------------------------------------------ */
		--nav-brand-size: 72px;
		--nav-brand-radius: 16px;

		--nav-badge-shadow:
			0 2px 4px rgb(20 34 51 / 10%),
			0 7px 18px rgb(20 34 51 / 22%);

		/* --- Motion -------------------------------------------------------- */
		/* iOS-flavoured. --ease-out is a deep ease that decelerates late;
		   --ease-spring overshoots slightly and is reserved for things that
		   appear (sheets, tiles), never for colour or opacity. */
		--ease-out: cubic-bezier(0.22, 1, 0.36, 1);
		--ease-spring: cubic-bezier(0.34, 1.4, 0.64, 1);
		--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);

		--t-fast: 140ms;
		--t: 240ms;
		--t-slow: 420ms;
		--t-sheet: 520ms;

		/* ------------------------------------------------------------------
		   NAV COLLAPSE — speed
		   ------------------------------------------------------------------
		   How long the bar takes to settle between its resting and scrolled
		   states: height, the logo's size and position, the material and the
		   shadow all move together over this duration.

		   Longer = slower and more deliberate. Below about 300ms it starts to
		   read as a snap rather than a settle.
		   ------------------------------------------------------------------ */
		/* ------------------------------------------------------------------
		   APP ICON FLIP — timing
		   ------------------------------------------------------------------
		   The app tile turns over to show the install QR, then turns back, on a
		   loop. One number controls the whole cycle. At the 9s default:

		     app icon showing   ~3.4s
		     turning over       ~0.7s
		     QR showing         ~4.2s
		     turning back       ~0.7s

		   Raise it to linger longer on each side; lower it to flip more often.
		   Stops entirely for anyone who has asked for reduced motion.
		   ------------------------------------------------------------------ */
		--appflip-cycle: 9s;

		--t-nav: 700ms;
		--ease-nav: cubic-bezier(0.33, 0.9, 0.2, 1);

		/* Dark mode is deliberately NOT implemented yet (agreed to defer).
		   color-scheme is pinned to light so the browser does not dark-ify form
		   controls, scrollbars and autofill against a light page. Removing this
		   line is the first step when dark mode is picked up. */
		color-scheme: light;
	}

	/* ------------------------------------------------------------------
	   PHONE — a slimmer bar under a bigger badge
	   ------------------------------------------------------------------
	   At the desktop values a phone got a 56-point bar under a 58-point
	   badge: near enough identical that the badge read as part of the bar
	   rather than a tile resting on it.

	   These live on :root, not on .masthead. They did sit on .masthead, and
	   the consequence was that <main> — which derives its clearance from the
	   same two numbers in layout.css — kept reading the desktop pair and
	   left the hero 9 points too high on every phone.
	   ------------------------------------------------------------------ */
	@media (width < 34rem) {
		:root {
			--nav-h: 48px;
			--nav-brand-size: 62px;
		}
	}
}

/* ==== reset.css ==== */
/* ==========================================================================
   RESET
   ========================================================================== */

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

	* {
		margin: 0;
	}

	html {
		-webkit-text-size-adjust: 100%;
		text-size-adjust: 100%;
	}

	body {
		min-height: 100svh;
		-webkit-font-smoothing: antialiased;
		-moz-osx-font-smoothing: grayscale;
	}

	img,
	picture,
	video,
	canvas,
	svg {
		display: block;
		max-width: 100%;
	}

	input,
	button,
	textarea,
	select {
		font: inherit;
		color: inherit;
	}

	button {
		background: none;
		border: 0;
		padding: 0;
		cursor: pointer;
	}

	/* Paragraphs may break a long unbreakable string (a URL, a product code).
	   Headings must not — a hyphen-less mid-word break in a display headline is
	   the most visible typographic failure there is. Headings instead get a
	   column that is allowed to grow to fit them; see sections/hero.css. */
	p {
		overflow-wrap: break-word;
	}

	ul[role="list"],
	ol[role="list"] {
		list-style: none;
		padding: 0;
	}
}

/* ==== base.css ==== */
/* ==========================================================================
   BASE
   ========================================================================== */

@layer base {
	/* --- The two ends of the document ------------------------------------
	   Past the top and bottom of the page — the rubber-band region on a phone,
	   and any gap left when the content is shorter than the window — the
	   browser paints the CANVAS, which takes its background from this element.
	   Nothing of ours reaches there, so pulling up on the navy footer flashed
	   the light page colour underneath it.

	   One flat colour cannot fix that, because the canvas is painted at both
	   ends: navy would put a navy band above the hero. So the canvas gets the
	   footer's navy as its base and a light layer over it, sized to the document
	   and shifted two screens upward — light from well above the first pixel
	   down to the last, navy from there on.

	   This is paint, not layout: no element, no height, nothing to scroll into.
	   The first attempt padded the footer by a screen and pulled it back with a
	   negative margin, which measured as 844px of extra scroll. */
	html {
		background-color: var(--surface-inverse);
		background-image: linear-gradient(var(--surface-page) 0 0);
		background-repeat: no-repeat;
		background-size: 100% calc(100% + 200vh);
		background-position: 0 -200vh;

		scroll-behavior: smooth;
		/* iOS only honours momentum on the scroll container it is set on, and
		   Safari still needs the prefixed form on the document. */
		-webkit-overflow-scrolling: touch;
		/* Anchor targets must not hide under the floating nav. */
		/* Keeps an anchor target, and anything the keyboard focuses, clear of
		   the floating masthead — WCAG 2.2 AA, 2.4.11 Focus Not Obscured.

		   Derived rather than typed: the logo is centred on the bar and larger
		   than it, so the masthead's real occupied height is half the bar plus
		   half the logo. Change --nav-brand-size and this follows. */
		scroll-padding-top: calc(
			var(--space-sm) + (var(--nav-h) + var(--nav-brand-size)) / 2 + var(--space-md)
		);
	}

	body {
		font-family: var(--font);
		font-size: var(--fs-body);
		font-weight: 500; /* Satoshi Regular reads thin on screen; Medium is the body weight */
		line-height: var(--lh-body);
		color: var(--text);
		background: var(--surface-page);
		/* No sideways rubber-banding at the page edges. Several sections carry
		   rails that scroll sideways; without this, a flick that reaches the end
		   of a rail carries on into the page and the whole layout slides. */
		overscroll-behavior-x: none;
	}

	/* --- Touch ------------------------------------------------------------
	   Two defaults that make a phone feel like an application rather than a
	   document. The grey flash iOS paints over a tapped link arrives after the
	   press and fights whatever the control does itself; every control here has
	   its own :active state instead. And declaring manipulation removes the
	   300ms the browser otherwise waits to find out whether a tap was the start
	   of a double-tap zoom. */
	a,
	button,
	summary,
	[role="button"],
	input,
	select,
	textarea,
	label {
		-webkit-tap-highlight-color: transparent;
		touch-action: manipulation;
	}

	h1,
	h2,
	h3 {
		color: var(--text-strong);
		font-weight: 700;
		line-height: var(--lh-tight);
		letter-spacing: var(--ls-heading);
		/* Stops a heading ending in one orphaned word. */
		text-wrap: balance;
	}

	h1 {
		font-size: var(--fs-h1);
	}
	h2 {
		font-size: var(--fs-h2);
	}
	h3 {
		font-size: var(--fs-h3);
	}

	p {
		text-wrap: pretty;
	}

	a {
		color: var(--accent);
		text-decoration-thickness: 1px;
		text-underline-offset: 0.18em;
		transition: color var(--t-fast) var(--ease-out);
	}

	a:hover {
		color: var(--accent-hover);
	}

	strong,
	b {
		font-weight: 700;
	}

	::selection {
		background: var(--accent);
		color: var(--text-on-accent);
	}

	/* Focus is visible, generous and never removed. Keyboard users navigate
	   this site with the same confidence as mouse users. */
	:focus-visible {
		outline: 3px solid var(--accent);
		outline-offset: 3px;
		border-radius: var(--r-xs);
	}

	:focus:not(:focus-visible) {
		outline: none;
	}
}

/* ==== layout.css ==== */
/* ==========================================================================
   LAYOUT
   ========================================================================== */

@layer layout {
	.wrap {
		width: 100%;
		max-width: var(--content);
		margin-inline: auto;
		padding-inline: var(--gutter);
	}

	.wrap--wide {
		max-width: var(--content-wide);
	}

	.wrap--narrow {
		max-width: var(--content-narrow);
	}

	/* The masthead is fixed, so content needs clearance beneath it. A page whose
	   first section is meant to run under the bar (the hero) sets --main-top: 0
	   on itself rather than fighting a global rule. */
	main {
		/*
		 * The masthead's real footprint, not the bar's height.
		 *
		 * The badge is centred on the bar and taller than it, so what the page
		 * actually has to clear is the bar's offset from the top plus half of
		 * each. Measuring only --nav-h left the hero starting 15 points below
		 * the badge's chin, which read as the page being shoved under the
		 * navigation. This is the same derivation scroll-padding-top in
		 * base.css uses, so an anchored heading and the first screen now agree.
		 */
		padding-block-start: var(--main-top, calc(
			var(--space-sm) + (var(--nav-h) + var(--nav-brand-size)) / 2 + var(--space-xl)
		));
		/* clip, not hidden: hidden on one axis forces the other to scroll, which
		   would turn <main> into a scroll container and break sticky positioning
		   and the page's own scrollbar. clip has neither side effect.

		   This is a floor, not a licence — sideways overflow is still a bug
		   everywhere it happens. What it prevents is a full-bleed element that
		   momentarily exceeds the viewport (a scale-in, a rounding error at some
		   width nobody tested) turning into a horizontal scrollbar on a phone. */
		overflow-x: clip;
	}

	/* A page whose first element is a full-bleed coloured header supplies its
	   own clearance inside that header, so the global gap would double it. */
	body[data-route="offers"] main {
		--main-top: 0px;
	}

	.section {
		padding-block: var(--section-y);
	}

	.section--centred {
		text-align: center;
	}

	.section--centred > * {
		margin-inline: auto;
	}

	/* Sections carry their own copy width so long paragraphs stay readable
	   instead of running the full 1240px. */
	.section > p {
		max-width: 68ch;
	}

	.stack > * + * {
		margin-block-start: var(--flow, var(--space-sm));
	}

	/* A row of things that wraps instead of overflowing. Button groups, chips,
	   meta rows. Wrapping is the default here because a non-wrapping flex row is
	   the single most common cause of horizontal scroll on a phone. */
	.cluster {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		gap: var(--gap, var(--space-xs));
	}

	.cluster--centred {
		justify-content: center;
	}
}

/* ==== components/button.css ==== */
/* Buttons, the generic surface, and icon sizing. */

@layer components {
	/* --- Buttons ---------------------------------------------------------- */
	/* Filled shape, no border, generous radius. The hover lift is 1px — enough
	   to feel responsive, small enough not to look like a toy. */
	.button {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		gap: var(--space-2xs);
		min-height: 48px; /* comfortable touch target */
		padding: 0 var(--space-md);
		border-radius: var(--r-pill);
		background: var(--accent);
		color: var(--text-on-accent);
		font-weight: 700;
		font-size: var(--fs-body);
		letter-spacing: var(--ls-label);
		text-decoration: none;
		box-shadow: var(--shadow-sm);
		transition:
			background-color var(--t-fast) var(--ease-out),
			transform var(--t-fast) var(--ease-out),
			box-shadow var(--t-fast) var(--ease-out);
	}

	.button:hover {
		background: var(--accent-hover);
		color: var(--text-on-accent);
		transform: translateY(-1px);
		box-shadow: var(--shadow-md);
	}

	.button:active {
		transform: translateY(0) scale(0.97);
		box-shadow: var(--shadow-xs);
		/* The press is the one moment that must not feel eased. It goes down
		   immediately and comes back up on the transition above. */
		transition-duration: 60ms;
	}

	/* On a touch screen the hover lift can never fire, so the button would have
	   had no reaction at all between the tap and the next page. */
	@media (hover: none) {
		.button:hover {
			transform: none;
			box-shadow: var(--shadow-sm);
		}
	}

	/* Where the label alone is short, the button should not be. This is the
	   minimum comfortable width for a filled action. */
	.button {
		min-width: 7.5rem;
	}

	.button--quiet {
		background: var(--surface);
		color: var(--accent);
		box-shadow: var(--shadow-xs);
	}

	.button--quiet:hover {
		background: var(--surface);
		color: var(--accent-hover);
	}

	/* Reward yellow is intentionally special. It carries dark text because
	   white on #FFC71C fails contrast badly. */
	.button--reward {
		background: var(--reward);
		color: var(--gray-900);
	}

	.button--reward:hover {
		background: var(--reward);
		color: var(--gray-900);
		filter: brightness(1.04);
	}

	/* --- Surface ---------------------------------------------------------- */
	/* The base for every tile and panel. Note there is no border: depth comes
	   from the fill sitting on a slightly darker page plus a soft shadow. */
	/* For use on a dark or strongly coloured panel, where a blue fill would
	   disappear. Moved here from footer.css once the app panel needed it too. */
	.button--onDark {
		background: var(--text-inverse);
		color: var(--mkb-navy);
		justify-self: start;
	}

	.button--onDark:hover {
		background: var(--text-inverse);
		color: var(--mkb-navy);
		filter: brightness(0.94);
	}

	/* --- App icon ---------------------------------------------------------
	   The artwork already has rounded corners baked in with transparency
	   outside them, so the CSS radius exists only to keep the shadow following
	   the same curve instead of squaring off behind the image. */
	.appicon {
		display: block;
		inline-size: var(--appicon-size, 160px);
		block-size: var(--appicon-size, 160px);
		border-radius: calc(var(--appicon-size, 160px) * 0.225);
		box-shadow: var(--nav-badge-shadow);
		overflow: hidden;
	}

	.appicon img {
		inline-size: 100%;
		block-size: 100%;
		object-fit: contain;
	}

	.appicon--swap {
		position: relative;
	}

	.appicon__swap-image {
		position: absolute;
		inset: 0;
		transition:
			opacity var(--t-slow) var(--ease-in-out),
			transform var(--t-slow) var(--ease-in-out);
	}

	.appicon__swap-image--white {
		opacity: 0;
		transform: scale(1.04) rotate(2deg);
	}

	.appicon--swap:hover .appicon__swap-image--blue {
		opacity: 0;
		transform: scale(0.96) rotate(-2deg);
	}

	.appicon--swap:hover .appicon__swap-image--white {
		opacity: 1;
		transform: none;
	}

	/* --- App icon flip ----------------------------------------------------
	   Two faces on one card, turned by rotating the card rather than swapping
	   the images — so the movement has a direction and reads as one object
	   turning over rather than two pictures cross-fading.

	   The scale dip mid-turn is what gives it the pop: the card pulls back
	   slightly as it goes edge-on, then springs forward as the new face lands.
	   Without it the flip looks flat. */
	.appicon--flip {
		perspective: 900px;
		background: none;
		box-shadow: none; /* the shadow belongs to the faces, which do the moving */
	}

	.appicon__inner {
		position: relative;
		display: block;
		inline-size: 100%;
		block-size: 100%;
		transform-style: preserve-3d;
		animation: appflip var(--appflip-cycle) var(--ease-in-out) infinite;
	}

	.appicon__face {
		position: absolute;
		inset: 0;
		display: grid;
		place-items: center;
		border-radius: inherit;
		backface-visibility: hidden;
		box-shadow: var(--nav-badge-shadow);
		overflow: hidden;
	}

	/* The QR needs a light ground and a quiet margin to scan; the artwork's own
	   light modules are transparent. */
	.appicon__face--back {
		transform: rotateY(180deg);
		padding: 8%;
		background: var(--surface);
	}

	@keyframes appflip {
		/* icon, held */
		0%,
		36% {
			transform: rotateY(0deg) scale(1);
		}
		/* edge-on: pulled back */
		44% {
			transform: rotateY(90deg) scale(0.88);
		}
		/* QR lands with a small overshoot */
		50% {
			transform: rotateY(180deg) scale(1.04);
		}
		/* QR, held */
		54%,
		88% {
			transform: rotateY(180deg) scale(1);
		}
		96% {
			transform: rotateY(270deg) scale(0.88);
		}
		100% {
			transform: rotateY(360deg) scale(1);
		}
	}

	/* No turning for anyone who has asked for less movement — the app icon
	   simply stays put. The QR is still reachable: /get-app carries one. */
	@media (prefers-reduced-motion: reduce) {
		.appicon__inner {
			animation: none;
		}
	}

	/* --- Store buttons ----------------------------------------------------
	   The familiar two-line store lockup, set in our own type rather than
	   pasted in as artwork: a small verb line over the store name, with the
	   platform mark on the left. Kept deliberately small — this is a way to
	   leave the site, not the reason anyone is on it — so the whole control is
	   44px tall, which is also the touch-target floor.

	   The visible text IS the accessible text; aria-label on the link carries
	   the fuller wording ("Download myMKB on the App Store") and the two spans
	   inside are hidden from assistive tech so it is not read twice.

	   Ink rather than each store's brand colour. Two differently coloured
	   buttons side by side compete with each other and with the page; one
	   shared ink makes them read as a single pair of doors out. */
	/* A grid of equal columns sized to the widest button, inside a box only as
	   wide as it needs to be. Two store names of different lengths would
	   otherwise give the pair a ragged right edge; matching them makes the two
	   read as one object with two halves. */
	.storebtns {
		display: grid;
		grid-auto-flow: column;
		grid-auto-columns: 1fr;
		gap: var(--space-2xs);
		inline-size: fit-content;
		margin-inline: auto;
	}

	.storebtn {
		display: inline-flex;
		align-items: center;
		gap: var(--space-3xs);
		min-height: 44px;
		/* Trimmed deliberately, not by eye. The pair has to fit inside the app
		   panel on the narrowest common phone: 390 points less the page gutter
		   and the panel's own padding leaves 302, and two of these plus their
		   gap come to 282. Widen either padding and they no longer fit. */
		padding-inline: var(--space-2xs) var(--space-xs);
		border-radius: 12px;
		corner-shape: squircle;
		background: var(--mkb-navy);
		color: var(--text-inverse);
		text-decoration: none;
		box-shadow: var(--shadow-sm);
		transition:
			transform var(--t-fast) var(--ease-out),
			box-shadow var(--t-fast) var(--ease-out),
			background-color var(--t-fast) var(--ease-out);
	}

	.storebtn__mark {
		flex: none;
	}

	/* The mark is drawn on a 24 grid with its own optical centre; the Play
	   triangle sits a hair low against a two-line label without this. */
	.storebtn--android .storebtn__mark {
		transform: translateY(-0.5px);
	}

	.storebtn__text {
		display: grid;
		gap: 1px;
		text-align: start;
	}

	.storebtn__kicker {
		font-size: 0.5625rem; /* 9px — the lockup's small line, never body copy */
		font-weight: 500;
		line-height: 1;
		letter-spacing: 0.02em;
		text-transform: uppercase;
		white-space: nowrap; /* "Download on the" is one line or the lockup breaks */
		opacity: 0.78;
	}

	.storebtn__name {
		font-size: 0.9375rem; /* 15px */
		font-weight: 700;
		line-height: 1.1;
		letter-spacing: -0.01em;
		white-space: nowrap;
	}

	.storebtn:hover {
		color: var(--text-inverse);
		background: #0d4a76; /* one step up from --mkb-navy, so the lift is felt */
		transform: translateY(-1px);
		box-shadow: var(--shadow-md);
	}

	.storebtn:active {
		transform: translateY(0) scale(0.98);
		box-shadow: var(--shadow-xs);
	}

	/* On the blue app panel and the navy footer, navy on blue would vanish. */
	.storebtns--onDark .storebtn {
		background: var(--surface);
		color: var(--mkb-navy);
		box-shadow: 0 1px 3px rgb(6 30 50 / 18%), 0 8px 20px rgb(6 30 50 / 16%);
	}

	.storebtns--onDark .storebtn:hover {
		background: var(--surface);
		color: var(--mkb-navy);
		filter: brightness(0.95);
	}

	/* Below this the pair no longer fits on one line, and two stacked bars read
	   as a list of two choices rather than as a squeezed row. */
	@media (width < 23rem) {
		.storebtns {
			grid-auto-flow: row;
			inline-size: 100%;
		}
	}

	.surface {
		background: var(--surface);
		border-radius: var(--r-lg);
		box-shadow: var(--shadow-sm);
	}

	.surface--flush {
		overflow: hidden; /* lets imagery bleed to the rounded edge */
	}

	/* --- Icons ------------------------------------------------------------ */
	.icon {
		flex: none;
		display: block;
	}
}

/* ==== components/masthead.css ==== */
/* Floating masthead: bar, links, CTA, area picker, narrow-screen panel. */

@layer components {
	/* ======================================================================
	   MASTHEAD
	   ======================================================================
	   Floats over the content rather than sitting above it, so imagery and
	   colour pass beneath the material as the page scrolls. The blur is the
	   effect, not a finish. */

	.masthead {
		--masthead-width: 1080px;

		position: fixed;
		inset-block-start: var(--space-sm);
		inset-inline: 0;
		z-index: 60;
		padding-inline: var(--gutter);
		/* The bar is narrower than this wrapper; without this the empty margin
		   either side would swallow clicks meant for the page. */
		pointer-events: none;
	}

	.masthead__bar {
		pointer-events: auto;
		position: relative;
		display: flex;
		align-items: center;
		gap: var(--space-2xs);
		max-width: var(--masthead-width);
		min-height: var(--nav-h);
		margin-inline: auto;
		/* Left padding reserves the floating logo's width plus a gap, so nothing
		   in the bar can ever slide underneath it. */
		padding: var(--space-3xs) var(--space-3xs) var(--space-3xs)
			calc(var(--nav-brand-size) + var(--space-md));
		transition: background-color var(--t-nav) var(--ease-nav);
		border-radius: var(--nav-brand-radius);
		corner-shape: squircle;
		background: rgb(255 255 255 / 88%);
		box-shadow: var(--shadow-nav);
	}

	@supports (backdrop-filter: blur(4px)) {
		.masthead__bar {
			background: rgb(255 255 255 / 68%);
			backdrop-filter: blur(22px) saturate(180%);
		}
	}

	/* --- Settling on scroll -----------------------------------------------
	   --t-nav and --ease-nav in tokens.css describe a bar that settles as the
	   page moves under it; this is that behaviour.

	   Driven by the scroll position itself rather than by a listener, so it
	   cannot cost a frame and cannot get out of step with the page: no scroll
	   handler, no class toggling, no layout read. Over the first 120px of the
	   document the material thickens and the shadow deepens, so the bar reads
	   as lifting off content that is now passing beneath it — and it reverses
	   exactly as smoothly on the way back up.

   Only the material moves — colour and shadow, nothing that touches layout.
	   Shrinking the logo was tried and rejected: the bar reserves its width in
	   padding, so a smaller logo just left a hole, and animating the width
	   instead relaid the masthead on every scroll frame, which is exactly the
	   cost this approach exists to avoid.

	   Unsupported browsers get the resting state, which is the design as it
	   stood before this block existed. */
	@supports (animation-timeline: scroll()) {
		@media (prefers-reduced-motion: no-preference) {
			.masthead__bar {
				animation: masthead-settle linear both;
				animation-timeline: scroll(root block);
				animation-range: 0 120px;
			}
		}
	}

	@keyframes masthead-settle {
		to {
			background-color: rgb(255 255 255 / 90%);
			box-shadow:
				0 1px 2px rgb(20 34 51 / 6%),
				0 10px 30px rgb(20 34 51 / 14%);
		}
	}

	/* --- Floating logo ----------------------------------------------------
	   Positioned over the bar rather than laid out inside it, so the bar's
	   height places no limit on how big the logo can be. It is centred on the
	   bar and overhangs equally above and below; when --nav-brand-size is
	   larger than the bar height, that overhang simply grows.

	   Aligned to the bar's own left edge with the same calculation the bar uses
	   to centre itself, so the logo and the bar share one left edge at every
	   width. */
	.masthead__brand {
		/* It contains only images, so an underline draws nothing today — but it
		   would the moment any text is added beside them. */
		text-decoration: none;
		position: absolute;
		z-index: 2;
		pointer-events: auto;
		display: block;
		inline-size: var(--nav-brand-size);
		block-size: var(--nav-brand-size);
		inset-inline-start: max(var(--gutter), (100% - var(--masthead-width)) / 2);
		inset-block-start: calc((var(--nav-h) - var(--nav-brand-size)) / 2);
		border-radius: var(--nav-brand-radius);
		corner-shape: squircle;
		box-shadow: var(--nav-badge-shadow);
		overflow: hidden;
	}

	.masthead__badge {
		position: absolute;
		inset: 0;
		display: block;
		inline-size: 100%;
		block-size: 100%;
		aspect-ratio: 1;
		border-radius: var(--nav-brand-radius);
		/* The icon and bar share the same continuous corner treatment. */
		corner-shape: squircle;
		object-fit: cover;
		transition:
			opacity var(--t-slow) var(--ease-in-out),
			transform var(--t-slow) var(--ease-in-out);
	}

	.masthead__badge--white {
		opacity: 0;
		transform: scale(1.04) rotate(2deg);
	}

	.masthead__brand:hover .masthead__badge--blue,
	.masthead__brand:focus-visible .masthead__badge--blue {
		opacity: 0;
		transform: scale(0.96) rotate(-2deg);
	}

	.masthead__brand:hover .masthead__badge--white,
	.masthead__brand:focus-visible .masthead__badge--white {
		opacity: 1;
		transform: none;
	}

	/* On a phone the bar was 56 tall and the badge 58 — near enough the same
	   height that the badge read as a panel butted onto the bar rather than a
	   tile resting on it, and the whole masthead looked heavier than it is.
	   The bar comes down to 48 and the badge goes up to 62, so it now stands
	   7 points proud top and bottom and the relationship is legible.

	   48 is not arbitrary: it is exactly what the bar can be without squeezing
	   its contents, being the 40-point controls plus the 4-point padding either
	   side. The controls keep a 48-point hit area regardless — see below. */
	/* --- Breathing ---------------------------------------------------------
	   The badge lifts and settles on a slow cycle. It is the only thing on the
	   page that moves without being asked to, so it is kept under a 3% swing
	   and given a long, even curve — enough to catch the eye as alive, not
	   enough to pull attention off whatever the visitor is reading.

	   It scales the anchor rather than the images inside it, because those two
	   already carry the hover swap; putting both on one element would mean the
	   hover fighting the loop for the same property. */
	@media (prefers-reduced-motion: no-preference) {
		.masthead__brand {
			animation: brand-breathe 3.6s var(--ease-in-out) infinite;
		}

		/* A press or a pointer takes precedence: the badge holds still while it
		   is being interacted with, so the swap underneath reads cleanly. */
		.masthead__brand:hover,
		.masthead__brand:focus-visible,
		.masthead__brand:active {
			animation-play-state: paused;
		}
	}

	@keyframes brand-breathe {
		0%,
		100% {
			transform: scale(1);
		}
		50% {
			transform: scale(1.03);
		}
	}


	.masthead__nav {
		display: flex;
		align-items: center;
		gap: var(--space-3xs);
		margin-inline-end: auto;
	}

	/* Navigation stays typographic: hover is a small colour and scale change,
	   without adding a background shape behind the label. */
	.masthead__link {
		display: inline-flex;
		align-items: center;
		height: 34px;
		padding-inline: var(--space-xs);
		border-radius: var(--r-pill);
		color: var(--text);
		font-size: var(--fs-sm);
		font-weight: 700;
		text-decoration: none;
		white-space: nowrap;
		transition:
			color var(--t) var(--ease-out),
			transform var(--t) var(--ease-out);
	}

	.masthead__link:hover {
		color: var(--accent-hover);
		transform: scale(1.06);
	}

	.masthead__link[aria-current="page"] {
		background: var(--accent-soft);
		color: var(--accent-hover);
	}

	.masthead__tools {
		display: flex;
		align-items: center;
		gap: var(--space-3xs);
	}

	.masthead__cta {
		display: inline-flex;
		align-items: center;
		gap: var(--space-3xs);
		height: 36px;
		padding-inline: var(--space-xs);
		border-radius: var(--nav-brand-radius);
		background: transparent;
		color: var(--accent);
		font-size: var(--fs-sm);
		font-weight: 700;
		text-decoration: none; /* it is a link now, so that it works without JS */
		white-space: nowrap;
		transition:
			color var(--t-fast) var(--ease-out),
			transform var(--t-fast) var(--ease-out);
	}

	.masthead__cta:hover {
		color: var(--accent-hover);
		transform: translateY(-1px);
	}

	.masthead__cta:active {
		transform: translateY(0) scale(0.98);
	}

	/* Below desktop the bar hands this to the floating button — see
	   components/app-fab.css. Two reasons, and the second is the real one:
	   the bar is 48 points tall on a phone and a labelled action crowds the
	   badge and the menu into it; and an app invitation belongs where a thumb
	   already is, not in the one corner of a phone a thumb cannot reach.

	   It is the same control either way, so nothing is lost by the swap. */
	@media (width < 64rem) {
		.masthead__cta {
			display: none;
		}
	}

	.masthead__burger {
		display: none;
		align-items: center;
		justify-content: center;
		width: 44px;
		height: 44px;
		border-radius: var(--r-pill);
		color: var(--text-strong);
		transition:
			background-color var(--t-fast) var(--ease-out),
			transform var(--t-fast) var(--ease-out);
	}

	.masthead__burger:hover {
		background: rgb(20 34 51 / 5%);
	}

	.masthead__burger:active {
		transform: scale(0.92);
	}

	/* The bar is slimmer than the touch-target floor on a phone, so the target
	   is grown past the drawn control rather than the control being grown past
	   what the bar can hold. Nothing is painted; it only collects the press. */
	.masthead__burger,
	.masthead__cta {
		position: relative;
	}

	.masthead__burger::after,
	.masthead__cta::after {
		content: "";
		position: absolute;
		/* 40 drawn + 4 above + 4 below = a 48-point target on a 48-point bar. */
		inset-block: -4px;
		inset-inline: -2px;
	}

	/* The bars become a cross while the panel is open, so the control always
	   describes what pressing it will do next. Two marks stacked in one box and
	   turned through each other — a crossfade alone reads as a glitch, the
	   quarter turn makes it one object changing its mind. */
	.masthead__burger__icons {
		display: grid;
		place-items: center;
		inline-size: 22px;
		block-size: 22px;
	}

	.masthead__burger__icons .icon {
		grid-area: 1 / 1;
		transition:
			opacity var(--t-fast) var(--ease-out),
			transform var(--t) var(--ease-spring);
	}

	.masthead__burger .icon--close {
		opacity: 0;
		transform: rotate(-90deg) scale(0.7);
	}

	.masthead__burger[aria-expanded="true"] .icon--menu {
		opacity: 0;
		transform: rotate(90deg) scale(0.7);
	}

	.masthead__burger[aria-expanded="true"] .icon--close {
		opacity: 1;
		transform: none;
	}

	/* Where a finger is the pointer there is no hover to reveal anything, so
	   the two bar controls take the full 44px floor rather than the compact
	   desktop height. */
	@media (pointer: coarse) {
		.masthead__cta {
			height: 40px;
			padding-inline: var(--space-sm);
		}
	}

	/* Both controls come down to 40 so the bar can be 48 — 40 drawn plus the
	   4-point padding either side. This has to sit after the 44-point base
	   rules above: same specificity, so it is source order doing the work. */
	@media (width < 34rem) {
		.masthead__burger {
			width: 40px;
			height: 40px;
		}
	}

	/* --- Area switcher ---------------------------------------------------- */

	.areapick {
		position: relative;
	}

	.areapick__trigger {
		display: inline-flex;
		align-items: center;
		gap: var(--space-3xs);
		height: 40px;
		padding-inline: var(--space-xs) var(--space-2xs);
		border-radius: var(--r-pill);
		background: color-mix(in srgb, var(--accent) 8%, transparent);
		color: var(--text);
		font-size: var(--fs-sm);
		font-weight: 700;
		white-space: nowrap;
		transition: background-color var(--t-fast) var(--ease-out);
	}

	.areapick__trigger:hover {
		background: color-mix(in srgb, var(--accent) 14%, transparent);
	}

	.areapick__trigger .icon:first-child {
		color: var(--accent);
	}

	.areapick__label {
		max-inline-size: 9ch;
		overflow: hidden;
		text-overflow: ellipsis;
	}

	.areapick__caret {
		color: var(--text-muted);
		transition: transform var(--t) var(--ease-out);
	}

	.areapick__trigger[aria-expanded="true"] .areapick__caret {
		transform: rotate(180deg);
	}

	.areapick__menu {
		position: absolute;
		inset-block-start: calc(100% + var(--space-2xs));
		inset-inline-end: 0;
		z-index: 2;
		min-width: 232px;
		padding: var(--space-2xs);
		border-radius: var(--r-lg);
		background: var(--surface);
		box-shadow: var(--shadow-lg);
	}

	/* Springs open from slightly small and low, and retreats the same way it
	   came — see the panel below for why display is in the transition list. */
	.areapick__menu {
		transform-origin: top right;
		transition:
			opacity var(--t) var(--ease-out),
			transform var(--t) var(--ease-spring),
			display var(--t) allow-discrete;
	}

	.areapick__menu[hidden] {
		display: none;
		opacity: 0;
		transform: translateY(-6px) scale(0.97);
		transition:
			opacity var(--t-fast) var(--ease-out),
			transform var(--t-fast) var(--ease-out),
			display var(--t-fast) allow-discrete;
	}

	@starting-style {
		.areapick__menu:not([hidden]) {
			opacity: 0;
			transform: translateY(-6px) scale(0.97);
		}
	}

	.areapick__title,
	.areapick__note {
		padding: var(--space-2xs) var(--space-xs);
		font-size: var(--fs-xs);
		color: var(--text-muted);
	}

	.areapick__option {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: var(--space-sm);
		padding: var(--space-2xs) var(--space-xs);
		border-radius: var(--r-sm);
		color: var(--text-strong);
		font-weight: 700;
		text-decoration: none;
		transition: background-color var(--t-fast) var(--ease-out);
	}

	.areapick__option:hover {
		background: var(--surface-sunken);
		color: var(--text-strong);
	}

	.areapick__option[aria-current="true"] {
		color: var(--accent-hover);
	}

	.areapick__option[aria-current="true"] .icon {
		color: var(--accent);
	}

	/* --- Narrow-screen panel ---------------------------------------------- */

	.masthead__panel {
		pointer-events: auto;
		max-width: var(--masthead-width);
		margin: var(--space-2xs) auto 0;
		padding: var(--space-2xs);
		border-radius: var(--r-xl);
		background: rgb(255 255 255 / 94%);
		box-shadow: var(--shadow-lg);
		/* A phone held sideways has barely 380px of height. Without this the
		   last two destinations sit below the fold with no way to reach them. */
		max-block-size: calc(100svh - var(--nav-h) - var(--space-2xl));
		overflow-y: auto;
		overscroll-behavior: contain;
		-webkit-overflow-scrolling: touch;
	}

	@supports (backdrop-filter: blur(4px)) {
		.masthead__panel {
			backdrop-filter: blur(22px) saturate(180%);
		}
	}

	/* Opening AND closing.
	 *
	 * The panel used to spring in and then simply vanish, because [hidden] is
	 * display:none and an animation cannot play on a box that is not there.
	 * `display` and `overlay` are animatable with allow-discrete: the browser
	 * holds the box on screen for the length of the transition, then removes it.
	 * @starting-style supplies the state to come FROM on the way in.
	 *
	 * Where the pair is unsupported the panel appears and disappears instantly,
	 * which is exactly what it did before. */
	.masthead__panel {
		transform-origin: top center;
		transition:
			opacity var(--t) var(--ease-out),
			transform var(--t) var(--ease-spring),
			display var(--t) allow-discrete;
	}

	.masthead__panel[hidden] {
		display: none;
		opacity: 0;
		/* Leaves along its own shorter path — an exit that mirrors the entrance
		   feels slow, because the visitor has already decided. */
		transform: translateY(-8px) scale(0.98);
		transition:
			opacity var(--t-fast) var(--ease-out),
			transform var(--t-fast) var(--ease-out),
			display var(--t-fast) allow-discrete;
	}

	@starting-style {
		.masthead__panel:not([hidden]) {
			opacity: 0;
			transform: translateY(-10px) scale(0.97);
		}
	}

	.masthead__panel-link {
		display: flex;
		align-items: center;
		justify-content: space-between;
		padding: var(--space-xs) var(--space-sm);
		border-radius: var(--r-md);
		color: var(--text-strong);
		font-size: var(--fs-lead);
		font-weight: 700;
		text-decoration: none;
		transition:
			color var(--t) var(--ease-out),
			transform var(--t) var(--ease-out);
	}

	.masthead__panel-link .icon {
		color: var(--text-muted);
	}

	.masthead__panel-link:hover {
		color: var(--accent-hover);
		transform: scale(1.02);
	}

	/* A finger gets no hover, so the press itself has to answer. The arrow
	   leans towards where the link is about to take you. */
	.masthead__panel-link:active {
		background: var(--surface-sunken);
		transform: scale(0.99);
	}

	.masthead__panel-link .icon {
		transition: transform var(--t) var(--ease-out);
	}

	.masthead__panel-link:hover .icon,
	.masthead__panel-link:active .icon {
		transform: translateX(3px);
	}

	.masthead__panel-link[aria-current="page"] {
		background: var(--accent-soft);
		color: var(--accent-hover);
	}

	/* Below this the links do not fit beside the tools without crowding, so they
	   move into the panel. Nothing is removed — same destinations.

	   48rem, not 60rem. At 60 a tablet held upright — 810 or 834 points, which
	   is most of them — was given a hamburger with 400 points of empty bar
	   beside it. Measured at the tightest width this rule now allows: the logo
	   reserves 96, the five labels and their gaps come to 370 and the app link
	   to 97, which leaves better than 100 points spare inside 768. */
	@media (width < 48rem) {
		.masthead__nav {
			display: none;
		}

		/* The area control moves into the panel rather than shrinking to an
		   unlabelled icon. A pin and a chevron with no text announce nothing to
		   a screen reader and tell a sighted visitor nothing either. */
		.areapick {
			display: none;
		}

		.masthead__burger {
			display: inline-flex;
		}

		/* The links carried `margin-inline-end: auto` and were what held the
		   tools against the right edge. With them hidden the tools fell back to
		   the left, leaving the bar's whole right half empty — the logo cannot
		   do the job in their place because it is positioned, not laid out. */
		.masthead__tools {
			margin-inline-start: auto;
		}
	}

	.masthead__panel-group {
		padding: var(--space-sm) var(--space-sm) var(--space-2xs);
	}

	.masthead__panel-heading {
		font-size: var(--fs-xs);
		font-weight: 700;
		letter-spacing: var(--ls-label);
		color: var(--text-muted);
	}

	.masthead__panel-note {
		padding: 0 var(--space-sm) var(--space-xs);
		font-size: var(--fs-xs);
		color: var(--text-muted);
	}

	.masthead__panel-area {
		display: flex;
		align-items: center;
		justify-content: space-between;
		padding: var(--space-2xs) var(--space-sm);
		border-radius: var(--r-md);
		color: var(--text-strong);
		font-weight: 700;
		text-decoration: none;
	}

	.masthead__panel-area:hover {
		background: var(--surface-sunken);
		color: var(--text-strong);
	}

	.masthead__panel-area[aria-current="true"] {
		color: var(--accent-hover);
	}

	.masthead__panel-area[aria-current="true"] .icon {
		color: var(--accent);
	}

	.masthead__panel-rule {
		height: 1px;
		margin: var(--space-2xs) var(--space-sm);
		background: var(--line);
	}
}

/* ==== components/footer.css ==== */
/* Site footer.
 *
 * Four link columns beside the brand, and shorter than the three it replaced.
 * The height came out of the padding, the wordmark and the gaps rather than out
 * of the content: a tall footer made of air reads as an ending nobody wrote,
 * while a dense one reads as a directory.
 */

@layer components {
	/* ======================================================================
	   FOOTER
	   ====================================================================== */

	.footer {
		margin-block-start: var(--section-y);
		padding-block: var(--space-2xl) var(--space-md);
		background: var(--surface-inverse);
		color: var(--footer-text);
	}

	.footer__top {
		display: grid;
		gap: var(--space-xl);
		/* The brand column is sized to its wordmark and one sentence; everything
		   left over goes to the links, which are what people came down here for. */
		grid-template-columns: minmax(0, 1fr) minmax(0, 2.4fr);
	}

	@media (width < 64rem) {
		.footer__top {
			grid-template-columns: minmax(0, 1fr);
			gap: var(--space-lg);
		}
	}

	.footer__brand {
		display: grid;
		gap: var(--space-xs);
		align-content: start;
	}

	.footer__logo {
		width: auto;
		height: 42px;
	}

	.footer__line {
		max-width: 34ch;
		font-size: var(--fs-sm);
		line-height: var(--lh-snug);
		color: var(--footer-text);
	}

	.footer__cta {
		justify-self: start;
	}

	.footer__links {
		display: grid;
		gap: var(--space-lg) var(--space-md);
		/* Four columns where there is room, two on a phone — never one. A single
		   column of eighteen links is most of the height this footer just lost. */
		grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
	}

	.footer__group {
		display: grid;
		gap: var(--space-3xs);
		align-content: start;
	}

	.footer__heading {
		margin-block-end: var(--space-3xs);
		font-size: var(--fs-xs);
		font-weight: 700;
		letter-spacing: var(--ls-label);
		text-transform: uppercase;
		color: var(--text-inverse);
	}

	/* :not(.button) matters more than it looks. Without it this rule outweighs
	   .button--onDark — one class against two — and repaints the white pill's
	   navy label in pale footer blue, which is unreadable on white. */
	.footer a:not(.button) {
		display: inline-flex;
		align-items: center;
		gap: var(--space-3xs);
		/* WCAG 2.2 AA, 2.5.8 Target Size (Minimum). At this font size the line
		   box alone is 22px — two short of the floor. Costs nothing visually
		   and it is measured, not guessed: see the a11y probe in BUILD-LOG. */
		min-height: 24px;

		color: var(--footer-text);
		font-size: var(--fs-sm);
		text-decoration: none;
		width: fit-content;
		transition: color var(--t-fast) var(--ease-out);
	}

	.footer a:not(.button):hover {
		color: var(--text-inverse);
		text-decoration: underline;
	}

	.footer__social .icon {
		flex: 0 0 auto;
		color: var(--text-inverse);
	}

	.footer__socials {
		display: flex;
		flex-wrap: wrap;
		gap: var(--space-3xs);
	}

	.footer a.footer__social {
		justify-content: center;
		width: 36px;
		height: 36px;
		border-radius: 50%;
		transition:
			background-color var(--t) var(--ease-out),
			transform var(--t) var(--ease-out);
	}

	.footer a.footer__social:hover {
		background: rgb(255 255 255 / 10%);
		text-decoration: none;
		transform: scale(1.08);
	}

	.footer__note {
		max-width: 26ch;
		font-size: var(--fs-sm);
		color: var(--footer-muted);
	}

	.footer__base {
		display: flex;
		flex-wrap: wrap;
		justify-content: space-between;
		align-items: center;
		gap: var(--space-2xs) var(--space-md);
		margin-block-start: var(--space-xl);
		padding-block-start: var(--space-sm);
		border-block-start: 1px solid rgb(255 255 255 / 14%);
		font-size: var(--fs-sm);
		color: var(--footer-muted);
	}

	/* Only present while the site is running on sample data. Legible, but not
	   dressed up as a warning — it is a statement of fact. */
	.footer__sample {
		padding: 0.15rem var(--space-2xs);
		border-radius: var(--r-xs);
		background: rgb(255 255 255 / 12%);
		color: var(--text-inverse);
		font-size: var(--fs-xs);
		font-weight: 700;
		letter-spacing: var(--ls-label);
	}

	/* On a dark surface, focus rings must not be the same blue as the page. */
	.footer :focus-visible {
		outline-color: var(--text-inverse);
	}
}

/* ==== components/app-sheet.css ==== */
/* The app invitation sheet.
 *
 * One component, two postures. On a phone it is a sheet that rises from the
 * bottom edge and can be flicked away, which is what a phone user expects. On a
 * desktop it is a centred panel that scales in, because a bottom sheet on a
 * 27-inch monitor is a phone interface wearing the wrong clothes.
 *
 * Built on <dialog>, so the browser supplies focus trapping, Escape and
 * background inertness. The CSS below only has to handle appearance and motion.
 */

@layer components {
	.appsheet {
		/* <dialog> ships with a border, padding, a white background and centring
		   we do not want. It becomes a full-viewport flex container instead, and
		   the panel inside is the thing that looks like a sheet. */
		width: 100%;
		max-width: none;
		height: 100%;
		max-height: none;
		margin: 0;
		padding: 0;
		border: 0;
		background: none;
		overflow: hidden;
	}

	.appsheet[open] {
		display: flex;
		align-items: flex-end;
		justify-content: center;
	}

	.appsheet::backdrop {
		background: rgb(10 26 40 / 45%);
		opacity: 0;
		transition: opacity var(--t) var(--ease-out);
	}

	.appsheet.is-open::backdrop {
		opacity: 1;
	}

	@supports (backdrop-filter: blur(4px)) {
		.appsheet::backdrop {
			backdrop-filter: blur(3px);
		}
	}

	/* The panel receives initial focus so the title is announced first. It is a
	   container, not a control, so it must not draw a ring. Every real control
	   inside it keeps its own. */
	.appsheet__panel:focus {
		outline: none;
	}

	.appsheet__panel {
		position: relative;
		width: min(30rem, 100%);
		padding: var(--space-lg) var(--space-lg)
			max(var(--space-lg), env(safe-area-inset-bottom));
		border-radius: var(--r-2xl) var(--r-2xl) 0 0;
		background: var(--surface);
		box-shadow: var(--shadow-lg);
		text-align: center;

		/* Starts off-screen; .is-open brings it up. */
		transform: translateY(100%);
		transition: transform var(--t-sheet) var(--ease-out);
	}

	.appsheet.is-open .appsheet__panel {
		transform: translateY(0);
	}

	/* While a finger is on the sheet it must track the finger exactly — any
	   easing here reads as lag. */
	.appsheet.is-dragging .appsheet__panel {
		transition: none;
		transform: translateY(var(--drag, 0px));
	}

	.appsheet__grab {
		display: block;
		width: 40px;
		height: 4px;
		margin: 0 auto var(--space-md);
		border-radius: var(--r-pill);
		background: var(--gray-300);
	}

	.appsheet__close {
		position: absolute;
		inset-block-start: var(--space-sm);
		inset-inline-end: var(--space-sm);
		display: flex;
		align-items: center;
		justify-content: center;
		width: 40px;
		height: 40px;
		border-radius: var(--r-pill);
		color: var(--text-muted);
		transition:
			background-color var(--t-fast) var(--ease-out),
			color var(--t-fast) var(--ease-out);
	}

	.appsheet__close:hover {
		background: var(--surface-sunken);
		color: var(--text-strong);
	}

	.appsheet__title {
		font-size: var(--fs-h3);
		text-wrap: balance;
	}

	.appsheet__lead {
		max-width: 34ch;
		margin: var(--space-2xs) auto 0;
		color: var(--text-muted);
	}

	.appsheet .storebtns {
		margin-block-start: var(--space-lg);
	}

	/* The app icon is meaningful on any device, unlike the QR it replaced, so
	   it shows at every width — above the title, where it identifies what is
	   being offered before a word is read. */
	.appsheet__scan {
		display: grid;
		justify-items: center;
		margin-block-end: var(--space-md);
	}

	.appsheet__scan .appicon {
		border-radius: 18px;
	}

	/* Desktop: centred panel, and the QR earns its place because installing on
	   the machine you are reading this on is not possible. */
	@media (width >= 40rem) {
		.appsheet[open] {
			align-items: center;
		}

		.appsheet__panel {
			padding: var(--space-xl);
			border-radius: var(--r-2xl);
			transform: translateY(10px) scale(0.96);
			opacity: 0;
			transition:
				transform var(--t) var(--ease-out),
				opacity var(--t) var(--ease-out);
		}

		.appsheet.is-open .appsheet__panel {
			transform: none;
			opacity: 1;
		}

		.appsheet__grab {
			display: none;
		}


	}

	/* Nothing slides or scales; it is simply there. */
	@media (prefers-reduced-motion: reduce) {
		.appsheet__panel,
		.appsheet.is-open .appsheet__panel {
			transform: none;
			opacity: 1;
		}
	}
}

/* ==== components/app-fab.css ==== */
/* The floating app button.
 *
 * Only exists below desktop width, where the masthead gave up its "myMKB" link
 * to keep the bar short. Same control, moved to where a thumb already rests.
 *
 * Positioned with two custom properties rather than with inset directly, so the
 * JavaScript that drags it has one thing to write and the CSS keeps ownership
 * of the safe-area maths and the clamping. With scripting off they hold their
 * declared values and the button simply sits bottom-right.
 */

/* Registered so they compute to a real length. A plain custom property hands
   getComputedStyle() back the literal "calc(...)" it was written as, which
   parseFloat reads as NaN — the drag code needs both of these in pixels to
   clamp against, and was falling back to a guessed 80. */
@property --fab-inset-block {
	syntax: "<length>";
	inherits: false;
	initial-value: 24px;
}

@property --fab-inset-top {
	syntax: "<length>";
	inherits: false;
	initial-value: 80px;
}

@layer components {
	.appfab {
		--fab-size: 56px;
		/* Distance from the viewport edges. The bottom keeps clear of the home
		   indicator; the side is the page gutter, so the button hangs off the
		   same line the content does. */
		--fab-inset-inline: var(--gutter);
		--fab-inset-block: max(var(--space-md), env(safe-area-inset-bottom));
		/* How far up it may be dragged. The masthead floats, so the limit is
		   its real occupied height — bar offset plus half the bar and half the
		   badge, the same derivation layout.css uses — and a gap under it.
		   Without this the button could be parked behind the navigation. */
		--fab-inset-top: calc(
			var(--space-sm) + (var(--nav-h) + var(--nav-brand-size)) / 2 + var(--space-2xs)
		);

		position: fixed;
		inset-block-end: var(--fab-inset-block);
		inset-inline-end: var(--fab-inset-inline);
		z-index: 65; /* over the page, under the offline notice at 70 */

		display: none; /* the width query below turns it on */
		place-items: center;
		inline-size: var(--fab-size);
		block-size: var(--fab-size);
		border-radius: var(--nav-brand-radius);
		corner-shape: squircle;
		background: var(--accent);
		color: var(--text-on-accent);
		text-decoration: none;
		box-shadow:
			0 2px 6px rgb(20 34 51 / 16%),
			0 10px 24px rgb(20 34 51 / 20%);
		/* A drag has to move the button, not scroll the page underneath it. */
		touch-action: none;
		/* And not start the browser's own link-drag, or select anything. */
		-webkit-user-drag: none;
		user-select: none;
		transition:
			background-color var(--t-fast) var(--ease-out),
			transform var(--t) var(--ease-out),
			box-shadow var(--t) var(--ease-out);
	}

	/* Desktop keeps the link in the bar, where there is room to label it.
	   Below that the bar is too short to carry it and this takes over. */
	@media (width < 64rem) {
		.appfab {
			display: grid;
		}
	}

	/* On /get-app the button would offer the page the visitor is already on. */
	body[data-route="get-app"] .appfab {
		display: none;
	}

	.appfab:hover {
		background: var(--accent-hover);
		color: var(--text-on-accent);
	}

	.appfab:active {
		transform: scale(0.94);
		transition-duration: 60ms;
	}

	/* --- Being dragged ----------------------------------------------------
	   The button lifts towards the finger and the shadow spreads to match, so
	   it reads as picked up rather than smeared. No transition on transform
	   while dragging: it has to track the finger exactly, and any easing at
	   all is felt as lag. */
	.appfab.is-dragging {
		transition: none;
		transform: scale(1.08);
		box-shadow:
			0 4px 10px rgb(20 34 51 / 18%),
			0 18px 40px rgb(20 34 51 / 26%);
		cursor: grabbing;
	}

	/* Let go, and it travels to the edge it was nearest. Spring, because the
	   button is an object arriving somewhere rather than a value changing. */
	.appfab.is-settling {
		transition:
			inset-block-end var(--t-slow) var(--ease-spring),
			inset-inline-start var(--t-slow) var(--ease-spring),
			inset-inline-end var(--t-slow) var(--ease-spring),
			transform var(--t-fast) var(--ease-out),
			box-shadow var(--t) var(--ease-out);
	}

	/* Parked on the left, it leans out of the way by the same amount it would
	   on the right — one rule for both, because the JS writes the side. */
	.appfab[data-side="start"] {
		inset-inline: var(--fab-inset-inline) auto;
	}

	.appfab[data-side="end"] {
		inset-inline: auto var(--fab-inset-inline);
	}

	@media (prefers-reduced-motion: reduce) {
		.appfab.is-settling {
			transition: none;
		}
	}
}

/* ==== components/banner.css ==== */
/* Campaign banners — height bands on the ERP's four-column span grid.
 *
 * Same-height artwork shares a band (1, then 2, then 3/4/6). Inside a band,
 * `col_span` places tiles on a four-unit frame so rows stay flush. The column
 * count doubles with the viewport so phone-authored proportions hold on desktop.
 */

@layer components {
	.bannergrid {
		display: grid;
		gap: var(--space-sm);
		margin: 0;
		padding: 0;
	}

	.bannergrid__band {
		--banner-cols: 4;
		--banner-gap: var(--space-2xs);

		display: grid;
		grid-template-columns: repeat(var(--banner-cols), minmax(0, 1fr));
		grid-auto-flow: dense;
		gap: var(--banner-gap);
	}

	/* Double (not re-pick) so packed frames of four still sit flush: at 8,
	   two frames side by side; at 12, three. */
	@media (width >= 48rem) {
		.bannergrid__band {
			--banner-cols: 8;
		}
	}

	@media (width >= 100rem) {
		.bannergrid__band {
			--banner-cols: 12;
		}
	}

	.bannergrid__cell {
		grid-column: span var(--banner-col, 4);
		/* The span pair IS the ratio. */
		aspect-ratio: var(--banner-col, 4) / var(--banner-row, 2);
		container-type: inline-size;
	}

	/* On narrow viewports, full-width strips stay full width; smaller tiles
	   still share the four-column band. */
	@media (width < 30rem) {
		.bannergrid__band {
			--banner-cols: 4;
		}
	}

	/* --- The banner itself ------------------------------------------------ */

	.banner {
		display: grid;
		place-items: center;
		inline-size: 100%;
		block-size: 100%;
		overflow: hidden;
		border-radius: var(--r-lg);
		corner-shape: squircle;
		background: linear-gradient(145deg, var(--accent), var(--mkb-navy));
		color: var(--text-inverse);
		text-decoration: none;
		box-shadow: var(--shadow-sm);
	}

	.banner__image {
		inline-size: 100%;
		block-size: 100%;
		object-fit: cover;
	}

	.banner__title {
		padding: var(--space-2xs);
		font-size: clamp(0.8125rem, 0.7rem + 0.6cqi, var(--fs-h3));
		font-weight: 700;
		line-height: var(--lh-snug);
		letter-spacing: var(--ls-heading);
		text-align: center;
		text-wrap: balance;
	}

	/* --- On a coloured host ------------------------------------------------ */
	.bannergrid--onDark .banner {
		background: rgb(255 255 255 / 16%);
		box-shadow: var(--shadow-md);
	}

	.bannergrid--onDark .banner[data-tint="1"] {
		background: rgb(10 61 98 / 45%);
	}

	.bannergrid--onDark .banner[data-tint="2"] {
		background: rgb(255 199 28 / 22%);
	}
}

/* ==== components/connection.css ==== */
/* Connection status pill.
 *
 * Bottom centre, so it never covers the masthead or the thing being read, and
 * so a thumb resting at the bottom of a phone does not sit on top of it. The
 * material matches the masthead: this is chrome, not content. */

@layer components {
	.netstate {
		position: fixed;
		inset-block-end: max(var(--space-sm), env(safe-area-inset-bottom));
		inset-inline: 0;
		z-index: 70;
		display: grid;
		justify-items: center;
		padding-inline: var(--gutter);
		/* The strip spans the viewport so the pill can centre in it; only the
		   pill itself should ever intercept a tap. */
		pointer-events: none;
	}

	.netstate[hidden] {
		display: none;
	}

	.netstate__pill {
		display: inline-flex;
		align-items: center;
		gap: var(--space-2xs);
		max-width: 100%;
		padding: var(--space-2xs) var(--space-sm);
		border-radius: var(--r-pill);
		background: var(--surface-inverse);
		color: var(--text-inverse);
		font-size: var(--fs-sm);
		font-weight: 700;
		box-shadow: var(--shadow-lg);
		animation: netstate-in var(--t) var(--ease-spring) both;
	}

	.netstate__dot {
		flex: none;
		inline-size: 8px;
		block-size: 8px;
		border-radius: 50%;
		background: var(--offer);
	}

	/* Reconnected: the same pill, held briefly, in the positive colour. */
	.netstate.is-online .netstate__dot {
		background: var(--positive);
	}

	@keyframes netstate-in {
		from {
			opacity: 0;
			transform: translateY(12px) scale(0.94);
		}
		to {
			opacity: 1;
			transform: none;
		}
	}

	@media (prefers-reduced-motion: reduce) {
		.netstate__pill {
			animation: none;
		}
	}

	/* =====================================================================
	   Busy
	   =====================================================================
	   Set on a results region while a filter change is in flight. On a good
	   connection the page has already reloaded before this is visible; on a
	   bad one it is the difference between "working" and "broken".

	   Deliberately no spinner. The results are still on screen and still
	   readable — dimming them slightly says "these are about to change"
	   without hiding what is there. */

	[aria-busy="true"] {
		opacity: 0.55;
		transition: opacity var(--t) var(--ease-out) 120ms;
		/* A stale result must not be clickable: the answer is already changing. */
		pointer-events: none;
	}
}

/* ==== components/variant-switch.css ==== */
/* Review-only variant switcher.
 *
 * Visible only while a ?<section>= override is in the URL, so ordinary visitors
 * never see it. Delete this file, partials/variant-switcher.php and
 * app/variants.php once every section has been finalised.
 */

@layer components {
	/* Review-only. Appears with ?categories= and is deleted along with the
	   unused variants once a choice is made. */
	.variant-switch {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		gap: var(--space-2xs);
		margin-block-end: var(--space-md);
		padding: var(--space-2xs) var(--space-sm);
		border-radius: var(--r-pill);
		background: var(--surface-sunken);
		font-size: var(--fs-xs);
		color: var(--text-muted);
		width: fit-content;
	}

	.variant-switch a {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		/* Both axes: "grid" is only 23px wide at this size. */
		min-width: 24px;
		min-height: 24px;
		font-weight: 700;
		text-decoration: none;
	}

	.variant-switch a[aria-current="true"] {
		text-decoration: underline;
		text-underline-offset: 0.25em;
	}

}

/* ==== sections/hero.css ==== */
/* Home hero — the first screen.
 *
 * Two ideas carry it. The headline is set at display size and tight, so the
 * brand speaks before anything else does. And on a wide screen the photograph
 * runs off the right edge of the viewport rather than sitting politely inside
 * the content column — the one editorial gesture on the page, and the reason it
 * does not read as a template.
 *
 * Everything above the fold is real: the delivery area shown is the one the
 * catalog will actually be read with, and the link beside it switches it.
 */

@layer components {
	.hero {
		/* Distance from the viewport edge to the centred content column. Used to
		   align the copy with every other section while the media ignores it. */
		--hero-edge: max(var(--gutter), (100% - var(--content)) / 2);

		display: grid;
		gap: var(--space-xl);
		/* Top spacing comes from the masthead clearance on <main>; only the
		   bottom is this section's to own. */
		padding-block-end: var(--section-y);
		padding-inline: var(--gutter);
	}

	.hero__media {
		min-width: 0;
	}

	.hero__title {
		/* Not --fs-display. That maximum is sized for a full-width headline; in
		   a 46% column it makes the longest word wider than the column, and the
		   word breaks. Capped here to what the column can actually carry. */
		font-size: clamp(2.25rem, 0.7rem + 4.6vw, 4.5rem);
		font-weight: 700;
		line-height: var(--lh-display);
		letter-spacing: var(--ls-display);
		color: var(--text-strong);
		text-wrap: balance;
	}

	.hero__lead {
		max-width: 46ch;
		margin-block-start: var(--space-md);
		font-size: var(--fs-lead);
		line-height: var(--lh-snug);
		color: var(--text);
	}

	.hero__actions {
		display: flex;
		flex-wrap: wrap;
		gap: var(--space-xs);
		margin-block-start: var(--space-lg);
	}

	/* Stacked, the two actions took their width from their own labels — a
	   short "Get myMKB" above a long "See this week's offers", with two ragged
	   right edges and no shared line for the eye to follow. Once they are on
	   separate lines they take the same width, and the pair reads as one
	   decision with two answers rather than two unrelated buttons. */
	@media (width < 30rem) {
		.hero__actions {
			display: grid;
			gap: var(--space-2xs);
		}

		.hero__actions > .button {
			width: 100%;
		}
	}

	/* The honest-about-place line. Small, but it is a control, not a caption. */
	.hero__context {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		gap: var(--space-3xs) var(--space-2xs);
		margin-block-start: var(--space-md);
		font-size: var(--fs-sm);
		color: var(--text-muted);
	}

	.hero__context .icon {
		color: var(--accent);
	}

	.hero__context strong {
		color: var(--text-strong);
	}

	/* The area switch is a flex item, not a word inside a sentence, so 2.5.8's
	   inline exception does not cover it — it needs the 24px floor of its own. */
	.hero__context a {
		display: inline-flex;
		align-items: center;
		min-height: 24px;
	}

	.hero__media {
		overflow: hidden;
		border-radius: var(--r-xl);
		background: var(--surface-sunken); /* holds the space before the image decodes */
	}

	.hero__image {
		width: 100%;
		height: auto;
		aspect-ratio: 3 / 2;
		object-fit: cover;
	}

	@media (width >= 64rem) {
		.hero {
			/* min-content on the copy column is the safety net: if the headline
			   copy is ever changed to something longer, the column widens and
			   the photograph gives up the space, rather than the word breaking. */
			grid-template-columns: minmax(min-content, 46fr) minmax(0, 54fr);
			align-items: center;
			gap: var(--space-2xl);
			/* Copy stays on the content grid; the media runs to the edge. */
			padding-inline: var(--hero-edge) 0;
		}

		.hero__media {
			border-start-end-radius: 0;
			border-end-end-radius: 0;
		}

		.hero__image {
			aspect-ratio: 5 / 4;
			min-height: clamp(400px, 42vw, 580px);
		}
	}

	/* The page's one authored entrance. Content rises a short distance on a deep
	   ease-out; the photograph settles a beat behind it. Pure CSS, so it never
	   depends on JavaScript to become visible. */
	@media (prefers-reduced-motion: no-preference) {
		.hero__title,
		.hero__lead,
		.hero__actions,
		.hero__context {
			animation: hero-rise var(--t-slow) var(--ease-out) both;
		}

		.hero__lead {
			animation-delay: 70ms;
		}

		.hero__actions {
			animation-delay: 140ms;
		}

		.hero__context {
			animation-delay: 210ms;
		}

		.hero__media {
			animation: hero-settle 620ms var(--ease-out) both;
			/* It settles FROM 1.03. On a wide screen the photograph is already
			   flush with the right edge of the viewport, so growing it about a
			   centre point pushed it past — and for the 620ms of the entrance
			   the page had a horizontal scrollbar. Anchored to that edge, the
			   extra 3% is taken out of the left and the top instead. */
			transform-origin: right center;
		}
	}

	@keyframes hero-rise {
		from {
			opacity: 0;
			transform: translateY(14px);
		}
	}

	@keyframes hero-settle {
		from {
			opacity: 0;
			transform: scale(1.03);
		}
	}
}

/* ==== sections/campaigns.css ==== */
/* Home campaign strip — the heading only.
 *
 * The banners are components/banner.css. They are sized by the ERP on a
 * four-column span grid, and that layout is shared with every other placement,
 * so it does not belong to this section.
 *
 * What used to live here was a fixed 16:8 card in a two-then-four column grid.
 * That is what made a square campaign come out letterboxed: it forced one shape
 * onto artwork the ERP had already given a shape to.
 */

@layer components {
	.campaigns__head {
		display: flex;
		flex-wrap: wrap;
		align-items: end;
		justify-content: space-between;
		gap: var(--space-sm);
		margin-block-end: var(--space-lg);
	}

	.campaigns__eyebrow {
		margin-block-end: var(--space-3xs);
		color: var(--accent);
		font-size: var(--fs-xs);
		font-weight: 700;
		letter-spacing: var(--ls-label);
		text-transform: uppercase;
	}

	.campaigns__head h2 {
		font-size: var(--fs-h2);
		text-wrap: balance;
	}

	.campaigns__head > a {
		display: inline-flex;
		align-items: center;
		gap: var(--space-3xs);
		min-height: 44px; /* it is a control, and here a thumb is the pointer */
		font-weight: 700;
		text-decoration: none;
	}

	.campaigns__head > a .icon {
		transition: transform var(--t) var(--ease-out);
	}

	.campaigns__head > a:hover .icon {
		transform: translateX(3px);
	}
}

/* ==== sections/app.css ==== */
/* Home: the app section, and the /get-app page.
 *
 * A single blue panel, and the only place on the page where the brand colour is
 * allowed to fill a large area. That scarcity is the point — it makes this the
 * loudest thing on the page without any of it having to shout.
 *
 * The blue is --accent, not --mkb-blue. White text sits on this panel, and the
 * exact brand blue scores 4.48:1 against white, just under the 4.5 the standard
 * requires. See COLOURS.md, "The Two-Value Rule".
 *
 * FUTURE: when real myMKB screenshots exist, they belong in .appcta__scan in
 * place of the QR card — a phone-shaped frame at 9:19.5, roughly 300px wide on
 * desktop. Keep the QR beneath it; the two do different jobs.
 */

@layer components {
	/* The panel carries the page's own vertical rhythm, from the `section`
	   class in partials/sections/app.php, like every other block on the home
	   page. It used to carry none at all: the campaign strip above it had its
	   bottom padding zeroed and this section had no padding of its own, so the
	   campaign cards sat directly on the blue panel with nothing between them,
	   and the gap below it was half the gap between every other pair. */
	.appcta__panel {
		display: grid;
		gap: var(--space-xl);
		/* --space-md at the bottom of the range, not --space-lg: on a phone the
		   panel is only 350 points wide, and 32 points of padding on each side
		   of that is a frame around the writing rather than room for it. */
		padding: clamp(var(--space-md), 4vw, var(--space-2xl));
		border-radius: var(--r-2xl);
		background: var(--accent);
		color: var(--text-inverse);
	}

	.appcta__title {
		max-width: 16ch;
		color: var(--text-inverse);
		font-size: var(--fs-h2);
		text-wrap: balance;
	}

	.appcta__lead {
		max-width: 46ch;
		margin-block-start: var(--space-sm);
		font-size: var(--fs-lead);
		line-height: var(--lh-snug);
		color: var(--on-accent-text);
	}

	.appcta .storebtns {
		margin-block: var(--space-lg) 0;
		margin-inline: 0 auto;
	}

	.appcta__scan {
		display: grid;
		justify-items: center;
		justify-self: center;
	}

	/* The icon is a blue tile sitting on a blue panel, so it needs a deeper
	   shadow than the same tile would on white to read as a separate object. */
	.appcta__scan .appicon {
		border-radius: 24px;
		box-shadow:
			0 2px 6px rgb(6 30 50 / 16%),
			0 8px 18px rgb(6 30 50 / 18%);
	}

	.appcta__scan-label {
		margin-block-start: var(--space-2xs);
		font-size: var(--fs-sm);
		font-weight: 700;
		letter-spacing: var(--ls-label);
		color: var(--on-accent-muted);
		text-align: center;
	}

	/* A tablet has room for the copy and the tile side by side; stacked, the
	   panel ran to most of a screen height for four lines of writing. */
	@media (width >= 48rem) {
		.appcta__panel {
			grid-template-columns: minmax(0, 1fr) auto;
			align-items: center;
			gap: var(--space-xl);
		}
	}

	@media (width >= 64rem) {
		.appcta__panel {
			gap: var(--space-2xl);
		}
	}

	/* --- /get-app page ---------------------------------------------------- */

	.getapp .storebtns {
		margin-block: var(--space-lg) 0;
		margin-inline: 0 auto;
	}

	.getapp__pending {
		margin-block-start: var(--space-md);
		padding: var(--space-sm) var(--space-md);
		border-radius: var(--r-md);
		background: var(--surface-tint);
		color: var(--text);
		font-size: var(--fs-sm);
	}

	.getapp__store-note {
		margin-block-start: var(--space-sm);
		color: var(--text-muted);
		font-size: var(--fs-sm);
	}

	.getapp__scan {
		display: grid;
		justify-items: start;
		margin-block-start: var(--space-2xl);
	}
}

/* ==== sections/deals.css ==== */
/* Home: this week's offers, and the offer tile itself.
 *
 * Tiles, not cards. The difference matters: no outline, no equal-height box with
 * a border around it. A white fill on the slightly cooler page, one soft shadow,
 * and the photograph running all the way to the rounded edge.
 *
 * The price is the loudest thing in the tile, because it is the reason anyone is
 * looking. Everything else — title, conditions, the add control — is quieter by
 * a deliberate step.
 */

@layer components {
	.deals__head {
		display: flex;
		flex-wrap: wrap;
		align-items: end;
		justify-content: space-between;
		gap: var(--space-sm) var(--space-md);
		margin-block-end: var(--space-lg);
	}

	.deals__note {
		margin-block-start: var(--space-3xs);
		font-size: var(--fs-sm);
		color: var(--text-muted);
	}

	.deals__all {
		display: inline-flex;
		align-items: center;
		gap: var(--space-3xs);
		/* WCAG 2.2 AA, 2.5.8 Target Size (Minimum). At this font size the line
		   box alone is 22px — two short of the floor. Costs nothing visually
		   and it is measured, not guessed: see the a11y probe in BUILD-LOG. */
		min-height: 24px;

		font-size: var(--fs-sm);
		font-weight: 700;
		text-decoration: none;
		white-space: nowrap;
	}

	.deals__all:hover {
		text-decoration: underline;
		text-underline-offset: 0.25em;
	}

	.deals__empty {
		max-width: 52ch;
		padding: var(--space-lg);
		border-radius: var(--r-lg);
		background: var(--surface);
		box-shadow: var(--shadow-sm);
		color: var(--text-muted);
	}

	/* Column counts are explicit rather than auto-fill. Auto-fill with a small
	   minimum gives six cramped columns on a wide screen; with a large one it
	   gives a single column on a phone. Two-up on mobile is what every grocery
	   app does, and it is right — you can compare two prices at a glance. */
	.deals__grid {
		display: grid;
		align-items: start;
		gap: var(--space-sm);
		grid-template-columns: repeat(2, minmax(0, 1fr));
		list-style: none;
		margin: 0;
		padding: 0;
	}

	@media (width >= 48rem) {
		.deals__grid {
			gap: var(--space-md);
			grid-template-columns: repeat(3, minmax(0, 1fr));
		}
	}

	@media (width >= 64rem) {
		.deals__grid {
			grid-template-columns: repeat(4, minmax(0, 1fr));
		}
	}

	/* --- Tile ------------------------------------------------------------- */

	.tile {
		display: flex;
		flex-direction: column;
		overflow: hidden; /* lets the image reach the rounded corner */
		border-radius: var(--r-lg);
		background: var(--surface);
		box-shadow: var(--shadow-sm);
		transition:
			transform var(--t-fast) var(--ease-out),
			box-shadow var(--t-fast) var(--ease-out);
	}

	.tile:hover {
		transform: translateY(-2px);
		box-shadow: var(--shadow-md);
	}

	/* Fixed media frame — every product photo fits inside the same square.
	   Tall/wide artwork no longer stretches the card. */
	.tile__media {
		position: relative;
		aspect-ratio: 1;
		flex: none;
		overflow: hidden;
		background: var(--surface-sunken);
	}

	.tile__image {
		width: 100%;
		height: 100%;
		object-fit: contain;
		object-position: center;
	}

	/* No photograph. A quiet brand mark reads as intentional; an empty grey
	   rectangle reads as broken. */
	.tile__placeholder {
		display: grid;
		place-items: center;
		width: 100%;
		height: 100%;
		background: var(--surface-tint);
	}

	.tile__placeholder img {
		width: 34%;
		height: auto;
		opacity: 0.16; /* a watermark, not the subject of the tile */
	}

	.tile__badge {
		position: absolute;
		inset-block-start: var(--space-2xs);
		inset-inline-start: var(--space-2xs);
		padding: 0.2rem var(--space-2xs);
		border-radius: var(--r-pill);
		background: var(--offer);
		color: var(--text-inverse);
		font-size: var(--fs-xs);
		font-weight: 700;
		letter-spacing: var(--ls-label);
	}

	/* --- Tile body: one geometry for every tile ---------------------------
	   Reserved rows keep title / brand / price / terms aligned across a row,
	   without a growing spring that left empty space under short titles.

	     1  title   exactly two lines, clamped
	     2  brand   one line, blank when the product has no brand
	     3  price   the loudest thing in the tile
	     4  terms   one line: limits, minimum spend, members-only
	     5  add */
	.tile__body {
		display: grid;
		grid-template-rows: auto var(--tile-line) auto var(--tile-line) auto;
		gap: var(--space-3xs);
		padding: var(--space-sm);
	}

	.tile {
		/* One reserved text line at --fs-sm, and the price's own size — both
		   referenced by the reserved rows above, and both overridable by a
		   denser grid (see .shop__grid--compact). */
		--tile-line: 1.25rem;
		--tile-price-size: var(--fs-h3);
	}

	.tile__title {
		grid-row: 1;
		/* Two lines, always. A one-line name keeps the second line's space so
		   its neighbour's two-line name does not shove everything below it
		   down; a three-line name is cut rather than allowed to do the same. */
		display: -webkit-box;
		-webkit-box-orient: vertical;
		-webkit-line-clamp: 2;
		line-clamp: 2;
		overflow: hidden;
		min-block-size: calc(2em * var(--lh-snug));
		font-size: var(--fs-body);
		font-weight: 700;
		line-height: var(--lh-snug);
		color: var(--text-strong);
	}

	/* The title is a link to the product. Without this it inherits the base
	   anchor colour and the browser's underline, and a grid of blue underlined
	   product names looks like an unstyled page — nothing else on this site
	   underlines a link at rest. The underline appears on hover instead, so the
	   fact that it IS a link is still discoverable. */
	.tile__title a {
		color: inherit;
		text-decoration: none;
	}

	.tile__title a:hover {
		color: var(--accent-hover);
		text-decoration: underline;
		text-underline-offset: 0.2em;
	}

	.tile__price {
		grid-row: 3;
		display: flex;
		flex-wrap: wrap;
		align-items: baseline;
		align-content: start;
		gap: 0 var(--space-2xs);
		padding-block-start: var(--space-2xs);
		/* Two lines' worth, reserved. On a two-up phone grid the tile is about
		   135px wide inside its padding, which is not enough for "Rs 1,190"
		   beside a struck-through "Rs 1,450" — so the old price wraps in some
		   tiles and not others. Reserving the second line either way is what
		   keeps the row below it level. */
		min-block-size: calc(var(--tile-price-size) * var(--lh-tight) + var(--tile-line));
	}

	/* Wide enough for both to sit on one line, so the reserve comes off. */
	@media (width >= 48rem) {
		.tile__price {
			min-block-size: 0;
		}

		.shop__grid--compact .tile__price {
			min-block-size: calc(var(--tile-price-size) * var(--lh-tight) + var(--tile-line));
		}
	}

	.tile__price strong {
		font-size: var(--tile-price-size);
		font-weight: 700;
		letter-spacing: var(--ls-heading);
		color: var(--text-strong);
		/* Lining, evenly spaced digits so prices line up down a column. */
		font-variant-numeric: tabular-nums lining-nums;
	}

	.tile__was {
		font-size: var(--fs-sm);
		color: var(--text-muted);
		font-variant-numeric: tabular-nums lining-nums;
		text-decoration-thickness: 1px;
	}

	.tile__terms {
		grid-row: 4;
		font-size: var(--fs-xs);
		color: var(--text-muted);
		/* One line only. "Members only · On bills over Rs 2,000 · Limit 6" is
		   real and would otherwise take three. */
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
	}

	.tile__brand {
		grid-row: 2;
		overflow: hidden;
		font-size: var(--fs-sm);
		line-height: var(--tile-line);
		color: var(--text-muted);
		text-overflow: ellipsis;
		white-space: nowrap;
	}

	.tile__add {
		grid-row: 5;
		display: inline-flex;
		align-items: center;
		justify-content: center;
		min-height: 44px;
		margin-block-start: var(--space-2xs);
		padding-inline: var(--space-md);
		border-radius: var(--r-pill);
		background: var(--accent-soft);
		color: var(--accent-hover);
		font-size: var(--fs-sm);
		font-weight: 700;
		text-decoration: none;
		transition:
			background-color var(--t-fast) var(--ease-out),
			color var(--t-fast) var(--ease-out);
	}

	.tile__add:hover {
		background: var(--accent);
		color: var(--text-on-accent);
	}

	/* Touch has no hover to borrow from, so the press has to carry the whole
	   acknowledgement. It fills the same way hover does and gives a little
	   under the finger — fast in, because the visitor is already navigating. */
	.tile__add:active {
		background: var(--accent);
		color: var(--text-on-accent);
		transform: scale(0.97);
		transition-duration: 60ms;
	}

	/* The lift on hover is a pointer affordance and does nothing on a phone,
	   where nothing hovers. A press on the tile itself gets the answer instead. */
	@media (hover: none) {
		.tile:hover {
			transform: none;
			box-shadow: var(--shadow-sm);
		}

		.tile:active {
			transform: scale(0.985);
			box-shadow: var(--shadow-xs);
			transition-duration: 60ms;
		}
	}
}

/* ==== sections/categories.css ==== */
/* Home: shop by category — five presentations, one data source.
 *
 * Each variant is self-contained under its own prefix (cats-tiles, cats-pills,
 * cats-text, cats-drop, cats-rail). Deleting four of them and their partials
 * leaves the fifth working, with nothing to untangle.
 *
 * A note that applies to all of them: the per-category colour comes from the ERP
 * and has never been contrast-checked. It is therefore only ever used as a dot,
 * or as a wash mixed down to ~12% over white. A 12% wash is light regardless of
 * the hue, so the dark text on top is safe whatever colour the ERP sends. The
 * plain declaration before each color-mix is the fallback for browsers without
 * it — they get the standard tint rather than nothing.
 */

@layer components {
	.categories__head {
		display: flex;
		flex-wrap: wrap;
		align-items: end;
		justify-content: space-between;
		gap: var(--space-sm) var(--space-md);
		margin-block-end: var(--space-lg);
	}

	.categories__all {
		display: inline-flex;
		align-items: center;
		gap: var(--space-3xs);
		/* WCAG 2.2 AA, 2.5.8 Target Size (Minimum). At this font size the line
		   box alone is 22px — two short of the floor. Costs nothing visually
		   and it is measured, not guessed: see the a11y probe in BUILD-LOG. */
		min-height: 24px;

		font-size: var(--fs-sm);
		font-weight: 700;
		text-decoration: none;
		white-space: nowrap;
	}

	.categories__all:hover {
		text-decoration: underline;
		text-underline-offset: 0.25em;
	}

	.categories__empty {
		max-width: 52ch;
		padding: var(--space-lg);
		border-radius: var(--r-lg);
		background: var(--surface);
		box-shadow: var(--shadow-sm);
		color: var(--text-muted);
	}

	/* =====================================================================
	   A — tiles
	   ===================================================================== */

	.cats-tiles {
		display: grid;
		gap: var(--space-sm);
		grid-template-columns: repeat(2, minmax(0, 1fr));
		list-style: none;
		margin: 0;
		padding: 0;
	}

	@media (width >= 48rem) {
		.cats-tiles {
			grid-template-columns: repeat(4, minmax(0, 1fr));
		}
	}

	@media (width >= 64rem) {
		.cats-tiles {
			grid-template-columns: repeat(6, minmax(0, 1fr));
		}
	}

	.cats-tile {
		display: grid;
		gap: var(--space-2xs);
		justify-items: center;
		height: 100%;
		padding: var(--space-md) var(--space-2xs);
		border-radius: var(--r-lg);
		background: var(--surface-tint);
		background: color-mix(in srgb, var(--cat, var(--mkb-blue)) 12%, #fff);
		color: var(--text-strong);
		text-align: center;
		text-decoration: none;
		transition:
			transform var(--t-fast) var(--ease-out),
			box-shadow var(--t-fast) var(--ease-out);
	}

	.cats-tile:hover {
		color: var(--text-strong);
		transform: translateY(-2px);
		box-shadow: var(--shadow-md);
	}

	.cats-tile__art {
		display: grid;
		place-items: center;
		width: 44px;
		height: 44px;
		border-radius: var(--r-pill);
		background: rgb(255 255 255 / 65%);
	}

	.cats-tile__art img {
		width: 26px;
		height: 26px;
		object-fit: contain;
	}

	.cats-tile__dot {
		width: 14px;
		height: 14px;
		border-radius: var(--r-pill);
		background: var(--cat, var(--accent));
	}

	.cats-tile__label {
		font-size: var(--fs-sm);
		font-weight: 700;
		line-height: var(--lh-snug);
	}

	/* =====================================================================
	   B — pills
	   ===================================================================== */

	.cats-pills {
		display: flex;
		flex-wrap: wrap;
		gap: var(--space-2xs);
		list-style: none;
		margin: 0;
		padding: 0;
	}

	.cats-pill {
		display: inline-flex;
		align-items: center;
		gap: var(--space-2xs);
		min-height: 44px;
		padding-inline: var(--space-md);
		border-radius: var(--r-pill);
		background: var(--surface);
		color: var(--text-strong);
		font-size: var(--fs-sm);
		font-weight: 700;
		text-decoration: none;
		box-shadow: var(--shadow-xs);
		transition:
			background-color var(--t-fast) var(--ease-out),
			box-shadow var(--t-fast) var(--ease-out),
			transform var(--t-fast) var(--ease-out);
	}

	.cats-pill:hover {
		background: var(--surface);
		color: var(--text-strong);
		transform: translateY(-1px);
		box-shadow: var(--shadow-sm);
	}

	.cats-pill__dot {
		width: 9px;
		height: 9px;
		border-radius: var(--r-pill);
		background: var(--cat, var(--accent));
	}

	/* =====================================================================
	   C — text only
	   ===================================================================== */

	.cats-text {
		columns: 2;
		column-gap: var(--space-2xl);
		list-style: none;
		margin: 0;
		padding: 0;
	}

	@media (width >= 48rem) {
		.cats-text {
			columns: 3;
		}
	}

	@media (width >= 64rem) {
		.cats-text {
			columns: 4;
		}
	}

	.cats-text li {
		break-inside: avoid;
	}

	.cats-text__link {
		display: block;
		padding-block: var(--space-2xs);
		color: var(--text-strong);
		font-size: var(--fs-h3);
		font-weight: 700;
		letter-spacing: var(--ls-heading);
		text-decoration: none;
		/* Underline grows from nothing on hover rather than appearing at full
		   length — the only movement in this variant. */
		background-image: linear-gradient(var(--accent), var(--accent));
		background-repeat: no-repeat;
		background-position: 0 100%;
		background-size: 0 2px;
		transition:
			background-size var(--t) var(--ease-out),
			color var(--t-fast) var(--ease-out);
	}

	.cats-text__link:hover {
		color: var(--accent-hover);
		background-size: 100% 2px;
	}

	/* =====================================================================
	   D — dropdown, built on <details>
	   ===================================================================== */

	.cats-drop {
		width: fit-content;
		min-width: min(20rem, 100%);
	}

	.cats-drop__trigger {
		display: flex;
		align-items: center;
		gap: var(--space-2xs);
		min-height: 52px;
		padding-inline: var(--space-md);
		border-radius: var(--r-pill);
		background: var(--surface);
		color: var(--text-strong);
		font-weight: 700;
		cursor: pointer;
		box-shadow: var(--shadow-sm);
		list-style: none; /* removes the default triangle */
	}

	.cats-drop__trigger::-webkit-details-marker {
		display: none;
	}

	.cats-drop__count {
		margin-inline-start: auto;
		padding-inline: var(--space-2xs);
		border-radius: var(--r-pill);
		background: var(--surface-tint);
		color: var(--accent-hover);
		font-size: var(--fs-xs);
	}

	.cats-drop__caret {
		color: var(--text-muted);
		transition: transform var(--t) var(--ease-out);
	}

	.cats-drop[open] .cats-drop__caret {
		transform: rotate(180deg);
	}

	.cats-drop__list {
		display: grid;
		gap: var(--space-3xs);
		margin-block-start: var(--space-2xs);
		padding: var(--space-2xs);
		border-radius: var(--r-lg);
		background: var(--surface);
		box-shadow: var(--shadow-md);
		list-style: none;
	}

	.cats-drop__item {
		display: flex;
		align-items: center;
		gap: var(--space-2xs);
		padding: var(--space-2xs) var(--space-sm);
		border-radius: var(--r-md);
		color: var(--text-strong);
		font-weight: 700;
		text-decoration: none;
	}

	.cats-drop__item:hover {
		background: var(--surface-sunken);
		color: var(--text-strong);
	}

	/* =====================================================================
	   E — horizontal rail
	   ===================================================================== */

	.cats-rail {
		display: flex;
		gap: var(--space-md);
		overflow-x: auto;
		scroll-snap-type: x proximity;
		list-style: none;
		margin: 0;
		/* Bleeds to the viewport edge so the row reads as continuing rather than
		   stopping at the content column. */
		margin-inline: calc(var(--gutter) * -1);
		padding: var(--space-2xs) var(--gutter) var(--space-md);
		scrollbar-width: thin;
	}

	.cats-rail li {
		scroll-snap-align: start;
	}

	.cats-rail__item {
		display: grid;
		gap: var(--space-2xs);
		justify-items: center;
		width: 88px;
		color: var(--text-strong);
		text-align: center;
		text-decoration: none;
	}

	.cats-rail__art {
		display: grid;
		place-items: center;
		width: 72px;
		height: 72px;
		border-radius: var(--r-pill);
		background: var(--surface-tint);
		background: color-mix(in srgb, var(--cat, var(--mkb-blue)) 14%, #fff);
		transition: transform var(--t-fast) var(--ease-out);
	}

	.cats-rail__item:hover .cats-rail__art {
		transform: translateY(-2px) scale(1.03);
	}

	.cats-rail__art img {
		width: 34px;
		height: 34px;
		object-fit: contain;
	}

	.cats-rail__initial {
		font-size: var(--fs-h3);
		font-weight: 700;
		color: var(--text-strong);
	}

	.cats-rail__label {
		font-size: var(--fs-xs);
		font-weight: 700;
		line-height: var(--lh-snug);
	}
}

/* ==== sections/story.css ==== */
/* Home: the MKB story — three presentations, one set of words.
 *
 * This section closes the page before the footer, so all three are deliberately
 * quieter than the hero and the blue app panel above them. Nothing here competes
 * for the eye; it is the part of the page that slows down.
 */

@layer components {
	.story__title {
		max-width: 18ch;
		font-size: var(--fs-h2);
		text-wrap: balance;
	}

	.story__lead {
		max-width: 52ch;
		margin-block-start: var(--space-sm);
		font-size: var(--fs-lead);
		line-height: var(--lh-snug);
		color: var(--text);
	}

	.story__body {
		max-width: 52ch;
		margin-block-start: var(--space-sm);
		color: var(--text-muted);
	}

	.story__copy .button {
		margin-block-start: var(--space-lg);
	}

	/* =====================================================================
	   A — editorial: photograph beside the words
	   ===================================================================== */

	.story__split {
		display: grid;
		gap: var(--space-xl);
		align-items: center;
	}

	.story__media {
		overflow: hidden;
		border-radius: var(--r-xl);
		background: var(--surface-sunken);
	}

	.story__image {
		width: 100%;
		height: auto;
		aspect-ratio: 4 / 3;
		object-fit: cover;
	}

	@media (width >= 64rem) {
		.story__split {
			/* Image first, so this section mirrors the hero instead of repeating
			   its shape further down the same page. */
			grid-template-columns: minmax(0, 46fr) minmax(min-content, 54fr);
			gap: var(--space-2xl);
		}
	}

	/* =====================================================================
	   B — statement: type only
	   ===================================================================== */

	/* Width is set in rem, not ch. A ch measure resolves against this element's
	   1rem font size, not the 3rem heading inside it, which made the container
	   far too narrow and broke the sentence into four ragged lines. */
	.story__statement {
		max-width: min(100%, 44rem);
		margin-inline: auto;
		text-align: center;
	}

	.story__big {
		font-size: clamp(1.875rem, 0.95rem + 3vw, 3.25rem);
		font-weight: 700;
		line-height: var(--lh-tight);
		letter-spacing: var(--ls-heading);
		text-wrap: balance;
	}

	/* The year is the one fact in the sentence worth a second look. */
	.story__year {
		color: var(--accent);
	}

	.story__statement .story__lead {
		max-width: 46ch;
		margin-inline: auto;
		color: var(--text-muted);
	}

	/* =====================================================================
	   C — gallery: words plus three photographs
	   ===================================================================== */

	.story__gallery {
		display: grid;
		gap: var(--space-xl);
	}

	.story__shots {
		display: grid;
		gap: var(--space-sm);
		grid-template-columns: repeat(2, minmax(0, 1fr));
		list-style: none;
		margin: 0;
		padding: 0;
	}

	/* The first photograph earns twice the width — three equal images read as a
	   set of thumbnails rather than as one idea. */
	.story__shots li:first-child {
		grid-column: 1 / -1;
	}

	.story__shot {
		width: 100%;
		height: 100%;
		aspect-ratio: 4 / 3;
		object-fit: cover;
		border-radius: var(--r-lg);
		background: var(--surface-sunken);
	}

	@media (width >= 64rem) {
		.story__gallery {
			grid-template-columns: minmax(0, 38fr) minmax(0, 62fr);
			align-items: center;
			gap: var(--space-2xl);
		}

		.story__shots {
			grid-template-columns: repeat(3, minmax(0, 1fr));
		}

		.story__shots li:first-child {
			grid-column: auto;
			grid-row: span 1;
		}

		.story__shots li:first-child .story__shot {
			aspect-ratio: 3 / 4;
		}
	}
}

/* ==== sections/find.css ==== */
/* Home: "Come and see us" — the shop list above the footer.
 *
 * A live map per card rather than a picture of one or a link promising one.
 * That is the whole reason this section is worth its height: the question it
 * answers is "can I walk there", and only a draggable map answers it.
 *
 * The card is the site's usual tile — white on the cooler page, one soft
 * shadow, no outline — with the map bled to the top edge the way the product
 * tiles bleed their photograph.
 */

@layer components {
	.find__head {
		display: flex;
		flex-wrap: wrap;
		align-items: end;
		justify-content: space-between;
		gap: var(--space-sm) var(--space-md);
		margin-block-end: var(--space-lg);
	}

	.find__head h2 {
		font-size: var(--fs-h2);
		text-wrap: balance;
	}

	.find__note {
		margin-block-start: var(--space-3xs);
		font-size: var(--fs-sm);
		color: var(--text-muted);
	}

	.find__head > a {
		display: inline-flex;
		align-items: center;
		gap: var(--space-3xs);
		min-height: 44px;
		font-size: var(--fs-sm);
		font-weight: 700;
		text-decoration: none;
		white-space: nowrap;
	}

	.find__head > a .icon {
		transition: transform var(--t) var(--ease-out);
	}

	.find__head > a:hover .icon {
		transform: translateX(3px);
	}

	/* One column on a phone. A map squeezed into half a phone's width shows one
	   street and is worse than useless. */
	.find__grid {
		display: grid;
		gap: var(--space-sm);
		grid-template-columns: minmax(0, 1fr);
		margin: 0;
		padding: 0;
		list-style: none;
	}

	@media (width >= 40rem) {
		.find__grid {
			grid-template-columns: repeat(2, minmax(0, 1fr));
			gap: var(--space-md);
		}
	}

	@media (width >= 72rem) {
		.find__grid {
			grid-template-columns: repeat(4, minmax(0, 1fr));
		}
	}

	.find__card {
		display: flex;
		flex-direction: column;
		overflow: hidden; /* lets the map reach the rounded corner */
		border-radius: var(--r-lg);
		background: var(--surface);
		box-shadow: var(--shadow-sm);
		transition: box-shadow var(--t-fast) var(--ease-out);
	}

	.find__card:hover {
		box-shadow: var(--shadow-md);
	}

	.find__map {
		display: block;
		inline-size: 100%;
		block-size: 190px;
		border: 0;
		background: var(--surface-sunken); /* holds the space before it loads */
	}

	.find__body {
		display: flex;
		flex: 1;
		flex-direction: column;
		gap: var(--space-3xs);
		padding: var(--space-sm);
	}

	.find__name {
		font-size: var(--fs-h3);
	}

	.find__name a {
		color: inherit;
		text-decoration: none;
	}

	.find__name a:hover {
		color: var(--accent-hover);
		text-decoration: underline;
		text-underline-offset: 0.2em;
	}

	.find__region {
		font-size: var(--fs-xs);
		font-weight: 700;
		letter-spacing: var(--ls-label);
		text-transform: uppercase;
		color: var(--text-muted);
	}

	.find__address {
		display: flex;
		align-items: start;
		gap: var(--space-3xs);
		margin-block-start: var(--space-3xs);
		font-size: var(--fs-sm);
		color: var(--text);
	}

	.find__address .icon {
		margin-block-start: 2px; /* optical: the pin's point sits low in its box */
		color: var(--accent);
	}

	/* Pushed to the foot of the card so the two controls line up across a row
	   however long the address above them runs. */
	.find__actions {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		justify-content: space-between;
		gap: var(--space-2xs);
		margin-block-start: auto;
		padding-block-start: var(--space-2xs);
	}

	.find__call,
	.find__directions {
		display: inline-flex;
		align-items: center;
		gap: var(--space-3xs);
		min-height: 44px; /* both are tapped, and one of them dials a phone */
		font-size: var(--fs-sm);
		font-weight: 700;
		text-decoration: none;
		white-space: nowrap;
	}

	.find__call {
		color: var(--text-strong);
	}

	.find__call .icon {
		color: var(--accent);
	}

	.find__call:hover {
		color: var(--accent-hover);
	}

	.find__directions .icon {
		transition: transform var(--t) var(--ease-out);
	}

	.find__directions:hover .icon {
		transform: translateX(3px);
	}
}

/* ==== sections/offers.css ==== */
/* /offers — the promotional surface.
 *
 * The rest of the site is calm on purpose. This page is not: a supermarket's
 * offers page is advertising, and pretending otherwise would be designing for a
 * portfolio rather than for a shopper. So the header is a full blue field, the
 * count is set large, and campaign artwork sits above the fold.
 *
 * What does NOT change is the discipline underneath — no borders, the same
 * tiles, the same tokens, every colour pairing contrast-checked. Loud is a
 * matter of area and hierarchy here, not of adding decoration.
 */

@layer components {
	/* =====================================================================
	   Promotional header
	   ===================================================================== */

	.offers__masthead {
		padding-block: calc(var(--nav-h) + var(--space-xl)) var(--space-xl);
		margin-block-start: calc(var(--main-top, 0px) * -1);
		background: var(--accent);
		color: var(--text-inverse);
	}

	.offers__masthead-inner {
		display: grid;
		gap: var(--space-md);
		align-items: end;
	}

	@media (width >= 56rem) {
		.offers__masthead-inner {
			grid-template-columns: minmax(0, 1fr) minmax(0, 22rem);
			gap: var(--space-2xl);
		}
	}

	.offers__title {
		color: var(--text-inverse);
		font-size: clamp(2rem, 1.1rem + 3vw, 3.5rem);
		line-height: var(--lh-tight);
		letter-spacing: var(--ls-display);
		text-wrap: balance;
	}

	.offers__count {
		margin-block-start: var(--space-2xs);
		font-size: var(--fs-lead);
		color: var(--on-accent-text);
	}

	.offers__count strong {
		color: var(--text-inverse);
	}

	.offers__lead {
		color: var(--on-accent-muted);
		font-size: var(--fs-sm);
	}

	/* --- Campaign strip ---------------------------------------------------
	   Same span grid as home. On desktop only, shrink ~15% so the masthead
	   stays the focus; phones keep the full-width strip. */

	.offers__masthead .bannergrid {
		margin-block-start: var(--space-lg);
	}

	@media (width >= 48rem) {
		.offers__masthead .bannergrid {
			width: 85%;
			max-inline-size: 85%;
			margin-inline: auto;
		}
	}

	/* =====================================================================
	   Controls
	   ===================================================================== */

	.offers__controls {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		justify-content: space-between;
		gap: var(--space-sm);
		margin-block: var(--space-lg);
	}

	.offers__toolbar {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		justify-content: space-between;
		gap: var(--space-sm);
		flex: 1 1 100%;
	}

	.offers__search {
		display: flex;
		gap: var(--space-2xs);
		flex: 1 1 12rem;
		min-width: 0;
	}

	.offers__search input {
		flex: 1;
		min-width: 0;
		min-height: 44px;
		appearance: none;
		padding-inline: var(--space-md);
		border: 0;
		border-radius: var(--r-pill);
		background: var(--surface);
		box-shadow: var(--shadow-xs);
	}

	.offers__search button {
		min-height: 44px;
		padding-inline: var(--space-md);
		border-radius: var(--r-pill);
		background: var(--accent);
		color: var(--text-on-accent);
		font-size: var(--fs-sm);
		font-weight: 700;
		transition:
			background-color var(--t-fast) var(--ease-out),
			transform var(--t-fast) var(--ease-out);
	}

	.offers__search button:hover {
		background: var(--accent-hover);
	}

	.offers__search button:active {
		transform: scale(0.97);
		transition-duration: 60ms;
	}

	.offers__channels {
		display: flex;
		flex: 1 0 100%;
		gap: var(--space-sm);
		padding-block-end: var(--space-xs);
		border-block-end: 1px solid var(--line);
		overflow-x: auto;
		overscroll-behavior-inline: contain;
		scrollbar-width: none;
		-webkit-overflow-scrolling: touch;
	}

	.offers__channels::-webkit-scrollbar {
		display: none;
	}

	.offers__channel {
		flex: none;
		display: inline-flex;
		align-items: center;
		min-height: 40px;
		padding-block: var(--space-2xs);
		margin-block-end: calc((var(--space-xs) + 1px) * -1);
		border-block-end: 2px solid transparent;
		color: var(--text-muted);
		font-weight: 700;
		text-decoration: none;
	}

	.offers__channel:hover,
	.offers__channel[aria-current="true"] {
		color: var(--text-strong);
		border-block-end-color: var(--accent);
	}

	/* The rule under the selected tab slides between tabs rather than blinking
	   from one to the next — the colour and the width move together, so it
	   reads as one underline travelling. */
	.offers__channel {
		transition:
			color var(--t) var(--ease-out),
			border-color var(--t) var(--ease-out);
	}

	.offers__filters {
		display: flex;
		flex-wrap: wrap;
		gap: var(--space-2xs);
	}

	/* On a phone eleven categories wrap to five rows and push every offer below
	   the fold. Same answer as the shop's departments: one rail, running to the
	   screen edge, fading and snapping. */
	@media (width < 48rem) {
		.offers__filters {
			flex-wrap: nowrap;
			margin-inline: calc(var(--gutter) * -1);
			padding-inline: var(--gutter);
			overflow-x: auto;
			overscroll-behavior-inline: contain;
			scrollbar-width: none;
			scroll-snap-type: inline proximity;
			scroll-padding-inline: var(--gutter);
			-webkit-overflow-scrolling: touch;
			mask-image: linear-gradient(
				to right,
				transparent 0,
				#000 var(--gutter),
				#000 calc(100% - var(--space-lg)),
				transparent 100%
			);
		}

		.offers__filters::-webkit-scrollbar {
			display: none;
		}

		.offers__chip {
			flex: none;
			scroll-snap-align: start;
		}
	}

	.offers__chip {
		display: inline-flex;
		align-items: center;
		gap: var(--space-2xs);
		min-height: 44px;
		padding-block: 4px;
		padding-inline: 6px var(--space-md);
		border-radius: var(--r-pill);
		background: var(--surface);
		color: var(--text-strong);
		font-size: var(--fs-sm);
		font-weight: 700;
		text-decoration: none;
		box-shadow: var(--shadow-xs);
		transition:
			background-color var(--t-fast) var(--ease-out),
			color var(--t-fast) var(--ease-out),
			transform var(--t-fast) var(--ease-out);
	}

	.offers__chip-mark {
		display: grid;
		place-items: center;
		inline-size: 32px;
		block-size: 32px;
		border-radius: 50%;
		background: var(--surface-sunken);
		color: var(--accent);
		overflow: hidden;
		flex: none;
	}

	.offers__chip-logo {
		display: block;
		inline-size: 100%;
		block-size: 100%;
		object-fit: cover;
	}

	.offers__chip-label {
		white-space: nowrap;
	}

	.offers__chip:hover {
		background: var(--surface-sunken);
		color: var(--text-strong);
	}

	.offers__chip:active {
		transform: scale(0.95);
		transition-duration: 60ms;
	}

	/* The selected filter is a filled shape, not just a colour, so it survives a
	   greyscale or colour-blind reading. */
	.offers__chip[aria-current="true"] {
		background: var(--accent);
		color: var(--text-on-accent);
	}

	.offers__chip[aria-current="true"] .offers__chip-mark {
		background: color-mix(in srgb, var(--text-on-accent) 18%, transparent);
		color: var(--text-on-accent);
	}

	.offers__sort {
		display: flex;
		align-items: center;
		gap: var(--space-2xs);
		font-size: var(--fs-sm);
	}

	.offers__sort label {
		color: var(--text-muted);
		font-weight: 700;
	}

	.offers__sort select {
		min-height: 40px;
		padding-inline: var(--space-sm);
		border: 0;
		border-radius: var(--r-pill);
		background: var(--surface);
		color: var(--text-strong);
		font-weight: 700;
		box-shadow: var(--shadow-xs);
	}


	/* The sort control is a real <select>, so the list itself is the platform's
	   own — which is right on a phone, where the OS picker beats anything we
	   could draw. Only the closed state is ours: the native arrow is replaced
	   with the same chevron the rest of the site uses, and the box is given the
	   pill and shadow every other control here has. */
	.offers__sort select {
		appearance: none;
		padding-inline: var(--space-sm) var(--space-xl);
		background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2361707F' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6.5 9.75 5.5 5 5.5-5'/%3E%3C/svg%3E");
		background-repeat: no-repeat;
		background-position: right var(--space-sm) center;
		cursor: pointer;
		transition: box-shadow var(--t-fast) var(--ease-out);
	}

	.offers__sort select:hover {
		box-shadow: var(--shadow-sm);
	}

	.offers__sort-go {
		min-height: 40px;
		padding-inline: var(--space-md);
		border-radius: var(--r-pill);
		background: var(--surface-sunken);
		color: var(--accent-hover);
		font-size: var(--fs-sm);
		font-weight: 700;
	}

	/* Scripting can submit on change, so the button becomes redundant. It is
	   hidden by JS adding this class — never hidden by default, or it would be
	   unreachable without JavaScript. */
	.offers__sort.is-auto .offers__sort-go {
		display: none;
	}

	/* =====================================================================
	   Results
	   ===================================================================== */

	.offers__grid {
		display: grid;
		align-items: start;
		gap: var(--space-sm);
		grid-template-columns: repeat(2, minmax(0, 1fr));
		list-style: none;
		margin: 0;
		padding: 0;
	}

	@media (width >= 48rem) {
		.offers__grid {
			gap: var(--space-md);
			grid-template-columns: repeat(3, minmax(0, 1fr));
		}
	}

	@media (width >= 64rem) {
		.offers__grid {
			grid-template-columns: repeat(5, minmax(0, 1fr));
		}
	}

	.offers__grid--narrow {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}

	@media (width >= 64rem) {
		.offers__grid--narrow {
			grid-template-columns: repeat(4, minmax(0, 1fr));
		}
	}

	.offers__empty {
		max-width: 56ch;
		padding: var(--space-lg);
		border-radius: var(--r-lg);
		background: var(--surface);
		box-shadow: var(--shadow-sm);
		color: var(--text-muted);
	}

	/* --- B: grouped ------------------------------------------------------- */

	.offers__groups {
		display: grid;
		gap: var(--space-2xl);
	}

	.offers__group-head {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		gap: var(--space-2xs) var(--space-sm);
		margin-block-end: var(--space-md);
	}

	.offers__group-title {
		font-size: var(--fs-h3);
	}

	.offers__group-count {
		padding-inline: var(--space-2xs);
		border-radius: var(--r-pill);
		background: var(--surface-tint);
		color: var(--accent-hover);
		font-size: var(--fs-xs);
		font-weight: 700;
	}

	.offers__group-all {
		display: inline-flex;
		align-items: center;
		gap: var(--space-3xs);
		min-height: 24px; /* WCAG 2.2 AA 2.5.8 */
		margin-inline-start: auto;
		font-size: var(--fs-sm);
		font-weight: 700;
		text-decoration: none;
	}

	.offers__group-all:hover {
		text-decoration: underline;
		text-underline-offset: 0.25em;
	}

	/* --- C: browse -------------------------------------------------------- */

	.offers__browse {
		display: grid;
		gap: var(--space-lg);
	}

	/* Below this width the chips above already provide the same navigation, so
	   the rail would be a duplicate of controls already on screen. */
	.offers__rail {
		display: none;
	}

	@media (width >= 64rem) {
		.offers__browse {
			grid-template-columns: 14rem minmax(0, 1fr);
			gap: var(--space-2xl);
			align-items: start;
		}

		.offers__rail {
			display: block;
			position: sticky;
			top: calc(var(--nav-h) + var(--space-lg));
			padding: var(--space-sm);
			border-radius: var(--r-lg);
			background: var(--surface);
			box-shadow: var(--shadow-sm);
		}
	}

	.offers__rail-title {
		font-size: var(--fs-xs);
		font-weight: 700;
		letter-spacing: var(--ls-label);
		text-transform: uppercase;
		color: var(--text-muted);
		padding-inline: var(--space-2xs);
		margin-block-end: var(--space-2xs);
	}

	.offers__rail ul {
		display: grid;
		gap: 2px;
		list-style: none;
		margin: 0;
		padding: 0;
	}

	.offers__rail a {
		display: block;
		padding: var(--space-2xs) var(--space-sm);
		border-radius: var(--r-md);
		color: var(--text);
		font-size: var(--fs-sm);
		font-weight: 700;
		text-decoration: none;
	}

	.offers__rail a:hover {
		background: var(--surface-sunken);
		color: var(--text-strong);
	}

	.offers__rail a[aria-current="true"] {
		background: var(--accent-soft);
		color: var(--accent-hover);
	}

	/* =====================================================================
	   Closing invitation
	   ===================================================================== */

	.offers__cta-inner {
		display: grid;
		gap: var(--space-sm);
		justify-items: center;
		max-width: 46rem;
		padding: var(--space-2xl) var(--space-lg);
		border-radius: var(--r-2xl);
		background: var(--surface-inverse);
		color: var(--footer-text);
		text-align: center;
	}

	.offers__cta-inner h2 {
		color: var(--text-inverse);
	}
}

/* ==== sections/shop.css ==== */
/* /shop and /product.
 *
 * Browsing, not campaigning — so the header is a pale blue wash rather than the
 * full blue field the offers page uses. Same family, one step quieter, which is
 * what keeps "offers" feeling like the loud page.
 *
 * Products carry a state offers do not: they can be out of stock. That is shown
 * on the tile and again on the product page, using the backend's own wording.
 * The site never writes its own availability copy.
 */

@layer components {
	/* =====================================================================
	   Shop header
	   ===================================================================== */

	.shop__masthead {
		padding-block: var(--space-lg) var(--space-xl);
		margin-block-end: var(--space-lg);
		background: var(--surface-tint);
	}

	.shop__crumbs {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		gap: var(--space-3xs) var(--space-2xs);
		margin-block-end: var(--space-sm);
		font-size: var(--fs-sm);
		color: var(--text-muted);
	}

	.shop__crumbs a {
		/* 24px floor for WCAG 2.2 AA 2.5.8. A breadcrumb is a list of links,
		   not a link inside a sentence, so the inline exception does not cover
		   it. inline-flex is what lets min-height apply to an inline element. */
		display: inline-flex;
		align-items: center;
		min-height: 24px;
		text-decoration: none;
	}

	.shop__crumbs a:hover {
		text-decoration: underline;
		text-underline-offset: 0.25em;
	}

	.shop__title {
		font-size: clamp(1.75rem, 1.1rem + 2.2vw, 3rem);
		line-height: var(--lh-tight);
		text-wrap: balance;
	}

	.shop__count {
		display: inline-flex;
		align-items: center;
		gap: var(--space-3xs);
		margin-block-start: var(--space-2xs);
		color: var(--text-muted);
	}

	.shop__count .icon {
		color: var(--accent);
	}

	.shop__count strong {
		color: var(--text-strong);
	}

	/* =====================================================================
	   Controls
	   ===================================================================== */

	.shop__controls {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		justify-content: space-between;
		gap: var(--space-sm);
		margin-block-end: var(--space-md);
	}

	/* Basis is small and shrinking is allowed: an 18rem basis cannot fit inside
	   a 320px screen once the gutters are taken off, and the form pushed the
	   page sideways. */
	.shop__search {
		display: flex;
		gap: var(--space-2xs);
		flex: 1 1 12rem;
		min-width: 0;
	}

	.shop__search input {
		flex: 1;
		min-width: 0;
		min-height: 44px;
		/* Safari draws type=search with its own inset shape and rounded ends
		   that ignore the pill and the shadow set below. */
		appearance: none;
		padding-inline: var(--space-md);
		border: 0;
		border-radius: var(--r-pill);
		background: var(--surface);
		box-shadow: var(--shadow-xs);
	}

	.shop__search button,
	.shop__sort-go {
		min-height: 44px;
		padding-inline: var(--space-md);
		border-radius: var(--r-pill);
		background: var(--accent);
		color: var(--text-on-accent);
		font-size: var(--fs-sm);
		font-weight: 700;
	}

	.shop__search button,
	.shop__sort-go {
		transition:
			background-color var(--t-fast) var(--ease-out),
			transform var(--t-fast) var(--ease-out);
	}

	.shop__search button:hover {
		background: var(--accent-hover);
	}

	.shop__search button:active,
	.shop__sort-go:active {
		transform: scale(0.97);
		transition-duration: 60ms;
	}

	.shop__sort {
		display: flex;
		align-items: center;
		gap: var(--space-2xs);
		font-size: var(--fs-sm);
	}

	.shop__sort label {
		color: var(--text-muted);
		font-weight: 700;
	}

	.shop__sort select {
		min-height: 44px;
		padding-inline: var(--space-sm);
		border: 0;
		border-radius: var(--r-pill);
		background: var(--surface);
		color: var(--text-strong);
		font-weight: 700;
		box-shadow: var(--shadow-xs);
	}


	/* The sort control is a real <select>, so the list itself is the platform's
	   own — which is right on a phone, where the OS picker beats anything we
	   could draw. Only the closed state is ours: the native arrow is replaced
	   with the same chevron the rest of the site uses, and the box is given the
	   pill and shadow every other control here has. */
	.shop__sort select {
		appearance: none;
		padding-inline: var(--space-sm) var(--space-xl);
		background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2361707F' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6.5 9.75 5.5 5 5.5-5'/%3E%3C/svg%3E");
		background-repeat: no-repeat;
		background-position: right var(--space-sm) center;
		cursor: pointer;
		transition: box-shadow var(--t-fast) var(--ease-out);
	}

	.shop__sort select:hover {
		box-shadow: var(--shadow-sm);
	}

	.shop__sort-go {
		background: var(--surface-sunken);
		color: var(--accent-hover);
	}

	.shop__sort.is-auto .shop__sort-go {
		display: none;
	}

	/* Department and brand rows scroll sideways rather than wrapping to four
	   lines — with 24 departments, wrapping would push the products off screen
	   before a single one is seen.

	   Three things make a sideways rail feel like one on a phone rather than a
	   row that happens to be cut off:

	     - it runs to the very edge of the screen, so the chip under the cut is
	       visibly half-there and the row reads as continuing;
	     - it fades out at that edge instead of ending on a hard crop;
	     - it snaps, so a flick lands on a chip rather than between two.

	   The negative margin pulls the rail out through the page gutter, and the
	   matching padding puts the first chip back where the copy above it starts.
	   scroll-padding keeps a snapped chip off the fade. */
	.shop__chips {
		display: flex;
		align-items: center;
		gap: var(--space-2xs);
		margin-inline: calc(var(--gutter) * -1);
		margin-block-end: var(--space-sm);
		padding-inline: var(--gutter);
		padding-block-end: var(--space-2xs);
		overflow-x: auto;
		overscroll-behavior-inline: contain;
		scrollbar-width: none;
		scroll-snap-type: inline proximity;
		scroll-padding-inline: var(--gutter);
		-webkit-overflow-scrolling: touch;
		/* The scrollbar is hidden, so the fade is the only thing telling the
		   visitor there is more. It is a mask rather than a gradient overlay so
		   it works over the tinted header and the page alike. */
		mask-image: linear-gradient(
			to right,
			transparent 0,
			#000 var(--gutter),
			#000 calc(100% - var(--space-lg)),
			transparent 100%
		);
	}

	.shop__chips::-webkit-scrollbar {
		display: none;
	}

	.shop__category-groups {
		display: grid;
		gap: var(--space-2xs);
		margin-block-end: var(--space-sm);
	}

	.shop__chipset {
		display: grid;
		gap: var(--space-3xs);
		margin-block-end: var(--space-sm);
	}

	/* Inside the group list the wrapper's own grid gap does the spacing. */
	.shop__category-groups .shop__chipset {
		margin-block-end: 0;
	}

	.shop__chipset .shop__chips {
		margin-block-end: 0;
	}

	/* The label sits above its rail rather than in front of it. Inside the
	   scroller it scrolled away with the chips, so a rail flicked halfway along
	   was an unlabelled row of names; and on a phone it ate a third of the
	   visible width before the first chip appeared. */
	.shop__chips-label {
		display: block;
		font-size: var(--fs-xs);
		font-weight: 700;
		letter-spacing: var(--ls-label);
		text-transform: uppercase;
		color: var(--text-muted);
	}

	.shop__chip {
		flex: none;
		display: inline-flex;
		align-items: center;
		/* 40, not 38. Two short of the touch floor is the kind of miss nobody
		   can see and every thumb can feel. */
		min-height: 40px;
		padding-inline: var(--space-md);
		border-radius: var(--r-pill);
		background: var(--surface);
		color: var(--text-strong);
		font-size: var(--fs-sm);
		font-weight: 700;
		text-decoration: none;
		white-space: nowrap;
		box-shadow: var(--shadow-xs);
		scroll-snap-align: start;
		transition:
			background-color var(--t-fast) var(--ease-out),
			color var(--t-fast) var(--ease-out),
			transform var(--t-fast) var(--ease-out);
	}

	.shop__chip:hover {
		background: var(--surface-sunken);
		color: var(--text-strong);
	}

	.shop__chip:active {
		transform: scale(0.95);
		transition-duration: 60ms;
	}

	.shop__chip[aria-current="true"] {
		background: var(--accent);
		color: var(--text-on-accent);
	}

	.shop__chip--quiet {
		min-height: 36px;
		font-size: var(--fs-xs);
		box-shadow: none;
		background: var(--surface-sunken);
	}

	.shop__chip--quiet[aria-current="true"] {
		background: var(--accent);
		color: var(--text-on-accent);
	}

	/* =====================================================================
	   Results
	   ===================================================================== */

	.shop__results {
		padding-block-end: var(--section-y);
	}

	.shop__grid {
		display: grid;
		align-items: start;
		gap: var(--space-sm);
		grid-template-columns: repeat(2, minmax(0, 1fr));
		list-style: none;
		margin: 0;
		padding: 0;
	}

	@media (width >= 48rem) {
		.shop__grid {
			gap: var(--space-md);
			grid-template-columns: repeat(3, minmax(0, 1fr));
		}
	}

	@media (width >= 64rem) {
		.shop__grid {
			grid-template-columns: repeat(5, minmax(0, 1fr));
		}
	}

	@media (width >= 48rem) {
		.shop__grid--compact {
			grid-template-columns: repeat(4, minmax(0, 1fr));
			gap: var(--space-sm);
		}
	}

	@media (width >= 64rem) {
		.shop__grid--compact {
			grid-template-columns: repeat(7, minmax(0, 1fr));
		}
	}

	.shop__grid--compact .tile__media {
		aspect-ratio: 1;
	}

	.shop__grid--compact .tile__body {
		padding: var(--space-2xs);
	}

	.shop__grid--compact .tile__title {
		font-size: var(--fs-sm);
	}

	.shop__grid--compact .tile {
		--tile-price-size: var(--fs-body);
	}

	.shop__empty {
		max-width: 56ch;
		padding: var(--space-lg);
		border-radius: var(--r-lg);
		background: var(--surface);
		box-shadow: var(--shadow-sm);
		color: var(--text-muted);
	}

	/* Out of stock: dimmed, never hidden. A shopper looking for something needs
	   to know it exists and is unavailable, not be left wondering. */
	.tile--out .tile__media,
	.tile--out .tile__price,
	.row--out .row__media {
		opacity: 0.5;
	}

	.tile__out {
		position: absolute;
		inset-block-end: var(--space-2xs);
		inset-inline: var(--space-2xs);
		padding: 0.15rem var(--space-2xs);
		border-radius: var(--r-sm);
		background: var(--gray-900);
		color: var(--text-inverse);
		font-size: var(--fs-xs);
		font-weight: 700;
		text-align: center;
	}

	.tile__add--off {
		background: var(--surface-sunken);
		color: var(--text-muted);
	}

	/* --- B: list ---------------------------------------------------------- */

	.shop__list {
		display: grid;
		gap: var(--space-2xs);
		list-style: none;
		margin: 0;
		padding: 0;
	}

	.row {
		display: grid;
		grid-template-columns: 72px minmax(0, 1fr) auto;
		align-items: center;
		gap: var(--space-sm);
		padding: var(--space-2xs);
		border-radius: var(--r-lg);
		background: var(--surface);
		box-shadow: var(--shadow-xs);
	}

	@media (width >= 40rem) {
		.row {
			grid-template-columns: 88px minmax(0, 1fr) auto auto;
			gap: var(--space-md);
			padding: var(--space-2xs) var(--space-md) var(--space-2xs) var(--space-2xs);
		}
	}

	.row__media {
		overflow: hidden;
		border-radius: var(--r-md);
		background: var(--surface-tint);
		aspect-ratio: 1;
	}

	.row__media img {
		width: 100%;
		height: 100%;
		object-fit: cover;
	}

	.row__placeholder {
		display: grid;
		place-items: center;
		width: 100%;
		height: 100%;
	}

	.row__placeholder img {
		width: 60%;
		height: auto;
		object-fit: contain;
		opacity: 0.18;
	}

	.row__title {
		font-size: var(--fs-body);
		font-weight: 700;
	}

	.row__title a {
		color: var(--text-strong);
		text-decoration: none;
	}

	.row__title a:hover {
		color: var(--accent-hover);
	}

	.row__brand,
	.row__out {
		font-size: var(--fs-xs);
		color: var(--text-muted);
	}

	.row__price {
		display: flex;
		flex-direction: column;
		align-items: end;
		font-variant-numeric: tabular-nums lining-nums;
	}

	.row__price strong {
		font-size: var(--fs-h3);
		color: var(--text-strong);
	}

	.row__price s {
		font-size: var(--fs-sm);
		color: var(--text-muted);
	}

	.shop__pager {
		display: grid;
		grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
		align-items: center;
		gap: var(--space-sm);
		margin-block-start: var(--space-xl);
		padding-block-start: var(--space-md);
		border-block-start: 1px solid var(--line);
	}

	.shop__pager a {
		display: inline-flex;
		align-items: center;
		gap: var(--space-2xs);
		min-height: 44px;
		width: fit-content;
		padding-inline: var(--space-sm);
		border-radius: var(--r-pill);
		background: var(--surface);
		box-shadow: var(--shadow-sm);
		color: var(--accent-hover);
		font-weight: 700;
		text-decoration: none;
	}

	.shop__pager a:last-child {
		justify-self: end;
	}

	.shop__pager a:hover {
		background: var(--surface-tint);
	}

	.shop__pager p {
		font-size: var(--fs-sm);
		color: var(--text-muted);
		white-space: nowrap;
	}

	.shop__pager strong {
		color: var(--text-strong);
	}

	@media (width < 30rem) {
		.shop__pager {
			grid-template-columns: 1fr 1fr;
		}

		.shop__pager p {
			grid-column: 1 / -1;
			grid-row: 1;
			justify-self: center;
		}
	}

	/* On a phone the price and action sit under the name rather than squeezing
	   four columns into 320px. */
	@media (width < 40rem) {
		.row {
			grid-template-columns: 72px minmax(0, 1fr);
		}

		.row__price {
			grid-column: 2;
			align-items: start;
		}

		.row__action {
			grid-column: 1 / -1;
		}

		.row__action .tile__add {
			width: 100%;
		}
	}

	/* =====================================================================
	   Product page
	   ===================================================================== */

	.product {
		padding-block: var(--space-lg) var(--section-y);
	}

	.pd {
		display: grid;
		gap: var(--space-xl);
		margin-block-start: var(--space-md);
	}

	@media (width >= 64rem) {
		.pd--split {
			grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
			gap: var(--space-2xl);
			align-items: start;
		}
	}

	.pd--stacked {
		max-width: 56rem;
		margin-inline: auto;
	}

	.pd__media {
		position: relative;
		overflow: hidden;
		border-radius: var(--r-xl);
		background: var(--surface-tint);
		aspect-ratio: 4 / 3;
	}

	.pd__image {
		width: 100%;
		height: 100%;
		object-fit: cover;
	}

	.pd__placeholder {
		display: grid;
		place-items: center;
		width: 100%;
		height: 100%;
	}

	.pd__placeholder img {
		width: 34%;
		height: auto;
		opacity: 0.18;
	}

	.pd__badge {
		position: absolute;
		inset-block-start: var(--space-sm);
		inset-inline-start: var(--space-sm);
		padding: 0.25rem var(--space-2xs);
		border-radius: var(--r-pill);
		background: var(--offer);
		color: var(--text-inverse);
		font-size: var(--fs-sm);
		font-weight: 700;
	}

	.pd__brand {
		font-size: var(--fs-sm);
		font-weight: 700;
		letter-spacing: var(--ls-label);
		color: var(--text-muted);
	}

	.pd__title {
		margin-block-start: var(--space-3xs);
		font-size: clamp(1.5rem, 1rem + 1.8vw, 2.5rem);
		line-height: var(--lh-tight);
		text-wrap: balance;
	}

	.pd__pricing {
		display: flex;
		flex-wrap: wrap;
		align-items: baseline;
		gap: var(--space-2xs) var(--space-sm);
		margin-block-start: var(--space-md);
	}

	.pd__price {
		font-size: clamp(1.75rem, 1.2rem + 1.6vw, 2.5rem);
		font-weight: 700;
		letter-spacing: var(--ls-heading);
		color: var(--text-strong);
		font-variant-numeric: tabular-nums lining-nums;
	}

	.pd__was {
		font-size: var(--fs-lead);
		color: var(--text-muted);
		font-variant-numeric: tabular-nums lining-nums;
	}

	.pd__saving {
		padding: 0.15rem var(--space-2xs);
		border-radius: var(--r-pill);
		background: var(--positive);
		color: var(--text-inverse);
		font-size: var(--fs-sm);
		font-weight: 700;
	}

	.pd__out {
		margin-block-start: var(--space-sm);
		padding: var(--space-2xs) var(--space-sm);
		border-radius: var(--r-md);
		background: var(--surface-sunken);
		color: var(--text-strong);
		font-weight: 700;
	}

	.pd__terms {
		display: grid;
		gap: var(--space-3xs);
		margin-block-start: var(--space-md);
		padding: 0;
		list-style: none;
		font-size: var(--fs-sm);
		color: var(--text-muted);
	}

	.pd__add {
		margin-block-start: var(--space-lg);
	}

	.pd__note {
		display: flex;
		align-items: start;
		gap: var(--space-2xs);
		margin-block-start: var(--space-md);
		font-size: var(--fs-sm);
		color: var(--text-muted);
	}

	.pd__note .icon {
		flex: none;
		margin-block-start: 2px;
		color: var(--accent);
	}

	.pd__description {
		margin-block-start: var(--space-xl);
	}

	.pd__description h2 {
		font-size: var(--fs-h3);
		margin-block-end: var(--space-2xs);
	}

	.product__related {
		margin-block-start: var(--space-3xl);
	}

	.product__related h2 {
		margin-block-end: var(--space-md);
	}
}

/* ==== sections/about.css ==== */
/* /about
 *
 * The quietest page on the site, and deliberately so. Offers shouts, shop is
 * businesslike, and this one slows down — it is read rather than scanned.
 *
 * Both layouts are built to hold two or three times the current amount of
 * writing without changing, because the copy here is the part MKB still has to
 * write and it will almost certainly be longer than what stands in for it now.
 */

@layer components {
	.about__masthead {
		padding-block: var(--space-lg) var(--space-2xl);
		margin-block-end: var(--space-2xl);
		background: var(--surface-tint);
	}

	.about__title {
		font-size: clamp(2.25rem, 1.2rem + 3.4vw, 4rem);
		line-height: var(--lh-display);
		letter-spacing: var(--ls-display);
	}

	.about__lead {
		max-width: 46ch;
		margin-block-start: var(--space-md);
		font-size: var(--fs-lead);
		line-height: var(--lh-snug);
		color: var(--text);
	}

	/* =====================================================================
	   A — narrative
	   ===================================================================== */

	.about__chapters {
		display: grid;
		gap: var(--space-3xl);
	}

	.about__chapter {
		display: grid;
		gap: var(--space-lg);
		align-items: center;
	}

	@media (width >= 64rem) {
		.about__chapter {
			grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
			gap: var(--space-2xl);
		}

		/* Alternating sides. Three identical left-image blocks would read as a
		   template; alternating makes the page feel written. */
		.about__chapter:nth-child(even) .about__chapter-media {
			order: 2;
		}
	}

	.about__chapter-media {
		overflow: hidden;
		border-radius: var(--r-xl);
		background: var(--surface-sunken);
	}

	.about__photo {
		width: 100%;
		height: auto;
		aspect-ratio: 4 / 3;
		object-fit: cover;
	}

	.about__chapter-copy h2 {
		max-width: 20ch;
		font-size: var(--fs-h2);
		text-wrap: balance;
	}

	.about__chapter-copy p {
		max-width: 52ch;
		margin-block-start: var(--space-sm);
		font-size: var(--fs-lead);
		line-height: var(--lh-snug);
		color: var(--text);
	}

	/* =====================================================================
	   B — facts
	   ===================================================================== */

	.about__facts {
		display: grid;
		gap: var(--space-lg);
		grid-template-columns: 1fr;
	}

	@media (width >= 48rem) {
		.about__facts {
			grid-template-columns: repeat(2, minmax(0, 1fr));
			gap: var(--space-xl);
		}
	}

	.about__fact h2 {
		font-size: var(--fs-h3);
	}

	.about__fact p {
		max-width: 46ch;
		margin-block-start: var(--space-2xs);
		color: var(--text);
	}

	.about__figure {
		margin: var(--space-2xl) 0 0;
		overflow: hidden;
		border-radius: var(--r-2xl);
		background: var(--surface-sunken);
	}

	.about__figure .about__photo {
		aspect-ratio: 21 / 9;
	}

	/* =====================================================================
	   Contact
	   ===================================================================== */

	.about__contact-inner {
		display: grid;
		gap: var(--space-xl);
	}

	@media (width >= 56rem) {
		.about__contact-inner {
			grid-template-columns: minmax(0, 1fr) minmax(0, 22rem);
			gap: var(--space-2xl);
		}
	}

	.about__details {
		display: grid;
		gap: var(--space-sm);
		margin-block-start: var(--space-md);
	}

	.about__details div {
		display: grid;
		gap: var(--space-3xs);
	}

	.about__details dt {
		font-size: var(--fs-xs);
		font-weight: 700;
		letter-spacing: var(--ls-label);
		text-transform: uppercase;
		color: var(--text-muted);
	}

	.about__details dd {
		margin: 0;
		font-size: var(--fs-lead);
		color: var(--text-strong);
	}

	.about__contact-pending {
		max-width: 52ch;
		margin-block-start: var(--space-md);
		color: var(--text-muted);
	}

	.about__contact-app {
		display: grid;
		gap: var(--space-2xs);
		align-content: start;
		padding: var(--space-lg);
		border-radius: var(--r-2xl);
		background: var(--surface-inverse);
		color: var(--footer-text);
	}

	.about__contact-app h2 {
		color: var(--text-inverse);
		font-size: var(--fs-h3);
	}

	.about__contact-app .button {
		margin-block-start: var(--space-2xs);
		justify-self: start;
	}
}

/* ==== sections/branches.css ==== */
/* /about — branches
 *
 * The chooser is the whole point of this section, and it is deliberately the
 * quietest thing in it: type on the page's own background, one rule beneath it,
 * and the chosen name underlined in blue. No pill, no tint, no box. It sits
 * immediately above a full-width photograph, and anything with a fill of its own
 * would fight that photograph for the eye at exactly the moment the reader is
 * meant to be looking at the shop.
 */

@layer components {
	.branches__title {
		font-size: var(--fs-h2);
		text-wrap: balance;
	}

	.branches__lead {
		max-width: 56ch;
		margin-block-start: var(--space-sm);
		font-size: var(--fs-lead);
		line-height: var(--lh-snug);
		color: var(--text);
	}

	/* =====================================================================
	   The chooser
	   ===================================================================== */

	.branches__choose {
		display: flex;
		gap: var(--space-lg);
		margin-block: var(--space-xl) var(--space-lg);
		border-block-end: 1px solid var(--line);

		/* More branches than fit are scrolled to, never wrapped into a second
		   row — a chooser that changes height as it grows would push the
		   photograph down the page. */
		overflow-x: auto;
		overscroll-behavior-inline: contain;
		scrollbar-width: none;
		scroll-snap-type: inline proximity;
		-webkit-overflow-scrolling: touch;
		/* With the scrollbar hidden the fade is the only sign there is more to
		   the right — the same affordance the shop's department rail uses. */
		mask-image: linear-gradient(
			to right,
			#000 0,
			#000 calc(100% - var(--space-lg)),
			transparent 100%
		);
	}

	.branches__choose::-webkit-scrollbar {
		display: none;
	}

	.branches__pick {
		flex: none;
		display: inline-flex;
		align-items: center;
		/* It is a tab, and a tab is pressed with a thumb here. */
		min-height: 44px;
		scroll-snap-align: start;
		padding-block: var(--space-2xs);
		/* Overlaps the container's rule so the active underline replaces it
		   rather than sitting above it. */
		margin-block-end: -1px;
		border-block-end: 2px solid transparent;
		color: var(--text-muted);
		font-size: var(--fs-lead);
		font-weight: 700;
		letter-spacing: var(--ls-heading);
		text-decoration: none;
		transition:
			color var(--t-fast) var(--ease-out),
			border-color var(--t-fast) var(--ease-out);
	}

	.branches__pick:hover {
		color: var(--text-strong);
		border-block-end-color: var(--line-strong);
	}

	.branches__pick[aria-current] {
		color: var(--text-strong);
		border-block-end-color: var(--accent);
	}

	/* =====================================================================
	   A branch
	   ===================================================================== */

	.branches__panel[hidden] {
		display: none;
	}

	.branches__figure {
		margin: 0;
		overflow: hidden;
		border-radius: var(--r-2xl);
		background: var(--surface-sunken);
	}

	.branches__photo {
		width: 100%;
		height: auto;
		aspect-ratio: 3 / 2;
		object-fit: cover;
	}

	@media (width >= 48rem) {
		.branches__photo {
			aspect-ratio: 21 / 9;
		}
	}

	.branches__body {
		display: grid;
		gap: var(--space-xl);
		margin-block-start: var(--space-xl);
	}

	@media (width >= 64rem) {
		.branches__body {
			grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
			gap: var(--space-2xl);
		}
	}

	.branches__tagline {
		font-size: var(--fs-h3);
		font-weight: 700;
		letter-spacing: var(--ls-heading);
		color: var(--text-strong);
		text-wrap: balance;
	}

	.branches__story p + p {
		max-width: 60ch;
		margin-block-start: var(--space-sm);
		color: var(--text);
	}

	.branches__story .button {
		margin-block-start: var(--space-lg);
	}

	.branches__subhead {
		font-size: var(--fs-sm);
		font-weight: 700;
		letter-spacing: var(--ls-label);
		text-transform: uppercase;
		color: var(--text-muted);
	}

	.branches__aside {
		display: grid;
		gap: var(--space-sm);
		align-content: start;
	}

	.branches__features {
		display: grid;
		gap: var(--space-2xs);
	}

	.branches__features li {
		display: grid;
		grid-template-columns: auto minmax(0, 1fr);
		gap: var(--space-2xs);
		align-items: start;
		color: var(--text);
	}

	.branches__tick {
		/* Optical, not mathematical: the tick reads as centred on the first line
		   of text only once it is nudged down onto its baseline. */
		margin-block-start: 3px;
		color: var(--accent);
	}

	.branches__details {
		display: grid;
		gap: var(--space-xs);
		margin: 0;
		padding-block-start: var(--space-sm);
		border-block-start: 1px solid var(--line);
	}

	.branches__details div {
		display: grid;
		gap: var(--space-3xs);
	}

	.branches__details dt {
		font-size: var(--fs-xs);
		font-weight: 700;
		letter-spacing: var(--ls-label);
		text-transform: uppercase;
		color: var(--text-muted);
	}

	.branches__details dd {
		margin: 0;
		color: var(--text-strong);
	}

	.branches__call {
		display: inline-flex;
		align-items: center;
		gap: var(--space-2xs);
		width: fit-content;
		font-weight: 700;
		color: var(--accent);
	}

	.branches__map-card {
		overflow: hidden;
		margin-block-start: var(--space-2xs);
		border-radius: var(--r-lg);
		background: var(--surface);
		box-shadow: var(--shadow-sm);
	}

	.branches__map {
		display: block;
		inline-size: 100%;
		block-size: 180px;
		border: 0;
		background: var(--surface-sunken);
	}

	.branches__map-copy {
		display: grid;
		gap: var(--space-2xs);
		padding: var(--space-sm);
		font-size: var(--fs-sm);
	}

	.branches__map-copy span,
	.branches__map-copy a {
		display: flex;
		align-items: flex-start;
		gap: var(--space-3xs);
	}

	.branches__map-copy span {
		color: var(--text);
	}

	.branches__map-copy a {
		width: fit-content;
		font-weight: 700;
		text-decoration: none;
	}

	.branches__block {
		margin-block-start: var(--space-2xl);
	}

	/* =====================================================================
	   Gallery
	   ===================================================================== */

	.branches__gallery {
		display: grid;
		gap: var(--space-md);
		margin-block-start: var(--space-md);
		grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
	}

	.branches__gallery li {
		overflow: hidden;
		border-radius: var(--r-lg);
		background: var(--surface-sunken);
	}

	.branches__shot {
		display: block;
		width: 100%;
		height: auto;
		aspect-ratio: 4 / 3;
		object-fit: cover;
	}

	/* =====================================================================
	   Reviews
	   ===================================================================== */

	.branches__reviews {
		display: grid;
		gap: var(--space-md);
		margin-block-start: var(--space-md);
		grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr));
	}

	.branches__review {
		display: grid;
		align-content: start;
		gap: var(--space-2xs);
		padding: var(--space-lg);
		border: 1px solid var(--line);
		border-radius: var(--r-lg);
		background: var(--surface);
	}

	.branches__rating {
		display: flex;
		gap: 2px;
	}

	.branches__star {
		color: var(--reward);
	}

	.branches__star--empty {
		color: var(--gray-300);
	}

	.branches__review blockquote {
		margin: 0;
		color: var(--text-strong);
		font-size: var(--fs-lead);
		line-height: var(--lh-snug);
	}

	.branches__reviewer {
		display: flex;
		flex-wrap: wrap;
		gap: var(--space-2xs);
		font-size: var(--fs-sm);
		font-weight: 700;
		color: var(--text-strong);
	}

	.branches__reviewer span {
		font-weight: 500;
		color: var(--text-muted);
	}

	/* Present only while the copy is ours rather than MKB's. Legible, not dressed
	   up as a warning — it is a statement of fact, like the catalogue notice in
	   the footer. */
	.branches__sample {
		margin-block-start: var(--space-md);
		font-size: var(--fs-xs);
		color: var(--text-muted);
	}

	.branches__sample code {
		font-size: inherit;
	}
}

/* ==== sections/careers.css ==== */
/* /careers — useful evergreen content without pretending a vacancy is open. */

@layer components {
	.careers__masthead {
		padding-block: var(--space-lg) var(--space-2xl);
		margin-block-end: var(--space-lg);
		background: var(--surface-tint);
	}

	.careers__hero,
	.careers__interest-grid,
	.careers__cta-inner {
		display: grid;
		gap: var(--space-xl);
		align-items: center;
	}

	.careers__eyebrow {
		margin-block-end: var(--space-2xs);
		color: var(--accent);
		font-size: var(--fs-xs);
		font-weight: 700;
		letter-spacing: var(--ls-label);
		text-transform: uppercase;
	}

	.careers__title {
		max-width: 15ch;
		font-size: clamp(2.25rem, 1.2rem + 3.4vw, 4rem);
		line-height: var(--lh-display);
		letter-spacing: var(--ls-display);
		text-wrap: balance;
	}

	.careers__lead,
	.careers__section-head > p:last-child,
	.careers__interest-grid > div > p:last-child {
		max-width: 55ch;
		margin-block-start: var(--space-sm);
		color: var(--text);
		font-size: var(--fs-lead);
		line-height: var(--lh-snug);
	}

	.careers__actions {
		margin-block-start: var(--space-lg);
	}

	.careers__hero-media {
		margin: 0;
		overflow: hidden;
		border-radius: var(--r-2xl);
		background: var(--surface-sunken);
	}

	.careers__photo {
		display: block;
		inline-size: 100%;
		block-size: auto;
		aspect-ratio: 4 / 3;
		object-fit: cover;
	}

	.careers__section-head h2,
	.careers__interest h2,
	.careers__cta h2 {
		max-width: 22ch;
		font-size: var(--fs-h2);
		text-wrap: balance;
	}

	.careers__roles {
		display: grid;
		gap: var(--space-md);
		margin-block-start: var(--space-xl);
	}

	.careers__roles article {
		padding: var(--space-lg);
		border-radius: var(--r-lg);
		background: var(--surface);
		box-shadow: var(--shadow-sm);
	}

	.careers__roles h3,
	.careers__steps h3 {
		font-size: var(--fs-h3);
	}

	.careers__roles p,
	.careers__steps p {
		margin-block-start: var(--space-2xs);
		color: var(--text);
	}

	.careers__interest {
		background: var(--surface-tint);
	}

	.careers__steps {
		display: grid;
		gap: var(--space-sm);
		margin: 0;
		padding: 0;
		list-style: none;
	}

	.careers__steps li {
		display: grid;
		grid-template-columns: auto minmax(0, 1fr);
		gap: var(--space-sm);
		align-items: start;
	}

	.careers__steps li > span {
		display: grid;
		place-items: center;
		inline-size: 40px;
		block-size: 40px;
		border-radius: 50%;
		background: var(--accent);
		color: var(--text-on-accent);
		font-weight: 700;
	}

	.careers__cta {
		background: var(--surface-inverse);
		color: var(--footer-text);
	}

	.careers__cta h2 {
		color: var(--text-inverse);
	}

	.careers__cta .careers__eyebrow {
		color: var(--on-accent-muted);
	}

	.careers__cta .cluster {
		align-items: center;
	}

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

	@media (width >= 64rem) {
		.careers__hero {
			grid-template-columns: minmax(0, 1.1fr) minmax(22rem, 0.9fr);
			gap: var(--space-2xl);
		}

		.careers__interest-grid {
			grid-template-columns: minmax(0, 1fr) minmax(22rem, 0.85fr);
			gap: var(--space-2xl);
		}

		.careers__cta-inner {
			grid-template-columns: minmax(0, 1fr) auto;
		}
	}
}

/* ==== sections/loyalty.css ==== */
/* /loyalty — public loyalty programme overview. */

@layer components {
	.loyalty__masthead {
		padding-block: var(--space-lg) var(--space-2xl);
		margin-block-end: var(--space-lg);
		background:
			radial-gradient(120% 80% at 100% 0%, color-mix(in srgb, var(--accent) 18%, transparent), transparent 55%),
			var(--surface-tint);
	}

	.loyalty__hero,
	.loyalty__tiers-grid,
	.loyalty__notes-inner {
		display: grid;
		gap: var(--space-xl);
		align-items: center;
	}

	@media (width >= 56rem) {
		.loyalty__hero {
			grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
			gap: var(--space-2xl);
		}

		.loyalty__tiers-grid {
			grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
			align-items: start;
		}

		.loyalty__notes-inner {
			grid-template-columns: minmax(0, 1.2fr) minmax(16rem, 0.8fr);
		}
	}

	.loyalty__eyebrow {
		margin-block-end: var(--space-2xs);
		color: var(--accent);
		font-size: var(--fs-xs);
		font-weight: 700;
		letter-spacing: var(--ls-label);
		text-transform: uppercase;
	}

	.loyalty__title {
		max-width: 14ch;
		font-size: clamp(2.25rem, 1.2rem + 3.4vw, 4rem);
		line-height: var(--lh-display);
		letter-spacing: var(--ls-display);
		text-wrap: balance;
	}

	.loyalty__lead,
	.loyalty__section-head > p:last-child,
	.loyalty__tiers-grid > div > p:last-child,
	.loyalty__notes-inner > div > p:last-child {
		max-width: 52ch;
		margin-block-start: var(--space-sm);
		color: var(--text);
		font-size: var(--fs-lead);
		line-height: var(--lh-snug);
	}

	.loyalty__actions {
		margin-block-start: var(--space-lg);
	}

	.loyalty__card-stage {
		margin: 0;
		perspective: 1200px;
	}

	/* Framed card: slight image zoom inside, outer glow + breathe. */
	.loyalty__card-frame {
		--loyalty-tilt: -2.5deg;

		position: relative;
		inline-size: 100%;
		max-inline-size: 28rem;
		margin-inline: auto;
		aspect-ratio: 2126 / 1417;
		overflow: hidden;
		border-radius: 1.25rem;
		isolation: isolate;
		transform: rotate(var(--loyalty-tilt));
	}

	.loyalty__card-frame img,
	.loyalty__card {
		display: block;
		inline-size: 100%;
		block-size: 100%;
		object-fit: cover;
		/* Magnify artwork ~8% inside the frame. */
		transform: scale(1.08);
		transform-origin: center;
	}

	@keyframes loyalty-breathe {
		0%,
		100% {
			transform: rotate(var(--loyalty-tilt)) scale(1);
			filter: brightness(1);
		}
		50% {
			transform: rotate(var(--loyalty-tilt)) scale(1.035) translateY(-0.2rem);
			filter: brightness(1.06);
		}
	}

	@keyframes loyalty-glow-gold {
		0%,
		100% {
			box-shadow:
				0 0 0 1px color-mix(in srgb, #f0c14b 55%, transparent),
				0 0 1.1rem color-mix(in srgb, #f5c842 45%, transparent),
				0 0 2.4rem color-mix(in srgb, #d4a017 32%, transparent),
				0 1.25rem 2.5rem rgba(0, 0, 0, 0.16);
		}
		50% {
			box-shadow:
				0 0 0 1px color-mix(in srgb, #ffe08a 70%, transparent),
				0 0 1.75rem color-mix(in srgb, #ffd34d 65%, transparent),
				0 0 3.4rem color-mix(in srgb, #e6b422 48%, transparent),
				0 1.35rem 2.75rem rgba(0, 0, 0, 0.18);
		}
	}

	@keyframes loyalty-glow-diamond {
		0%,
		100% {
			box-shadow:
				0 0 0 1px color-mix(in srgb, #c9e7ff 55%, transparent),
				0 0 1.1rem color-mix(in srgb, #9fd4ff 42%, transparent),
				0 0 2.4rem color-mix(in srgb, #6eb6f0 30%, transparent),
				0 1.25rem 2.5rem rgba(0, 0, 0, 0.16);
		}
		50% {
			box-shadow:
				0 0 0 1px color-mix(in srgb, #eaf6ff 75%, transparent),
				0 0 1.75rem color-mix(in srgb, #b9e0ff 62%, transparent),
				0 0 3.4rem color-mix(in srgb, #7ec4f7 48%, transparent),
				0 1.35rem 2.75rem rgba(0, 0, 0, 0.18);
		}
	}

	.loyalty__card-frame--gold {
		box-shadow:
			0 0 0 1px color-mix(in srgb, #f0c14b 55%, transparent),
			0 0 1.25rem color-mix(in srgb, #f5c842 55%, transparent),
			0 0 2.75rem color-mix(in srgb, #d4a017 40%, transparent),
			0 1.25rem 2.5rem rgba(0, 0, 0, 0.16);
		animation:
			loyalty-breathe 4.2s ease-in-out infinite,
			loyalty-glow-gold 3.6s ease-in-out infinite;
	}

	.loyalty__card-frame--diamond {
		--loyalty-tilt: 1.5deg;

		box-shadow:
			0 0 0 1px color-mix(in srgb, #c9e7ff 60%, transparent),
			0 0 1.25rem color-mix(in srgb, #9fd4ff 50%, transparent),
			0 0 2.75rem color-mix(in srgb, #6eb6f0 38%, transparent),
			0 1.25rem 2.5rem rgba(0, 0, 0, 0.16);
		animation:
			loyalty-breathe 4.6s ease-in-out infinite,
			loyalty-glow-diamond 3.8s ease-in-out infinite;
		animation-delay: 0s, 0.4s;
	}

	@media (prefers-reduced-motion: reduce) {
		.loyalty__card-frame--gold,
		.loyalty__card-frame--diamond {
			animation: none;
		}
	}

	.loyalty__section-head h2,
	.loyalty__tiers-grid h2,
	.loyalty__notes-inner h2 {
		max-width: 18ch;
		font-size: var(--fs-h2);
		text-wrap: balance;
	}

	.loyalty__steps {
		display: grid;
		gap: var(--space-md);
		margin-block-start: var(--space-xl);
		padding: 0;
		list-style: none;
	}

	@media (width >= 48rem) {
		.loyalty__steps {
			grid-template-columns: repeat(3, minmax(0, 1fr));
		}
	}

	.loyalty__steps > li {
		display: grid;
		grid-template-columns: auto 1fr;
		gap: var(--space-sm);
		padding: var(--space-lg);
		border-radius: var(--r-lg);
		background: var(--surface);
		box-shadow: var(--shadow-xs);
	}

	.loyalty__step-num {
		display: grid;
		place-items: center;
		inline-size: 2rem;
		block-size: 2rem;
		border-radius: 50%;
		background: var(--accent);
		color: var(--text-on-accent);
		font-weight: 800;
		font-size: var(--fs-sm);
	}

	.loyalty__steps h3 {
		margin: 0;
		font-size: var(--fs-md);
	}

	.loyalty__steps p {
		margin-block-start: var(--space-2xs);
		color: var(--text-muted);
		font-size: var(--fs-sm);
	}

	.loyalty__tier-list {
		margin-block-start: var(--space-md);
		padding-inline-start: 1.1rem;
		color: var(--text);
		line-height: var(--lh-snug);
	}

	.loyalty__tier-list li + li {
		margin-block-start: var(--space-2xs);
	}

	.loyalty__gallery {
		display: grid;
		gap: var(--space-lg);
	}

	@media (width >= 40rem) {
		.loyalty__gallery {
			grid-template-columns: repeat(2, minmax(0, 1fr));
		}
	}

	.loyalty__gallery-item {
		margin: 0;
	}

	.loyalty__gallery-item .loyalty__card-frame {
		max-inline-size: none;
	}

	.loyalty__gallery-item--gold .loyalty__card-frame {
		--loyalty-tilt: -1.5deg;
	}

	.loyalty__gallery-item--diamond .loyalty__card-frame {
		--loyalty-tilt: 1.5deg;
	}

	.loyalty__gallery-item figcaption {
		margin-block-start: var(--space-sm);
		color: var(--text-muted);
		font-size: var(--fs-xs);
		font-weight: 700;
		letter-spacing: var(--ls-label);
		text-transform: uppercase;
		text-align: center;
	}

	.loyalty__notes {
		background: var(--surface-tint);
	}

	.loyalty__cta-box {
		display: grid;
		gap: var(--space-sm);
		justify-items: start;
		padding: var(--space-xl);
		border-radius: var(--r-xl);
		background: var(--surface);
		box-shadow: var(--shadow-sm);
	}

	.loyalty__cta-box p {
		margin: 0;
		font-size: var(--fs-lead);
		font-weight: 700;
		text-wrap: balance;
	}
}

/* ==== sections/notfound.css ==== */
/* 404.
 *
 * Composed rather than centred. A centred column of apologies is the default
 * every site has; this one is a page with a job — find what the visitor was
 * after — so it is laid out like the rest of the site and reads left to right.
 *
 * The numeral is set very large and very quiet. It states what happened without
 * a headline having to spend words on it, which is what frees the actual
 * heading to say something useful. */

@layer components {
	.notfound {
		padding-block-start: calc(var(--nav-h) + var(--space-2xl));
	}

	.notfound__code {
		font-size: clamp(4rem, 2rem + 12vw, 9rem);
		font-weight: 900;
		line-height: 0.82;
		letter-spacing: var(--ls-display);
		/* Quiet enough to read as texture rather than as the message. */
		color: var(--surface-sunken);
		user-select: none;
	}

	.notfound__title {
		margin-block-start: var(--space-sm);
		font-size: var(--fs-h1);
		line-height: var(--lh-tight);
		letter-spacing: var(--ls-display);
	}

	.notfound__lead {
		max-width: 46ch;
		margin-block-start: var(--space-sm);
		font-size: var(--fs-lead);
		line-height: var(--lh-snug);
		color: var(--text-muted);
	}

	/* --- Search ---------------------------------------------------------- */

	.notfound__search {
		margin-block-start: var(--space-xl);
	}

	.notfound__search-label {
		display: block;
		margin-block-end: var(--space-2xs);
		font-size: var(--fs-xs);
		font-weight: 700;
		letter-spacing: var(--ls-label);
		text-transform: uppercase;
		color: var(--text-muted);
	}

	.notfound__search-row {
		display: flex;
		flex-wrap: wrap;
		gap: var(--space-2xs);
	}

	.notfound__search-input {
		/* min-width: 0 lets this shrink below its intrinsic width, which is what
		   keeps a 320px screen from scrolling sideways. */
		flex: 1 1 12rem;
		min-width: 0;
		min-height: 48px;
		padding-inline: var(--space-sm);
		border: 0;
		border-radius: var(--r-pill);
		background: var(--surface);
		color: var(--text-strong);
		font: inherit;
		box-shadow: var(--shadow-sm);
	}

	.notfound__search-input::placeholder {
		color: var(--text-muted);
	}

	/* --- Departments ------------------------------------------------------ */

	.notfound__depts {
		margin-block-start: var(--space-xl);
	}

	.notfound__depts-title {
		font-size: var(--fs-xs);
		font-weight: 700;
		letter-spacing: var(--ls-label);
		text-transform: uppercase;
		color: var(--text-muted);
	}

	.notfound__depts-list {
		display: flex;
		flex-wrap: wrap;
		gap: var(--space-2xs);
		margin-block-start: var(--space-2xs);
		list-style: none;
		padding: 0;
	}

	.notfound__depts-list a {
		display: inline-flex;
		align-items: center;
		min-height: 40px;
		padding-inline: var(--space-md);
		border-radius: var(--r-pill);
		background: var(--surface);
		color: var(--text-strong);
		font-size: var(--fs-sm);
		font-weight: 700;
		text-decoration: none;
		box-shadow: var(--shadow-xs);
		transition:
			background-color var(--t-fast) var(--ease-out),
			transform var(--t-fast) var(--ease-out);
	}

	.notfound__depts-list a:hover {
		background: var(--accent-soft);
		color: var(--accent-hover);
		transform: translateY(-1px);
	}

	.notfound .cluster {
		margin-block-start: var(--space-xl);
	}
}

/* ==== utilities.css ==== */
/* ==========================================================================
   UTILITIES
   ========================================================================== */

@layer utilities {
	.visually-hidden:not(:focus):not(:active) {
		position: absolute;
		width: 1px;
		height: 1px;
		overflow: hidden;
		clip-path: inset(50%);
		white-space: nowrap;
	}

	.text-muted {
		color: var(--text-muted);
	}
	.text-lead {
		font-size: var(--fs-lead);
		line-height: var(--lh-snug);
	}
	.text-display {
		font-size: var(--fs-display);
		font-weight: 700; /* DESIGN.md: Display is Bold, not Black */
		line-height: var(--lh-display);
		letter-spacing: var(--ls-display);
		color: var(--text-strong);
		text-wrap: balance;
	}
}

/* ==== motion.css ==== */
/* ==========================================================================
   PROGRESSIVE ENHANCEMENT — squircles
   ==========================================================================
   iOS corners are not circular arcs; they are continuous curves, which is a
   large part of why Apple's UI feels softer than a plain border-radius. Where
   the browser supports it, opt in. Everywhere else the normal radius applies
   and nothing looks broken. */

@supports (corner-shape: squircle) {
	@layer components {
		.surface,
		.button {
			corner-shape: squircle;
		}
	}
}

/* ==========================================================================
   CROSS-DOCUMENT VIEW TRANSITIONS
   ==========================================================================
   The whole site exists to promote an app, so moving between its pages should
   not feel like a stack of documents. Two declarations buy that: the browser
   takes a picture of the outgoing page, a picture of the incoming one, and
   cross-fades between them.

   Two properties make it worth having rather than a gimmick:

     - The masthead and the floating logo are NAMED, so the browser matches
       them across the two pages and holds them still while everything else
       changes. The navigation stops re-drawing on every click.
     - It costs nothing where it is unsupported. A browser that does not know
       @view-transition ignores the whole block and navigates normally.

   Turning it off is deleting this block. Nothing else refers to it. */

@view-transition {
	navigation: auto;
}

::view-transition-old(root),
::view-transition-new(root) {
	/* Shorter than the browser default. A page transition that can be noticed
	   as a transition is already too slow. */
	animation-duration: 240ms;
	animation-timing-function: var(--ease-out, ease-out);
}

@media (prefers-reduced-motion: no-preference) {
	.masthead__bar {
		view-transition-name: masthead;
	}

	.masthead__brand {
		view-transition-name: brand;
	}
}

/* ==========================================================================
   SCROLL-DRIVEN REVEALS
   ==========================================================================
   Sections lift into place as they enter the viewport. Driven by the scroll
   position itself rather than by JavaScript: there is no observer, no listener
   and no layout read, so it cannot cost a frame.

   The @supports guard is load-bearing, not caution. These rules start at
   opacity 0 — in a browser that cannot run the timeline, that is a blank page
   rather than a missing animation. Inside the guard, a browser either supports
   both halves or sees neither.

   The other way content could be hidden permanently is a timeline that never
   becomes active, on a page too short to scroll. That was measured rather than
   assumed: on a viewport tall enough that no page scrolls, every [data-reveal]
   element resolves to opacity 1. The before-phase does not strand them.

   `view()` measures each element against the viewport individually, so a row
   of tiles animates as its own row rather than to a page-wide clock.

   The range is expressed entirely in `entry`, the plainest of the named ranges:
   begin as the element starts entering the viewport, finish when it is 70% of
   the way in. Mixing range names (entry ... cover ...) is legal and was tried
   first, but it is the corner of this feature most likely to differ between
   implementations, and it buys nothing here. */

@supports (animation-timeline: view()) {
	@media (prefers-reduced-motion: no-preference) {
		@layer utilities {
			[data-reveal] {
				animation: reveal-rise linear both;
				animation-timeline: view();
				animation-range: entry 0% entry 70%;
			}

			/* Something already on screen at load must not be mid-animation:
			   the reader would see the top of the page assemble itself around
			   them. The first section opts out. */
			[data-reveal="none"] {
				animation: none;
			}
		}

		@keyframes reveal-rise {
			from {
				opacity: 0;
				transform: translateY(22px);
			}
			to {
				opacity: 1;
				transform: none;
			}
		}
	}
}

/* ==========================================================================
   REDUCED MOTION
   ==========================================================================
   Honour the system setting. Movement is removed; nothing depends on animation
   to be understood. */

@media (prefers-reduced-motion: reduce) {
	html {
		scroll-behavior: auto;
	}

	*,
	*::before,
	*::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
		scroll-behavior: auto !important;
	}
}
