@charset "utf-8";
/* ==========================================================================
   site.css — the whole design system. One stylesheet, mobile first.
   --------------------------------------------------------------------------
   Colours, fonts and metrics come from css/tokens.css, which is generated
   from config.js. Nothing business-specific is written here.

   TWO RULES THIS FILE EXISTS TO ENFORCE

   1. Yellow is only ever on something you can tap. Blue carries the
      identity. Selected and active states are blue on a pale blue fill.
      There is no third accent colour.

   2. A border means "this is a discrete thing you can act on" — a service
      card, a package, a review, the form, the hours table. Headings, tag
      rows, closing lines and list sections sit plainly on the background.
      Boxing everything is what makes a page look templated, so .card is
      applied deliberately and never as a default wrapper.

   Desktop and tablet layout is added in one block at the end of the file
   (step 6). Everything above it is the phone.
   ========================================================================== */

/* ---------------------------------------------------------- 1. type scale */
/* Not business-specific, so it lives here rather than in config. Typography
   does the work photographs normally would, so the scale is deliberately
   wide and the line height generous. */
:root {
  --step--1: 0.875rem;
  --step-0:  1.0625rem;
  --step-1:  1.1875rem;
  --step-2:  1.375rem;
  --step-3:  1.75rem;
  --step-4:  2.125rem;
  --step-5:  2.75rem;

  --s-1: 0.375rem;
  --s-2: 0.625rem;
  --s-3: 1rem;
  --s-4: 1.5rem;
  --s-5: 2rem;
  --s-6: 2.75rem;
  --s-7: 3.5rem;

  --bar-height: 3.5rem;

  /* --float-size, --float-gap and --float-reserve come from config.js via
     tokens.css. --float-lift is not a design value: JavaScript sets it at
     runtime to raise the button clear of the footer. */
  --float-lift: 0px;
}

/* -------------------------------------------------------------- 2. reset */
*, *::before, *::after { box-sizing: border-box; }

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--step-0);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

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

/* EVERY inline icon carries a size of its own, set here rather than left to
   the browser to work out.
   An <svg> with a viewBox and no width/height attributes has an aspect ratio
   but NO intrinsic size, and engines resolve `height: auto` on that
   differently: Chromium settles on a size and draws the icon, WebKit
   collapses it to zero height. The menu burger and the drawer close button
   were the only two icons with no sizing rule of their own, so they were the
   only two that fell back to that — correct colour, correct 44px button,
   correct tap behaviour, and nothing visible on an iPhone.
   Sizing every icon here means an icon can never be sizeless again, whatever
   component it is dropped into.
   The specificity is deliberately the lowest available — a bare type
   selector — so every per-component rule further down this file still wins
   without being touched. This is a floor, not a decision. */
svg { max-width: 100%; display: block; width: var(--icon); height: var(--icon); }

/* EVERY inline icon takes its colour from the text around it.
   An <svg> with no fill attribute renders BLACK, whatever the button's
   colour is. That is what made the menu burger and the drawer's close button
   invisible: both sat black-on-blue at about 2:1 while the button around
   them was correctly set to white. Three other icons happened to work only
   because three separate rules named them individually — and enumerating is
   exactly how two got missed.
   The road signs are excluded: they carry statutory colours of their own and
   must never inherit anything from the page. */
svg:not(.sign) { fill: currentColor; }

/* A road sign is not an icon. Its colours are statutory and its size is set
   by whatever lays it out, so both defaults above are handed straight back. */
.sign { width: auto; height: auto; }
button, input, select, textarea { font: inherit; color: inherit; }

h1, h2, h3, h4 {
  margin: 0;
  color: var(--blue);
  line-height: 1.15;
  letter-spacing: -0.015em;
  text-wrap: balance;
}

/* One step down on a phone. At --step-4 the page title was more than twice
   the size of the business name in the top bar — and the name is the thing
   that says whose site this is. Restored at the tablet breakpoint, where
   there is room for it. Phone only, and size only. */
h1 { font-size: var(--step-3); font-weight: 700; }
/* Steps down with h1 on a phone for the same reason and by the same amount:
   left at --step-3 it would be exactly the size of the page title above it,
   and a section heading that matches the title is not a hierarchy. */
h2 { font-size: var(--step-2); font-weight: 700; }
h3 { font-size: var(--step-1); font-weight: 650; }
h4 { font-size: var(--step-0); font-weight: 650; }

p, ul, ol, dl { margin: 0; }
p { max-width: var(--measure); }

ul, ol { padding-left: 1.25em; }

a { color: var(--blue); text-underline-offset: 0.18em; }
a:hover { color: var(--blue-deep); }

strong, b { font-weight: 650; }

/* ---------------------------------------------------- 3. focus and motion */
/* One visible focus style everywhere. Never removed, only re-coloured where
   the background would swallow it. */
:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
  border-radius: 2px;
}

.topbar :focus-visible,
.drawer :focus-visible,
.footer :focus-visible {
  outline-color: var(--on-blue);
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* ------------------------------------------------------------- 4. layout */
/* ONE max content width, held on every page. Only a block that deliberately
   wants the full screen — the codes strip, the paperwork block — breaks out,
   and it does so by being a full-width parent with its own inner wrap. */
.wrap {
  width: 100%;
  max-width: var(--max-width);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* The floating action button is fixed to the bottom of the viewport, so at
   the end of a page it comes to rest just above the footer. This holds that
   band free inside <main>, which is why a short page ends with some empty
   space: the button lands in reserved space rather than on top of a link.
   --float-reserve is derived from the button's own size and gap in
   tokens.css, so the two can never drift apart, and it is 0 when the button
   is switched off. */
main { padding-bottom: var(--float-reserve, 0px); }

.section { padding-block: var(--s-6); }
.section + .section { padding-top: 0; }
.section--tight { padding-block: var(--s-5); }

/* A full-bleed band. Its child .wrap keeps the text on the same measure as
   everything else, so only the colour runs edge to edge. */
.bleed { width: 100%; }
.bleed--blue { background: var(--blue); color: var(--on-blue); }
.bleed--blue h2, .bleed--blue h3 { color: var(--on-blue); }

.stack > * + * { margin-top: var(--s-3); }
.stack--tight > * + * { margin-top: var(--s-2); }
.stack--loose > * + * { margin-top: var(--s-4); }

.lede {
  font-size: var(--step-1);
  color: var(--ink);
  max-width: var(--measure);
}

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--surface);
  color: var(--blue);
  padding: var(--s-2) var(--s-3);
  z-index: 100;
  font-weight: 650;
}
.skip:focus { left: var(--s-3); top: var(--s-3); }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}

/* ------------------------------------------------------------ 5. top bar */
.topbar {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--blue);
  color: var(--on-blue);
}

.topbar__inner {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  min-height: var(--bar-height);
  max-width: var(--max-width);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* The business name, set in type. There is no logo.
   It wraps rather than truncating: between the burger and EN|AF there is
   about 200px on a 390px phone, and a business name is the one string on
   the site that must never be cut off. A longer name than this one takes a
   second line and the bar grows to fit it. */
.topbar__name {
  flex: 1 1 auto;
  min-width: 0;
  padding-block: var(--s-1);
  font-size: var(--step-0);
  font-weight: 650;
  line-height: 1.2;
  letter-spacing: -0.005em;
  color: var(--on-blue);
  text-decoration: none;
  text-wrap: balance;
}
.topbar__name:hover { color: var(--on-blue); text-decoration: underline; }

.topbar__burger {
  flex: 0 0 auto;
  display: none;                 /* shown only when JavaScript is running */
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  margin-left: calc(var(--s-2) * -1);
  background: none;
  border: 0;
  border-radius: var(--radius-small);
  color: var(--on-blue);
  cursor: pointer;
}
.js .topbar__burger { display: inline-flex; }
.topbar__burger:hover { background: rgb(255 255 255 / 0.12); }

/* EN | AF. The active language is filled, never in yellow — yellow means
   "tap me", and the language you are already reading is not an action. */
.langs {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  border: 1px solid rgb(255 255 255 / 0.35);
  border-radius: var(--radius-small);
  overflow: hidden;
}

.langs__btn {
  min-width: var(--tap);
  min-height: var(--tap);
  padding-inline: var(--s-2);
  background: none;
  border: 0;
  color: var(--on-blue);
  font-size: var(--step--1);
  font-weight: 650;
  letter-spacing: 0.04em;
  cursor: pointer;
}
.langs__btn + .langs__btn { border-left: 1px solid rgb(255 255 255 / 0.35); }
.langs__btn:hover { background: rgb(255 255 255 / 0.12); }
.langs__btn[aria-pressed="true"] {
  background: var(--surface);
  color: var(--blue);
  cursor: default;
}

/* ------------------------------------------------------------- 6. drawer */
/* With JavaScript off this is a plain block of links under the bar, which is
   why the burger is hidden until `.js` is set. Nothing about navigating the
   site depends on script. */
.drawer {
  background: var(--blue);
  color: var(--on-blue);
  border-top: 1px solid rgb(255 255 255 / 0.2);
}

.drawer__list {
  list-style: none;
  margin: 0;
  padding: 0 var(--gutter) var(--s-3);
  max-width: var(--max-width);
  margin-inline: auto;
}

.drawer__link {
  display: flex;
  align-items: center;
  min-height: var(--tap);
  padding: var(--s-2) var(--s-2);
  color: var(--on-blue);
  text-decoration: none;
  font-weight: 600;
  border-radius: var(--radius-small);
}
.drawer__link:hover { background: rgb(255 255 255 / 0.12); color: var(--on-blue); }

/* The page you are on: a pale fill, blue text. Same rule as every other
   selected state on the site. */
.drawer__link[aria-current="page"] {
  background: var(--blue-tint);
  color: var(--blue);
}

.drawer__close { display: none; }

/* --- once JavaScript is running, the same markup becomes an off-canvas --- */
.js .drawer {
  position: fixed;
  inset: 0 auto 0 0;
  z-index: 60;
  width: min(20rem, 84vw);
  border-top: 0;
  border-right: 1px solid rgb(255 255 255 / 0.2);
  padding-top: var(--s-3);
  overflow-y: auto;
  overscroll-behavior: contain;
  transform: translateX(-100%);
  transition: transform 180ms ease;
}
.js .drawer[data-open="true"] { transform: translateX(0); }
.js .drawer[hidden] { display: block; }   /* hidden is handled by transform */

.js .drawer__close {
  /* block-level, so margin-left:auto can push it to the trailing edge */
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  margin: 0 var(--gutter) var(--s-2) auto;
  background: none;
  border: 0;
  border-radius: var(--radius-small);
  color: var(--on-blue);
  cursor: pointer;
}
.js .drawer__close:hover { background: rgb(255 255 255 / 0.12); }

.drawer__backdrop {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: rgb(17 20 24 / 0.5);
  opacity: 0;
  transition: opacity 180ms ease;
}
.drawer__backdrop[hidden] { display: none; }
.drawer__backdrop[data-open="true"] { opacity: 1; }

body[data-drawer="open"] { overflow: hidden; }

/* ------------------------------------------------------------ 7. buttons */
/* Yellow = do this. Outlined = the alternative. Stacked on a phone, side by
   side from tablet up. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: var(--tap);
  padding: var(--s-2) var(--s-4);
  border: 2px solid transparent;
  border-radius: var(--radius-small);
  font-size: var(--step-0);
  font-weight: 650;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 120ms ease, border-color 120ms ease;
}

.btn--primary {
  background: var(--yellow);
  border-color: var(--yellow);
  color: var(--on-yellow);
}
.btn--primary:hover {
  background: var(--yellow-deep);
  border-color: var(--yellow-deep);
  color: var(--on-yellow);
}

.btn--outline {
  background: transparent;
  border-color: var(--blue);
  color: var(--blue);
}
.btn--outline:hover { background: var(--blue-tint); color: var(--blue); }

/* On a blue block the outline has to be visible against blue. */
.bleed--blue .btn--outline,
.footer .btn--outline {
  border-color: var(--on-blue);
  color: var(--on-blue);
}
.bleed--blue .btn--outline:hover,
.footer .btn--outline:hover {
  background: rgb(255 255 255 / 0.12);
  color: var(--on-blue);
}

.btn-row { display: grid; gap: var(--s-2); }

.btn__icon { flex: 0 0 auto; width: 1.25em; height: 1.25em; }

/* A text link that stands on its own beneath a block. Not a button: it is a
   secondary path, and giving it a border would box something you cannot act
   on in one tap. */
.textlink {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  min-height: var(--tap);
  font-weight: 650;
  color: var(--blue);
}

/* -------------------------------------------------------------- 8. cards */
/* A border here is a promise: this is one discrete thing, and there is
   something to do with it. */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s-4);
}

/* The block a page is built around — a lesson package on the Prices page. */
.card--lead {
  border-color: var(--line-strong);
  border-width: 2px;
  padding: var(--s-5) var(--s-4);
}

/* Deliberately quieter than its neighbours: the fallback, not the offer. */
.card--quiet {
  background: transparent;
  border-style: dashed;
  padding: var(--s-3) var(--s-4);
}

.card__title { margin-bottom: var(--s-1); }
.card__body { color: var(--ink); }

.cards { display: grid; gap: var(--s-3); }
/* A grid that fits its count: two cards fill the row as two, one as one,
   rather than leaving a third of the row empty. For the grouped package
   cards and the two lesson cards on /prices/. */
.cards--fit { --cards-min: 16rem; }

/* ----------------------------------------- 9. unknowns, marked as unknown */
/* A blank we know is blank. Muted, dotted underline — it has to read as
   deliberately empty rather than broken, because the person reading it will
   be the one who fills it in. */
.placeholder {
  color: var(--grey);
  border-bottom: 1px dotted var(--grey);
  padding-bottom: 0.05em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* A grey note: "package name to be confirmed", "role to be confirmed". */
.tbc {
  display: block;
  color: var(--grey);
  font-size: var(--step--1);
  line-height: 1.4;
}

/* One small grey line where a source genuinely needs stating. Never used to
   explain the site to its owner in the site's own voice. */
.note {
  color: var(--grey);
  font-size: var(--step--1);
  max-width: var(--measure);
}

/* --------------------------------------------------------- 10. photo slot */
/* No photograph exists. Each slot is drawn as a deliberate graphic block, so
   the page looks finished with none and better with one. Required dimensions
   are set per slot from config.images. */
.photo {
  display: grid;
  place-items: center;
  aspect-ratio: var(--photo-ratio, 4 / 3);
  background:
    repeating-linear-gradient(
      -45deg,
      var(--blue-tint) 0 10px,
      transparent 10px 20px
    ),
    var(--surface);
  border: 1px solid var(--blue-line);
  border-radius: var(--radius);
  color: var(--grey);
  font-size: var(--step--1);
  text-align: center;
  padding: var(--s-3);
}
.photo--square { --photo-ratio: 1 / 1; }
.photo--wide { --photo-ratio: 8 / 3; }

/* A real photograph in a slot. width/height are on the <img> itself so the
   box is reserved before the file arrives; the radius matches the drawn
   placeholder it replaces, so a page looks the same with or without one. */
.photo--img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius);
  background: var(--blue-tint);
}
.photo--img-wide { aspect-ratio: 8 / 3; object-fit: cover; }
.person__photo.photo--img { aspect-ratio: 1 / 1; object-fit: cover; }

/* --- the Photos section on /about/ ---------------------------------------
   Two tiles across on a phone, more on wider screens. Tiles past the first
   eight carry .gallery__item--more and are hidden only under .js — with
   JavaScript off every photo shows and the button is not drawn. */
.gallery {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--s-2);
  grid-template-columns: repeat(2, 1fr);
}
/* NOTHING IS CROPPED. The tile is 3:4 — the shape a phone takes a portrait
   photo in, and the shape of all but one of these — so those fill it exactly,
   edge to edge, with no backdrop showing at all. A photo of any other shape
   is fitted whole (object-fit: contain) and the space beside it is filled
   with a blurred, scaled copy of that same photo, so there is no hard
   letterbox bar either.

   This replaces `object-fit: cover` on a 4:3 tile, which cropped a portrait
   photo to a landscape box: about a third of every one of these went, and
   what goes first is heads and feet. It shipped that way and was wrong. */
.gallery__item {
  position: relative;
  margin: 0;
  aspect-ratio: 3 / 4;
  overflow: hidden;
  border-radius: var(--radius-small);
  background: var(--blue-tint);
}

/* The blurred fill. Inset past all four edges and scaled up so the blur's own
   soft edge is never inside the tile. Purely decorative: it is the same
   photograph the <img> above it already carries alt text for, which is why
   it is a background rather than a second image in the document. */
.gallery__item::before {
  content: "";
  position: absolute;
  inset: -10%;
  background-image: var(--tile);
  background-size: cover;
  background-position: center;
  filter: blur(16px);
}

/* Positioned, so it paints over the backdrop: both are z-index auto, and a
   positioned element that comes later in the document wins. */
.gallery__img {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.js .gallery:not(.is-open) .gallery__item--more { display: none; }
.gallery__more { display: none; }
.js .gallery__more { display: inline-flex; }
.js .gallery__more[hidden] { display: none; }

/* --------------------------------------------------------------- 11. pills */
.pills {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  list-style: none;
  margin: 0;
  padding: 0;
}

.pill {
  display: inline-block;
  padding: var(--s-1) var(--s-3);
  border-radius: 999px;
  font-size: var(--step--1);
  font-weight: 600;
}

/* Pale yellow, not yellow: this is a label, not a button. */
.pill--tag { background: var(--yellow-tint); color: var(--ink); }
.pill--place { background: var(--blue-tint); color: var(--blue); }

/* -------------------------------------------------------------- 12. stars */
.stars { display: inline-flex; gap: 0.1em; color: var(--yellow); }
.stars svg { width: 1.1em; height: 1.1em; }

.rating {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  flex-wrap: wrap;
}
.rating__text { font-size: var(--step--1); }

/* ------------------------------------------------------- 13. floating CTA */
/* Round, yellow, bottom right, above the content. It must not sit over the
   footer: JavaScript raises it by --float-lift as the footer comes into
   view, and the footer carries matching bottom padding so that even with
   JavaScript off the button never lands on top of anything. */
.float {
  position: fixed;
  right: var(--float-gap);
  bottom: calc(var(--float-gap) + var(--float-lift));
  z-index: 30;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--float-size);
  height: var(--float-size);
  border-radius: 50%;
  background: var(--yellow);
  color: var(--on-yellow);
  box-shadow: 0 2px 10px rgb(17 20 24 / 0.28);
  transition: bottom 120ms ease, background-color 120ms ease;
}
.float:hover { background: var(--yellow-deep); color: var(--on-yellow); }
.float svg { width: 1.75rem; height: 1.75rem; }

/* -------------------------------------------------------------- 14. footer */
/* Stacks on a phone. Three columns at 390px does not fit, and trying is what
   broke this repeatedly in design. */
.footer {
  background: var(--blue);
  color: var(--on-blue);
  /* room for the floating button, so it can never cover anything here */
  padding-block: var(--s-5) calc(var(--s-5) + var(--float-size));
}

.footer a { color: var(--on-blue); }
.footer a:hover { color: var(--on-blue); }

.footer__items {
  display: grid;
  gap: var(--s-3);
  list-style: none;
  margin: 0;
  padding: 0;
}

.footer__item {
  display: flex;
  align-items: flex-start;
  gap: var(--s-2);
  min-height: var(--tap);
}

.footer__item svg {
  flex: 0 0 auto;
  width: 1.25rem;
  height: 1.25rem;
  margin-top: 0.15rem;
}

.footer__link {
  display: inline-flex;
  align-items: center;
  /* When business.facebookUrl is null this element is not a single anchor
     but a label span plus a marked-blank span, and inline-flex makes those
     two flex items — which do not wrap, and .placeholder is nowrap besides,
     so the line ran past the right edge of a 360px phone and took the whole
     page's horizontal scroll with it. A sentence wraps; R___ must not. */
  flex-wrap: wrap;
  gap: 0.35em;
  min-height: var(--tap);
  font-weight: 650;
  text-decoration: none;
}
.footer__link:hover { text-decoration: underline; }
.footer__link .placeholder { white-space: normal; }

/* A marked blank in the footer sits on --blue, where --grey is unreadable.
   --on-blue-muted is the token for secondary text on blue. */
.footer .placeholder {
  color: var(--on-blue-muted);
  border-bottom-color: var(--on-blue-muted);
}

.footer__hours { color: var(--on-blue-muted); }

/* The one grey line: this is a demonstration. On blue, "grey" is the muted
   blue-white. */
.footer__demo {
  margin-top: var(--s-4);
  padding-top: var(--s-3);
  border-top: 1px solid rgb(255 255 255 / 0.2);
  color: var(--on-blue-muted);
  font-size: var(--step--1);
  max-width: var(--measure);
}

/* ==================================================== 15. page components */

/* --- home hero ---------------------------------------------------------
   Words then photo, in one grid so the photo can move up beside the words on
   desktop without either changing section. */
.home-hero { display: grid; gap: var(--s-4); }
.home-hero__photo { --photo-ratio: 4 / 3; }

/* --- codes strip -------------------------------------------------------- */
.codes-strip__text {
  margin: 0;
  max-width: none;
  font-size: var(--step-1);
  font-weight: 650;
}

/* --- a plain list of short items, not a boxed thing -------------------- */
.inline-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-1) var(--s-3);
  max-width: var(--measure);
}
.inline-list > li { position: relative; }
.inline-list > li + li::before {
  content: "·";
  position: absolute;
  left: calc(var(--s-3) / -2 - 0.15em);
  color: var(--grey);
}
.inline-list--light > li + li::before { color: var(--on-blue-muted); }

/* --- reviews ------------------------------------------------------------
   Heights vary with the text. Forcing them equal would add empty space to
   the short ones, and the short ones are most of them. */
.reviews { display: grid; gap: var(--s-3); }

.review {
  margin: 0;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: var(--s-4);
}

.review__quote {
  margin: 0 0 var(--s-2);
  font-size: var(--step-1);
  line-height: 1.45;
  color: var(--ink);
}

.review__by { color: var(--grey); font-size: var(--step--1); font-weight: 600; }

/* --- the rating block on /reviews/ -------------------------------------- */
.score { display: grid; gap: var(--s-1); justify-items: start; }
.score__number {
  margin: 0;
  font-size: var(--step-5);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--blue);
  font-variant-numeric: tabular-nums;
}
.score__stars { margin: 0; display: inline-flex; gap: 0.1em; color: var(--yellow); }
.score__stars svg { width: 1.5rem; height: 1.5rem; }
.score__count { margin: 0; color: var(--grey); font-size: var(--step--1); }

/* --- prices ------------------------------------------------------------- */
.price {
  margin: var(--s-2) 0 var(--s-3);
  font-size: var(--step-2);
  font-weight: 700;
  color: var(--blue);
  font-variant-numeric: tabular-nums;
}
.card--quiet .price { font-size: var(--step-1); }

.package__name { margin: 0; font-weight: 650; }
.package__details {
  list-style: none;
  margin: var(--s-3) 0 0;
  padding: 0;
  display: grid;
  gap: var(--s-1);
  color: var(--ink);
}
.package__details > li { padding-left: 1.1em; position: relative; }
.package__details > li::before {
  content: "";
  position: absolute;
  left: 0; top: 0.62em;
  width: 0.4em; height: 0.4em;
  border-radius: 50%;
  background: var(--blue-line);
}
.package .btn { width: 100%; }

/* --- how it works: the timeline -----------------------------------------
   Vertical on a phone, with a line running down through the markers. Four
   across on desktop, where the line runs horizontally instead. */
.timeline {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--s-5);
}

.timeline__step {
  position: relative;
  display: grid;
  grid-template-columns: 2.5rem 1fr;
  gap: var(--s-3);
  align-items: start;
}

.timeline__marker {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  background: var(--blue);
  color: var(--on-blue);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* The connecting line: drawn from each marker down to the next one, so it
   never overshoots past the last step. */
.timeline__step:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 1.25rem;
  top: 2.5rem;
  bottom: calc(var(--s-5) * -1);
  width: 2px;
  transform: translateX(-1px);
  background: var(--blue-line);
}

.paperwork h2 { margin-bottom: var(--s-2); }

/* --- what to bring ------------------------------------------------------ */
.bring {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--s-2);
  max-width: var(--measure);
}
.bring > li {
  padding-left: 1.4em;
  position: relative;
}
.bring > li::before {
  content: "";
  position: absolute;
  left: 0; top: 0.55em;
  width: 0.5em; height: 0.5em;
  border-radius: 50%;
  background: var(--blue);
}

/* --- about: the people -------------------------------------------------- */
.person__photo { margin-bottom: var(--s-3); }
.codes-block__title { color: var(--on-blue); }
.tbc--light { color: var(--on-blue-muted); }

/* --- contact: hours ----------------------------------------------------- */
.contact-grid { display: grid; gap: var(--s-5); }

.hours { margin: 0; padding: var(--s-2) var(--s-4); }
.hours__row {
  display: flex;
  justify-content: space-between;
  gap: var(--s-3);
  padding-block: var(--s-2);
  border-bottom: 1px solid var(--line);
}
.hours__row:last-child { border-bottom: 0; }
.hours dt { font-weight: 600; }
.hours dd { margin: 0; font-variant-numeric: tabular-nums; }
.hours .is-closed { color: var(--grey); }

/* --- contact: the embedded map ------------------------------------------
   Full width of its column at a fixed aspect ratio, so there is no layout
   jump while it loads and no fixed pixel width at 360px. */
.map {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  border: 0;
  border-radius: var(--radius);
}

.address {
  font-style: normal;
  line-height: 1.5;
}

/* --- contact: the form -------------------------------------------------- */
.form { display: grid; gap: var(--s-4); }
.form__grid { display: grid; gap: var(--s-3); }

.field { display: grid; gap: var(--s-1); margin: 0; max-width: none; }
.field label { font-weight: 600; font-size: var(--step--1); }

.field input,
.field select,
.field textarea {
  min-height: var(--tap);
  padding: var(--s-2) var(--s-3);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-small);
  width: 100%;
}
.field textarea { min-height: 6rem; resize: vertical; }
.field input:focus-visible,
.field select:focus-visible,
.field textarea:focus-visible { border-color: var(--blue); }

.field [aria-invalid="true"] { border-color: var(--error); background: var(--error-tint); }

.field__error {
  color: var(--error);
  font-size: var(--step--1);
  font-weight: 600;
}

.form__error {
  padding: var(--s-2) var(--s-3);
  background: var(--error-tint);
  border: 1px solid var(--error);
  border-radius: var(--radius-small);
  color: var(--error);
  font-weight: 600;
}
.form__error[hidden] { display: none; }

.form-success[hidden] { display: none; }
.form-success { display: grid; gap: var(--s-3); }

.btn--wide { width: 100%; }

/* ======================================================================== */
/* 17. TABLET AND DESKTOP                                                    */
/* ------------------------------------------------------------------------ */
/* Everything above this line is the phone, which is what most visitors will  */
/* open this on, on mobile data. Two breakpoints, and no more: one where a    */
/* second column starts to fit, one where a third does.                       */
/*                                                                            */
/* The content width does not change here — .wrap holds one max width on      */
/* every page and only the deliberate full-bleed bands break out of it. What  */
/* changes is how many things sit side by side.                               */
/* ======================================================================== */

@media (min-width: 40em) {

  /* Buttons side by side rather than stacked. */
  .btn-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s-3);
  }
  .btn-row > .btn { flex: 0 1 auto; }
  .btn--wide { width: auto; min-width: 16rem; }
  .package .btn { width: auto; align-self: start; }

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

  /* The full scale, from the tablet breakpoint up. The top bar's name is
     --step-0 everywhere now, so it no longer needs a rule here. */
  h1 { font-size: var(--step-4); }
  h2 { font-size: var(--step-3); }

  .cards { grid-template-columns: repeat(2, 1fr); }
  .cards--fit { grid-template-columns: repeat(auto-fit, minmax(var(--cards-min), 1fr)); }
  .cards--fit > .card { max-width: 34rem; }

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

  /* Heights follow the text. Forcing a row of reviews to the tallest one
     pads out the short ones, and most of them are short. */
  .reviews { grid-template-columns: repeat(2, 1fr); align-items: start; }

  .cards--people { grid-template-columns: repeat(2, 1fr); }

  /* Hours and address, side by side. */
  .contact-grid { grid-template-columns: 1fr 1fr; align-items: start; }

  /* Short fields in two columns, the message box spanning both. */
  .form__grid { grid-template-columns: 1fr 1fr; gap: var(--s-3) var(--s-4); }
  .field--wide { grid-column: 1 / -1; }


  .score__number { font-size: 4.5rem; }
}

@media (min-width: 62em) {

  :root {
    --step-3: 2rem;
    --step-4: 2.75rem;
    --step-5: 3.5rem;
  }

  .cards { grid-template-columns: repeat(3, 1fr); }
  .cards--fit { grid-template-columns: repeat(auto-fit, minmax(var(--cards-min), 1fr)); }
  .reviews { grid-template-columns: repeat(3, 1fr); }
  .gallery { grid-template-columns: repeat(4, 1fr); }

  /* Words on the left, photo on the right. Centred against each other
     rather than top-aligned: the copy is short, and hanging it off the top
     of a tall photo left a hole. */
  .home-hero {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--s-5);
    align-items: center;
  }

  /* Four steps across, with the connecting line running horizontally
     between the markers instead of down through them. */
  .timeline {
    grid-template-columns: repeat(4, 1fr);
    gap: var(--s-4);
  }
  .timeline__step {
    grid-template-columns: 1fr;
    gap: var(--s-3);
    /* Each step is stretched to the height of the tallest one. Without this,
       the spare height is absorbed by the marker's row and the four
       headings no longer line up with each other. */
    align-content: start;
  }
  .timeline__step:not(:last-child)::before {
    left: 2.5rem;
    right: calc(var(--s-4) * -1);
    top: 1.25rem;
    bottom: auto;
    width: auto;
    height: 2px;
    transform: translateY(-1px);
  }

  .cards--people { grid-template-columns: repeat(2, 1fr); max-width: 44rem; }

  /* More room around the button on a big screen. Set on :root, not on
     .float — --float-reserve is resolved on <main>, so overriding the gap
     only inside .float would move the button without moving the space held
     for it. */
  :root { --float-gap: var(--float-gap-wide); }
}

/* A wide, short window — a phone held sideways. Keep the drawer usable and
   do not let the hero photo push everything off the screen. */
@media (max-height: 30em) and (orientation: landscape) {
  .js .drawer { padding-top: var(--s-2); }
  .home-hero__photo { --photo-ratio: 16 / 9; }
}
