/* ==========================================================================
   Guesthouse Island Council 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;
}

/* --------------------------------------------------------------------------
   THAANA, for a site written in Dhivehi
   --------------------------------------------------------------------------
   The same arrangement as the rufiyaa sign above, and for the same reason: a
   visitor's phone has no Thaana font, so a Dhivehi page that does not bring
   one prints fifty boxes. Scoped to U+0780 to U+07B1, so an English page
   never downloads it and nothing else on the page changes.

   The face is FARUMA, the President's Office font, subset to the Thaana block.
   It ships on the owner's explicit instruction of 2026-09-19, given after he
   was shown exactly what it means here: these products are SOLD, so every
   buyer receives their own copy of this file and serves it to their own
   visitors, which is redistribution rather than use. The font's own copyright
   reads "All rights reserved" and carries no licence URL, so nothing in the
   file itself grants that. He confirmed he holds the rights.

   The alternative, built and measured first, is Noto Sans Thaana, which
   states SIL OFL in its own name table and may be bundled by anyone. If the
   Faruma position ever needs to change, that is the swap, and it is one file.

   **One weight only.** Faruma has no bold, so `font-weight: 400 700` is
   deliberate: it tells the browser this face covers bold and stops it
   synthesising one. Faux bold thickens the vowel marks into the letters they
   sit above, which is worse than no bold at all, so Dhivehi headings are told
   apart by size and colour the way the printed papers do it.
   ------------------------------------------------------------------------ */
@font-face {
  font-family: Thaana;
  src: url("../fonts/thaana.woff2?v=1.0.0") format("woff2"),
       url("../fonts/thaana.woff?v=1.0.0") format("woff");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0780-07B1;
}

/* --------------------------------------------------------------------------
   ROBOTO, the type this product is set in
   --------------------------------------------------------------------------
   One file, every weight. This is the VARIABLE face, so 100 to 900 comes out
   of 43 KB rather than out of five static files, and a heading can sit at 780
   without shipping a seventh weight to get there.

   Self hosted rather than pulled from Google, for three reasons that all
   matter to a product somebody else installs: a buyer's site should not
   announce every visitor to a third party, a font that arrives over someone
   else's network is a font that fails on the day it does not, and a fetch to
   fonts.googleapis.com is a fetch a privacy notice has to declare.

   Licensed Apache 2.0, which permits bundling and redistribution — the whole
   point here, because every buyer receives their own copy and serves it to
   their own visitors. The licence text ships beside it in
   `public/fonts/roboto-LICENSE.txt` and must stay there.

   Split latin and latin-ext the way Google splits it: an ordinary English or
   Dhivehi page downloads 43 KB and never asks for the second file. --------- */
@font-face {
  font-family: Roboto;
  src: url("../fonts/roboto.woff2?v=1.0.0") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: Roboto;
  src: url("../fonts/roboto-ext.woff2?v=1.0.0") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                 U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

: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, Thaana, Roboto, -apple-system, BlinkMacSystemFont, "Segoe UI", "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: 14px;
  --radius-lg: 22px;
  --radius-pill: 999px;

  /* Two shadows stacked rather than one: a tight contact shadow that reads as
     an edge, and a wide soft one that reads as height. One blurred shadow on
     its own is the grey smudge that dates a page more than any other single
     thing on it. */
  --shadow-sm: 0 1px 2px rgba(15, 23, 42, .05), 0 1px 1px rgba(15, 23, 42, .04);
  --shadow: 0 1px 2px rgba(15, 23, 42, .05), 0 8px 24px -8px rgba(15, 23, 42, .14);
  --shadow-lg: 0 2px 4px rgba(15, 23, 42, .05), 0 24px 56px -16px rgba(15, 23, 42, .22);

  /* One curve for everything that moves. Slightly overshooting ease out, so a
     card that lifts on hover arrives rather than slides. */
  --ease: cubic-bezier(.2, .7, .3, 1);

  --wrap: 1200px;
  --wrap-narrow: 760px;
  --gutter: 22px;
  --band-y: 5.5rem;
  --head-h: 72px;

  /* 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: 46px;

  /* Typography personality. Every theme moves these rather than restating
     the rules that use them.
     Roboto is a variable face here, so a heading weight is a number on a
     dial rather than a file: 750 costs exactly what 700 costs. */
  --h1: clamp(2.35rem, 5.6vw, 3.9rem);
  --h2: clamp(1.7rem, 3.4vw, 2.5rem);
  --h3: 1.22rem;
  --head-weight: 750;
  --head-tracking: -0.025em;
  --head-transform: none;
  --head-line: 1.1;
  --body-size: 16.5px;
  --body-line: 1.7;

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

/* Logical, not left: a Dhivehi page sets dir="rtl" and the bullets have to
   move to the other side with the text. Every directional rule in this file
   is written this way for that reason, and there are only a dozen of them
   because the layout is flex and grid, which mirror on their own. */
ul, ol { margin: 0 0 1rem; padding-inline-start: 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;
}

/* Hidden until focused, by CLIPPING rather than by being pushed off the side.
   `left: -9999px` is the idiom everyone reaches for and it only works in a
   left to right page: in RTL the browser grows the scrollable canvas 9999px
   the other way, and with `overflow-x: hidden` on the body the whole document
   is pushed out of view. A Dhivehi site rendered COMPLETELY BLANK because of
   this one line, with no error anywhere and the markup perfectly correct.
   Clipping takes the element out of the picture without creating any overflow
   at all, which is what .sr-only two rules below has always done. */
.skip {
  position: absolute;
  inset-inline-start: 0;
  top: 0;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  clip-path: inset(50%);
  white-space: nowrap;
  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 {
  width: auto;
  height: auto;
  overflow: visible;
  clip: auto;
  clip-path: none;
  inset-inline-start: 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: .16em;
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: var(--tracking);
  margin-inline-end: 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
   -------------------------------------------------------------------------- */

/* The bar frosts over whatever scrolls under it rather than sitting on a
   solid slab. `color-mix` keeps it in the theme's own surface colour, and the
   @supports test is what stops a browser without backdrop-filter showing a
   see through header: there, it stays opaque. */
.site-head {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}

@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .site-head {
    background: color-mix(in srgb, var(--surface) 82%, transparent);
    backdrop-filter: blur(14px) saturate(160%);
    -webkit-backdrop-filter: blur(14px) saturate(160%);
  }
}

.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-inline-start: 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) {
  position: relative;
  padding: .55rem .8rem;
  border-radius: var(--radius-pill);
  color: var(--ink-2);
  font-size: .945rem;
  font-weight: 500;
  white-space: nowrap;
  transition: color .15s var(--ease), background .15s var(--ease);
}
.site-nav a:not(.btn):hover { background: var(--surface-2); color: var(--ink); text-decoration: none; }

/* The page you are on is marked by a short bar under the word as well as by
   colour, because colour alone is a distinction somebody with a colour
   vision difference cannot see, and because a coloured word among grey words
   reads as a link rather than as a position. */
.site-nav a.is-active { color: var(--brand-fg); }
.site-nav a.is-active::after {
  content: "";
  position: absolute;
  inset-inline: .8rem;
  bottom: .18rem;
  height: 2px;
  border-radius: 2px;
  background: currentColor;
}
.site-nav .btn { margin-inline-start: .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-inline-start: auto; display: flex; flex-wrap: wrap; gap: 1rem; }

/* The floating WhatsApp button. Only drawn when a number is set. */
.wa-fab {
  position: fixed;
  inset-inline-end: 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: .72rem 1.4rem;
  /* 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: .96rem;
  /* 550, not 650. Roboto's medium is a real drawn weight rather than a
     synthesised one, and at this size a heavy button label is the thing that
     makes a page look like a form from 2009. */
  font-weight: 550;
  letter-spacing: -.005em;
  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 .18s var(--ease), border-color .18s var(--ease),
              transform .18s var(--ease), box-shadow .18s var(--ease), color .18s var(--ease);
}
.btn:hover { text-decoration: none; }
.btn:active { transform: translateY(1px); }
.btn svg { width: 17px; height: 17px; flex: 0 0 auto; }

/* The keyboard ring is its own rule and never `outline: none`. It is drawn
   OUTSIDE the button so a filled button does not lose its first pixel to it. */
.btn:focus-visible,
.text-link:focus-visible,
.input:focus-visible {
  outline: 2px solid var(--brand-fg);
  outline-offset: 2px;
}

/* A filled button carries a little of its own colour under it. Coloured
   rather than grey, because a grey shadow under a blue button reads as dirt
   on the page; the same colour reads as the button sitting on it. */
.btn--brand {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--brand-ink);
  box-shadow: 0 1px 2px rgba(var(--brand-rgb), .24), 0 6px 16px -6px rgba(var(--brand-rgb), .5);
}
.btn--brand:hover {
  background: var(--brand-strong);
  border-color: var(--brand-strong);
  transform: translateY(-1px);
  box-shadow: 0 2px 4px rgba(var(--brand-rgb), .24), 0 12px 24px -8px rgba(var(--brand-rgb), .55);
}
.btn--brand:active { box-shadow: 0 1px 2px rgba(var(--brand-rgb), .3); }

.btn--accent {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
  box-shadow: 0 1px 2px rgba(var(--accent-rgb), .24), 0 6px 16px -6px rgba(var(--accent-rgb), .5);
}
.btn--accent:hover {
  background: var(--accent-strong);
  border-color: var(--accent-strong);
  transform: translateY(-1px);
}

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

/* A filled button on a dark panel gets a faint light ring.
   The buyer picks the brand colour, and a deep navy, a bottle green or a
   near black is a perfectly good brand colour that disappears completely on
   a dark hero: the button is still there, still clickable, and reads as a
   hole. CSS cannot measure the contrast, so this does not try to; a one
   pixel light edge costs nothing on a bright brand and rescues a dark one. */
.on-dark .btn--brand,
.on-dark .btn--accent {
  box-shadow: 0 0 0 1px rgba(255, 255, 255, .28), 0 8px 24px -8px rgba(0, 0, 0, .6);
}

/* 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;
  /* Clipped, not pushed off the side. `left: -9999px` here would blank every
     RTL page carrying a form, for the same reason it blanked the whole site
     from .skip above: in a right to left document that negative offset grows
     the scrollable canvas and `overflow-x: hidden` does the rest. */
  inset-inline-start: 0 !important;
  width: 1px; height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  clip-path: inset(50%);
  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.6rem; }
.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 .22s var(--ease), box-shadow .22s var(--ease), border-color .22s var(--ease);
}
.card__body { padding: 1.5rem; flex: 1 1 auto; }
.card__body > :last-child { margin-bottom: 0; }
.card__foot { padding: 0 1.5rem 1.5rem; }

/* A card a visitor can click LIFTS. Three pixels and a deeper shadow is the
   whole difference between a page that feels made this year and a page of
   boxes, and it costs one rule. A card that is not a link does not move,
   because a thing that moves under the pointer and then does nothing is
   worse than a thing that never moved. */
a.card:hover, .card--link:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-lg);
  border-color: var(--line-strong);
  text-decoration: none;
}
a.card:focus-visible { outline: 2px solid var(--brand-fg); outline-offset: 3px; }
a.card:hover .card__title { color: var(--brand-fg); }

/* The picture inside a card grows very slightly while the card is lifted.
   Clipped by the card's own overflow, so nothing escapes the corner radius. */
a.card .cover, .card--link .cover { transition: transform .5s var(--ease); }
a.card:hover .cover, .card--link:hover .cover { transform: scale(1.04); }

.card__title { font-size: 1.14rem; margin-bottom: .45rem; color: var(--ink); letter-spacing: -.015em; }
.card__text { color: var(--ink-3); font-size: .95rem; margin-bottom: 0; }
.card__meta {
  display: flex; flex-wrap: wrap; gap: .5rem 1rem;
  font-size: .82rem; color: var(--ink-3);
  margin-bottom: .5rem;
}

/* 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. */
/* Two gradients, not one flat wash. The first darkens the START edge, where
   the words are, and lets the far side of the photograph stay a photograph;
   the second weights the bottom so a button row always has something behind
   it. A single even wash over the whole picture is what makes a hero look
   like a slide from a 2006 deck: the image is dimmed everywhere and helps
   nowhere. */
.hero__veil {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(100deg, rgba(8, 13, 23, .86) 0%, rgba(8, 13, 23, .62) 42%, rgba(8, 13, 23, .18) 100%),
    linear-gradient(to top, rgba(8, 13, 23, .55) 0%, rgba(8, 13, 23, 0) 45%);
}
.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;
}

/* --- the bento grid ----------------------------------------------------- */

/*
   Two rows of three identical cards is what dates a page now.

   The owner's verdict on ninety three products was "all looks old", and this
   was the reason: the type and the shadows had been brought up to date and
   every section underneath was still a uniform three across grid of tiles
   the same size, each with a small icon, a title, two lines and a Read more.
   That layout is the signature of an agency template from about 2017. Nothing
   in it is broken; it is simply what everybody's site looked like.

   So the tiles stop being the same size. One carries the weight of the
   section and the rest sit around it, which is the arrangement every site
   built this year uses and the reason it reads as current.

   **Six columns, and the spans are chosen per COUNT rather than per item.**
   That matters because the markup does not know how many items a buyer
   typed: a `:nth-child` cycle that looks handsome at six leaves a hole at
   four, and four is what eighty eight of the ninety profiles actually have.
   `:has()` lets the grid ask how many children it has and lay them out
   accordingly, so every real count in this catalogue fills its rows
   exactly:

       2 items   3 3
       3 items   2 2 2
       4 items   4 2 / 2 4          <- the features band, 88 profiles
       6 items   4 2 / 2 4 / 3 3
       7 items   4 2 / 2 2 2 / 3 3
       8 items   4 2 / 2 2 2 / 2 2 2
       11 items  4 2 / then nine thirds

   A browser without `:has()` gets the default: every tile a third, which is
   the old grid. That is the right fallback — a plainer page rather than a
   broken one.
*/

.bento {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 1.6rem;
}

/* The default tile is a third of the row. Every rule below widens one. */
.bento > * { grid-column: span 2; }

/* Two of anything are halves, never a third and a hole. */
.bento:has(> :nth-child(2):last-child) > * { grid-column: span 3; }

/* Four: two thirds and a third, then a third and two thirds. The asymmetry
   between the two rows is the whole point; two rows of halves would be neat
   and would read as a table. */
.bento:has(> :nth-child(4):last-child) > :nth-child(1),
.bento:has(> :nth-child(4):last-child) > :nth-child(4) { grid-column: span 4; }

.bento:has(> :nth-child(6):last-child) > :nth-child(1),
.bento:has(> :nth-child(6):last-child) > :nth-child(4) { grid-column: span 4; }
.bento:has(> :nth-child(6):last-child) > :nth-child(5),
.bento:has(> :nth-child(6):last-child) > :nth-child(6) { grid-column: span 3; }

.bento:has(> :nth-child(7):last-child) > :nth-child(1) { grid-column: span 4; }
.bento:has(> :nth-child(7):last-child) > :nth-child(6),
.bento:has(> :nth-child(7):last-child) > :nth-child(7) { grid-column: span 3; }

.bento:has(> :nth-child(8):last-child) > :nth-child(1) { grid-column: span 4; }

.bento:has(> :nth-child(11):last-child) > :nth-child(1) { grid-column: span 4; }

/* --- what a wide tile does with the room ------------------------------- */

/*
   A tile twice the width has to earn it, or the section reads as one card
   that failed to wrap. The wide ones get a larger heading and body text at
   reading size rather than caption size.

   Carried on two custom properties rather than by naming every class that
   might be inside a tile. The first version set `font-size` on
   `.tile__title` directly and reached nothing in the services band, because
   those are `.card__title` — and the next new section would have missed too.
   A property set on the grid CHILD is inherited by whatever is in it, and
   each heading decides what to do with it.
*/
.bento > * { --tile-title: 1.1rem; --tile-text: .95rem; }

.bento:has(> :nth-child(4):last-child) > :nth-child(1),
.bento:has(> :nth-child(4):last-child) > :nth-child(4),
.bento:has(> :nth-child(6):last-child) > :nth-child(1),
.bento:has(> :nth-child(6):last-child) > :nth-child(4),
.bento:has(> :nth-child(7):last-child) > :nth-child(1),
.bento:has(> :nth-child(8):last-child) > :nth-child(1),
.bento:has(> :nth-child(11):last-child) > :nth-child(1) {
  --tile-title: 1.44rem;
  --tile-text: 1.04rem;
}

.bento .tile__title,
.bento .card__title {
  font-size: var(--tile-title);
  letter-spacing: -.022em;
}

.bento .tile__text,
.bento .card__text { font-size: var(--tile-text); }

/* --- the tile ----------------------------------------------------------- */

/*
   A surface, not a paragraph. `.feature` used to be bare text in a grid
   cell, which is why the features band looked like a column of notes: the
   only thing separating one claim from the next was the gap.
*/
.tile {
  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);
  position: relative;
  overflow: hidden;
  transition: transform .22s var(--ease), box-shadow .22s var(--ease), border-color .22s var(--ease);
}

.tile__title {
  font-size: 1.1rem;
  letter-spacing: -.015em;
  margin: 0 0 .45rem;
  color: var(--ink);
}

.tile__text {
  color: var(--ink-3);
  font-size: .95rem;
  margin: 0;
}

/* The number on a tile that has one. Big, faint and behind the words, which
   is how a set of steps is numbered now rather than with a circled digit in
   front of the heading. */
.tile__index {
  position: absolute;
  inset-block-end: -.18em;
  inset-inline-end: .35em;
  font-size: 4.6rem;
  font-weight: 800;
  line-height: 1;
  color: var(--brand-fg);
  opacity: .1;
  pointer-events: none;
  user-select: none;
}

.tile .icon-tile { margin-bottom: 1.1rem; }

/* --- a section in a panel ---------------------------------------------- */

/*
   The alternating band used to be a full width tint, edge to edge, which
   stacks into horizontal stripes down the page. Inset it and round it and
   the page reads as a column of cards instead, which is the other half of
   why a site looks like this year rather than five years ago.

   The inset is a margin so the panel keeps the page's own gutter on a phone,
   where it flattens back to a tint with no rounding: a rounded panel with
   sixteen pixels either side of it is a stripe with corners.
*/
.band--panel {
  background: transparent;
  padding-block: 0;
  /* A third, not the whole band rhythm: the bands either side of this one
     still carry their own padding, so a full margin here put a hundred and
     seventy six pixels of nothing between two sections. */
  margin-block: calc(var(--band-y) * .34);
}

.band--panel > .wrap {
  background: var(--surface-2);
  border-radius: calc(var(--radius-lg) * 1.6);
  padding-block: calc(var(--band-y) * .9);
  padding-inline: clamp(var(--gutter), 4vw, 3.4rem);
  max-width: calc(var(--wrap) + 6rem);
}

@media (max-width: 720px) {
  .band--panel { margin-block: 0; padding-block: var(--band-y); }
  .band--panel > .wrap {
    border-radius: 0;
    padding-inline: var(--gutter);
    padding-block: 0;
    background: transparent;
  }
  .band--panel { background: var(--surface-2); }
}

/* --- narrow ------------------------------------------------------------- */

/*
   Below a thousand pixels the six column grid becomes two columns of three,
   and below seven hundred it is one column. Every span rule above is
   overridden here rather than written per breakpoint, so a new count added
   to the list above cannot forget to be responsive.
*/
@media (max-width: 1000px) {
  .bento { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .bento > *,
  .bento:has(> :nth-child(2):last-child) > *,
  .bento:has(> :nth-child(4):last-child) > :nth-child(1),
  .bento:has(> :nth-child(4):last-child) > :nth-child(4),
  .bento:has(> :nth-child(6):last-child) > :nth-child(1),
  .bento:has(> :nth-child(6):last-child) > :nth-child(4),
  .bento:has(> :nth-child(6):last-child) > :nth-child(5),
  .bento:has(> :nth-child(6):last-child) > :nth-child(6),
  .bento:has(> :nth-child(7):last-child) > :nth-child(1),
  .bento:has(> :nth-child(7):last-child) > :nth-child(6),
  .bento:has(> :nth-child(7):last-child) > :nth-child(7),
  .bento:has(> :nth-child(8):last-child) > :nth-child(1),
  .bento:has(> :nth-child(11):last-child) > :nth-child(1) { grid-column: span 3; }
}

@media (max-width: 700px) {
  .bento { grid-template-columns: minmax(0, 1fr); gap: 1.1rem; }
  .bento > * { grid-column: span 1 !important; }
  .tile { padding: 1.3rem; }
  .tile__index { font-size: 4rem; }
}

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

.feature { text-align: start; }
.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-inline-end: 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-inline-start: 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-inline-start: 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; }
}

/* --------------------------------------------------------------------------
   13. Right to left
   --------------------------------------------------------------------------
   A Dhivehi site sets dir="rtl" on <html> and almost nothing here has to
   change, because the layout is flex and grid and every directional rule in
   this file is written with logical properties. Flex order, grid columns,
   `margin-inline-start`, `padding-inline-start`, `inset-inline-end` and
   `text-align: start` all mirror on their own.

   What does NOT mirror on its own is anything drawn with a transform or an
   asymmetric glyph, because the browser has no idea an arrow points anywhere.
   That is this block, and it is deliberately this short: a long RTL override
   block is a sign the rules above are wrong and should be fixed there instead.
   ------------------------------------------------------------------------ */

/* The arrow in a "Read more" link points at the text it leads to, so it has
   to be flipped as a shape, not merely nudged the other way. */
[dir="rtl"] .text-link svg { transform: scaleX(-1); }
[dir="rtl"] .text-link:hover svg { transform: scaleX(-1) translateX(3px); }

/* The FAQ chevron turns about the horizontal axis, so it is already correct
   and is named here only so the next person does not "fix" it.
   [dir="rtl"] .faq[open] .faq__q svg — deliberately no rule. */

/* Latin runs inside Dhivehi text: a phone number, an email address, a price
   with its rufiyaa sign, a roman numeral in a heading. Left to itself the
   bidi algorithm will reorder a trailing full stop or bracket around them and
   print `(+960 7771234)` as `)+960 7771234(`. `unicode-bidi: isolate` scopes
   each run so the punctuation stays where it was written. */
[dir="rtl"] .ltr,
[dir="rtl"] time,
[dir="rtl"] a[href^="tel:"],
[dir="rtl"] a[href^="mailto:"] {
  unicode-bidi: isolate;
  direction: ltr;
  display: inline-block;
}

/* A number and its currency mark belong together and read left to right even
   inside Dhivehi, which is how every Maldivian price list is set. */
[dir="rtl"] .price,
[dir="rtl"] .money {
  unicode-bidi: isolate;
  direction: ltr;
}
