html { scroll-behavior: smooth; -webkit-font-smoothing: antialiased; }
body { margin: 0; background: #F4F2EF; color: #111110; font-family: 'Manrope', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-weight: 300; }
* { box-sizing: border-box; }
a { color: inherit; text-decoration: none; }
a:hover { color: #6E6963; }
input, select, textarea, button { font-family: inherit; }
::selection { background: #111110; color: #F4F2EF; }

/* --- hover / focus helpers (dc-runtime style-hover / style-focus equivalents) --- */
.nav-a:hover { opacity: 1 !important; }
.cta-btn:hover { opacity: 0.82; }
.btn-onwhite:hover { background: #F4F2EF !important; color: #111110 !important; }
.btn-onpaper:hover { background: #FFFFFF !important; color: #111110 !important; }
.btn-outline:hover { background: rgba(255,255,255,0.12) !important; color: #FFFFFF !important; }
.ul-link { border-bottom: 1px solid rgba(17,17,16,0.3); }
.ul-link:hover { border-bottom: 1px solid #111110; color: #111110; }
.proj-photo:hover .view-badge { opacity: 1 !important; }
.chev { display: inline-block; width: 1.25em; height: 1.25em; vertical-align: -0.3em; margin-left: -0.15em; }
.proj-card:hover .proj-arrow { transform: translateX(5px); }
.proj-card .ph img { transition: transform 700ms cubic-bezier(.2,.7,.2,1); }
.proj-card:hover .ph img { transform: scale(1.05); }
/* Projects index: 3-up grid, responsive down. */
.proj-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(16px, 1.6vw, 28px); }
/* Fill the track width (like the home flex cards) so the height cap can't
   shrink card width and open up oversized gaps. */
.proj-grid .proj-card { width: 100%; }
@media (max-width: 860px) { .proj-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 520px) { .proj-grid { grid-template-columns: 1fr; } }
.field:focus { border-bottom: 1px solid #F4F2EF !important; }

main a:hover { color: inherit; }

/* --- photo frame (replaces the authoring-time image-slot component) --- */
.ph { position: absolute; inset: 0; display: block; overflow: hidden; background: #E7E4DF; }
.ph img { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

/* --- projects index filter --- */
.filter-bar { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: clamp(28px, 3vw, 44px); }
.filter-chip { font-size: 11px; letter-spacing: 0.20em; text-transform: uppercase; font-weight: 500; padding: 11px 18px; border: 1px solid rgba(17,17,16,0.22); background: none; color: #57534E; cursor: pointer; transition: background 300ms ease, color 300ms ease, border-color 300ms ease; }
.filter-chip:hover { border-color: rgba(17,17,16,0.5); color: #111110; }
.filter-chip[aria-pressed="true"] { background: #111110; border-color: #111110; color: #F4F2EF; }

/* --- global footprint map --- */
/* The pin box is exactly the dot's size and is centred on the city
   coordinate, so translate(-50%,-50%) places the DOT on the coordinate.
   The label is positioned beside it and never shifts the dot. */
.geo-pin { position: absolute; width: 9px; height: 9px; transform: translate(-50%, -50%); pointer-events: none; }
.geo-dot { position: absolute; inset: 0; border-radius: 50%; background: #111110; }
.geo-dot::after { content: ""; position: absolute; inset: -3px; border-radius: 50%; border: 1px solid rgba(17,17,16,0.4); animation: geopulse 2.8s ease-out infinite; }
.geo-label { position: absolute; top: 50%; left: 16px; transform: translateY(-50%); font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase; color: #111110; white-space: nowrap; font-weight: 400; }
.geo-pin.left .geo-label { left: auto; right: 16px; }
@keyframes geopulse { 0% { transform: scale(0.7); opacity: 0.7; } 100% { transform: scale(2.6); opacity: 0; } }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .geo-dot::after { animation: none; }
}

/* ============================================================
   MPA additions — behaviour that used to live in JS re-renders.
   The header, mobile menu and project filter are now driven by
   CSS + small class toggles instead of innerHTML replacement.
   ============================================================ */

/* --- skip link (was missing entirely) --- */
.skip-link { position: absolute; left: -9999px; top: 0; z-index: 200; background: #111110; color: #F4F2EF; padding: 14px 20px; font-size: 12px; letter-spacing: 0.20em; text-transform: uppercase; font-weight: 400; }
.skip-link:focus { left: 0; }

/* --- visible keyboard focus --- */
:focus-visible { outline: 2px solid currentColor; outline-offset: 3px; }

/* ------------------------------------------------------------
   Header. Two colour modes:
     .hdr--overlay  transparent, white ink, sits over a dark hero
     .hdr--solid    paper background, dark ink
   Colour flows through --hdr-fg so nav links no longer need the
   per-element inline colours the SPA computed in JS.
   ------------------------------------------------------------ */
.hdr { position: fixed; top: 0; left: 0; right: 0; z-index: 60; transition: background 500ms ease, color 400ms ease, border-color 400ms ease; }
.hdr--overlay { color: #FFFFFF; --hdr-fg: #FFFFFF; --hdr-cta-bg: #FFFFFF; --hdr-cta-ink: #111110; --hdr-dim: 1; background: linear-gradient(to bottom, rgba(0,0,0,0.42) 0%, rgba(0,0,0,0.12) 60%, rgba(0,0,0,0) 100%); border-bottom: 1px solid transparent; }
.hdr--solid { color: #111110; --hdr-fg: #111110; --hdr-cta-bg: #111110; --hdr-cta-ink: #F4F2EF; --hdr-dim: 1; background: rgba(244,242,239,0.92); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); border-bottom: 1px solid rgba(17,17,16,0.10); }
/* ------------------------------------------------------------
   Header size on scroll.

   The bar, the mark and the wordmark all start large and settle
   into the compact state once past the scroll threshold. app.js
   adds .is-scrolled; everything below is pure CSS off that class,
   so there is no per-frame JS and no layout thrash.

   #spacer transitions with them. On pages whose header is opaque
   from the top the spacer is what holds the content clear of the
   fixed bar — if it stayed at the tall height the content would
   sit 30px too low once the header shrank, and if it snapped it
   would jump. Matching durations makes it glide instead.
   ------------------------------------------------------------ */
.hdr-bar { display: flex; align-items: center; justify-content: space-between; gap: 32px; padding: 0 clamp(20px,4vw,56px); height: 104px; transition: height 420ms cubic-bezier(.16,.7,.3,1); }
.hdr.is-scrolled .hdr-bar { height: 74px; }

.brand { display: flex; align-items: center; gap: 12px; flex: 0 0 auto; }
.brand-mark { display: block; flex: 0 0 auto; width: 44px; height: 44px; transition: width 420ms cubic-bezier(.16,.7,.3,1), height 420ms cubic-bezier(.16,.7,.3,1); }
.hdr.is-scrolled .brand-mark { width: 24px; height: 24px; }
.brand-name { font-size: 17px; letter-spacing: 0.30em; font-weight: 400; text-transform: uppercase; transition: font-size 420ms cubic-bezier(.16,.7,.3,1); }
.hdr.is-scrolled .brand-name { font-size: 13px; }

#spacer.spacer { height: 0; }
#spacer.spacer--solid { height: 104px; transition: height 420ms cubic-bezier(.16,.7,.3,1); }
.hdr.is-scrolled + #spacer.spacer--solid { height: 74px; }

@media (max-width: 620px) {
  .hdr-bar { height: 84px; }
  .brand-mark { width: 34px; height: 34px; }
  .brand-name { font-size: 15px; }
  #spacer.spacer--solid { height: 84px; }
}

@media (prefers-reduced-motion: reduce) {
  .hdr-bar, .brand-mark, .brand-name, #spacer.spacer--solid { transition: none; }
}

.nav-a { font-size: 12px; letter-spacing: 0.20em; text-transform: uppercase; font-weight: 400; padding: 6px 0; color: var(--hdr-fg); border-bottom: 1px solid transparent; opacity: var(--hdr-dim); transition: opacity 300ms ease, color 400ms ease; }
.nav-a[aria-current="page"] { opacity: 1; border-bottom-color: currentColor; }
.cta-btn { font-size: 12px; letter-spacing: 0.20em; text-transform: uppercase; font-weight: 500; padding: 14px 22px; background: var(--hdr-cta-bg); color: var(--hdr-cta-ink); transition: background 500ms ease, color 400ms ease, opacity 300ms ease; }
.cta-btn:hover { color: var(--hdr-cta-ink); }

/* Desktop nav / mobile nav swap — a media query now, not a JS branch on
   window.innerWidth. This is why the header no longer re-renders on resize. */
.nav-desk { display: none; align-items: center; gap: clamp(16px,2.4vw,36px); margin-left: auto; }
.nav-mob { display: flex; margin-left: auto; align-items: center; gap: 18px; }
@media (min-width: 821px) {
  .nav-desk { display: flex; }
  .nav-mob { display: none; }
  .menu-panel { display: none !important; }
}

.menu-btn { background: none; border: none; color: inherit; padding: 12px 0; display: flex; flex-direction: column; gap: 6px; cursor: pointer; width: 44px; align-items: flex-end; }
.menu-btn span { display: block; width: 26px; height: 1px; background: currentColor; }
.menu-cur { font-size: 11px; letter-spacing: 0.24em; text-transform: uppercase; opacity: 1; }

/* Menu panel is present in every prerendered page and hidden by default,
   so opening it is a class toggle rather than a re-render. */
.menu-panel { display: none; flex-direction: column; padding: 8px clamp(20px,4vw,56px) 36px; border-top: 1px solid rgba(17,17,16,0.08); gap: 2px; }
.hdr.is-menu-open .menu-panel { display: flex; }
.hdr.is-menu-open { background: rgba(244,242,239,0.98); color: #111110; --hdr-fg: #111110; --hdr-cta-bg: #111110; --hdr-cta-ink: #F4F2EF; --hdr-dim: 1; backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); }
.m-link { font-size: 30px; font-weight: 200; letter-spacing: -0.02em; padding: 14px 0; display: flex; align-items: baseline; gap: 14px; opacity: 1; color: #111110; }
.m-link[aria-current="page"] { opacity: 1; }
.m-cta { margin-top: 20px; font-size: 12px; letter-spacing: 0.20em; text-transform: uppercase; font-weight: 500; background: #111110; color: #F4F2EF; padding: 18px 22px; text-align: center; }

/* --- project filter: all cards ship in the HTML, JS only hides --- */
.proj-card[hidden] { display: none; }

/* --- contact form success state, prerendered and hidden --- */
.form-sent { display: none; min-height: 340px; flex-direction: column; justify-content: center; }
.contact-wrap.is-sent .form-sent { display: flex; }
.contact-wrap.is-sent form { display: none; }

/* --- cross-document transitions: keeps MPA navigation feeling seamless.
       Silently ignored by browsers that do not support it. --- */
@view-transition { navigation: auto; }
@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}

/* --- contact-page quote estimator --- */
/* The result panels are prerendered and toggled with the hidden
   attribute; this keeps a stray display rule from unhiding them. */
[data-quote-out][hidden], [data-quote-na][hidden] { display: none; }
/* Number spinners are chrome the dark panel does not need. */
[data-quote-size] { -moz-appearance: textfield; appearance: textfield; }
[data-quote-size]::-webkit-outer-spin-button,
[data-quote-size]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
[data-quote-size]::placeholder { color: rgba(244,242,239,0.34); }

/* --- footer accreditation marks (PIA, SAIA) --------------------
   Both files are supplied as WHITE artwork, which is invisible on
   this paper-coloured footer. invert() flips the white ink to the
   footer's near-black without needing a second set of files.

   Note the two behave differently: PIA is white-on-transparent and
   inverts to clean dark ink, while SAIA's figure block is an opaque
   white square, so inverting turns that square dark with the figures
   knocked out of it. Both read correctly — but this is a rendering
   trick, not a brand-approved asset. If SAIA or PIA supply positive
   (dark) versions, drop them in and delete the filter.
   ---------------------------------------------------------------- */
/* --- footer layout: practice mark left, link columns centred,
   affiliations right. A grid rather than space-between, so the middle
   column is the real middle regardless of how wide the outer two get. --- */
.ftr-grid { display: grid; grid-template-columns: 1fr auto 1fr; gap: clamp(32px, 4vw, 64px); align-items: start; }
.ftr-brand { justify-self: start; max-width: 280px; }
.ftr-links { justify-self: center; display: flex; flex-wrap: wrap; gap: clamp(28px, 4vw, 56px); }
.ftr-affil { justify-self: end; display: flex; flex-direction: column; gap: 16px; }

/* Below the three-across breakpoint the zones stack and left-align. */
@media (max-width: 1080px) {
  .ftr-grid { grid-template-columns: 1fr; gap: clamp(36px, 6vw, 56px); }
  .ftr-brand, .ftr-links, .ftr-affil { justify-self: start; max-width: none; }
}

.accred { display: flex; flex-direction: column; align-items: flex-start; gap: 20px; }
.accred img { height: 52px; width: auto; display: block; filter: invert(1); opacity: 1; transition: opacity 300ms ease; }
.accred img:hover { opacity: 1; }
@media (max-width: 1080px) { .accred { flex-direction: row; align-items: center; gap: 30px; } }
@media (max-width: 520px) { .accred { gap: 22px; } .accred img { height: 40px; } }
