/* ==========================================================================
   Bike Wash Website — public website stylesheet
   --------------------------------------------------------------------------
   Hand written, shipped as a static file. There is no build step: no Vite,
   no npm, no Tailwind. A buyer uploads this folder and it works.

   This file is the SKELETON: layout, rhythm, components and behaviour, all
   of it driven by the custom properties in :root. It contains almost no
   colour of its own and no personality at all.

   public/css/themes.css is the other half. It carries five theme blocks,
   each selected by one class on <html>, and each one redefines the same
   tokens and adds its own rules. That split is the only reason five
   genuinely different looks are affordable: every section partial draws the
   same markup, and a theme decides what that markup feels like.

   Light and dark are a separate axis: [data-theme="dark"] on <html>, set
   before the first paint by an inline script. Every theme is defined for
   both.

   Sections
     1. Tokens
     2. Reset and base
     3. Layout: wrap, band, rhythm
     4. Header
     5. Footer
     6. Buttons and links
     7. Forms
     8. Cards and grids
     9. The section types
    10. List and article pages
    11. Reveal on scroll, and reduced motion
    12. Responsive
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   U+20C2 RUFIYAA SIGN
   --------------------------------------------------------------------------
   The sign was encoded in Unicode 18.0, which is weeks old at the time of
   writing. Every font installed on the machine this was built on was read and
   its cmap searched: 341 fonts, none of them carry it. A buyer's VISITOR will
   have no better a font than that, so a product that wants to print the sign
   has to bring the glyph with it. That is what this 1 KB file is.

   `unicode-range` is what makes putting Rufiyaa first in every font stack
   free: the browser downloads this face only when the page actually contains
   U+20C2, and every other character in the document falls straight through to
   the real body font. Nothing else on the page changes.

   The outlines come from artwork supplied by Devcity, traced to curves and
   built into a font by tools/rufiyaa/build_rufiyaa_font.py, which is checked
   in beside the artwork so the font can be rebuilt rather than trusted.

   A buyer who would rather print plain letters sets the currency symbol to
   `Rf` in Settings, and none of this is used.
   ------------------------------------------------------------------------ */
@font-face {
  font-family: Rufiyaa;
  src: url("../fonts/rufiyaa.woff2?v=1.0.0") format("woff2"),
       url("../fonts/rufiyaa.woff?v=1.0.0") format("woff");
  font-weight: 400 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+20C2;
}

:root {
  /* --brand and --accent, with their rgb channels, ink and shades, are
     written into the page by the layout from the buyer's two colour
     settings. The fallbacks here are only for a page rendered without
     them, which should not happen. */
  --brand: #1f5fbf;
  --brand-rgb: 31, 95, 191;
  --brand-ink: #ffffff;
  --brand-strong: #1a50a1;
  --brand-light: #5b8ad2;

  /* The brand used as INK rather than as a block. On a white page it is the
     colour itself; on a dark one it is lifted, because the buyer picked a
     colour that had to read on paper and a deep navy or a bottle green is
     all but invisible on a dark background. Editorial's navy measured
     1.55:1 against its own dark paper, which is a "Read more" nobody can
     see. Every `color:` use of the brand goes through this; every
     background use keeps --brand. */
  --brand-fg: var(--brand);
  --accent: #e08a1e;
  --accent-rgb: 224, 138, 30;
  --accent-ink: #10151f;
  --accent-strong: #bc7419;
  --accent-light: #e9a854;

  /* "Rufiyaa" is first on purpose, and it costs nothing. The @font-face at the
     top of this file declares a unicode-range of exactly U+20C2, so a browser
     downloads that 1 KB file only when the page actually prints the rufiyaa
     sign, and every other character falls straight through to the stack below.
     It is first rather than last because a fallback font that happens to gain
     the glyph later would otherwise win and draw a different mark. */
  --font-body: Rufiyaa, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-head: var(--font-body);
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --ink: #131820;
  --ink-2: #3c4654;
  --ink-3: #6a7483;
  --ink-on-dark: #f3f5f8;

  --bg: #ffffff;
  --surface: #ffffff;
  --surface-2: #f4f6f9;
  --surface-3: #e9edf3;
  --dark-bg: #0e1522;

  --line: #dfe4ec;
  --line-strong: #c3cbd8;

  --radius: 12px;
  --radius-lg: 20px;
  --radius-pill: 999px;

  --shadow-sm: 0 1px 2px rgba(15, 23, 42, .06);
  --shadow: 0 4px 16px rgba(15, 23, 42, .08);
  --shadow-lg: 0 18px 48px rgba(15, 23, 42, .14);

  --wrap: 1180px;
  --wrap-narrow: 760px;
  --gutter: 20px;
  --band-y: 4.5rem;
  --head-h: 68px;

  /* One height for everything in the header bar. The icon button used to be
     a fixed 38px beside a button whose height came out of its own padding,
     so the two controls next to each other were never the same size: 36 and
     38 in four themes, 48.5 and 38 in Bold. A theme that changes its button
     padding moves this with it. */
  --ctl-h: 42px;

  /* Typography personality. Every theme moves these rather than restating
     the rules that use them. */
  --h1: clamp(2.1rem, 5.2vw, 3.5rem);
  --h2: clamp(1.55rem, 3.2vw, 2.25rem);
  --h3: 1.18rem;
  --head-weight: 700;
  --head-tracking: -0.02em;
  --head-transform: none;
  --head-line: 1.14;
  --body-size: 16px;
  --body-line: 1.68;

  /* What a card is. Glass rewrites all four. */
  --card-bg: var(--surface);
  --card-border: 1px solid var(--line);
  --card-shadow: var(--shadow-sm);
  --card-blur: none;

  --hero-min: 68vh;

  color-scheme: light;
}

html[data-theme="dark"] {
  --ink: #eef1f6;
  --ink-2: #c3cbd8;
  --ink-3: #93a0b2;

  /* --brand-light is only a 22% lift, which is enough for a mid tone and not
     enough for a dark one, so the real lift is a mix and --brand-light is
     the fallback where color-mix is not available. */
  --brand-fg: var(--brand-light);

  --bg: #0d1420;
  --surface: #141d2c;
  --surface-2: #192335;
  --surface-3: #202c42;

  --line: #26324a;
  --line-strong: #364461;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .5);
  --shadow: 0 6px 20px rgba(0, 0, 0, .5);
  --shadow-lg: 0 20px 52px rgba(0, 0, 0, .6);

  --card-bg: var(--surface);

  color-scheme: dark;
}

@supports (color: color-mix(in srgb, #000 50%, #fff)) {
  html[data-theme="dark"] { --brand-fg: color-mix(in srgb, var(--brand) 42%, #ffffff); }
}

/* --------------------------------------------------------------------------
   2. Reset and base
   -------------------------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Every in page anchor lands below the sticky header rather than under
     it, including the enquiry form the contact redirect jumps to. */
  scroll-padding-top: calc(var(--head-h) + 16px);
}

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--body-size);
  line-height: var(--body-line);
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  /* Nothing on this site may scroll sideways. A theme that wants a wide
     decorative layer clips it here rather than letting the page move. */
  overflow-x: hidden;
}

h1, h2, h3, h4 {
  margin: 0 0 .5rem;
  font-family: var(--font-head);
  font-weight: var(--head-weight);
  letter-spacing: var(--head-tracking);
  text-transform: var(--head-transform);
  line-height: var(--head-line);
  color: var(--ink);
}
h1 { font-size: var(--h1); }
h2 { font-size: var(--h2); }
h3 { font-size: var(--h3); }
h4 { font-size: 1rem; }

p { margin: 0 0 1rem; }
p:last-child { margin-bottom: 0; }

a { color: var(--brand-fg); text-decoration: none; }
a:hover { text-decoration: underline; }

img, svg, video { max-width: 100%; height: auto; display: block; }

ul, ol { margin: 0 0 1rem; padding-left: 1.2rem; }
li { margin-bottom: .3rem; }

blockquote { margin: 0; }

hr { border: 0; border-top: 1px solid var(--line); margin: 2rem 0; }

code, pre { font-family: var(--font-mono); font-size: .9em; }

[hidden] { display: none !important; }
[x-cloak] { display: none !important; }

::selection { background: var(--brand); color: var(--brand-ink); }

:focus-visible {
  /* --brand-fg, not --brand: a dark brand colour draws a focus ring you
     cannot see on a dark page, which is the one ring that has to be
     visible. */
  outline: 3px solid var(--brand-fg);
  outline-offset: 2px;
  border-radius: 3px;
}

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 200;
  padding: .7rem 1.1rem;
  background: var(--brand);
  color: var(--brand-ink);
  border-radius: 0 0 var(--radius) 0;
}
/* Inset a little rather than flush into the corner, so the focus ring is
   drawn on all four sides. A ring cut off by the top and the left of the
   window is the one ring a keyboard user cannot do without. */
.skip:focus { left: 6px; top: 6px; border-radius: var(--radius); }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

.muted { color: var(--ink-3); }
.small { font-size: .875rem; }
.center { text-align: center; }
.nowrap { white-space: nowrap; }
.mb-0 { margin-bottom: 0; }

/* --------------------------------------------------------------------------
   3. Layout: wrap, band, rhythm
   -------------------------------------------------------------------------- */

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin: 0 auto;
  /* One place decides the side gutter, and it is never a padding shorthand
     that a band could zero out. */
  padding-inline: var(--gutter);
}
.wrap--narrow { max-width: var(--wrap-narrow); }

.band {
  position: relative;
  padding-block: var(--band-y);
}
.band:first-child { padding-top: calc(var(--band-y) * .7); }

/* Every other ordinary band carries this. Each theme decides what it means:
   a faint tint in Classic, a solid colour block in Bold, nothing at all in
   Editorial. One hook, five readings. */
.band--alt { background: var(--surface-2); }

.band--flush { padding-block: 0; }

.band__head {
  max-width: 720px;
  margin-bottom: 2.2rem;
}
.band__head--center { margin-inline: auto; text-align: center; }

/* --tracking rather than a bare letter-spacing, and the matching negative
   right margin, because letter spacing puts a space after the LAST letter
   too and that space is inside the box. A centred line therefore sits half
   a tracking unit to the left of true centre: about 1.2px on Bold's
   eyebrow, directly under a centred display heading, which is exactly where
   it shows. Pulling the right edge back by one unit cancels it, and on a
   left aligned eyebrow it changes nothing at all. A theme that wants
   different tracking moves --tracking, not letter-spacing. */
.eyebrow {
  display: block;
  --tracking: .14em;
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: var(--tracking);
  margin-right: calc(var(--tracking) * -1);
  text-transform: uppercase;
  color: var(--brand-fg);
  margin-bottom: .6rem;
}

.lede {
  font-size: 1.08rem;
  color: var(--ink-2);
  margin-bottom: 0;
}

.prose { max-width: 70ch; color: var(--ink-2); }
.prose h2, .prose h3 { color: var(--ink); margin-top: 2rem; }
.prose h2:first-child, .prose h3:first-child { margin-top: 0; }
.prose--wide { max-width: none; }
.prose--center { margin-inline: auto; text-align: center; }

/* --------------------------------------------------------------------------
   4. Header
   -------------------------------------------------------------------------- */

.site-head {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}

.site-head__inner {
  display: flex;
  align-items: center;
  gap: 1rem;
  min-height: var(--head-h);
}

.brand {
  display: flex;
  align-items: center;
  gap: .65rem;
  color: var(--ink);
  font-weight: 700;
  letter-spacing: -0.01em;
  min-width: 0;
  flex: 0 1 auto;
}
.brand:hover { text-decoration: none; }
.brand__mark {
  width: 38px; height: 38px;
  border-radius: 10px;
  display: grid; place-items: center;
  background: var(--brand);
  color: var(--brand-ink);
  font-weight: 800;
  font-size: .9rem;
  overflow: hidden;
  flex: 0 0 auto;
}
.brand__mark img { width: 100%; height: 100%; object-fit: contain; }
.brand__text { min-width: 0; }
.brand__name {
  display: block;
  font-size: 1.02rem;
  line-height: 1.2;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The tagline in the header.
   It was one nowrap line with an ellipsis, which assumed a short tagline. Of
   the forty four businesses shipped with this product, thirty two write one
   longer than fits, so the ellipsis was the normal case rather than the
   exception: a buyer typing their own real line gets it cut off in the corner
   of every page, which reads as the product failing rather than as their own
   words being long.
   It wraps to two lines and is clamped there instead, so a long tagline is
   READ and a very long one still cannot push the header out of shape. The
   nowrap and the ellipsis stay gone: there is no width at which cutting a
   sentence mid word is the better answer. */
.brand__tag {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  font-size: .74rem;
  line-height: 1.32;
  font-weight: 500;
  color: var(--ink-3);
  letter-spacing: 0;
  max-width: 44ch;
}

.site-nav {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: .25rem;
}
/* `:not(.btn)` is the whole reason the call to action in the header looks
   like a button at all. `.site-nav a` is one class and one type, which
   outranks every rule in the button block, so the button used to be drawn
   with the NAV LINK's box: 11px of side padding where every other button on
   the site has 20, weight 550 instead of 650, and 36px tall beside a 38px
   icon button. Colour was patched here once (the three rules below); the
   size, the padding and the weight were not, and the only way to see it is
   to measure the header rather than read it.

   Excluding the button also lets each theme's own button rules reach it,
   which is why Bold was the one theme that looked right. */
.site-nav a:not(.btn) {
  padding: .5rem .7rem;
  border-radius: var(--radius);
  color: var(--ink-2);
  font-size: .94rem;
  font-weight: 550;
  white-space: nowrap;
}
.site-nav a:not(.btn):hover { background: var(--surface-2); color: var(--ink); text-decoration: none; }
.site-nav a.is-active { color: var(--brand-fg); }
.site-nav .btn { margin-left: .4rem; white-space: nowrap; }
/* Kept, and now belt and braces: without them the call to action was drawn
   in the nav link colour, dark blue wording on a dark blue button. */
.site-nav a.btn { color: var(--ink); }
.site-nav a.btn--brand { color: var(--brand-ink); }
.site-nav a.btn--accent { color: var(--accent-ink); }
.site-nav a.btn:hover { text-decoration: none; }

.head-tools { display: flex; align-items: center; gap: .35rem; flex: 0 0 auto; }

.icon-btn {
  display: inline-grid; place-items: center;
  width: var(--ctl-h); height: var(--ctl-h);
  border: 1px solid var(--line);
  background: transparent;
  color: var(--ink-2);
  border-radius: var(--radius);
  cursor: pointer;
}
.icon-btn:hover { background: var(--surface-2); color: var(--ink); }
.icon-btn svg { width: 18px; height: 18px; }

.nav-toggle { display: none; }

.nav-scrim {
  position: fixed;
  inset: var(--head-h) 0 0;
  background: rgba(10, 16, 26, .45);
  z-index: 90;
}

/* --------------------------------------------------------------------------
   5. Footer
   -------------------------------------------------------------------------- */

.site-foot {
  background: var(--dark-bg);
  color: #c9d2e0;
  padding-block: 3.2rem 1.5rem;
  margin-top: 0;
}
/* The height matches the logo lockup in the first column, so the four
   column headings sit on one line and the four lists under them start on
   one line too. Without it the first column leads with a 34px mark and the
   other three with a 22px line of small capitals, and the footer's top line
   steps down by about 10px between column one and column two. */
.site-foot h4 {
  display: flex;
  align-items: center;
  min-height: 34px;
  color: #ffffff;
  font-size: .82rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  margin-bottom: .9rem;
}
.site-foot a { color: #c9d2e0; }
.site-foot a:hover { color: #ffffff; }
.site-foot p { color: #a8b3c4; }

.foot-grid {
  display: grid;
  gap: 2rem;
  grid-template-columns: minmax(0, 1.4fr) repeat(auto-fit, minmax(160px, 1fr));
}
.foot-links { list-style: none; margin: 0; padding: 0; }
.foot-links li { margin-bottom: .45rem; }

.foot-brand { display: flex; align-items: center; gap: .6rem; margin-bottom: .9rem; }
.foot-brand .brand__mark { width: 34px; height: 34px; }
.foot-brand span { color: #fff; font-weight: 700; }

.foot-hours { list-style: none; margin: 0; padding: 0; font-size: .9rem; }
.foot-hours li { margin-bottom: .35rem; color: #a8b3c4; }

.social-row { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: 1rem; }
.social-row a {
  width: 36px; height: 36px;
  display: grid; place-items: center;
  border: 1px solid rgba(255, 255, 255, .18);
  border-radius: var(--radius);
  color: #dfe5ee;
}
.social-row a:hover { background: var(--brand); border-color: var(--brand); color: var(--brand-ink); }
.social-row svg { width: 17px; height: 17px; }

.foot-base {
  margin-top: 2.4rem;
  padding-top: 1.2rem;
  border-top: 1px solid rgba(255, 255, 255, .12);
  display: flex;
  flex-wrap: wrap;
  gap: .6rem 1.2rem;
  align-items: center;
  font-size: .86rem;
}
.foot-base__links { margin-left: auto; display: flex; flex-wrap: wrap; gap: 1rem; }

/* The floating WhatsApp button. Only drawn when a number is set. */
.wa-fab {
  position: fixed;
  right: 18px;
  bottom: 18px;
  z-index: 95;
  width: 52px; height: 52px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: #1faa53;
  color: #fff;
  box-shadow: var(--shadow-lg);
}
.wa-fab:hover { background: #179245; text-decoration: none; }
.wa-fab svg { width: 26px; height: 26px; }

/* --------------------------------------------------------------------------
   6. Buttons and links
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .45rem;
  padding: .68rem 1.25rem;
  /* The same height as the icon buttons beside it in the header, and the
     same height as every other button whatever its label, so a row of two
     never comes out 41.8 and 42.1. */
  min-height: var(--ctl-h);
  font: inherit;
  font-size: .95rem;
  font-weight: 650;
  line-height: 1.2;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: var(--surface-2);
  color: var(--ink);
  cursor: pointer;
  text-align: center;
  transition: background .15s ease, border-color .15s ease, transform .15s ease, color .15s ease;
}
.btn:hover { text-decoration: none; }
.btn svg { width: 17px; height: 17px; flex: 0 0 auto; }

.btn--brand { background: var(--brand); border-color: var(--brand); color: var(--brand-ink); }
.btn--brand:hover { background: var(--brand-strong); border-color: var(--brand-strong); }

.btn--accent { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.btn--accent:hover { background: var(--accent-strong); border-color: var(--accent-strong); }

.btn--outline { background: transparent; border-color: var(--line-strong); color: var(--ink); }
.btn--outline:hover { border-color: var(--brand-fg); color: var(--brand-fg); }

/* On a dark hero or colour block the outline has to be drawn in the ink of
   that block, not in the page's own. */
.on-dark .btn--outline { border-color: rgba(255, 255, 255, .5); color: #fff; }
.on-dark .btn--outline:hover { background: rgba(255, 255, 255, .12); border-color: #fff; color: #fff; }

.btn--lg { padding: .85rem 1.6rem; font-size: 1.02rem; }
.btn--block { width: 100%; }

.btn-row { display: flex; flex-wrap: wrap; align-items: stretch; gap: .7rem; }

/* A centred band centres its heading and its wording and then left aligned
   its buttons, because .btn-row is a flex row and text-align does not reach
   a flex container's items. It was plainly wrong on Bold's home page, where
   an eyebrow, a display heading and two lines of type are all centred over
   two buttons hugging the left edge of the 780px column. Wherever the text
   above is centred, so is the row under it. */
.hero--center .btn-row,
.band__head--center ~ .btn-row,
.prose--center ~ .btn-row { justify-content: center; }

.text-link {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  font-weight: 650;
  color: var(--brand-fg);
}
.text-link svg { width: 15px; height: 15px; transition: transform .15s ease; }
.text-link:hover { text-decoration: none; }
.text-link:hover svg { transform: translateX(3px); }

/* --------------------------------------------------------------------------
   7. Forms
   -------------------------------------------------------------------------- */

.form-grid {
  display: grid;
  gap: 1.1rem;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.form-field { min-width: 0; }
.form-field--wide { grid-column: 1 / -1; }

.label {
  display: block;
  font-size: .88rem;
  font-weight: 650;
  color: var(--ink);
  margin-bottom: .35rem;
}
.label__req { color: #c0392b; }

.input, .textarea, .select {
  width: 100%;
  padding: .72rem .85rem;
  font: inherit;
  font-size: .96rem;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
}
.textarea { min-height: 140px; resize: vertical; }
.input:focus, .textarea:focus, .select:focus {
  outline: none;
  border-color: var(--brand-fg);
  box-shadow: 0 0 0 3px rgba(var(--brand-rgb), .22);
}
.input::placeholder, .textarea::placeholder { color: var(--ink-3); }

.field-error {
  display: block;
  margin-top: .3rem;
  font-size: .85rem;
  font-weight: 550;
  color: #c0392b;
}
html[data-theme="dark"] .field-error { color: #ff9c92; }

/* The honeypot. Hidden from people in three ways at once, because a form
   filler that reads only one of them still fills it in, and a screen reader
   must not announce it either. */
.hp {
  position: absolute !important;
  left: -9999px !important;
  width: 1px; height: 1px;
  overflow: hidden;
  opacity: 0;
}

.notice {
  display: flex;
  gap: .7rem;
  align-items: flex-start;
  padding: .9rem 1.1rem;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--surface-2);
  margin-bottom: 1.4rem;
}
.notice svg { width: 19px; height: 19px; flex: 0 0 auto; margin-top: .15rem; }
.notice--ok { background: rgba(16, 150, 90, .1); border-color: rgba(16, 150, 90, .35); color: #0c6c44; }
.notice--bad { background: rgba(192, 57, 43, .1); border-color: rgba(192, 57, 43, .35); color: #a8281c; }
html[data-theme="dark"] .notice--ok { color: #6fdda6; }
html[data-theme="dark"] .notice--bad { color: #ff9c92; }

/* --------------------------------------------------------------------------
   8. Cards and grids
   -------------------------------------------------------------------------- */

/* auto-FIT, not auto-fill, and the difference is only visible when there are
   fewer items than columns: auto-fill keeps the empty tracks, so three team
   members in a four column grid sit in the left three quarters of the band
   with a hole on the right, 145px off centre under a heading that IS
   centred. auto-fit collapses the empty track and the row fills the width.
   With a full row the two behave identically, so nothing else moves. */
.grid { display: grid; gap: 1.4rem; }
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); }
.grid--4 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr)); }

.card {
  background: var(--card-bg);
  border: var(--card-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--card-shadow);
  backdrop-filter: var(--card-blur);
  -webkit-backdrop-filter: var(--card-blur);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}
.card__body { padding: 1.35rem; flex: 1 1 auto; }
.card__body > :last-child { margin-bottom: 0; }
.card__foot { padding: 0 1.35rem 1.35rem; }

a.card:hover, .card--link:hover { box-shadow: var(--shadow); text-decoration: none; }
a.card:hover .card__title { color: var(--brand-fg); }

.card__title { font-size: 1.1rem; margin-bottom: .45rem; color: var(--ink); }
.card__text { color: var(--ink-3); font-size: .94rem; margin-bottom: 0; }
.card__meta {
  display: flex; flex-wrap: wrap; gap: .5rem 1rem;
  font-size: .82rem; color: var(--ink-3);
  margin-bottom: .5rem;
}

/* A PRICE is not metadata.
   The bookable cards began by reusing .card__meta, which is .82rem because it
   was written for "Client, 2026" under a work item. On a booking card the
   number is the thing a visitor came to the page to read, and at that size it
   printed smaller than the summary above it — in the editorial theme, whose
   .card__meta adds small capitals and letter spacing, smaller still and in a
   different case from every other figure on the site.
   So the price has a class of its own: the amount carries the weight, the unit
   stays quiet beside it, and no theme's metadata styling reaches either. */
.card__price {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .1rem .5rem;
  margin-bottom: .55rem;
}
.card__price strong {
  font-size: 1.15rem; color: var(--ink);
  letter-spacing: -0.01em; font-variant: normal;
}
.card__price .muted { font-size: .82rem; }

/* A cover picture, or a deterministic gradient when there is none. The
   aspect ratio is fixed so a grid of cards is a grid and not a staircase,
   whatever shape the buyer uploads. */
.cover {
  aspect-ratio: 16 / 10;
  background: var(--surface-3);
  background-size: cover;
  background-position: center;
  position: relative;
  display: grid;
  place-items: center;
  flex: 0 0 auto;
}
.cover--tall { aspect-ratio: 4 / 5; }
.cover--wide { aspect-ratio: 21 / 9; }
.cover__monogram {
  font-size: clamp(1.8rem, 6vw, 3rem);
  font-weight: 800;
  color: rgba(255, 255, 255, .92);
  letter-spacing: .04em;
  text-shadow: 0 2px 10px rgba(0, 0, 0, .3);
}
.cover img { width: 100%; height: 100%; object-fit: cover; }

.icon-tile {
  width: 46px; height: 46px;
  border-radius: 12px;
  display: grid; place-items: center;
  background: rgba(var(--brand-rgb), .12);
  color: var(--brand-fg);
  margin-bottom: 1rem;
}
.icon-tile svg { width: 22px; height: 22px; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  padding: .2rem .6rem;
  border-radius: var(--radius-pill);
  background: var(--surface-3);
  color: var(--ink-2);
  font-size: .78rem;
  font-weight: 650;
}
.chip--brand { background: rgba(var(--brand-rgb), .14); color: var(--brand-fg); }
.chip-row { display: flex; flex-wrap: wrap; gap: .5rem; margin-bottom: 1.6rem; }
a.chip:hover { text-decoration: none; background: var(--brand); color: var(--brand-ink); }
a.chip.is-active { background: var(--brand); color: var(--brand-ink); }

/* --------------------------------------------------------------------------
   9. The section types
   -------------------------------------------------------------------------- */

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

.hero {
  position: relative;
  display: flex;
  align-items: center;
  min-height: var(--hero-min);
  padding-block: calc(var(--band-y) * 1.2);
  background: var(--surface-2);
  background-size: cover;
  background-position: center;
  overflow: hidden;
}
.hero--short { --hero-min: 40vh; }
.hero--tall { --hero-min: 84vh; }

/* The overlay is a separate layer rather than a gradient baked into the
   background, so the same hero works with a photograph, with the shipped
   artwork and with no picture at all. */
.hero__veil {
  position: absolute;
  inset: 0;
  background: linear-gradient(105deg, rgba(10, 16, 26, .78), rgba(10, 16, 26, .35));
}
.hero__inner { position: relative; z-index: 2; width: 100%; }
.hero__content { max-width: 680px; }
.hero--center .hero__content { max-width: 780px; margin-inline: auto; text-align: center; }
.hero h1 { margin-bottom: 1rem; }
.hero__sub {
  font-size: clamp(1.02rem, 2vw, 1.2rem);
  margin-bottom: 1.8rem;
  max-width: 60ch;
}
.hero--center .hero__sub { margin-inline: auto; }

/* on-dark is applied by the partial whenever the hero has a picture or a
   veil behind it, so the ink is decided by what is actually there rather
   than by the theme hoping. */
.on-dark, .on-dark h1, .on-dark h2, .on-dark h3 { color: var(--ink-on-dark); }
.on-dark .hero__sub, .on-dark .lede, .on-dark p { color: rgba(255, 255, 255, .88); }
.on-dark .eyebrow { color: #fff; opacity: .85; }

/* The shipped artwork, used when no photograph has been uploaded.
   Faint by default, and that is a correctness rule rather than taste: the
   hero only switches to light ink when there is a real PICTURE behind it,
   so a strong dark drawing under dark type would be unreadable. A theme
   that wants the artwork louder also has to darken the hero and light the
   ink, and Parallax and Bold both do exactly that. */
.hero__art {
  position: absolute;
  inset: 0;
  z-index: 1;
  background-size: cover;
  background-position: center;
  opacity: .18;
}

/* --- features / services / work grids ---------------------------------- */

.feature { text-align: left; }
.feature h3 { margin-bottom: .4rem; }
.feature p { color: var(--ink-3); font-size: .94rem; }

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

.band--stats {
  background: var(--brand);
  color: var(--brand-ink);
  background-size: cover;
  background-position: center;
}
.band--stats h2 { color: inherit; }
.stat-row {
  display: grid;
  gap: 1.6rem;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr));
  text-align: center;
}
.stat__value {
  font-family: var(--font-head);
  font-size: clamp(2rem, 5vw, 3rem);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1;
}
.stat__label {
  display: block;
  --tracking: .1em;
  margin-top: .5rem;
  font-size: .86rem;
  letter-spacing: var(--tracking);
  /* Same reason as the eyebrow: the trailing space belongs to the box, and
     this column is centred. */
  margin-right: calc(var(--tracking) * -1);
  text-transform: uppercase;
  opacity: .85;
}

/* --- logos / names band ------------------------------------------------- */

.logo-row {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 2.4rem;
  align-items: center;
  justify-content: center;
}
.logo-row__item {
  font-family: var(--font-head);
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: .02em;
  color: var(--ink-3);
}
a.logo-row__item:hover { color: var(--brand-fg); text-decoration: none; }

/* --- testimonials ------------------------------------------------------- */

.quote {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  padding: 1.6rem;
  background: var(--card-bg);
  border: var(--card-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--card-shadow);
  backdrop-filter: var(--card-blur);
  -webkit-backdrop-filter: var(--card-blur);
  height: 100%;
}
.quote__mark {
  font-family: Georgia, "Times New Roman", serif;
  font-size: 2.6rem;
  line-height: .7;
  color: var(--brand-fg);
  opacity: .45;
}
.quote__text { font-size: 1.02rem; color: var(--ink-2); flex: 1 1 auto; }
.quote__who { display: flex; align-items: center; gap: .75rem; }
.quote__who > span { min-width: 0; }
.quote__name { display: block; font-weight: 700; color: var(--ink); font-size: .95rem; }
.quote__role { display: block; font-size: .84rem; color: var(--ink-3); }
.stars { display: flex; gap: 2px; color: var(--accent); }
.stars svg { width: 15px; height: 15px; }

.avatar {
  width: 44px; height: 44px;
  border-radius: 50%;
  display: grid; place-items: center;
  background: rgba(var(--brand-rgb), .14);
  color: var(--brand-fg);
  font-weight: 700;
  font-size: .9rem;
  flex: 0 0 auto;
  overflow: hidden;
}
.avatar img { width: 100%; height: 100%; object-fit: cover; }
.avatar--lg { width: 92px; height: 92px; font-size: 1.5rem; }

/* --- team --------------------------------------------------------------- */

.person { text-align: center; }
.person .avatar { margin: 0 auto 1rem; }
.person__name { font-size: 1.02rem; margin-bottom: .2rem; }
.person__role { font-size: .86rem; color: var(--brand-fg); font-weight: 650; margin-bottom: .6rem; }
.person__bio { font-size: .9rem; color: var(--ink-3); }
.person .social-row { justify-content: center; }
.person .social-row a { border-color: var(--line); color: var(--ink-3); }

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

/* Built on <details> and <summary>, so every answer is open to a crawler,
   works with the keyboard for free, and is readable with JavaScript
   switched off. An Alpine or hand rolled accordion would have hidden the
   answers from all three. */
.faq-list { max-width: 820px; }
.faq { border-bottom: 1px solid var(--line); }
.faq:first-child { border-top: 1px solid var(--line); }
.faq__q {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.05rem 0;
  font-family: var(--font-head);
  font-size: 1.02rem;
  font-weight: 650;
  color: var(--ink);
  cursor: pointer;
  list-style: none;
}
/* Both are needed: the pseudo element is the standard, the webkit marker is
   what Safari actually draws. */
.faq__q::marker, .faq__q::-webkit-details-marker { display: none; content: ""; }
.faq__q svg { width: 18px; height: 18px; flex: 0 0 auto; margin-top: .25rem; color: var(--brand-fg); transition: transform .18s ease; }
.faq[open] .faq__q svg { transform: rotate(180deg); }
.faq__a { padding-bottom: 1.15rem; color: var(--ink-2); max-width: 70ch; }

/* --- cta ---------------------------------------------------------------- */

.band--cta { background: var(--dark-bg); color: var(--ink-on-dark); }
/* A plain call to action band is --dark-bg and so is the footer, so a page
   that ends with one runs the two together into a single dark slab with
   140px of nothing in the middle of it. The seam goes on the BAND and not
   on the footer, because the footer is not its sibling: the band is inside
   <main>. Over a light band it is invisible, which is why it needs no
   condition beyond the brand toned one, which is a different colour. */
.band--cta:not(.is-brand) { border-bottom: 1px solid rgba(255, 255, 255, .13); }
.band--cta h2 { color: inherit; }
.band--cta.is-brand { background: var(--brand); color: var(--brand-ink); }
/* The band IS the brand colour, so the primary button has to invert or it
   disappears into its own background. */
.band--cta.is-brand .btn--brand {
  background: var(--brand-ink);
  border-color: var(--brand-ink);
  color: var(--brand);
}
.band--cta.is-brand .btn--brand:hover { opacity: .88; }
.cta-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1.6rem;
}
.cta-inner__text { max-width: 620px; }
.cta-inner__text p { margin-bottom: 0; opacity: .88; }

/* --- map ---------------------------------------------------------------- */

.map-frame {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 7;
  min-height: 260px;
  background: var(--surface-3);
  border-radius: var(--radius-lg);
  overflow: hidden;
  border: var(--card-border);
}
.map-frame iframe, .map-frame img { width: 100%; height: 100%; border: 0; object-fit: cover; }
.map-placeholder {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  text-align: center;
  padding: 1.5rem;
  color: var(--ink-3);
}

/* --- text with image ---------------------------------------------------- */

.split {
  display: grid;
  gap: 2.4rem;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
  align-items: center;
}
.split--flip .split__media { order: -1; }
.split__media img, .split__media .cover {
  border-radius: var(--radius-lg);
  overflow: hidden;
  width: 100%;
}

/* --- contact band ------------------------------------------------------- */

.contact-split {
  display: grid;
  gap: 2.4rem;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  align-items: start;
}
.detail-list { list-style: none; margin: 0; padding: 0; }
.detail-list li {
  display: flex;
  gap: .8rem;
  align-items: flex-start;
  padding: .75rem 0;
  border-bottom: 1px solid var(--line);
}
.detail-list li:last-child { border-bottom: 0; }
.detail-list svg { width: 18px; height: 18px; color: var(--brand-fg); flex: 0 0 auto; margin-top: .2rem; }
.detail-list__label {
  display: block;
  font-size: .76rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: .1rem;
}
/* display:block, because this is a <span> and opening hours are several
   of them in a row: inline, three lines of hours print as one sentence
   with no separator, which looked like a copy mistake rather than a CSS
   one. */
.detail-list__value { display: block; color: var(--ink); font-weight: 550; word-break: break-word; }

/* --------------------------------------------------------------------------
   10. List and article pages
   -------------------------------------------------------------------------- */

.page-hero {
  background: var(--surface-2);
  border-bottom: 1px solid var(--line);
  padding-block: 3.2rem 2.6rem;
}
.page-hero h1 { margin-bottom: .6rem; }
.page-hero .lede { max-width: 62ch; }

.crumbs {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
  align-items: center;
  font-size: .84rem;
  color: var(--ink-3);
  margin-bottom: .9rem;
}
.crumbs a { color: var(--ink-3); }
.crumbs a:hover { color: var(--brand-fg); }

.article__meta {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem 1.2rem;
  font-size: .88rem;
  color: var(--ink-3);
  margin-bottom: 1.6rem;
}
.article__cover {
  border-radius: var(--radius-lg);
  overflow: hidden;
  margin-bottom: 2rem;
}
.article__body { font-size: 1.04rem; }
.article__body p { margin-bottom: 1.15rem; }

.gallery {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 220px), 1fr));
}
.gallery a, .gallery figure {
  margin: 0;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface-3);
  aspect-ratio: 4 / 3;
}
.gallery img { width: 100%; height: 100%; object-fit: cover; }

.pager { display: flex; gap: .3rem; flex-wrap: wrap; justify-content: center; margin-top: 2.4rem; }
.pager a, .pager span {
  display: inline-grid;
  place-items: center;
  min-width: 40px;
  height: 40px;
  padding: 0 .6rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink-2);
  font-size: .92rem;
  font-weight: 600;
}
.pager a:hover { border-color: var(--brand-fg); color: var(--brand-fg); text-decoration: none; }
.pager .is-current { background: var(--brand); border-color: var(--brand); color: var(--brand-ink); }
.pager .is-disabled { opacity: .4; }

.empty-note {
  padding: 3rem 1.5rem;
  text-align: center;
  color: var(--ink-3);
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius-lg);
}

/* --------------------------------------------------------------------------
   11. Reveal on scroll, and reduced motion
   -------------------------------------------------------------------------- */

/* A .reveal element is VISIBLE by default and is only hidden once the
   script has decided it is safe to animate it. That order matters: with the
   opposite default, a visitor with JavaScript switched off, a crawler, and
   a headless screenshot all see a blank page, which is exactly the defect
   that has to be photographed to be found. */
.reveal { opacity: 1; transform: none; }

.reveal.is-armed {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity .6s ease, transform .6s cubic-bezier(.2, .7, .3, 1);
}
.reveal.is-armed.is-in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    transition: none !important;
    animation: none !important;
  }
  /* Whatever the script did, nothing stays hidden and nothing moves. */
  .reveal, .reveal.is-armed { opacity: 1 !important; transform: none !important; }
  /* A fixed background is motion by another name: the picture sits still
     while the page slides over it. Off with the rest of it. */
  [class*="parallax"], .hero, .band--stats { background-attachment: scroll !important; }
}

/* --------------------------------------------------------------------------
   12. Responsive
   -------------------------------------------------------------------------- */

@media (max-width: 980px) {
  .contact-split { grid-template-columns: 1fr; }
}

@media (max-width: 860px) {
  :root { --gutter: 18px; }

  /* --band-y is NOT set here any more, because setting it did nothing: this
     file is loaded first and `.theme-classic` outranks `:root`, so every one
     of the five themes kept its desktop figure and Parallax poured 96px
     above and below every band on a 400px phone. Scaling the padding itself
     works whatever a theme set, and it keeps each theme's own rhythm rather
     than flattening all five to one number. */
  .band:not(.band--flush) { padding-block: calc(var(--band-y) * .62); }
  .band:not(.band--flush):first-child { padding-top: calc(var(--band-y) * .44); }

  .nav-toggle { display: inline-grid; }

  /* The nav is taken out of the flow on this breakpoint, and its
     `margin-left: auto` went with it, so the theme switch and the menu
     button used to sit immediately after the business name with 120px of
     empty header to the right of them. */
  .head-tools { margin-left: auto; }

  .site-nav {
    position: fixed;
    inset: var(--head-h) 0 auto 0;
    z-index: 95;
    flex-direction: column;
    align-items: stretch;
    gap: .15rem;
    padding: 1rem var(--gutter) 1.4rem;
    background: var(--surface);
    border-bottom: 1px solid var(--line);
    box-shadow: var(--shadow-lg);
    max-height: calc(100vh - var(--head-h));
    overflow-y: auto;
  }
  /* The negative margin cancels the link's own side padding, so the WORDING
     starts on the page gutter, in line with the left edge of the full width
     button underneath. Without it the list sits 10px in from the button and
     the drawer reads as two columns that nearly line up. */
  .site-nav a:not(.btn) { padding: .8rem .6rem; margin-inline: -.6rem; font-size: 1rem; }
  .site-nav .btn { margin: .6rem 0 0; }

  .split--flip .split__media { order: 0; }
  .foot-grid { grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr)); }
}

@media (max-width: 620px) {
  :root { --hero-min: 58vh; --body-size: 15.5px; }

  .form-grid { grid-template-columns: 1fr; }
  .brand__tag { display: none; }
  .btn { width: 100%; }
  .btn-row .btn { width: auto; flex: 1 1 auto; }
  .site-nav .btn { width: 100%; }
  .cta-inner { flex-direction: column; align-items: flex-start; }
  .foot-base__links { margin-left: 0; }
}

@media (max-width: 420px) {
  :root { --gutter: 16px; }
  .brand__name { font-size: .95rem; }
}

@media print {
  .site-head, .site-foot, .wa-fab, .nav-toggle, .head-tools { display: none !important; }
  body { background: #fff; color: #000; }
  .band { padding-block: 1rem; }
}
