/* ==========================================================================
   James Santiago Landscape Architect · Website v3
   Built by GoldsWebsite.com
   --------------------------------------------------------------------------
   Design direction: "Open Frame".

     Premise   The photography is the practice's best salesman. So the page is
               a quiet cool-grey field, and white panels and image frames float
               on top of it. Nothing on this site touches the edge of the
               viewport; canvas is visible on all four sides of everything.

     Surfaces  Cool porcelain canvas + white panels. No warm cast anywhere.
     Anchor    One deep harbor blue. It appears in exactly three roles:
               primary buttons, small chips, and the single dark CTA panel.
               All other colour on the page comes from the photographs.
     Type      DM Sans throughout. Headings at 600, everything else at 400/500.
               There is no uppercase tracked label anywhere on this site.
     Geometry  Soft. 6 to 20px radii. Separation is whitespace and shadow, not
               rules. There is not a single page-spanning hairline in the
               layout, only a footer rule and hairlines inside panels.
     Motion    Content settles in. Nothing performs. No parallax, no masked
               line reveals, no cursor-tracked previews. Off under
               prefers-reduced-motion.

   TWO RULES THAT KEEP THIS LOOK HONEST. Do not break them in later edits:
     1. ELEVATION BUDGET. Three shadow tokens, one role each, and nothing wears
        two at once:
          --shadow-panel   resting panels and cards (.panel, .card, .credential)
          --shadow-media   photographic frames only (.frame)
          --shadow-chrome  surfaces that float clear of the page: the stuck
                           header, the mobile sheet, the call bar, the focused
                           skip link
        Hover never changes shadow here; the hover affordance is a 1px lift plus
        a colour change. Never stack the tokens and never raise the alpha;
        shadow is doing all the structural work and it tips into "template" the
        moment it gets heavy.
     2. ONE DARK OBJECT PER PAGE. The CTA panel is it. Do not add a second dark
        band, a dark footer or a dark hero. The single inversion near the end is
        what gives the page its one moment of weight.

   Every colour token below carries its measured contrast ratio. Those numbers
   are the reason the values look darker than a typical "muted grey". Please
   do not lighten them.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1 · Tokens
   -------------------------------------------------------------------------- */
:root {
  /* --- Surfaces. Cool, low chroma, never warm. --- */
  --canvas:        #EEF1F4;  /* the field everything floats on */
  --canvas-deep:   #E4E9ED;  /* one step down; recessed areas */
  --surface:       #FFFFFF;  /* every floating panel */
  --surface-quiet: #F7F9FB;  /* cells nested inside a white panel */
  --tint:          #DFE9F0;  /* chip fills, image placeholder before load */

  /* --- Ink. Ratios measured against white / canvas / tint. --- */
  --ink:      #0F1A21;  /* headings, values.        17.65 : 1 on white */
  --ink-body: #3C4A54;  /* body copy, nav links.     9.13 : 1 on white */
  --ink-mute: #55646F;  /* captions, meta, labels.   6.11 : 1 on white,
                           5.39 on canvas, 5.00 on canvas-deep, 4.96 on tint.
                           TIGHTEST HEADROOM IN THE SYSTEM, 0.46 above the
                           4.5:1 floor. Never lighten it, and never set it
                           below 14px on --tint. */

  /* --- The single anchor hue. --- */
  --anchor:      #0E3F5C;  /* buttons, chips, CTA panel.  11.15 : 1 vs white */
  --anchor-deep: #0A2E44;  /* pressed / hover.            14.12 : 1 vs white */
  --link:        #0A5478;  /* text links (always underlined). 8.23 : 1 white */
  --focus:       #0E6BA8;  /* focus ring. 5.70 : 1 white, 5.02 : 1 canvas */

  /* --- Lines. There are almost none; see the header comment. --- */
  --hairline:        #DDE3E9;  /* decorative, inside panels only */
  --hairline-strong: #D6DEE5;  /* the two footer rules */
  --border-ui:       #768695;  /* functional borders. 3.74 white, 3.30 canvas */
  --border-quiet:    #CFD8E0;  /* chips only: decorative, not a boundary */

  /* --- On the one dark panel. --- */
  --on-dark:      #FFFFFF;              /* 11.15 : 1 on --anchor */
  --on-dark-soft: #F2F6F8;              /* 10.25 : 1 on --anchor */
  --on-dark-mute: #B9CBD8;              /*  6.69 : 1 on --anchor */
  --on-dark-line: rgba(255, 255, 255, 0.18);

  /* --- The one non-blue hue on the site: invalid form state, nothing else. --- */
  --error:      #B3261E;  /* 6.54 : 1 on white, 5.66 : 1 on --error-tint */
  --error-tint: #FBEBEA;

  /* --- Radii. Nothing on this site is square (the inverse of v2). --- */
  --r-xs: 6px;    /* dots, tiny marks */
  --r-sm: 10px;   /* buttons, inputs, selects */
  --r-md: 12px;   /* chips, nested cells */
  --r-lg: 16px;   /* photographic frames */
  --r-xl: clamp(14px, 1.8vw, 20px);  /* panels, hero media, CTA panel */

  /* --- Shadows. Wide, far-offset, low alpha. See ELEVATION BUDGET above. --- */
  --shadow-panel: 0 1px 2px rgba(15, 26, 33, 0.04), 0 12px 28px -14px rgba(15, 26, 33, 0.10);
  --shadow-media: 0 18px 44px -26px rgba(14, 63, 92, 0.22);  /* tinted with the
                    anchor rather than black, so photographs read as lit by
                    cool daylight instead of pasted onto grey */
  --shadow-chrome: 0 1px 2px rgba(15, 26, 33, 0.05), 0 10px 26px -16px rgba(15, 26, 33, 0.14);

  /* --- Type. One family, no second face. --- */
  --sans: "DM Sans", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
          "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  /* --- Spacing. 4px base. Every gap in the build comes from this ramp. --- */
  --s-1:  4px;
  --s-2:  8px;
  --s-3:  12px;
  --s-4:  16px;
  --s-5:  20px;
  --s-6:  24px;
  --s-7:  32px;
  --s-8:  40px;
  --s-9:  56px;
  --s-10: 72px;
  --s-11: 96px;
  --s-12: 128px;

  /* --- Rhythm. The wrap is narrow on purpose: the panels need canvas
         showing on both sides in order to float. --- */
  --wrap:      1240px;
  --gutter:    clamp(16px, 4vw, 40px);
  --section:   clamp(64px, 8vw, 112px);
  --pad:       clamp(24px, 4vw, 52px);
  --header-h:  68px;

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* --------------------------------------------------------------------------
   2 · Reset & base
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + 28px);
}

body {
  margin: 0;
  background: var(--canvas);
  color: var(--ink-body);
  font-family: var(--sans);
  font-size: 1.0625rem;      /* 17px */
  line-height: 1.72;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-tap-highlight-color: rgba(14, 63, 92, 0.12);
  overflow-x: hidden;
}

img, svg, video { display: block; max-width: 100%; }
ul, ol { margin: 0; padding: 0; list-style: none; }
button { font: inherit; color: inherit; }

/* --------------------------------------------------------------------------
   3 · Type scale
   --------------------------------------------------------------------------
   Headings are 600. Everything else (nav, buttons, labels, chips) is 500 or
   400. That gap is what makes the page read as authored rather than defaulted.
   Sizes are given with their px equivalents so the scale cannot drift.
   -------------------------------------------------------------------------- */
h1, h2, h3 {
  margin: 0;
  color: var(--ink);
  font-weight: 600;
  text-wrap: balance;
}

h1 {
  font-size: clamp(2.5rem, 1.6rem + 3.6vw, 4rem);   /* 40 → 64px */
  line-height: 1.06;
  letter-spacing: -0.028em;
  max-width: 17ch;
}

h2 {
  font-size: clamp(1.875rem, 1.35rem + 2vw, 2.75rem); /* 30 → 44px */
  line-height: 1.10;
  letter-spacing: -0.024em;
  max-width: 20ch;
}

.h2--sm {
  font-size: clamp(1.625rem, 1.4rem + 1.1vw, 2.125rem); /* 26 → 34px */
  line-height: 1.16;
  letter-spacing: -0.02em;
  max-width: 22ch;
}

h3 {
  font-size: clamp(1.25rem, 1.16rem + 0.4vw, 1.4375rem); /* 20 → 23px */
  line-height: 1.24;
  letter-spacing: -0.016em;
}

/* Was h4. The heading levels moved for outline order (h3 in a panel, h2 in the
   footer landmark); this class keeps the scale those sub-heads were drawn at. */
.h3--sm {
  font-size: 1.0625rem;   /* 17px */
  font-weight: 500;
  line-height: 1.34;
  letter-spacing: -0.008em;
}

p { margin: 0; }
p + p { margin-top: 1.15em; }

.lede {
  font-size: clamp(1.125rem, 1.05rem + 0.4vw, 1.3125rem);  /* 18 → 21px */
  line-height: 1.55;
  letter-spacing: -0.006em;
  color: var(--ink-body);
  max-width: 54ch;
}

.meta {
  font-size: 0.875rem;   /* 14px */
  line-height: 1.5;
  color: var(--ink-mute);
}

strong, b { font-weight: 600; color: var(--ink); }
em { font-style: italic; }

/* Figures never look ragged: LA0000795 sits beside 1982 constantly. */
.num { font-variant-numeric: tabular-nums; }

/* --------------------------------------------------------------------------
   4 · Layout primitives
   -------------------------------------------------------------------------- */
.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section { margin-top: var(--section); }
.section--tight { margin-top: clamp(40px, 5vw, 64px); }

/* The one white panel component. Everything on this site is one of these. */
.panel {
  background: var(--surface);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-panel);
  padding: var(--pad);
}

.panel--quiet { background: var(--surface-quiet); box-shadow: none; }

/* Photographic frame. Ratios are locked to the source files' native crops so
   object-fit: cover has nothing to trim. Do not invent new ratios. */
.frame {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-lg);
  background: var(--tint);
  box-shadow: var(--shadow-media);
}

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

.frame--wide    { aspect-ratio: 16 / 9; }   /* 1672 × 941 sources */
.frame--tall    { aspect-ratio: 3 / 4; }    /* 1086 × 1448 sources */
.frame--port    { aspect-ratio: 4 / 5; }    /* 1122 × 1402 portrait */
.frame--ultra   { aspect-ratio: 21 / 9; }   /* 1915 × 821 studio exterior */
.frame--square  { aspect-ratio: 1 / 1; }    /* 1 : 1. No consumer yet; the
                                               service-area map is a diagram and
                                               uses .map, which carries no
                                               photographic shadow */
.frame--xl      { border-radius: var(--r-xl); }

/* Section heading block. */
.head {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-5) var(--s-8);
  align-items: flex-end;
  justify-content: space-between;
}

.head__title { max-width: 30ch; }
.head h2 { margin-top: 14px; }
.head .lede { margin-top: var(--s-4); }
.head__aside { max-width: 34ch; }

.mt-3 { margin-top: var(--s-3); }
.mt-4 { margin-top: var(--s-4); }
.mt-5 { margin-top: var(--s-5); }
.mt-6 { margin-top: var(--s-6); }
.mt-7 { margin-top: var(--s-7); }

/* --------------------------------------------------------------------------
   5 · Chips, buttons, links
   -------------------------------------------------------------------------- */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: 30px;
  padding: 4px 13px;
  border-radius: var(--r-md);
  background: var(--tint);
  color: var(--anchor);
  font-size: 0.8125rem;   /* 13px */
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: 0;
}

.chip::before {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}

.chip--dark {
  background: rgba(255, 255, 255, 0.14);
  border: 1px solid var(--on-dark-line);
  color: var(--on-dark);
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: 48px;
  padding: 0 22px;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  font-size: 0.96875rem;   /* 15.5px */
  font-weight: 500;
  letter-spacing: 0.005em;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.16s var(--ease), border-color 0.16s var(--ease),
              color 0.16s var(--ease), transform 0.16s var(--ease);
}

.btn svg { width: 17px; height: 17px; flex: none; }

.btn--primary { background: var(--anchor); color: #fff; }
.btn--primary:hover { background: var(--anchor-deep); transform: translateY(-1px); }

/* Ghost buttons use --border-ui, not --border-quiet: on the canvas field a
   1.44:1 border reads as floating text rather than a control. */
.btn--ghost {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--border-ui);
}
.btn--ghost:hover { border-color: var(--anchor); color: var(--anchor); transform: translateY(-1px); }

.btn--light { background: #fff; color: var(--anchor); }
.btn--light:hover { background: var(--on-dark-soft); transform: translateY(-1px); }

.btn--sm { min-height: 42px; padding: 0 18px; font-size: 0.90625rem; }
.btn--block { width: 100%; }

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
}

a { color: var(--link); }

.tlink {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: var(--link);
  font-size: 0.9375rem;   /* 15px */
  font-weight: 500;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
  text-decoration-color: rgba(10, 84, 120, 0.35);
  transition: text-decoration-color 0.16s var(--ease), color 0.16s var(--ease);
}
.tlink:hover { text-decoration-color: currentColor; }
.tlink svg { width: 15px; height: 15px; transition: transform 0.2s var(--ease); }
.tlink:hover svg { transform: translateX(3px); }

/* Focus is visible everywhere and never animates. The outline follows each
   element's own border-radius. Do not add `border-radius: inherit` here, it
   changes the shape of whatever is focused. */
:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}
.on-dark :focus-visible { outline-color: #fff; }

/* The skip target is focused by the browser on fragment navigation, never by
   the visitor's own Tab, so it needs no indicator, and without this the global
   rule draws a 3px box around the whole content column. */
#main:focus, #main:focus-visible { outline: none; }

.skip-link {
  position: fixed;
  top: 10px;
  left: 10px;
  z-index: 200;
  padding: 12px 18px;
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--ink);
  font-weight: 500;
  text-decoration: none;
  /* Parked off-screen with no shadow: a shadow on a translated element still
     paints, and left a smudge in the top-left corner of every page. */
  transform: translateY(-160%);
  transition: transform 0.18s var(--ease);
}
.skip-link:focus-visible {
  transform: none;
  outline-offset: 3px;
  box-shadow: var(--shadow-chrome);
}

/* Provided for future pages; no current consumer. */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   6 · Header
   --------------------------------------------------------------------------
   Sits inside the .wrap, so canvas shows to its left and right. v2's header
   is a flat full-bleed bar with a hairline under it. At rest this one is
   transparent and weightless; past 16px of scroll it becomes a white panel
   that floats clear of the top edge.
   -------------------------------------------------------------------------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 60;
  padding-top: 8px;
}

/* Masks the 8px of canvas above the floating bar, so page content does not
   show through the gap as it scrolls underneath. */
.site-header::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 8px;
  background: var(--canvas);
  opacity: 0;
  transition: opacity 0.2s var(--ease);
}
.site-header.is-stuck::before { opacity: 1; }

.site-header__inner {
  display: flex;
  align-items: center;
  gap: var(--s-5);
  min-height: var(--header-h);
  padding: 0 var(--s-4) 0 var(--s-5);
  border-radius: var(--r-xl);
  background: transparent;
  transition: background-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
}

.site-header.is-stuck .site-header__inner {
  background: rgba(255, 255, 255, 0.92);
  -webkit-backdrop-filter: blur(14px) saturate(1.3);
  backdrop-filter: blur(14px) saturate(1.3);
  box-shadow: var(--shadow-chrome);
}

/* With the menu open the bar and the sheet below it are a single panel, so the
   bar takes the stuck treatment whether or not the page has been scrolled, and
   loses the corners on the edge it now shares. Before this, at the top of the
   page an open menu sat under a transparent bar, and at any scroll position
   there was an 8px stripe of live page showing between the two white panels,
   which reads as a rendering fault rather than as this design's floating gap. */
body.nav-open .site-header__inner {
  background: rgba(255, 255, 255, 0.92);
  -webkit-backdrop-filter: blur(14px) saturate(1.3);
  backdrop-filter: blur(14px) saturate(1.3);
  border-end-start-radius: 0;
  border-end-end-radius: 0;
}

@supports not ((backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px))) {
  .site-header.is-stuck .site-header__inner,
  body.nav-open .site-header__inner { background: #fff; }
}

.brand {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  text-decoration: none;
  color: var(--ink);
}

.brand__name {
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: -0.016em;
  white-space: nowrap;
}

.brand__rule {
  width: 1px;
  height: 15px;
  background: var(--border-quiet);
}

.brand__tag {
  font-size: 0.8125rem;
  color: var(--ink-mute);
  white-space: nowrap;
}

.nav {
  position: relative;   /* containing block for .nav__panel--wide, see 6a */
  display: flex;
  align-items: center;
  /* 20px, not 24. Two submenu carets were added to this bar and the row is
     capped by --wrap (1240px), not by the viewport, so the extra 24px had to
     come back out of the gaps or the full header would not fit at any width. */
  gap: var(--s-5);
  flex: none;   /* shed whole items at the breakpoints, never squeeze them */
}

.nav__link {
  position: relative;
  padding: 6px 0;
  color: var(--ink-body);
  font-size: 0.9375rem;
  font-weight: 500;
  text-decoration: none;
  /* Two-word items ("Service areas") must not break across lines: the bar is
     sized to shed whole items, and a wrapped link doubles the header height
     and drops the current-page dot below the baseline. */
  white-space: nowrap;
  transition: color 0.16s var(--ease);
}
.nav__link:hover { color: var(--anchor); }

/* aria-current="true" marks the section a child page sits inside. It takes the
   colour but not the dot: the dot means "this is the page you are on", and a
   service page is not the services hub. */
.nav__link[aria-current] { color: var(--anchor); }
.nav__link[aria-current="page"]::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -5px;
  width: 5px;
  height: 5px;
  margin-left: -2.5px;
  border-radius: 50%;
  background: currentColor;
}

/* --------------------------------------------------------------------------
   6a · Nav submenus
   --------------------------------------------------------------------------
   Services and Service areas each own a panel listing the pages beneath them.
   The top-level item stays a real link to its hub, so the panel is a second
   route and never the only one: a tap on a touch screen still lands somewhere.

   The panel opens on hover and on :focus-within. Focus lands on the top-level
   link first, which is inside .nav__group, so the panel is already visible by
   the time Tab reaches the links inside it. Escape closes it (script.js).
   -------------------------------------------------------------------------- */
.nav__group { position: relative; display: flex; align-items: center; gap: 4px; }

.nav__caret {
  flex: none;
  width: 6px;
  height: 6px;
  margin-top: -3px;
  border-right: 1.5px solid var(--ink-mute);
  border-bottom: 1.5px solid var(--ink-mute);
  transform: rotate(45deg);
  transition: transform 0.18s var(--ease), border-color 0.16s var(--ease);
}
.nav__group:hover .nav__caret,
.nav__group:focus-within .nav__caret {
  border-color: var(--anchor);
  transform: rotate(-135deg);
  margin-top: 2px;
}

.nav__panel {
  position: absolute;
  top: 100%;
  left: -14px;
  z-index: 10;
  margin-top: 12px;
  padding: var(--s-3);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow-chrome);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transition: opacity 0.16s var(--ease), transform 0.16s var(--ease),
              visibility 0s linear 0.16s;
}

.nav__group:hover > .nav__panel,
.nav__group:focus-within > .nav__panel {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition: opacity 0.16s var(--ease), transform 0.16s var(--ease), visibility 0s;
}
.nav__group.is-closed > .nav__panel {
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
}

/* The 12px of canvas between the bar and the panel would drop the hover the
   moment the pointer crossed it. This bridges the gap without painting. */
.nav__panel::before {
  content: "";
  position: absolute;
  inset: -14px 0 100% 0;
}

.nav__panel a {
  display: block;
  padding: 9px 12px;
  border-radius: var(--r-sm);
  color: var(--ink-body);
  font-size: 0.9375rem;
  font-weight: 500;
  line-height: 1.35;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color 0.14s var(--ease), color 0.14s var(--ease);
}
.nav__panel a:hover { background: var(--tint); color: var(--anchor); }

.nav__panel-foot {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid var(--hairline);
}
.nav__panel-foot a { color: var(--link); }

/* There used to be a second, three-column panel here listing all twenty-seven
   municipalities. It was removed: too much menu on desktop, and twenty-seven
   rows deep in the mobile sheet. The service areas hub lists them instead. */

/* The auto margin lives here, not on .nav. On .nav it stopped working the
   moment the nav was hidden, which is every width below 1090px: the phone
   button and the hamburger bunched up against the wordmark with the rest of the
   bar empty to their right. .header__actions is the one child of the bar that
   is present at every width, so it is the only safe place for it. */
.header__actions {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-left: auto;
}
.header__actions .btn { white-space: nowrap; }

.header__phone {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: var(--ink-body);
  font-size: 0.9375rem;
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
}
/* flex: none, or the icon is the first thing the flex algorithm crushes when
   the bar runs short: it collapses to 0px wide and silently hides the fact
   that the row no longer fits. The breakpoints below are what handle a short
   bar; nothing here is allowed to shrink. */
.header__phone svg { flex: none; width: 15px; height: 15px; color: var(--anchor); }
.header__phone:hover { color: var(--anchor); }

/* --------------------------------------------------------------------------
   6b · The menu toggle, in the header where people look for it
   --------------------------------------------------------------------------
   It carries both icons at once and shows one: bars while the menu is closed,
   an X while it is open. Both are in the button, so the box never changes size
   and the icon never has to be redrawn by script.

   It stays reachable while the menu is open because the header is sticky, the
   body is scroll-locked behind it, and the sheet does its own scrolling inside
   its max-height. Nothing about the open menu can push this button off screen.
   -------------------------------------------------------------------------- */
.nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid var(--border-ui);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}
.nav-toggle:hover { border-color: var(--anchor); color: var(--anchor); }

.nav-toggle__bars { display: block; }
.nav-toggle__bars span {
  display: block;
  width: 17px;
  height: 1.6px;
  background: currentColor;
  border-radius: 2px;
}
.nav-toggle__bars span + span { margin-top: 4px; }
.nav-toggle__x { display: none; width: 19px; height: 19px; }

body.nav-open .nav-toggle__bars { display: none; }
body.nav-open .nav-toggle__x { display: block; }
body.nav-open .nav-toggle { border-color: var(--anchor); color: var(--anchor); }

/* --------------------------------------------------------------------------
   6c · The bottom-right dock: back to top, and the call bar's own switch
   --------------------------------------------------------------------------
   No navigation lives here. The call bar can be dismissed with the X on its
   right-hand end, and this is where it goes when it is: one tap brings it back.
   -------------------------------------------------------------------------- */
.dock {
  position: fixed;
  right: var(--s-3);
  bottom: var(--s-3);
  z-index: 80;               /* over the call bar and the sheet */
  display: none;
  flex-direction: column;
  align-items: center;
  gap: var(--s-2);
}

.dock__btn {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  padding: 0;
  border: 1px solid var(--border-quiet);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.94);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  box-shadow: var(--shadow-chrome);
  color: var(--ink);
  cursor: pointer;
  transition: background-color 0.16s var(--ease), border-color 0.16s var(--ease),
              color 0.16s var(--ease), transform 0.16s var(--ease);
}
.dock__btn:hover { border-color: var(--anchor); color: var(--anchor); transform: translateY(-1px); }

/* The white ring is doing real work, not decoration: the dock floats over
   whatever happens to be behind it, and a plain dark disc disappeared entirely
   when it passed over the dark CTA panel. A border rather than a second
   box-shadow, so the elevation budget still holds at one shadow per element. */
.dock__calls {
  display: none;                       /* only while the call bar is dismissed */
  background: var(--anchor);
  border: 2px solid rgba(255, 255, 255, 0.92);
  color: #fff;
}
.dock__calls:hover { background: var(--anchor-deep); border-color: #fff; color: #fff; }
.dock__calls svg { width: 21px; height: 21px; }
body.calls-off .dock__calls { display: grid; }

.dock__top { width: 44px; height: 44px; }
.dock__top svg { width: 19px; height: 19px; }

/* Back to top only earns its place once there is a page above you. */
.dock__top {
  opacity: 0;
  visibility: hidden;
  transform: translateY(8px);
  transition: opacity 0.2s var(--ease), transform 0.2s var(--ease),
              visibility 0s linear 0.2s;
}
.dock.is-scrolled .dock__top {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition: opacity 0.2s var(--ease), transform 0.2s var(--ease), visibility 0s;
}

/* The menu covers the page, so neither of these has anything to do while it
   is open. */
body.nav-open .dock { display: none; }

@media (max-width: 1090px) {
  .nav-toggle { display: flex; }
}

.dock { display: flex; }

/* Above the call bar rather than on top of it, and back down to the corner
   once the bar has been dismissed. */
@media (max-width: 700px) {
  .dock { bottom: 84px; }
  body.calls-off .dock { bottom: var(--s-3); }
}

/* Mobile sheet. It hangs directly off the bottom of the header bar with no gap
   between them: see the body.nav-open rule in section 6. */
.sheet {
  display: none;
  padding: var(--s-5);
  border-radius: 0 0 var(--r-xl) var(--r-xl);
  background: var(--surface);
  box-shadow: var(--shadow-chrome);
  /* Reserve = 8px of header padding + the header height + 8px below. */
  max-height: calc(100vh - var(--header-h) - 16px);
  max-height: calc(100dvh - var(--header-h) - 16px);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}
body.nav-open .sheet { display: block; }

/* Scroll lock. `overflow: hidden` alone is ignored by iOS Safari, which lets
   the page carry on scrolling behind an open menu; the script pins the body and
   restores the exact scroll position on close, so this needs the offset too. */
body.nav-open {
  position: fixed;
  left: 0;
  right: 0;
  width: 100%;
  overflow: hidden;
}

/* The page behind the open menu is dimmed and, more to the point, taken out of
   the hit test. Without this the live page is still tappable everywhere around
   the sheet, so a tap meant to dismiss the menu navigated somewhere instead.
   Tinted with the canvas rather than black: this is the porcelain field coming
   forward, not a modal scrim, and it leaves the page's one dark object alone.
   z-index sits under the header's 60 so the bar and the sheet stay above it. */
body.nav-open::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 55;
  background: rgba(238, 241, 244, 0.72);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
}

.sheet__link {
  position: relative;
  display: block;
  padding: 14px 0;
  border-bottom: 1px solid var(--hairline);
  color: var(--ink);
  font-size: 1.0625rem;
  font-weight: 500;
  text-decoration: none;
}
/* Attribute presence, matching .nav__link above: the sheet has to colour the
   ancestor-section marker (aria-current="true") as well as the current page,
   or the 43 child pages lose their "you are in this section" cue exactly where
   the sheet is the only navigation there is. The dot below stays exact-match. */
.sheet__link[aria-current] { color: var(--anchor); }
.sheet__link[aria-current="page"]::after {
  content: "";
  position: absolute;
  right: 2px;
  top: 50%;
  width: 5px;
  height: 5px;
  margin-top: -2.5px;
  border-radius: 50%;
  background: currentColor;
}

/* Sheet submenus. Native <details>, so the mobile menu needs no script of its
   own, and the section you are already inside opens by default. */
.sheet__group { border-bottom: 1px solid var(--hairline); }

.sheet__summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  border-bottom: 0;
  cursor: pointer;
  list-style: none;
}
.sheet__summary::-webkit-details-marker { display: none; }
.sheet__summary[aria-current] { color: var(--anchor); }

.sheet__summary::after {
  content: "";
  flex: none;
  width: 9px;
  height: 9px;
  margin-top: -3px;
  border-right: 1.6px solid var(--anchor);
  border-bottom: 1.6px solid var(--anchor);
  transform: rotate(45deg);
  transition: transform 0.2s var(--ease);
}
.sheet__group[open] .sheet__summary::after { transform: rotate(-135deg); margin-top: 3px; }

.sheet__sub { display: grid; gap: 2px; padding-bottom: var(--s-4); }
.sheet__sub a {
  display: block;
  padding: 9px 12px;
  border-radius: var(--r-sm);
  color: var(--ink-body);
  font-size: 0.9375rem;
  line-height: 1.35;
  text-decoration: none;
}
.sheet__sub a:hover { background: var(--tint); color: var(--anchor); }
.sheet__sub a[aria-current="page"] { color: var(--anchor); }

.sheet__sub-all { font-weight: 500; color: var(--link); }

/* Twenty-seven names, so this one runs in two columns inside the sheet. */
.sheet__sub--cols { grid-template-columns: 1fr 1fr; gap: 2px var(--s-2); }
.sheet__sub--cols .sheet__sub-all { grid-column: 1 / -1; }
.sheet__sub--cols a:not(.sheet__sub-all) { padding-inline: 12px 6px; font-size: 0.90625rem; }

.sheet__foot { margin-top: var(--s-5); display: grid; gap: var(--s-3); }
.sheet__foot a:not(.btn) { color: var(--ink-body); font-size: 0.9375rem; text-decoration: none; }

/* On a short phone the menu is taller than the room it has and has to scroll
   inside itself, so it is tightened rather than left with rows below the fold.
   This block must stay AFTER the rules it overrides: it matches them on
   specificity, so placed earlier in the file it silently loses and the menu
   keeps its full height. 11px still clears the 44px touch target (11 + 24 + 11
   = 46), which is the floor these rows may not go under. */
@media (max-height: 660px) {
  .sheet { padding: var(--s-4); }
  .sheet__link { padding: 11px 0; }
  .sheet__sub { padding-bottom: var(--s-5); }
  .sheet__foot { margin-top: var(--s-4); gap: var(--s-2); }
}

/* The header sheds items in order rather than letting anything wrap to a second
   line, which would swell the bar.

   THE ORDER CHANGED. It used to be phone, then CTA, then nav, which left the
   phone number invisible from 1233px all the way down, and the call bar does
   not appear until 700px. So on every tablet and small laptop the business had
   no phone number in its header at all. For a licensed trade the phone is the
   conversion, so it now leaves last, and it never fully leaves: below the width
   where the digits fit it becomes an icon button that still dials.

   The order is now: the nav folds into the sheet, then the CTA button (which
   survives in the sheet and in the mobile call bar), then the phone's digits.

   The three widths are MEASURED BY 1px SWEEP against .site-header__inner, not
   estimated: arithmetic here has been wrong before because it is easy to forget
   the header's own padding-inline (36px), the flex gaps (20 + 20 + 12), and
   because a part read at a width where it has already been compressed gives its
   squeezed size rather than its natural one. Nothing in this bar may
   flex-shrink, so a bar that no longer fits overflows visibly instead of
   quietly compressing and passing the test. Re-sweep all three before adding a
   seventh nav item, a longer label or another submenu caret. */
/* First to go is the brand's tagline, the one part of the bar that carries no
   destination. Dropping it early is what lets the nav survive to 1090 instead
   of folding just under the 1240px wrap cap. */
@media (max-width: 1300px) {
  .brand__rule, .brand__tag { display: none; }
}

@media (max-width: 1090px) {
  .nav { display: none; }
  /* The menu button that replaces it is not in this bar at all; it is the fixed
     dock at the bottom right. See section 6b. */
}

@media (max-width: 640px) {
  .header__actions .btn { display: none; }
}

/* The digits go, the icon stays. Tapping it still calls the studio, and the
   link keeps its own aria-label so the number is still announced. */
@media (max-width: 760px) {
  .header__phone-num { display: none; }
  .header__phone {
    justify-content: center;
    width: 44px;
    height: 44px;
    border: 1px solid var(--border-ui);
    border-radius: var(--r-sm);
  }
  .header__phone:hover { border-color: var(--anchor); }
  .header__phone svg { width: 18px; height: 18px; }
}

@media (max-width: 950px) {
  .site-header__inner { padding-inline: var(--s-4); }
}

/* --------------------------------------------------------------------------
   7 · Hero
   -------------------------------------------------------------------------- */
.hero { padding-top: clamp(36px, 5.5vw, 72px); }

.hero h1 { margin-top: var(--s-5); }
.hero .lede { margin-top: 22px; }
.hero .actions { margin-top: var(--s-7); }

.hero__media {
  position: relative;
  margin-top: clamp(36px, 5.5vw, 60px);
}

/* The one piece of text allowed over a photograph on this site, and it leaves
   the photo entirely below 700px rather than shrinking on top of it. */
.credential {
  position: absolute;
  left: clamp(16px, 2.4vw, 28px);
  bottom: clamp(16px, 2.4vw, 28px);
  max-width: 340px;
  padding: 16px 20px;
  border-radius: var(--r-lg);
  background: rgba(255, 255, 255, 0.94);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  box-shadow: var(--shadow-panel);
}
.credential__place { color: var(--anchor); font-size: 0.8125rem; font-weight: 500; }
.credential__what { margin-top: 4px; color: var(--ink-body); font-size: 0.9375rem; line-height: 1.45; }

@media (max-width: 700px) {
  .credential {
    position: static;
    max-width: none;
    margin-top: var(--s-3);
    background: var(--surface);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

/* --------------------------------------------------------------------------
   8 · Fact row: tucks up under the hero with no section gap
   -------------------------------------------------------------------------- */
.facts {
  margin-top: clamp(20px, 2.4vw, 28px);
  padding: clamp(20px, 2.8vw, 30px);
}

.facts__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
}

.facts__cell { padding-inline: clamp(16px, 2.2vw, 28px); }
.facts__cell + .facts__cell { border-left: 1px solid var(--hairline); }
.facts__cell:first-child { padding-left: 0; }
.facts__cell:last-child { padding-right: 0; }

.facts__k { color: var(--ink-mute); font-size: 0.8125rem; font-weight: 500; }
.facts__v {
  margin-top: 5px;
  color: var(--ink);
  font-size: 1.1875rem;   /* 19px */
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: -0.012em;
}

@media (max-width: 900px) {
  .facts__grid { grid-template-columns: repeat(2, 1fr); row-gap: var(--s-5); }
  .facts__cell:nth-child(odd) { padding-left: 0; border-left: 0; }
  .facts__cell:nth-child(even) { padding-right: 0; }
}

@media (max-width: 520px) {
  .facts__grid { grid-template-columns: 1fr; row-gap: 0; }
  .facts__cell { padding: var(--s-4) 0; border-left: 0 !important; }
  .facts__cell + .facts__cell { border-top: 1px solid var(--hairline); }
  .facts__cell:first-child { padding-top: 0; }
  .facts__cell:last-child { padding-bottom: 0; }
}

/* --------------------------------------------------------------------------
   9 · Scope of practice: the three-service trio
   -------------------------------------------------------------------------- */
.trio {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(24px, 3vw, 40px);
  margin-top: clamp(28px, 3.4vw, 44px);
}

/* Column layout so the "Details" link lands on the same baseline in all three,
   however unevenly the copy above it runs. */
.svc { position: relative; display: flex; flex-direction: column; }
.svc h3 { margin-top: var(--s-5); }
.svc p { margin-top: 10px; font-size: 1rem; line-height: 1.66; }

.svc__list { margin-top: var(--s-4); display: grid; gap: 7px; }
.svc__list li {
  position: relative;
  padding-left: 16px;
  color: var(--ink-mute);
  font-size: 0.875rem;
  line-height: 1.5;
}
.svc__list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 8px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--anchor);
}

.svc .tlink {
  align-self: flex-start;
  margin-top: auto;
  padding-top: var(--s-4);
  font-size: 0.90625rem;
}

/* One stretched link makes the whole item clickable without duplicating link
   text for a screen reader. */
.svc__go::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--r-lg);
}

.svc .frame > img { transition: transform 0.7s var(--ease); }
@media (hover: hover) {
  .svc:hover .frame > img { transform: scale(1.035); }
  .svc:hover h3 { color: var(--anchor); }
}

@media (max-width: 860px) { .trio { grid-template-columns: 1fr; gap: var(--s-9); } }

/* --------------------------------------------------------------------------
   10 · Selected work: bare canvas, no panel behind it
   --------------------------------------------------------------------------
   All four sources are 1672 × 941, so every frame here is 16/9 and the two
   columns simply land at different heights. align-items: start is deliberate:
   forcing a second ratio would crop a quarter off two of the photographs.
   -------------------------------------------------------------------------- */
.work {
  display: grid;
  gap: clamp(20px, 2.4vw, 32px);
  margin-top: clamp(28px, 3.4vw, 44px);
}

.work__row {
  display: grid;
  grid-template-columns: 7fr 5fr;
  gap: clamp(20px, 2.4vw, 32px);
  align-items: start;
}
.work__row--flip { grid-template-columns: 5fr 7fr; }

.work__item .frame { border-radius: var(--r-xl); }
.work__cap { margin-top: 14px; }
.work__name { color: var(--ink); font-size: 1.0625rem; font-weight: 500; letter-spacing: -0.01em; }
.work__meta { margin-top: 3px; color: var(--ink-mute); font-size: 0.875rem; line-height: 1.5; }

.work__item .frame > img { transition: transform 0.6s var(--ease); }
@media (hover: hover) {
  .work__item:hover .frame > img { transform: scale(1.03); }
}

@media (max-width: 860px) {
  .work__row, .work__row--flip { grid-template-columns: 1fr; }
}

/* --------------------------------------------------------------------------
   11 · Split panels (the practice, the drawings, service area)
   -------------------------------------------------------------------------- */
.split {
  display: grid;
  grid-template-columns: 5fr 7fr;
  gap: clamp(28px, 4vw, 56px);
  align-items: center;
}
.split--even { grid-template-columns: 1fr 1fr; }
.split--top { align-items: start; }

/* Media moves to the right, and the columns swap with it, so the text column
   is always the wide one. */
.split--text-first { grid-template-columns: 7fr 5fr; }
.split--text-first > .split__media { order: 2; }

.split__text .chip + * { margin-top: 14px; }
.split__text .lede { margin-top: var(--s-4); }
.split__text h2 + p,
.split__text .h2--sm + p { margin-top: var(--s-4); }
.split__text p + p { margin-top: 1.1em; }

/* On a deep panel the photograph travels with the reader rather than leaving a
   column of dead white beneath it. Desktop only, and it is not motion; the
   image simply holds its place. */
@media (min-width: 901px) {
  .split--sticky > .split__media { align-self: stretch; }
  .split--sticky > .split__media > .frame { position: sticky; top: calc(var(--header-h) + 28px); }
}

/* Stacked, the photograph goes AFTER the words in every split. It used to lead,
   which meant a section opened with a picture and only named itself underneath,
   and on the services page it put the page-hero photograph directly on top of
   the first phase photograph with no heading of any kind between the two.
   .split--media-lead opts back out, for the blocks that carry their own heading
   above the split and so read correctly as title, picture, then copy. */
@media (max-width: 900px) {
  .split, .split--even, .split--text-first { grid-template-columns: 1fr; gap: var(--s-8); }
  .split > .split__media { order: 2; }
  .split--media-lead > .split__media { order: 0; }
}

/* Credential rows inside the architect panel */
.rows { margin-top: var(--s-6); }
.rows li {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  justify-content: space-between;
  align-items: baseline;
  padding: 13px 0;
  border-top: 1px solid var(--hairline);
}
.rows .k { color: var(--ink-mute); font-size: 0.875rem; }
.rows .v { color: var(--ink); font-size: 0.9375rem; font-weight: 500; text-align: right; }

/* Same rows, stacked, for narrow columns where a right-aligned value would
   wrap on some rows and not others. */
.rows--stack li { display: block; }
.rows--stack .v { display: block; margin-top: 3px; text-align: left; }

/* Tick list */
.ticks { display: grid; gap: 11px; }
.ticks li {
  position: relative;
  padding-left: 26px;
  font-size: 1rem;
  line-height: 1.6;
}
.ticks li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 6px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--tint);
}
.ticks li::after {
  content: "";
  position: absolute;
  left: 4.5px;
  top: 10px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--anchor);
}
/* Two columns only while a column can still hold a readable line: the
   .split__text column is narrower at 901px than the whole panel is at 900px. */
.ticks--two {
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
  gap: 11px var(--s-7);
}
@media (max-width: 700px) { .ticks--two { grid-template-columns: 1fr; } }

/* Caption line under a standalone photograph */
.figcap {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-6);
  justify-content: space-between;
  margin-top: 14px;
  color: var(--ink-mute);
  font-size: 0.875rem;
}

/* --------------------------------------------------------------------------
   12 · Process: a spec sheet, not a row of numbered circles
   -------------------------------------------------------------------------- */
.steps { margin-top: clamp(24px, 3vw, 36px); }

.step {
  display: grid;
  grid-template-columns: 40px 240px 1fr;
  gap: var(--s-5) var(--s-7);
  align-items: baseline;
  padding: var(--s-6) 0;
  border-top: 1px solid var(--hairline);
}
.step:first-child { border-top: 0; padding-top: var(--s-2); }
.step:last-child { padding-bottom: var(--s-2); }

.step__n {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--tint);
  color: var(--anchor);
  font-size: 0.875rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.step h3 { font-size: 1.1875rem; letter-spacing: -0.012em; }
.step p { color: var(--ink-mute); font-size: 0.96875rem; line-height: 1.62; max-width: 62ch; }

@media (max-width: 820px) {
  .step { grid-template-columns: 32px 1fr; gap: var(--s-3) var(--s-5); }
  .step h3 { grid-column: 2; }
  .step p { grid-column: 2; }
}

/* --------------------------------------------------------------------------
   13 · Service area
   -------------------------------------------------------------------------- */
/* A grid, not a wrapped flex row. Twenty-seven names of very different lengths
   wrapped into a flex row leave every row a different width and every column
   edge in a different place; equal cells give straight columns and a flush
   right edge. auto-fill means the one rule serves both consumers with no
   modifier: three columns inside the home page's text column, six across the
   service-areas panel, which caps at 1056px and so cannot fit a seventh.
   min-height rather than padding alone, so a name that wraps on a narrow screen
   keeps its row aligned with the rest. */
.areas {
  display: grid;
  /* 152px is the widest label ("Southwest Ranches", 123px) plus its padding and
     border, so no cell ever wraps at any column count this produces. */
  grid-template-columns: repeat(auto-fill, minmax(152px, 1fr));
  gap: var(--s-2);
  margin-top: var(--s-5);
}
.areas li { display: flex; }
.areas a {
  flex: 1;
  /* Without min-width: 0 a flex item cannot shrink below its min-content size,
     so on a 320px screen the three 76px tracks were overhung by up to 7px by
     the longest names and the right-hand column broke the list's edge. */
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 38px;
  padding: 6px 12px;
  border: 1px solid var(--border-quiet);
  border-radius: var(--r-md);
  background: var(--surface-quiet);
  color: var(--ink-body);
  font-size: 0.84375rem;   /* 13.5px */
  line-height: 1.4;
  text-align: center;
  text-decoration: none;
  transition: background-color 0.16s var(--ease), border-color 0.16s var(--ease),
              color 0.16s var(--ease), transform 0.16s var(--ease);
}
/* Elevation budget: the lift is 1px and a colour shift. No shadow appears. */
.areas a:hover {
  transform: translateY(-1px);
  border-color: #B8C7D4;
  background: var(--tint);
  color: var(--anchor);
}

/* Three fixed columns on phones and small tablets. auto-fill on its own falls
   to two columns anywhere under 600px, because the 152px minimum is sized for
   the longest name on one line ("Southwest Ranches"); forcing three and letting
   the four longest names wrap to two lines reads far better than a tall pair of
   stacked pills. Every row is equal height because the cells stretch, so the
   block stays a grid rather than a ragged list.

   Sweep-checked from 320px to 599px: no cell overflows its column at any width
   in the band, on the home page's narrow text column or on the wider hub. */
@media (max-width: 599px) {
  .areas { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* Inside the home page's text column the auto-fill floor is too coarse: from
   901px (where the split goes back to two columns) up to 1163px the column is
   only wide enough for two 152px tracks, so the same twenty-seven names that
   read as three tidy columns on a phone became a fourteen-row ladder with one
   orphan. Three fixed columns instead, for that one consumer. The full-width
   grid on the service areas hub is unaffected and keeps auto-filling. */
@media (min-width: 600px) {
  .split__text .areas { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* Only on real phone widths does the type need to come in. Between 480 and 600
   the cells are wide enough for the full 13.5px label. */
@media (max-width: 480px) {
  .areas { gap: 6px; }
  .areas a { padding: 6px 4px; font-size: 0.8125rem; }
}

.map {
  padding: clamp(12px, 2vw, 24px);
  border-radius: var(--r-lg);
  background: var(--tint);
}
.map img { width: 100%; height: auto; border-radius: var(--r-md); }

/* --------------------------------------------------------------------------
   14 · The one dark object on the page
   -------------------------------------------------------------------------- */
.cta {
  position: relative;
  overflow: hidden;
  padding: clamp(32px, 5vw, 68px);
  border-radius: var(--r-xl);
  background: var(--anchor);
  color: var(--on-dark-mute);
}

.cta__bg {
  position: absolute;
  inset: 0 0 0 auto;
  width: 46%;
  height: 100%;
  object-fit: cover;
  opacity: 0.22;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 45%);
  mask-image: linear-gradient(90deg, transparent, #000 45%);
  pointer-events: none;
}

.cta__inner { position: relative; max-width: 560px; }
.cta h2 { margin-top: var(--s-4); color: var(--on-dark); max-width: 18ch; }
.cta p { margin-top: var(--s-4); color: var(--on-dark-mute); max-width: 48ch; }

.cta__contact {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s-3) var(--s-7);
  margin-top: var(--s-7);
}
.cta__tel {
  color: #fff;
  font-size: 1.375rem;
  font-weight: 500;
  letter-spacing: -0.014em;
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 5px;
  text-decoration-color: var(--on-dark-line);
}
.cta__tel:hover { text-decoration-color: #fff; }
.cta__mail { color: #fff; font-size: 1.0625rem; font-weight: 500; text-underline-offset: 4px; }
.cta .actions { margin-top: var(--s-7); }

@media (max-width: 900px) { .cta__bg { display: none; } }

/* --------------------------------------------------------------------------
   15 · Footer: light. There is no dark slab at the bottom of this site.
   -------------------------------------------------------------------------- */
.site-footer {
  margin-top: clamp(56px, 7vw, 88px);
  padding-bottom: var(--s-8);
}

.site-footer__inner {
  padding-top: clamp(32px, 4vw, 48px);
  border-top: 1px solid var(--hairline-strong);
}

.footer-grid {
  display: grid;
  grid-template-columns: 4fr 2fr 2.4fr 3fr;
  gap: var(--s-7) var(--s-8);
}

/* Footer column headings are h2: they sit at the top level of the contentinfo
   landmark rather than under the last content section. The last three
   declarations restore what the base h2 rule would otherwise override. */
.footer-grid h2 {
  color: var(--ink);
  font-size: 0.9375rem;
  font-weight: 600;
  line-height: 1.34;
  letter-spacing: -0.008em;
  max-width: none;
}
.footer-grid ul { margin-top: var(--s-4); display: grid; gap: 10px; }
.footer-grid li, .footer-grid p { color: var(--ink-mute); font-size: 0.90625rem; line-height: 1.6; }
.footer-grid a {
  color: var(--ink-mute);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  text-decoration-color: currentColor;
  transition: color 0.16s var(--ease);
}
.footer-grid a:hover { color: var(--anchor); }

.footer-brand { color: var(--ink); font-size: 1.0625rem; font-weight: 600; letter-spacing: -0.016em; }
.footer-brand + p { margin-top: var(--s-3); max-width: 34ch; }

.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3) var(--s-6);
  justify-content: space-between;
  margin-top: var(--s-8);
  padding-top: var(--s-5);
  border-top: 1px solid var(--hairline-strong);
  color: var(--ink-mute);
  font-size: 0.84375rem;
}
.footer-legal { display: flex; flex-wrap: wrap; gap: var(--s-3) var(--s-5); }

@media (max-width: 820px) { .footer-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 480px) { .footer-grid { grid-template-columns: 1fr; } }

/* --------------------------------------------------------------------------
   16 · Mobile call bar: floats clear of the edges like everything else
   -------------------------------------------------------------------------- */
/* This used to slide in past 480px of scroll, which is more than half the first
   screen on a phone: the number was missing exactly while the visitor was
   deciding. It now simply sits there at mobile widths. That also takes the
   script out of the loop, so it is present with JavaScript disabled, and the
   old parked state (translated off-screen and visibility: hidden to keep it out
   of the tab order) is no longer needed, because it is never parked. */
.callbar {
  position: fixed;
  left: var(--s-3);
  right: var(--s-3);
  bottom: var(--s-3);
  z-index: 70;
  display: none;
  gap: var(--s-2);
  padding: var(--s-2);
  border-radius: var(--r-lg);
  background: rgba(255, 255, 255, 0.94);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  box-shadow: var(--shadow-chrome);
}
.callbar .btn { flex: 1; min-height: 44px; }

/* Dismissable. It is a persistent bar across the bottom of every page, and on a
   short screen that is a real amount of the page covered up, so it can be put
   away. The dock button on the right brings it back. */
.callbar__close {
  flex: none;
  display: grid;
  place-items: center;
  width: 36px;
  min-height: 44px;
  padding: 0;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--ink-mute);
  cursor: pointer;
  transition: color 0.16s var(--ease), background-color 0.16s var(--ease);
}
.callbar__close:hover { background: var(--tint); color: var(--anchor); }
.callbar__close svg { width: 17px; height: 17px; }

body.calls-off .callbar { display: none; }

/* The call bar floats at z-index 70, over the header's 60, so while the menu was
   open it sat on top of the sheet and ate the bottom rows of the menu: on a
   390x664 phone that was "Work" covered and "Articles" and "Contact" pushed off
   the screen, which left a services page unable to reach anything but services
   pages. The menu covers the page, so the bar has nothing to do while it is
   open, the same reasoning as the dock in section 6c. The sheet's own foot
   carries the number, the email and the CTA, so nothing is lost. */
body.nav-open .callbar { display: none; }

@media (max-width: 700px) {
  .callbar { display: flex; }
  /* Reserves the bar's own height plus its bottom offset, so the end of the
     page is never hidden behind it. */
  body { padding-bottom: 76px; }
  /* And the same reservation for the browser's own scroll-into-view. Without
     it, tabbing to a link near the bottom of the viewport parked the focus ring
     underneath the call bar: the element was scrolled to the very bottom edge,
     which is exactly where the bar sits. 84px = the 60px bar + its 12px bottom
     offset + 12px of clearance for the 3px ring at its 2px offset. */
  html { scroll-padding-bottom: 84px; }
}

/* --------------------------------------------------------------------------
   17 · Page hero (services / contact)
   -------------------------------------------------------------------------- */
.page-hero { padding-top: clamp(28px, 4vw, 52px); }
.page-hero h1 { margin-top: var(--s-4); max-width: 20ch; }
.page-hero .lede { margin-top: var(--s-5); }
.page-hero .actions { margin-top: var(--s-6); }

.crumbs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-bottom: var(--s-5);
  color: var(--ink-mute);
  font-size: 0.875rem;
}
.crumbs a {
  color: var(--ink-mute);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  text-decoration-color: currentColor;
}
.crumbs a:hover { color: var(--anchor); }

/* --------------------------------------------------------------------------
   18 · Services page
   --------------------------------------------------------------------------
   The service image stays on the SAME side in all three blocks. Alternating
   sides panel-to-panel is what both earlier versions did, and it reads as an
   agency portfolio. Variety comes from the content column instead.
   -------------------------------------------------------------------------- */
.scope-table { width: 100%; margin-top: var(--s-6); border-collapse: collapse; }
.scope-table caption {
  margin-bottom: var(--s-3);
  color: var(--ink-mute);
  font-size: 0.875rem;
  text-align: left;
}
.scope-table th, .scope-table td {
  padding: 13px var(--s-4);
  text-align: left;
  vertical-align: top;
  font-size: 0.9375rem;
  line-height: 1.55;
}
.scope-table th {
  width: 34%;
  color: var(--ink);
  font-weight: 500;
  background: var(--surface-quiet);
  border-radius: var(--r-sm) 0 0 var(--r-sm);
}
.scope-table td { color: var(--ink-body); }
.scope-table tr + tr th, .scope-table tr + tr td { border-top: 1px solid var(--hairline); }

/* The caption has to turn into a block with the rest of the table. Left as
   display: table-caption inside a table that is no longer a table, it generates
   its own anonymous table box and shrink-wraps to min-content: the heading came
   out 55px wide and four lines tall inside a 295px table. */
@media (max-width: 640px) {
  .scope-table, .scope-table caption, .scope-table tbody, .scope-table tr, .scope-table th, .scope-table td { display: block; width: 100%; }
  .scope-table th { border-radius: var(--r-sm) var(--r-sm) 0 0; }
  .scope-table td { padding-top: 10px; }
  .scope-table tr + tr th { border-top: 0; margin-top: var(--s-4); }
  .scope-table tr + tr td { border-top: 0; }
}

.tags { display: flex; flex-wrap: wrap; gap: var(--s-2); margin-top: var(--s-4); }
.tags li {
  padding: 6px 13px;
  border-radius: var(--r-md);
  background: var(--surface-quiet);
  border: 1px solid var(--border-quiet);
  color: var(--ink-body);
  font-size: 0.84375rem;
}

.cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(20px, 2.4vw, 32px);
  margin-top: clamp(28px, 3.4vw, 44px);
}
.card {
  padding: clamp(22px, 2.6vw, 32px);
  border-radius: var(--r-xl);
  background: var(--surface);
  box-shadow: var(--shadow-panel);
}
.card h3 { margin-top: var(--s-4); }
.card h3 + p { margin-top: 10px; font-size: 1rem; line-height: 1.65; }
.card .ticks { margin-top: var(--s-5); }
.card .ticks li { font-size: 0.9375rem; }
@media (max-width: 900px) { .cards { grid-template-columns: 1fr; } }

/* FAQ */
.faq { margin-top: var(--s-6); }
.faq details { border-top: 1px solid var(--hairline); }
.faq details:first-of-type { border-top: 0; }

.faq summary {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-5);
  padding: var(--s-5) 0;
  color: var(--ink);
  font-size: 1.0625rem;
  font-weight: 500;
  line-height: 1.45;
  cursor: pointer;
  list-style: none;
}
.faq summary::-webkit-details-marker { display: none; }

.faq summary::after {
  content: "";
  flex: none;
  width: 11px;
  height: 11px;
  margin-top: 6px;
  border-right: 1.6px solid var(--anchor);
  border-bottom: 1.6px solid var(--anchor);
  transform: rotate(45deg);
  transition: transform 0.2s var(--ease);
}
.faq details[open] summary::after { transform: rotate(-135deg); margin-top: 9px; }

.faq__body { padding-bottom: var(--s-5); max-width: 74ch; }
.faq__body p { font-size: 1rem; }

/* --------------------------------------------------------------------------
   19 · Contact page
   -------------------------------------------------------------------------- */
.form-layout {
  display: grid;
  grid-template-columns: 7fr 5fr;
  gap: clamp(28px, 4vw, 60px);
  align-items: start;
}
@media (max-width: 900px) { .form-layout { grid-template-columns: 1fr; } }

.field { display: grid; gap: 7px; }
.field label { color: var(--ink); font-size: 0.875rem; font-weight: 500; }
.optional { color: var(--ink-mute); font-weight: 400; }
.req { color: var(--anchor); }

.field input,
.field select,
.field textarea {
  width: 100%;
  min-height: 48px;
  padding: 12px 14px;
  border: 1px solid var(--border-ui);
  border-radius: var(--r-sm);
  background: var(--surface-quiet);
  color: var(--ink);
  font-family: inherit;
  font-size: 1rem;          /* 16px, never smaller; prevents iOS zoom */
  line-height: 1.5;
  transition: border-color 0.16s var(--ease), background-color 0.16s var(--ease);
}
.field textarea { min-height: 150px; resize: vertical; }
.field select {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%2355646F' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 12px 8px;
  padding-right: 38px;
}
.field input:hover, .field select:hover, .field textarea:hover { border-color: var(--ink-mute); }
.field input:focus, .field select:focus, .field textarea:focus { background: var(--surface); border-color: var(--anchor); }

/* Placeholders. --ink-mute measured on every fill a placeholder can sit on:
   5.79 : 1 on --surface-quiet, 5.29 : 1 on --error-tint, 6.11 : 1 on --surface.
   opacity:1 is required: Firefox's UA sheet applies 0.54 and would relighten it. */
.field input::placeholder,
.field textarea::placeholder { color: var(--ink-mute); opacity: 1; }

.field__error {
  display: none;
  color: var(--error);
  font-size: 0.875rem;
  line-height: 1.45;
}
.field.is-invalid input,
.field.is-invalid select,
.field.is-invalid textarea {
  border-color: var(--error);
  background: var(--error-tint);
}
.field.is-invalid .field__error { display: block; }

.form-grid { display: grid; gap: var(--s-5); }
.form-grid--2 { grid-template-columns: 1fr 1fr; }
@media (max-width: 620px) { .form-grid--2 { grid-template-columns: 1fr; } }

.checkbox {
  display: grid;
  grid-template-columns: 20px 1fr;
  gap: var(--s-3);
  align-items: start;
  color: var(--ink-body);
  font-size: 0.9375rem;
  line-height: 1.5;
  cursor: pointer;
}
.checkbox input {
  width: 20px;
  height: 20px;
  margin: 2px 0 0;
  accent-color: var(--anchor);
}
.checkbox.is-invalid { color: var(--error); }
.checkbox.is-invalid input { outline: 2px solid var(--error); outline-offset: 1px; border-radius: 4px; }
/* The consent error sits outside the <label>, so it needs its own rule. */
.checkbox.is-invalid + .field__error { display: block; }

/* Honeypot: hidden from people, catches bots. Keep it. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.form-success { display: none; }
.form-success.is-on { display: block; }

.info { display: grid; gap: var(--s-5); }
.info__card { padding: clamp(22px, 2.6vw, 30px); border-radius: var(--r-lg); background: var(--surface-quiet); }
.info__card h3 { font-size: 1.1875rem; }
.info__list { margin-top: var(--s-5); display: grid; gap: var(--s-5); }
.info__list .k { display: block; color: var(--ink-mute); font-size: 0.8125rem; font-weight: 500; }
.info__list .v { display: block; margin-top: 3px; color: var(--ink); font-size: 1rem; font-weight: 500; line-height: 1.45; }
.info__list a.v {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
  text-decoration-color: rgba(15, 26, 33, 0.4);
}
.info__list a.v:hover { color: var(--anchor); text-decoration-color: currentColor; }
.info__list address { font-style: normal; }
.info__tel { font-size: 1.375rem; letter-spacing: -0.014em; }

.hours { margin-top: var(--s-5); display: grid; gap: 10px; }
.hours li { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 2px var(--s-4); font-size: 0.9375rem; }
.hours strong { font-weight: 500; white-space: nowrap; }
.hours span { color: var(--ink-mute); white-space: nowrap; }

/* --------------------------------------------------------------------------
   20 · Placeholder pages: empty state and section index
   --------------------------------------------------------------------------
   Everything under services/, articles/ and service-areas/ is a real page with
   no copy in it yet. Two components carry those pages.

   .empty is the whole body of a leaf page. It is centred and given a lot of
   height on purpose: an unfinished page should read as deliberately blank, not
   as a page that failed to load. These pages carry no dark object at all, which
   is within the rule at the top of this file: one per page is a ceiling, not a
   quota.

   .linkgrid is navigation, not content. Without it nothing under the three
   folders is reachable, and a site whose pages cannot be walked is not a
   structure you can review.
   -------------------------------------------------------------------------- */
.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding-block: clamp(56px, 9vw, 104px);
}
.empty__title {
  margin-top: var(--s-5);
  font-size: clamp(1.875rem, 1.35rem + 2vw, 2.75rem);   /* the h2 scale */
  line-height: 1.10;
  letter-spacing: -0.024em;
  color: var(--ink);
  font-weight: 600;
}
.empty__note {
  margin-top: var(--s-4);
  max-width: 48ch;
  color: var(--ink-mute);
}
/* .actions is a left-aligned flex row everywhere else on the site; here it sits
   inside a centred column and has to centre with it. */
.empty__actions { justify-content: center; margin-top: var(--s-7); }
.empty__back { margin-top: var(--s-6); }

/* min(272px, 100%) rather than a bare 272px: on a 320px phone the panel's
   content box is 240px, and a fixed 272px track overflowed it by 32px. The
   min() collapses the track to the container below that width and leaves every
   wider layout identical. */
.linkgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(272px, 100%), 1fr));
  gap: var(--s-3);
}
.linkgrid a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  height: 100%;
  padding: 16px 18px;
  border-radius: var(--r-md);
  background: var(--surface-quiet);
  color: var(--ink);
  font-size: 0.9375rem;   /* 15px */
  font-weight: 500;
  line-height: 1.4;
  text-decoration: none;
  transition: background-color 0.16s var(--ease), color 0.16s var(--ease),
              transform 0.16s var(--ease);
}
/* Same affordance as everywhere else on this site: 1px of lift and a colour
   change. No shadow is added on hover. */
.linkgrid a:hover { transform: translateY(-1px); background: var(--tint); color: var(--anchor); }
.linkgrid svg {
  flex: none;
  width: 15px;
  height: 15px;
  color: var(--link);
  transition: transform 0.2s var(--ease);
}
.linkgrid a:hover svg { transform: translateX(3px); }
.linkgrid__note { display: block; margin-top: 3px; color: var(--ink-mute); font-size: 0.875rem; font-weight: 400; }

/* --------------------------------------------------------------------------
   21 · Motion
   --------------------------------------------------------------------------
   Content settles in; photographs do not animate; they are the content, so
   they are present the moment their section arrives.

   The hidden start state is gated on .js, which an inline script in <head>
   sets before first paint. So if the script fails or JS is off, the page is
   simply visible, never a blank screen.
   -------------------------------------------------------------------------- */
.js [data-reveal] {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.56s var(--ease), transform 0.56s var(--ease);
  transition-delay: var(--d, 0ms);
  will-change: opacity, transform;
}
.js [data-reveal].is-in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .js [data-reveal] { opacity: 1 !important; transform: none !important; }
  .svc:hover .frame > img,
  .work__item:hover .frame > img { transform: none !important; }
  /* Every hover lift on the site, not just the button: the README promises
     that lifts are switched off rather than shortened, so any new hover
     transform belongs on this list. */
  .btn:hover,
  .areas a:hover,
  .linkgrid a:hover,
  .linkgrid a:hover svg,
  .dock__btn:hover,
  .tlink:hover svg { transform: none !important; }
}
