*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body { margin: 0; background: var(--warm-white); }
img, video { max-width: 100%; height: auto; display: block; }
a { color: var(--navy); text-decoration: none; }
a:hover { color: var(--rose-deep); }
button { font: inherit; }

.wrap {
  width: 100%;
  max-inline-size: var(--wrap);
  margin-inline: auto;
  padding-inline: clamp(var(--space-4), 5vw, var(--space-12));
}

/* Centre section-level content (client request round 2: "make the content
   in the center positions"). Scoped to elements sitting directly inside
   .wrap, or one plain <div> below it (services.html's per-service wrapper
   is the only such case) - deep instances of the same classes inside
   card/grid markup (work-grid titles, case-study titles, contact-grid
   labels) are deliberately left alone, since those read as component
   titles rather than section headings and centring them would fight the
   left-aligned card body text beside them.

   Display-scale elements only - .label, headings, .display, .statement,
   .stats and section-level CTAs (below). .lede is deliberately absent:
   round 2 first centred it too, but About's opening .lede is two full
   sentences sitting directly above two plain <p> tags of comparable
   length that (correctly) stay start-aligned - centred text directly
   above identically-sized left-aligned text read as a mistake, not a
   design choice. The fix is at the rule, not the instance: classify by
   content shape, not by class name. Prose of any length - .lede included -
   stays aligned to the reading direction; only its block is centred,
   below.

   Written with :is() now that the duplication guard
   (tests/test_design_system.py, _bare_class_definitions) parses it
   correctly - it used to split on every comma including the ones inside
   :is(...), which both broke real :is() selectors apart into
   non-matching fragments and could silently miss a genuine duplicate
   hidden behind one. */
.wrap > :is(.label, h1, h2, h3, .display, .statement, .stats),
.wrap > div > :is(.label, h1, h2, h3, .display, .statement, .stats) {
  text-align: center;
}

/* Centre a lone section-level CTA. .button is inline-flex, so it never
   responds to margin-inline: auto on its own - switching it to a
   block-level flex box first is what lets the auto margins centre it.
   Deliberately just .wrap > .button (not the div-nested tier above): every
   multi-button group (hero__actions, masthead__actions) sits one level
   deeper still and must keep its own flex layout untouched. */
.wrap > .button {
  display: flex;
  width: fit-content;
  margin-inline: auto;
}

/* Body prose is centred, text and block both.

   Earlier rounds centred only the block and kept the text aligned to the
   reading direction, on the grounds that ragged-both-edges prose at a 34rem
   measure is harder to read. The client asked for centred content three
   times, naming individual paragraphs the third time. That is their call on
   their own site, so the rule now matches the instruction rather than the
   typographic preference, and it is applied at the rule instead of paragraph
   by paragraph so nothing is missed and nothing drifts back.

   `main` still scopes this, because the footer needs the same treatment but
   arrives at it through its own rule - this selector's specificity (0,1,2)
   would otherwise beat .footer__brand's (0,1,1) and centre one column while
   its neighbours stayed flush. */
main .wrap > p,
main .wrap > div > p {
  margin-inline: auto;
  text-align: center;
}

/* The .meta line under a tile or beside a title. Everything else nested
   deeper than the two tiers above - the service families, the Bride Show
   strands, biographies, quotes, the contact blocks - centres itself in its
   own stylesheet, so no class is defined in two places. The process rows are
   deliberately left out: that three-column layout is one the client approved
   and did not ask to change. */
main .meta { text-align: center; }

/* One rhythm for every section, at the client's request: the same block
   padding top and bottom, and no section carrying its own margins. Anything
   that needs breathing room asks for it inside its own band rather than by
   nudging the band, which is how the spacing drifted in the first place. */
.section {
  padding-block: var(--section-gap);
  margin-block: 0;
}
/* Full-bleed bands keep the same inner rhythm as the plain ones, so a tinted
   section and a bare one next to each other read as the same height of
   breathing space. */
.section > .wrap { margin-block: 0; }
.section--tinted { background: var(--off-white); }

/* A third ground, so a long page is not a white wall. The client asked for
   more contrast from the logo's colours while keeping the pale ground as the
   main colour, so this is --rose-light mixed down to a wash rather than used
   at strength: enough to read as a different surface, nowhere near enough to
   become the page's colour. Body text on it clears 12:1.

   Mixed from a locked token rather than added as a new one - the palette is
   still the same nine values. */
.section--blush {
  background: color-mix(in srgb, var(--rose-light) 22%, var(--warm-white));
  --hairline: color-mix(in srgb, var(--rose-deep) 22%, transparent);
}

/* Quiet line ornaments for the gaps between sections. Deliberately faint -
   the client asked for something "easy looking at as if it is not there" -
   and decorative, so they carry no text alternative and are skipped by
   assistive tech entirely. */
.ornament {
  display: block;
  margin-inline: auto;
  margin-block: var(--space-8);
  inline-size: clamp(6rem, 14vw, 9rem);
  block-size: auto;
  color: var(--rose-deep);
  opacity: 0.28;
}
.section--dark .ornament,
.final-cta .ornament { color: var(--rose-light); opacity: 0.4; }
/* Deliberately no per-ground override: the client asked for every divider to
   look the same, and varying the opacity by background is exactly how two of
   them ended up looking different. */

/* The client asked for the wide gaps between sections removed and simple
   lines used to separate them instead. A hairline on every section that
   follows another does that without touching sections that open a page. */
.section + .section { border-block-start: 1px solid var(--hairline); }

/* Coloured grounds, taken from the logo. The client asked for the two black
   bands to become "one navy blue and one pink", so:

     .section--dark / .final-cta  -> --navy   #1E2A44, light text
     .section--charcoal           -> --rose-light #D8B0A4, dark text

   Both values are from the nine locked palette tokens; nothing new is
   introduced. The pink is the one place the text colour has to invert:
   --warm-white on --rose-light measures 2.2:1, which fails AA outright,
   while --ink on it clears 9.7:1. So .section--charcoal is a LIGHT ground
   and is styled separately below rather than sharing the light-text rules -
   the class name is now a misnomer, kept only because a rename would touch
   every template and the reachability guard for no visual gain.

   .section--dark and .final-cta still share every selector so the ruleset
   cannot drift apart, and each class is defined once, for the duplication
   guard. */
.section--dark,
.final-cta {
  color: var(--warm-white);
  background: var(--navy);
  --hairline: color-mix(in srgb, var(--warm-white) 16%, transparent);
}
.section--dark h1,
.section--dark h2,
.section--dark h3,
.section--dark .display,
.section--dark .statement,
.section--dark .lede,
.final-cta .statement,
.final-cta .lede { color: inherit; }
.section--dark .label,
.final-cta .label { color: var(--rose-light); }
.section--dark .button--ghost,
.final-cta .button--ghost { color: var(--warm-white); }
.section--dark .button--ghost:hover,
.final-cta .button--ghost:hover { color: var(--rose-light); }

/* The pink ground. Dark text, so the label and ghost button need the
   opposite treatment to the navy band above. */
.section--charcoal {
  color: var(--ink);
  background: var(--rose-light);
  --hairline: color-mix(in srgb, var(--ink) 20%, transparent);
}
.section--charcoal h1,
.section--charcoal h2,
.section--charcoal h3,
.section--charcoal .display,
.section--charcoal .statement,
.section--charcoal .lede { color: inherit; }
/* --rose-deep on --rose-light measures 2.07:1 at 11px, which fails AA
   outright - it was the obvious "muted" choice and it is unusable. --charcoal
   clears 7.54:1 and still reads as quieter than the heading's --ink. Hover on
   the ghost button goes the same way, for the same reason. */
.section--charcoal .label { color: var(--charcoal); }
.section--charcoal .button--ghost { color: var(--ink); }
.section--charcoal .button--ghost:hover { color: var(--charcoal); }

/* Two dark/charcoal bands in a row read as one slab: each contributes
   ~128px of padding, so up to 256px of unbroken dark ground with nothing
   in it. Mark the real junction and tighten it, so the pair reads as two
   chapters of one dark spread rather than a single mass. :is() covers
   every adjacency combination (dark-dark, charcoal-charcoal, and either
   order of dark/charcoal) in one rule, so it keeps matching regardless of
   which two dark bands end up next to each other. Scoped to the adjacency,
   so it disappears if the rhythm changes again. */
:is(.section--dark, .section--charcoal) + :is(.section--dark, .section--charcoal) {
  border-block-start: 1px solid var(--hairline);
  padding-block-start: var(--space-16);
}

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

/* Buttons. Rose and navy live here — accents, not grounds. */
.button {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding-block: var(--space-3);
  padding-inline: var(--space-6);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  border: 1px solid currentColor;
  transition: color 220ms var(--ease), background-color 220ms var(--ease),
              transform 220ms var(--ease);
}
.button:hover { transform: translateY(-2px); }
.button--solid { background: var(--navy); border-color: var(--navy); color: var(--warm-white); }
.button--solid:hover { background: var(--rose-deep); border-color: var(--rose-deep); color: var(--warm-white); }
.button--ghost { background: transparent; color: var(--ink); }
.button--ghost:hover { color: var(--rose-deep); }

.rule { border: 0; border-block-start: 1px solid var(--hairline); margin-block: 0 var(--space-8); margin-inline: 0; }

/* Proof points. Shared: the homepage credibility section, About, and
   Bride Show all render this markup, so it cannot live in one page's
   stylesheet - that would force every other page to link the homepage's. */
.stats {
  list-style: none; margin-block: var(--space-12); padding: 0;
  display: grid; gap: var(--space-8);
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
}
.stats__value {
  display: block; font-family: var(--font-heading);
  font-size: clamp(2rem, 4vw, 3.25rem); color: var(--ink);
}
.stats__label {
  display: block; margin-block-start: var(--space-2);
  font-size: 0.8125rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--neutral);
}

/* Final CTA. Shared: Home, Bride Show, and Corporate all render this
   markup, so it cannot live in one page's stylesheet - that would force
   every other page to link the homepage's. Its dark treatment is defined
   together with .section--dark, above, rather than duplicated here. */

/* Reveal — set by reveal.js (Task 18). Visible by default so content is
   never hidden if JavaScript fails. */
.reveal { opacity: 1; }
.js .reveal { opacity: 0; transform: translateY(24px); }
.js .reveal.is-visible {
  opacity: 1;
  transform: none;
  transition: opacity var(--reveal-duration) var(--ease),
              transform var(--reveal-duration) var(--ease);
}

/* ---- Arabic ----------------------------------------------------------
   A designed layout, not a mirror. Naskh needs more leading, and the
   Latin tracking that flatters capitals damages connected script. */
[dir="rtl"] {
  /* Redefine the TOKENS, not just this element's font-family. body's own
     font-family rule reads var(--font-body) directly, and a direct
     declaration beats an inherited one - so setting font-family here would
     style html while body and everything under it kept rendering Arabic in
     Figtree, a Latin face with no Arabic glyphs. Redefining the custom
     properties fixes every consumer at once, since they all read these vars. */
  --font-body: "El Messiri", "Amiri", serif;
  --font-heading: "El Messiri", "Amiri", serif;
  font-family: var(--font-body);
  letter-spacing: 0;
}
[dir="rtl"] .display,
[dir="rtl"] h1,
[dir="rtl"] h2,
[dir="rtl"] h3,
[dir="rtl"] .statement {
  font-family: "El Messiri", "Amiri", serif;
  letter-spacing: 0;
  line-height: 1.28;
}
[dir="rtl"] .label {
  letter-spacing: 0;
  text-transform: none;
  font-size: 0.8125rem;
}
[dir="rtl"] .button { letter-spacing: 0; text-transform: none; }
[dir="rtl"] p, [dir="rtl"] .lede { line-height: 1.85; }
[dir="rtl"] .stats__label { letter-spacing: 0; text-transform: none; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .js .reveal { opacity: 1; transform: none; }
}

/* ---- Lightbox ----------------------------------------------------------
   Native <dialog>, injected once by lightbox.js. Shared: Bride Show and
   the project case study page both open it, so it lives here rather than
   forcing every gallery-bearing page to link a second stylesheet. */
/* The gallery viewer. The dialog is transparent - the ::backdrop is the
   room, the photograph floats in it, and the caption/counter bar sits
   beneath like a print's margin note. */
.lightbox {
  inline-size: 100vw; max-inline-size: 100vw;
  block-size: 100vh; max-block-size: 100vh;
  padding: var(--space-4); border: 0; background: transparent;
}
.lightbox[open] {
  display: grid; grid-template-rows: 1fr auto;
  gap: var(--space-3); justify-items: center;
}
.lightbox::backdrop { background: rgb(18 17 16 / 92%); }
.lightbox__frame { display: grid; place-items: center; min-block-size: 0; inline-size: 100%; }
.lightbox__image {
  max-inline-size: min(88vw, 70rem); max-block-size: 84vh; object-fit: contain;
  opacity: 1; transition: opacity 200ms ease;
}
.lightbox__image.is-switching { opacity: 0; }
.lightbox__bar {
  display: flex; align-items: baseline; gap: var(--space-6);
  color: var(--warm-white);
}
.lightbox__caption {
  margin: 0; font-size: 0.8125rem; letter-spacing: 0.08em; opacity: 0.85;
}
.lightbox__count {
  margin: 0; font-size: 0.8125rem; letter-spacing: 0.12em;
  color: var(--rose-light); font-variant-numeric: tabular-nums;
}
.lightbox__nav {
  position: fixed; inset-block: 0; margin-block: auto;
  inline-size: 2.75rem; block-size: 2.75rem; border-radius: 50%;
  display: grid; place-items: center;
  border: 1px solid rgb(251 249 247 / 35%); background: rgb(18 17 16 / 55%);
  color: var(--warm-white); font-size: 1.625rem; line-height: 1; cursor: pointer;
  padding: 0; padding-block-end: 0.2rem;
}
.lightbox__nav--prev { inset-inline-start: var(--space-4); }
.lightbox__nav--next { inset-inline-end: var(--space-4); }
.lightbox__nav:hover, .lightbox__nav:focus-visible {
  background: var(--rose-deep); border-color: var(--rose-deep);
}
.lightbox.is-single :is(.lightbox__nav, .lightbox__count) { display: none; }
.lightbox__close {
  position: fixed; inset-block-start: var(--space-3); inset-inline-end: var(--space-4);
  background: none; border: 0; color: var(--warm-white); font-size: 2rem; cursor: pointer;
}
.lightbox__close:hover, .lightbox__close:focus-visible { color: var(--rose-light); }
@media (max-width: 48rem) {
  .lightbox__nav--prev { inset-inline-start: var(--space-2); }
  .lightbox__nav--next { inset-inline-end: var(--space-2); }
  .lightbox__image { max-inline-size: 94vw; }
}
[data-lightbox] img { cursor: zoom-in; }

/* Injected by lightbox.js around each gallery image. A button carries its own
   appearance, padding and border, none of which is wanted - it has to lay out
   exactly as the <picture> it replaced, or every gallery grid shifts. The
   focus ring is the reason this is a button at all, so it is deliberately
   visible rather than reset away. */
.lightbox-trigger {
  display: block; inline-size: 100%;
  padding: 0; border: 0; background: none;
  font: inherit; color: inherit; cursor: zoom-in;
  -webkit-appearance: none; appearance: none;
}
.lightbox-trigger:focus-visible {
  outline: 2px solid var(--rose-deep);
  outline-offset: 3px;
}

/* ---- Utility strip -----------------------------------------------------
   A thin newspaper masthead-topper, above the masthead itself. Every page
   renders partials/nav.html, so this cannot live in a page stylesheet -
   that would force every other page to link the homepage's. Locations and
   handle are copy that already exists in the footer/contact page; nothing
   new is introduced here. */
.utility-strip {
  border-block-end: 1px solid var(--hairline);
}
.utility-strip__inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-4);
  padding-block: var(--space-2);
}
.utility-strip__places,
.utility-strip__social {
  margin: 0;
  font-size: 0.6875rem; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--neutral);
}
.utility-strip__social:hover { color: var(--rose-deep); }
.utility-strip__aside {
  display: flex; align-items: center; gap: var(--space-6);
}

[dir="rtl"] .utility-strip__places,
[dir="rtl"] .utility-strip__social { letter-spacing: 0; text-transform: none; font-size: 0.8125rem; }

/* ---- Masthead (Task 3's markup, styled here) ---------------------------
   Every page renders partials/nav.html, so this cannot live in a page
   stylesheet - that would force every other page to link the homepage's. */
.masthead {
  display: flex; align-items: center; gap: var(--space-8);
  padding: var(--space-6) clamp(var(--space-4), 5vw, var(--space-12));
  position: relative; z-index: 10;
  border-block-end: 1px solid var(--hairline);
}
.masthead__brand { color: var(--ink); }
/* The client supplied the real logo, so the text wordmark is gone. Sized by
   height so the mark keeps its own proportions; the tagline inside it is
   decorative at this scale, which is normal for a logo in a masthead. */
.masthead__logo { height: clamp(3rem, 5vw, 4rem); width: auto; display: block; }
.masthead__nav { display: flex; gap: var(--space-6); margin-inline-start: auto; }
.masthead__nav a {
  font-size: 0.6875rem; letter-spacing: 0.16em; text-transform: uppercase; color: var(--charcoal);
}
.masthead__nav a:hover { color: var(--rose-deep); }

/* Active-item underline. Drawn on ::after rather than border-block-end so it
   does not shift the text, and keyed off aria-current so the visual state and
   the accessible state cannot drift apart. */
.masthead__nav a { position: relative; padding-block-end: 0.25rem; }
.masthead__nav a::after {
  content: ""; position: absolute; inset-inline: 0; inset-block-end: 0;
  block-size: 1px; background: var(--rose-deep);
  transform: scaleX(0); transform-origin: center;
  transition: transform 0.25s ease;
}
.masthead__nav a:hover::after { transform: scaleX(1); }
.masthead__nav a[aria-current="page"] { color: var(--rose-deep); }
.masthead__nav a[aria-current="page"]::after { transform: scaleX(1); }

@media (prefers-reduced-motion: reduce) {
  .masthead__nav a::after { transition: none; }
}
.masthead__actions { display: flex; align-items: center; gap: var(--space-4); }

/* The masthead follows the reader down the page, at the client's request.
   The utility strip above it deliberately scrolls away - it is locations and
   a handle, not navigation, and keeping both pinned would cost twice the
   height on a phone.

   An opaque background is required, not optional: the header sits over
   content while scrolling, and --warm-white with no backdrop would let text
   show through it. */
.masthead {
  position: sticky;
  inset-block-start: 0;
  z-index: 20;
  background: var(--warm-white);
  border-block-end: 1px solid transparent;
  transition: border-color 200ms var(--ease), box-shadow 200ms var(--ease);
}
/* Set by nav.js once the page has scrolled, so the header only announces
   itself as floating when it actually is. */
.masthead.is-stuck {
  border-block-end-color: var(--hairline);
  box-shadow: 0 1px 16px color-mix(in srgb, var(--ink) 6%, transparent);
}
@media (prefers-reduced-motion: reduce) {
  .masthead { transition: none; }
}

/* Anchors and skip links must not land under the pinned header. */
:target { scroll-margin-block-start: 6rem; }

.masthead__social {
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--charcoal); padding: var(--space-2);
}
.masthead__social:hover { color: var(--rose-deep); }
.social-icon { fill: currentColor; display: block; }

/* The sprites are definitions-only <svg> elements near the top of every
   page. Left in the flow they would still occupy a line box in some engines.
   Defined here rather than in a section stylesheet because base.html renders
   the sprite on every page, including ones that link no section CSS at all. */
.icon-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
/* Icon and label on one line wherever they appear together. */
.with-icon {
  display: inline-flex; align-items: center; gap: var(--space-2);
}
.lang-switch button {
  background: none; border: 0; cursor: pointer; color: var(--neutral);
  font-size: 0.6875rem; letter-spacing: 0.16em;
}
.lang-switch button:hover { color: var(--ink); }
.masthead__toggle { display: none; background: none; border: 0; cursor: pointer; padding: var(--space-2); }
.masthead__bars {
  display: block; inline-size: 1.25rem; block-size: 1px; background: var(--ink);
  box-shadow: 0 -6px 0 var(--ink), 0 6px 0 var(--ink);
}

[dir="rtl"] .masthead__nav a,
[dir="rtl"] .lang-switch button { letter-spacing: 0; text-transform: none; font-size: 0.9375rem; }

@media (max-width: 60rem) {
  .masthead__toggle { display: block; margin-inline-start: auto; }
  .masthead__nav {
    display: none; position: absolute; inset-inline: 0; inset-block-start: 100%;
    flex-direction: column; gap: var(--space-4);
    padding: var(--space-8) clamp(var(--space-4), 5vw, var(--space-12));
    background: var(--warm-white); border-block-end: 1px solid var(--hairline);
  }
  .masthead.is-open .masthead__nav { display: flex; }
  .masthead__nav a { font-size: 0.875rem; }
}

/* ---- Footer (Task 3's markup, restructured into columns round 2) --------
   Rendered by every page via partials/footer.html - same reasoning as the
   masthead above. Round 2: the client found the single centred stack
   "empty" - the fix adds structure (a brand column, the six existing pages,
   the three existing contact links, a bottom bar) rather than new copy, per
   the brief's instruction not to invent content the minimal-footer brief
   never approved. */
.footer {
  padding-block: var(--space-16);
  background: var(--off-white);
  border-block-start: 1px solid var(--hairline);
}
.footer__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap: var(--space-12);
  padding-block-end: var(--space-12);
  border-block-end: 1px solid var(--hairline);
  /* The first line of every column - the brand, the first menu link, the
     "Ready to begin?" heading - sits on one shared baseline, per the client.
     Their font sizes differ, so aligning tops would still look staggered;
     baseline is the alignment the eye actually reads. */
  align-items: baseline;
}
/* Every footer column centres its own contents, at the client's request.
   align-items handles the flex children (the nav and link columns, whose
   anchors are flex items) and text-align handles the text inside them -
   one without the other leaves either the text or the boxes off-axis. */
.footer__col {
  display: flex; flex-direction: column; gap: var(--space-3);
  align-items: center; text-align: center;
}
.footer__brand {
  font-family: var(--font-heading); font-size: 1.25rem; letter-spacing: 0.08em; color: var(--ink);
  margin: 0;
}
.footer__heading {
  font-size: 0.6875rem; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--neutral); margin-block-end: var(--space-2);
}
[dir="rtl"] .footer__heading { letter-spacing: 0; text-transform: none; font-size: 0.875rem; }
.footer__places, .footer__line { color: var(--neutral); font-size: 0.875rem; margin: 0; }
.footer__nav, .footer__links {
  display: flex; flex-direction: column; gap: var(--space-3);
}
.footer__nav a, .footer__links a {
  font-size: 0.75rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--charcoal);
}
.footer__nav a:hover, .footer__links a:hover { color: var(--rose-deep); }

/* Bottom bar: its own capped, centred width rather than .wrap, because
   .wrap's centring rules (above) would otherwise catch this bar's CTA -
   the bottom bar's flex layout already positions it correctly. */
/* Centred with the rest of the footer, per the client: the bar stacks its
   contents on one centred axis rather than pushing them to opposite edges. */
.footer__bottom {
  max-inline-size: var(--wrap);
  margin-inline: auto;
  padding-inline: clamp(var(--space-4), 5vw, var(--space-12));
  padding-block-start: var(--space-8);
  display: flex; align-items: center; justify-content: center; flex-wrap: wrap;
  gap: var(--space-6);
  text-align: center;
}
.footer__legal {
  display: flex; flex-wrap: wrap; gap: var(--space-6);
}
.footer__legal a { font-size: 0.6875rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--neutral); }
.footer__legal a:hover { color: var(--rose-deep); }

[dir="rtl"] .footer__brand,
[dir="rtl"] .footer__nav a,
[dir="rtl"] .footer__links a,
[dir="rtl"] .footer__legal a { letter-spacing: 0; text-transform: none; font-size: 0.9375rem; }

@media (max-width: 40rem) {
  .footer__grid { grid-template-columns: 1fr; }
  .footer__col { align-items: center; }
  .footer__bottom { flex-direction: column; }
}

/* ---- Arrival animations ------------------------------------------------
   Armed by animate.js adding .img-fade to <html>, so the opacity:0 start
   state cannot strand an image on a page where the script never ran or was
   blocked. Under prefers-reduced-motion the class is never added, so these
   rules simply do not apply. */
.img-fade img {
  opacity: 0;
  transition: opacity 600ms var(--ease);
}
.img-fade img.is-loaded { opacity: 1; }

/* The counting figure is replaced character by character, so it needs a
   stable box - otherwise every page with a stat reflows on each frame as the
   digits change width. */
.stats__value { font-variant-numeric: tabular-nums; }

/* ---- Form fields -------------------------------------------------------
   Shared by the event wizard and the contact form. It began in
   start_event.css when the wizard was the only form on the site; the contact
   page now has one too, and a second copy would both duplicate the component
   and trip the no-duplicate-definitions guard. Moved rather than copied.

   Start-aligned on purpose, and allow-listed in the centring guard: a label
   belongs above the text the visitor types, and the inputs are start-aligned. */
.field {
  margin-block-end: var(--space-8);
  border: 0;
  padding: 0;
}
.field__label {
  display: block;
  font-family: var(--font-body);
  font-size: 0.8125rem;
  letter-spacing: 0.04em;
  color: var(--charcoal);
  margin-block-end: var(--space-2);
}
.field__hint {
  font-size: 0.8125rem;
  color: var(--neutral);
  margin-block: 0 var(--space-3);
}
.field__input,
.field__textarea {
  display: block;
  inline-size: 100%;
  padding-block: var(--space-3);
  padding-inline: var(--space-4);
  border: 1px solid var(--hairline);
  background: var(--warm-white);
  color: var(--ink);
  font: inherit;
  font-size: 1rem;
}
.field__input:focus,
.field__textarea:focus {
  outline: 2px solid var(--rose-deep);
  outline-offset: 2px;
}
.field__textarea { resize: vertical; min-block-size: 8rem; }
.field__error {
  color: var(--rose-deep);
  font-size: 0.8125rem;
  margin-block: var(--space-2) 0;
}

/* Font Awesome marks above each service family, at the client's request. Both
   the homepage cards and the Work page's .offer block use them, and the Work
   page does not link home.css - so this lives here, defined once, which is
   also what the no-duplicate-definitions guard wants.

   The card already names the service, so the icon is decorative and carries
   no text alternative. --rose-deep on the blush ground measures
   3.42:1, clearing the 3:1 WCAG asks of non-text contrast (1.4.11). */
.offer__icon {
  display: block;
  margin-inline: auto;
  margin-block-end: var(--space-3);
  color: var(--rose-deep);
}
.offer__icon svg { display: block; margin-inline: auto; fill: currentColor; }

/* The footer's call to action took the social column's place and the social
   marks moved to the bottom bar - the client asked for the two to swap. */
.footer__cta { align-items: center; gap: var(--space-4); }
.footer__social { display: flex; align-items: center; gap: var(--space-6); }
.footer__social a { color: var(--charcoal); display: inline-flex; padding: var(--space-1); }
.footer__social a:hover { color: var(--rose-deep); }

/* The 404 page's row of ways back. */
.lost-links {
  display: flex; flex-wrap: wrap; gap: var(--space-4);
  justify-content: center;
  margin-block-start: var(--space-8);
}
