/* ========================================================
   Just Build, Architectural Interiors
   Design system: quiet luxury, photography-led, minimal type
   ======================================================== */

:root {
  --c-bg:         #FBFAF8;
  --c-bg-alt:     #F3F1EA;
  --c-white:      #FFFFFF;
  /* Permanent dark-brown text (per explicit direction after the
     context-adaptive version — see body's background-image comment —
     kept switching color mid-scroll and, worse, still landed wrong on
     tall sections that spanned both a dark and a light part of the
     wash in one go). --c-ink/--c-ink-soft are fixed from here on; the
     wash itself is instead kept inside a light-to-white band (never
     truly dark) so fixed dark text always has strong contrast against
     it, everywhere, permanently. --c-ink-invert is for the couple of
     spots that flip to a solid DARK fill and need light text on top
     instead (a filled button's resting/hover state) — the inverse of
     the site's normal dark-on-light. */
  --c-ink:        #2A2015;
  --c-ink-soft:   #403524;
  --c-ink-invert: #FBF8F4;
  --c-sage:       #8A6F4E;
  --c-sage-deep:  #6B5642;
  --c-sage-glow:  #F0DFC0; /* brighter, warmer brown, for accent type on dark photography */
  --c-line:       #E1DED2;
  --nav-h: 96px;
  /* Marcellus, a quiet, refined display serif, used two ways: full-case
     for headlines/titles, small-caps (Marcellus SC) for short accent
     labels and wordmarks. Neither ships an italic or bold face, so
     nothing below sets font-style or a non-400 weight on these. */
  --font-display: 'Marcellus', serif;
  --font-accent: 'Marcellus SC', serif;
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  /* A continuous, gently-moving wash for the whole page rather than
     each section painting its own flat patch. Kept inside a light
     tan-to-white band — never a genuinely dark brown — specifically
     because text color on top of it is now fixed dark brown
     permanently (see --c-ink above): a wash that swung down to a deep
     espresso, the way earlier versions of this did, would put dark
     text directly on a dark background for a real part of every
     cycle. Staying light throughout is what makes "always dark text"
     actually safe rather than just usually-safe. Warmth still comes
     through in the tan dips; it's a much narrower swing than before,
     not a flat color, so the wash still moves and breathes. 25 stops
     sampling a raised-cosine/Hann-window curve (rather than a smaller
     number) keeps the interpolation between them close to that true
     smooth curve — widely-spaced stops were producing a visible kink
     at each one, reading as faint "chunks" of flatter color while
     scrolling. */
  background-image: linear-gradient(180deg,
    #9E8E78 0%,
    #A0907A 4.167%,
    #A49580 8.333%,
    #AC9E8A 12.5%,
    #B5A997 16.667%,
    #C0B5A6 20.833%,
    #CCC3B6 25%,
    #D9D1C6 29.167%,
    #E4DDD5 33.333%,
    #EDE8E2 37.5%,
    #F5F1EC 41.667%,
    #F9F6F2 45.833%,
    #FBF8F4 50%,
    #F9F6F2 54.167%,
    #F5F1EC 58.333%,
    #EDE8E2 62.5%,
    #E4DDD5 66.667%,
    #D9D1C6 70.833%,
    #CCC3B6 75%,
    #C0B5A6 79.167%,
    #B5A997 83.333%,
    #AC9E8A 87.5%,
    #A49580 91.667%,
    #A0907A 95.833%,
    #9E8E78 100%
  );
  background-size: 100% 2200px;
  background-repeat: repeat;
  color: var(--c-ink);
  font-family: 'Inter', sans-serif;
  font-weight: 300;
  -webkit-font-smoothing: antialiased;
  overscroll-behavior: none;
}

::-webkit-scrollbar { display: none; }
img { display: block; max-width: 100%; }


/* ---------- Type roles ---------- */
.script     { font-family: var(--font-accent); font-style: normal; font-weight: 400; letter-spacing: 0.03em; }
.headline   { font-family: var(--font-display); font-style: normal; font-weight: 400; letter-spacing: -0.012em; }
.eyebrow    { font-family: var(--font-accent); font-style: normal; font-weight: 400; letter-spacing: 0.04em; font-size: 18px; color: var(--c-ink-soft); }
.label      { font-family: 'Inter', sans-serif; font-weight: 500; font-size: 11px; letter-spacing: 0.22em; text-transform: uppercase; }
.body-copy  { font-family: 'Inter', sans-serif; font-weight: 300; font-size: 16px; line-height: 1.85; color: var(--c-ink-soft); }

/* Line-heights sized for Marcellus specifically: it sits taller and
   carries deeper descenders (g, y, p) than the font these were first
   tuned for. Too tight and multi-line headlines overlap themselves,
   or the split-line reveal's overflow:hidden clips a descender clean
   off the bottom of the line, exactly what was happening before. */
h1.headline { font-size: clamp(42px, 6.4vw, 84px); line-height: 1.13; }
h2.headline { font-size: clamp(32px, 4.2vw, 52px); line-height: 1.2; }
h3.headline { font-size: clamp(23px, 2.5vw, 30px); line-height: 1.24; }

/* ---------- Layout ---------- */
.wrap { max-width: 1440px; margin: 0 auto; padding: 0 28px; }
@media (min-width: 1024px) { .wrap { padding: 0 64px; } }
section { padding: 160px 0; }
.section-tight { padding: 108px 0; }
hr.line { border: none; border-top: 1px solid var(--c-line); margin: 0; }

/* ---------- Nav ----------
   Hidden by default once the visitor has scrolled past the hero, it
   only comes back when they scroll upward, rest their cursor at the very
   top of the viewport, or are still near the top of the page. Keeps the
   photography full-bleed instead of a bar sitting over it permanently.

   A translucent glass panel on every page (not a plain solid colour):
   a soft blur plus a light tint carries just enough separation to read
   as its own interface element while whatever sits behind it (a hero
   photo, or the page's own scrolled content) stays visible, blurred,
   through it. Two readable variants rather than one compromise colour:
     - base (every interior page, and the homepage once scrolled past
       the hero): a pale glass over light content, dark text.
     - .site-nav--hero:not(.scrolled) (homepage, still on the hero
       photo): a dark glass instead, so light text stays legible over
       whatever the photograph is doing underneath. */
/* Light glass again (not the dark-glass variant tried mid-experiment):
   nav text (wordmark, links) is permanently dark brown now (--c-ink),
   which needs a light backdrop to read against — the hero-only dark
   variant just below is unaffected, since it sets its own light TEXT
   colors directly and isn't reading --c-ink at all. */
header.site-nav {
  position: fixed; top: 0; left: 0; width: 100%; z-index: 50;
  height: var(--nav-h);
  display: flex; align-items: center;
  background: rgba(251, 250, 248, 0.58);
  backdrop-filter: blur(16px) saturate(1.4);
  -webkit-backdrop-filter: blur(16px) saturate(1.4);
  border-bottom: 1px solid rgba(225,222,210,0.55);
  transition: background 0.5s ease, border-color 0.5s ease, color 0.5s ease, transform 0.6s cubic-bezier(0.22,0.61,0.36,1);
}
header.site-nav.scrolled {
  background: rgba(251, 250, 248, 0.66);
  border-bottom-color: rgba(225,222,210,0.7);
}
header.site-nav.nav-hidden { transform: translateY(-100%); }

/* Hero variant, dark glass + light text, active only while still
   parked on the homepage hero (i.e. before .scrolled is added). */
header.site-nav.site-nav--hero:not(.scrolled) {
  background: rgba(23,24,15,0.3);
  border-bottom-color: rgba(255,255,255,0.16);
}
header.site-nav.site-nav--hero:not(.scrolled) .nav-mark,
header.site-nav.site-nav--hero:not(.scrolled) .nav-links a {
  color: rgba(255,255,255,0.92);
}
header.site-nav.site-nav--hero:not(.scrolled) .nav-mark .sub { color: rgba(255,255,255,0.85); }
header.site-nav.site-nav--hero:not(.scrolled) .nav-links a:hover,
header.site-nav.site-nav--hero:not(.scrolled) .nav-links a[aria-current="page"] {
  color: #fff; border-bottom-color: rgba(255,255,255,0.85);
}
header.site-nav.site-nav--hero:not(.scrolled) .nav-burger span { background: #fff; }
header.site-nav .wrap { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.nav-mark { display: flex; flex-direction: column; line-height: 1; text-decoration: none; color: inherit; }
.nav-mark .name { font-family: var(--font-accent); font-style: normal; font-weight: 400; letter-spacing: 0.02em; font-size: 24px; }
.nav-mark .sub { font-size: 9px; letter-spacing: 0.32em; text-transform: uppercase; color: var(--c-ink-soft); margin-top: 6px; font-family: 'Inter', sans-serif; font-weight: 500; text-shadow: 0 1px 10px rgba(0,0,0,0.18); transition: text-shadow 0.5s ease; }
header.site-nav.scrolled .nav-mark .sub { text-shadow: none; }
/* Grouped toward the right, close to the mobile-menu burger, rather
   than floating in the middle of the bar: the auto margin absorbs the
   free space that header.site-nav .wrap's justify-content:space-between
   would otherwise spread evenly, so the wordmark stays flush left
   while the links settle over toward the right edge. */
.nav-links { display: none; align-items: center; gap: 40px; margin-left: auto; margin-right: 40px; }
@media (min-width: 900px) { .nav-links { display: flex; } }
.nav-links a {
  font-family: 'Inter', sans-serif; font-size: 11px; font-weight: 500;
  letter-spacing: 0.2em; text-transform: uppercase; text-decoration: none;
  color: var(--c-ink-soft); transition: color 0.3s ease;
  padding-bottom: 4px; border-bottom: 1px solid transparent;
}
.nav-links a:hover, .nav-links a[aria-current="page"] { color: var(--c-ink); border-bottom-color: var(--c-sage); }

/* Philosophy split section */
.philosophy-grid { display: grid; grid-template-columns: 1fr; gap: 48px; align-items: center; }
@media (min-width: 900px) { .philosophy-grid { grid-template-columns: 1fr 1fr; gap: 96px; } }

/* Selected works masonry offset */
.tile-offset { margin-top: 28px; }
@media (min-width: 700px) { .tile-offset { margin-top: 64px; } }

/* Two-up grid that stacks on small phones */
.duo-grid { grid-template-columns: 1fr !important; }
@media (min-width: 560px) { .duo-grid { grid-template-columns: 1fr 1fr !important; } }
.nav-burger { display: flex; width: 22px; height: 16px; flex-direction: column; justify-content: space-between; cursor: pointer; }
@media (min-width: 900px) { .nav-burger { display: none; } }
.nav-burger span { display: block; height: 1px; background: var(--c-ink); }
/* A real photograph behind frosted glass rather than a flat page: the
   Obsidian Residence photo sits full-bleed and blurred, a dark tint
   over it for contrast, and the menu links in white on top — so Home,
   Portfolio, About and Contact are still faintly visible through the
   blur instead of vanishing behind a plain white panel. */
.mobile-menu {
  position: fixed; inset: 0; background: var(--c-ink); z-index: 60;
  display: none; flex-direction: column; justify-content: center; align-items: center; gap: 36px;
  overflow: hidden;
}
.mobile-menu.open { display: flex; }
.mobile-menu-bg {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  filter: blur(28px) saturate(1.1) brightness(0.7);
  transform: scale(1.15); /* hides the blur's own soft edge from ever peeking in at the viewport border */
}
.mobile-menu::before {
  content: ''; position: absolute; inset: 0; background: rgba(23,24,15,0.42);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
}
.mobile-menu .close, .mobile-menu a { position: relative; z-index: 1; }
.mobile-menu .close { color: rgba(255,255,255,0.85); }
.mobile-menu a { font-family: var(--font-display); font-size: 32px; font-style: normal; color: #fff; text-decoration: none; text-shadow: 0 2px 24px rgba(0,0,0,0.3); }
.mobile-menu a:hover, .mobile-menu a[aria-current="page"] { color: rgba(255,255,255,0.65); }
.mobile-menu a.mobile-menu-login {
  font-family: 'Inter', sans-serif; font-size: 12px; font-weight: 500;
  letter-spacing: 0.2em; text-transform: uppercase; color: #fff;
  border: 1px solid rgba(255,255,255,0.7); padding: 12px 28px; margin-top: 8px;
}
.mobile-menu a.mobile-menu-login:hover { background: #fff; color: var(--c-ink); border-color: #fff; }

/* ---------- Nav: Log In ----------
   A small outlined pill rather than a plain text link, so it reads as
   an action distinct from the page links beside it. Same light/dark
   swap as the rest of the nav between the hero and base states. */
.nav-login {
  font-family: 'Inter', sans-serif; font-size: 10px; font-weight: 500;
  letter-spacing: 0.16em; text-transform: uppercase; text-decoration: none;
  color: var(--c-ink); border: 1px solid var(--c-ink); padding: 9px 18px;
  transition: background 0.3s ease, color 0.3s ease, border-color 0.3s ease;
  white-space: nowrap;
}
.nav-login:hover { background: var(--c-ink); color: var(--c-ink-invert); }
header.site-nav.site-nav--hero:not(.scrolled) .nav-login { color: rgba(255,255,255,0.92); border-color: rgba(255,255,255,0.6); }
header.site-nav.site-nav--hero:not(.scrolled) .nav-login:hover { background: #fff; color: var(--c-ink); border-color: #fff; }
@media (max-width: 480px) { .nav-login { padding: 8px 12px; font-size: 9px; } }

/* ---------- Forms (Contact, Log In) ----------
   Shared underline-style fields: no boxes or borders except the one
   hairline beneath each input, consistent with the site's restraint
   elsewhere. */
.field { margin-bottom: 28px; }
.field label { display: block; font-family: 'Inter'; font-size: 11px; letter-spacing: 0.15em; text-transform: uppercase; color: var(--c-ink-soft); margin-bottom: 10px; }
.field input, .field select, .field textarea {
  width: 100%; border: none; border-bottom: 1px solid var(--c-line); background: transparent;
  padding: 12px 0; font-family: 'Inter'; font-weight: 300; font-size: 16px; color: var(--c-ink);
  outline: none; transition: border-color 0.3s ease;
}
.field input:focus, .field select:focus, .field textarea:focus { border-color: var(--c-sage); }
.field textarea { resize: vertical; min-height: 100px; }

/* ---------- Log In page ----------
   A single centered card rather than a two-column layout, since there's
   nothing to balance it against; kept narrow so it doesn't read as a
   full-width marketing section. */
.login-card { max-width: 400px; margin: 0 auto; width: 100%; }
.login-meta-row { display: flex; justify-content: space-between; align-items: center; margin-top: 8px; }
.login-meta-row a { font-family: 'Inter', sans-serif; font-size: 12px; color: var(--c-ink-soft); text-decoration: none; border-bottom: 1px solid var(--c-line); }
.login-meta-row a:hover { color: var(--c-ink); border-color: var(--c-ink); }
.login-note { margin-top: 32px; padding-top: 24px; border-top: 1px solid var(--c-line); font-size: 13px; color: var(--c-ink-soft); text-align: center; }
.mobile-menu .close { position: absolute; top: 36px; right: 28px; font-family: 'Inter'; font-size: 11px; letter-spacing: 0.2em; text-transform: uppercase; cursor: pointer; }

/* ---------- Buttons / links ---------- */
.btn {
  display: inline-block; font-family: 'Inter', sans-serif; font-size: 11px; font-weight: 500;
  letter-spacing: 0.2em; text-transform: uppercase; text-decoration: none;
  padding: 17px 38px; border: 1px solid var(--c-ink); color: var(--c-ink);
  transition: all 0.4s ease; background: transparent; cursor: pointer;
}
/* .btn's border/text is --c-ink, which is a light color in this
   theme; the moment that same color becomes a solid fill (hover, or
   .btn-fill's resting state) the text on top of it has to flip to
   --c-ink-invert instead of --c-white, or it's light-on-light. */
.btn:hover { background: var(--c-ink); color: var(--c-ink-invert); }
.btn-fill { background: var(--c-ink); color: var(--c-ink-invert); }
.btn-fill:hover { background: var(--c-sage-deep); border-color: var(--c-sage-deep); color: var(--c-ink-invert); }
.btn-ghost-light { border-color: rgba(255,255,255,0.5); color: var(--c-white); }
.btn-ghost-light:hover { background: var(--c-white); color: var(--c-ink); }
.btn-glass {
  display: inline-block; font-family: 'Inter', sans-serif; font-size: 11px; font-weight: 500;
  letter-spacing: 0.2em; text-transform: uppercase; text-decoration: none;
  padding: 15px 32px; color: var(--c-white);
  background: rgba(23,24,15,0.28); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  border: 1px solid rgba(255,255,255,0.4);
  transition: background 0.35s ease, border-color 0.35s ease;
}
.btn-glass:hover { background: rgba(23,24,15,0.45); border-color: rgba(255,255,255,0.7); }

/* A slow, continuous float + glow, reserved for a single standout CTA
   (the "Start a Conversation" button on About's closing section) so it
   quietly draws the eye without anyone having to hover first. Loops
   forever, never resting, unlike every other hover-only transition on
   the site. */
.btn-glow { animation: btn-glow-float 3.6s ease-in-out infinite; }
@keyframes btn-glow-float {
  0%, 100% { transform: translateY(0); box-shadow: 0 0 0 rgba(224,201,166,0); }
  50% { transform: translateY(-6px); box-shadow: 0 14px 32px -6px rgba(224,201,166,0.55); }
}
@media (prefers-reduced-motion: reduce) { .btn-glow { animation: none; } }
.text-link {
  font-family: 'Inter', sans-serif; font-size: 11px; font-weight: 500;
  letter-spacing: 0.18em; text-transform: uppercase; text-decoration: none;
  color: var(--c-ink); border-bottom: 1px solid var(--c-line); padding-bottom: 6px;
  transition: border-color 0.3s ease;
}
.text-link:hover { border-color: var(--c-ink); }

/* Quiet CTA, text-only, for a single restrained enquire touchpoint */
.quiet-cta {
  display: inline-flex; align-items: center; gap: 10px;
  font-family: var(--font-accent); font-style: normal; font-weight: 400; letter-spacing: 0.03em;
  font-size: 16px; text-decoration: none; color: inherit;
  border-bottom: 1px solid currentColor; padding-bottom: 3px;
  opacity: 0.9; transition: opacity 0.3s ease, gap 0.3s ease;
}
.quiet-cta:hover { opacity: 1; gap: 16px; }

/* Always-visible project metadata (SCDA-style), replaces hover-only captions */
.proj-meta { display: flex; justify-content: space-between; align-items: baseline; padding: 14px 2px 0; gap: 12px; }
.proj-meta .pname { font-family: var(--font-display); font-style: normal; font-size: 18px; color: var(--c-ink); }
.proj-meta .pmeta { font-family: 'Inter'; font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--c-ink-soft); white-space: nowrap; }

/* ---------- Image treatment: develop from black & white ----------
   Signature motion, move 1 of 2. No curtain, no wipe, the photo
   simply sits desaturated and slightly dimmed until it scrolls into
   view, then blends smoothly up to full color. One continuous fade,
   nothing sliding across it. Fires once per element, then rests. */
.img-block { overflow: hidden; position: relative; }
.img-block img {
  width: 100%; height: 100%; object-fit: cover;
  filter: grayscale(1) brightness(0.92); transform: scale(1.06);
  transition: filter 1.6s cubic-bezier(0.22,0.61,0.36,1),
              transform 2s cubic-bezier(0.22,0.61,0.36,1);
}
.img-block.in img { filter: grayscale(0) brightness(1); transform: scale(1); }

/* ---------- Fade-in on scroll ----------
   Signature motion, move 2 of 2 (for text/section blocks). Default
   state is fully visible so content never disappears if JS fails to
   load. JS arms the fade by adding .js-armed just before observing,
   then removes it (via .in) once the element is seen, once only. */
.reveal { transition: opacity 1.1s cubic-bezier(0.22,0.61,0.36,1), transform 1.1s cubic-bezier(0.22,0.61,0.36,1); }
.reveal.js-armed { opacity: 0; transform: translateY(28px) scale(0.985); }
.reveal.js-armed.in { opacity: 1; transform: translateY(0) scale(1); }

/* Staggered line reveal for headlines, each line rises in slightly
   after the last. Applied automatically by JS wrapping text in spans. */
.split-line { display: block; }
.split-line .sl-line { display: block; overflow: hidden; }
.split-line .sl-line > span {
  display: inline-block; transform: translateY(110%);
  transition: transform 0.95s cubic-bezier(0.16,1,0.3,1);
}
.split-line.in .sl-line > span { transform: translateY(0); }

/* Plain inline link (used in contact details wherever they appear) */
a.plain { font-family: 'Inter'; font-weight: 300; font-size: 14px; line-height: 1.8; color: var(--c-ink-soft); text-decoration: none; }
a.plain:hover { color: var(--c-ink); }

/* ---------- Footer ---------- */
footer { border-top: 1px solid var(--c-line); padding: 96px 0 40px; }
footer .foot-mark { font-family: var(--font-accent); font-style: normal; letter-spacing: 0.02em; font-size: 22px; }
footer .label { color: var(--c-ink-soft); margin-bottom: 14px; display: block; }
footer address { font-family: 'Inter'; font-weight: 300; font-size: 14px; line-height: 1.8; color: var(--c-ink-soft); font-style: normal; }
@media (max-width: 560px) { .foot-links-grid { grid-template-columns: 1fr !important; gap: 28px !important; } }
.foot-bottom { display: flex; flex-direction: column; gap: 16px; justify-content: space-between; align-items: flex-start; padding-top: 40px; margin-top: 64px; border-top: 1px solid var(--c-line); font-size: 11px; color: var(--c-ink-soft); }
@media (min-width: 700px) { .foot-bottom { flex-direction: row; align-items: center; } }
.foot-bottom a { color: var(--c-ink-soft); text-decoration: none; letter-spacing: 0.1em; text-transform: uppercase; font-size: 10px; }



/* ---------- Curated Collections: four static images ----------
   No auto-play, no motion until the visitor interacts. A clean 2x2 /
   4-across editorial grid. On hover/focus, a translucent frosted-glass
   panel settles over the image (backdrop-filter does the diffusing, so
   the photograph itself is never touched by a filter), and the project
   name appears clearly within that glass, rather than a solid overlay
   or permanent caption sitting over the image. Shared by the Portfolio
   page, whose gallery is built from the same markup and classes so
   both read as one system. */
.curated-item { position: relative; display: block; overflow: hidden; text-decoration: none; color: inherit; aspect-ratio: 3/4; }
.curated-item img { transition: filter 1.6s cubic-bezier(0.22,0.61,0.36,1), transform 2s cubic-bezier(0.22,0.61,0.36,1), transform 0.7s ease; }
.curated-item:hover img, .curated-item:focus-visible img { transform: scale(1.05); }
/* The glass itself: a faint tint plus a soft blur of whatever sits
   behind it (the photograph), never a heavy or opaque wash. */
.curated-item::after {
  content: ''; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: rgba(23,24,15,0.22);
  backdrop-filter: blur(0px) saturate(1.05);
  -webkit-backdrop-filter: blur(0px) saturate(1.05);
  border: 1px solid rgba(255,255,255,0);
  opacity: 0;
  transition: opacity 0.45s ease, backdrop-filter 0.45s ease, -webkit-backdrop-filter 0.45s ease, border-color 0.45s ease;
}
.curated-item:hover::after, .curated-item:focus-visible::after {
  opacity: 1;
  backdrop-filter: blur(6px) saturate(1.05);
  -webkit-backdrop-filter: blur(6px) saturate(1.05);
  border-color: rgba(255,255,255,0.16);
}
.curated-name {
  position: absolute; left: 0; right: 0; top: 50%; z-index: 2; text-align: center; padding: 0 22px;
  font-family: var(--font-accent); font-style: normal; font-weight: 400; letter-spacing: 0.03em;
  font-size: 16px; color: #fff; text-shadow: 0 1px 12px rgba(0,0,0,0.3);
  opacity: 0; transform: translateY(6px) translateY(-50%);
  transition: opacity 0.4s ease 0.05s, transform 0.4s ease 0.05s;
}
.curated-item:hover .curated-name, .curated-item:focus-visible .curated-name { opacity: 1; transform: translateY(0) translateY(-50%); }
/* Strictly hover/focus-only: no permanently-on fallback for touch or
   hybrid devices. Default state is always fully clear and sharp, with
   the glass and name appearing only while actually hovered or
   focused, and clearing the moment the pointer leaves. */
.curated-more { text-align: center; margin-top: 28px; }

/* ---------- Curated Collection carousel ----------
   Static by default, no auto-play: the track only ever moves when the
   visitor clicks an arrow, one project at a time. 4 items visible per
   view on desktop (matching the old 4-across grid), 2 on tablet/mobile,
   with the track width and visible-count read live off the rendered
   DOM (see js/site.js) rather than duplicating these breakpoints in
   JS, so the two can never drift out of sync. */
.curated-carousel-wrap { position: relative; }
.curated-carousel { overflow: hidden; }
.curated-track { display: flex; gap: 4px; transition: transform 0.7s cubic-bezier(0.22,0.61,0.36,1); will-change: transform; }
.curated-track .curated-item { flex: 0 0 calc(25% - 3px); }
@media (max-width: 900px) { .curated-track .curated-item { flex: 0 0 calc(50% - 2px); } }
@media (prefers-reduced-motion: reduce) { .curated-track { transition: none; } }
/* No circle, no background, no shadow: a plain small grey icon, just
   large enough to read clearly as clickable. */
.curated-arrow {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 5;
  width: 30px; height: 30px; padding: 0; margin: 0;
  display: flex; align-items: center; justify-content: center;
  border: none; background: none; cursor: pointer;
  color: var(--c-ink-soft); opacity: 0.85;
  transition: color 0.3s ease, opacity 0.3s ease;
}
.curated-arrow svg { width: 24px; height: 24px; display: block; }
.curated-arrow:hover { color: var(--c-ink); opacity: 1; }
.curated-arrow-prev { left: 4px; }
.curated-arrow-next { right: 4px; }
@media (min-width: 1024px) {
  .curated-arrow-prev { left: -28px; }
  .curated-arrow-next { right: -28px; }
}

/* ---------- Growing divider ----------
   A plain line that starts short and smoothly widens to fill its
   container once scrolled into view (see js/site.js), rather than
   appearing at full width immediately. Used under "View Full
   Portfolio" on the Home page and under the "Portfolio" heading on
   the Portfolio page. Slow and minimal, a single quiet flourish. */
/* A transform, not a width animation: the box is always its full
   final width, just scaled to nothing from the left edge, so growth
   genuinely reads as sweeping left-to-right rather than expanding
   outward from the center (which a centered width-based grow would
   do, since centering pushes both edges outward equally). */
.divider-grow { height: 1px; background: var(--c-line); width: 100%; transform-origin: left center; transform: scaleX(0); transition: transform 2s cubic-bezier(0.22,0.61,0.36,1); }
.divider-grow.in { transform: scaleX(1); }
@media (prefers-reduced-motion: reduce) { .divider-grow { transition: none; transform: scaleX(1); } }
/* A more noticeable variant for the Portfolio page: a real colour
   instead of the faint line-grey (sage rather than a neutral hairline)
   and a longer travel distance, but kept thin, still just the one
   quiet flourish rather than anything loud. */
.divider-grow.is-prominent { height: 1px; background: var(--c-sage); }

/* ---------- Portfolio gallery: clean 4-column grid ----------
   Exactly four images per row (fewer only on the final, partial row),
   uniform sizing and alignment, generous gaps both between rows and
   between columns, structured rather than masonry. Hover treatment
   (frosted glass + name) is shared with .curated-item. */
.port-grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 48px 32px; }
.port-grid-4 .curated-item { aspect-ratio: 5/4; }
@media (max-width: 900px) { .port-grid-4 { grid-template-columns: repeat(2, 1fr); gap: 32px 24px; } }
@media (max-width: 560px) { .port-grid-4 { grid-template-columns: 1fr; gap: 40px; } }
/* A project fades and settles down slightly before it's pulled from
   the grid flow (see js/site.js), and the reverse on the way back in,
   so switching categories reads as a smooth cross-fade rather than an
   abrupt reflow when items are added or removed from the grid. */
.port-grid-4 .curated-item { transition: opacity 0.4s ease, transform 0.4s ease; }
.port-grid-4 .curated-item.is-filtered-out { opacity: 0; transform: scale(0.96); pointer-events: none; }

/* ---------- Related projects (project detail pages) ----------
   Three-across, reusing .curated-item's own hover/name treatment, so
   every project page ends with a way to keep browsing rather than a
   dead end at the CTA. */
.related-projects-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 28px; }
.related-projects-grid .curated-item { aspect-ratio: 4/3; }
@media (max-width: 700px) { .related-projects-grid { grid-template-columns: 1fr; gap: 32px; } }

/* ---------- Portfolio category filters ----------
   Plain text tabs, matching the site's other quiet navigation
   elements rather than a boxed pill/segmented control. */
.portfolio-filters { display: flex; flex-wrap: wrap; gap: 12px 32px; margin-top: 36px; }
/* Portfolio page's own hero: bumped up a size beyond the site's
   default eyebrow/h1, since this page's heading is the whole point of
   the page rather than a small intro above a hero image. */
.port-hero-eyebrow { font-size: 24px; }
h1.port-hero-title { font-size: clamp(56px, 9vw, 120px); }
.filter-btn {
  background: none; border: none; padding: 0 0 6px; margin: 0; cursor: pointer;
  font-family: 'Inter', sans-serif; font-size: 11px; font-weight: 500;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--c-ink-soft);
  border-bottom: 1px solid transparent;
  transition: color 0.3s ease, border-color 0.3s ease;
}
.filter-btn:hover { color: var(--c-ink); }
.filter-btn.is-active { color: var(--c-ink); border-bottom-color: var(--c-sage); }
.port-empty-msg { grid-column: 1 / -1; }

/* ---------- Hero: full-screen, scroll-jacked project showcase ----------
   Landing slide + four projects, each filling the viewport. One wheel
   notch, arrow key, or swipe advances exactly one slide, the page
   itself never scrolls until the visitor reaches either end, at which
   point control hands back to normal scrolling seamlessly (see
   js/site.js). Falls back to a plain stacked scroll on narrow screens,
   where scroll-jacking a full viewport per slide feels heavy-handed. */
.hero-jack { position: relative; height: 100vh; min-height: 600px; overflow: hidden; background: var(--c-ink); }
/* A pure dissolve rather than a slide or drift, one frame of a
   continuous film gently giving way to the next, quick enough to feel
   responsive to a deliberate scroll rather than sluggish. The slow
   img zoom (Ken Burns) still runs longer than a single slide's rest
   time, so there's a hint of quiet motion under the crossfade. */
.hero-jack-slide {
  position: absolute; inset: 0; opacity: 0;
  transition: opacity 0.9s cubic-bezier(0.4,0,0.2,1);
  pointer-events: none; z-index: 1;
}
.hero-jack-slide.is-active { opacity: 1; pointer-events: auto; z-index: 3; }
.hero-jack-slide.is-prev { opacity: 0; z-index: 2; }
.hero-jack-slide img {
  width: 100%; height: 100%; object-fit: cover;
  transform: scale(1); transition: transform 6s linear;
}
.hero-jack-slide.is-active img { transform: scale(1.045); }
@media (prefers-reduced-motion: reduce) {
  .hero-jack-slide img { transition: none; transform: none; }
}
.hero-jack-slide::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(100deg, rgba(23,24,15,0.06) 0%, rgba(23,24,15,0.08) 40%, rgba(23,24,15,0.72) 100%);
}
.hero-jack-slide[data-slide="0"]::after {
  /* Deeper and taller than the other slides' scrim, purely so the
     landing copy stays legible against whatever the photo is doing
     underneath, without resorting to a boxed panel. */
  background: linear-gradient(to top, rgba(23,24,15,0.8) 0%, rgba(23,24,15,0.14) 55%, rgba(23,24,15,0.26) 100%);
}
.hero-jack-landing { position: absolute; left: 0; right: 0; bottom: 0; z-index: 1; padding: 0 28px 76px; color: #fff; }
@media (min-width: 1024px) { .hero-jack-landing { padding: 0 64px 76px; } }
/* A soft shadow rather than a panel, text stays part of the photo
   instead of sitting on a slab in front of it. */
.hero-jack-landing .script,
.hero-jack-landing .hero-title,
.hero-jack-landing .quiet-cta { text-shadow: 0 2px 24px rgba(0,0,0,0.4); }

/* ---------- Hero title: quiet intro + grand accent ----------
   "Your Space Beautifully" reveals at the normal split-line pace;
   "Reimagined" is the focal point, larger, a different (sage) colour,
   and held back to settle in slowly on its own, well after the first
   line has finished, so it reads as the deliberate, grander moment. */
.headline.hero-title { font-size: clamp(34px, 5.2vw, 64px); line-height: 1.16; }
/* "One Stone" reads as a quiet lead-in, sized up a little from the old
   subline treatment; "Thanos" is the dominant element, noticeably
   larger again and given the site's sage accent colour so the two
   lines read as a deliberate hierarchy rather than one flat title. */
.hero-title-quiet { display: block; font-size: 0.9em; letter-spacing: 0.01em; opacity: 0.94; }
.hero-title-accent {
  display: block; margin-top: 10px; font-size: 1.55em; letter-spacing: -0.01em;
  color: rgba(255,255,255,0.94); /* always on a dark photo, not the wash — a fixed light, not context-adaptive */
  opacity: 0; transform: translateY(28px) scale(0.96);
  animation: heroAccentIn 2.2s cubic-bezier(0.16,1,0.3,1) 1.05s forwards;
}
@keyframes heroAccentIn { to { opacity: 1; transform: translateY(0) scale(1); letter-spacing: 0.005em; } }
@media (prefers-reduced-motion: reduce) {
  .hero-title-accent { animation: none; opacity: 1; transform: none; }
}

/* Project info sits as a right-anchored panel over each slide, rather
   than a bottom-left caption, image stays the hero, copy reads like a
   quiet label beside it. */
.flip-info { position: absolute; top: 50%; right: 28px; z-index: 1; max-width: 380px; text-align: right; transform: translateY(-50%); }
@media (min-width: 1024px) { .flip-info { right: 64px; } }
.flip-info .cc-eyebrow { font-size: 11px; }
.flip-info .cc-title { font-size: clamp(28px, 4vw, 46px); margin-bottom: 0; display: block; }
.flip-desc { font-family: 'Inter', sans-serif; font-weight: 300; font-size: 14px; line-height: 1.85; color: rgba(255,255,255,0.82); margin: 16px 0 24px; }
.flip-info .text-link { color: #fff; border-color: rgba(255,255,255,0.4); }
.flip-info .text-link:hover { border-color: #fff; }
/* Project slides carry only the "View Project" link now, no eyebrow,
   title, or description, the photograph is the whole point. Moved to
   the bottom-right corner (a quiet, single control) rather than
   sitting mid-frame over the image. */
.flip-info.flip-info-minimal {
  top: auto; bottom: 30px; right: 92px; transform: none; max-width: none;
  opacity: 0; transition: opacity 0.5s ease 0.35s;
}
.hero-jack-slide.is-active .flip-info.flip-info-minimal { opacity: 1; }
@media (min-width: 1024px) { .flip-info.flip-info-minimal { right: 128px; } }
@media (max-width: 700px) { .flip-info.flip-info-minimal { left: auto; right: 28px; bottom: 56px; text-align: right; padding: 0; } }
.flip-hint {
  position: absolute; left: 50%; bottom: 76px; z-index: 4; transform: translateX(-50%);
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  font-family: 'Inter', sans-serif; font-size: 10px; font-weight: 500;
  letter-spacing: 0.3em; text-transform: uppercase; color: rgba(255,255,255,0.72);
  transition: opacity 0.6s ease;
}
.flip-hint::after { content: ''; width: 1px; height: 26px; background: rgba(255,255,255,0.5); animation: flipHintDrift 1.8s ease-in-out infinite; }
.flip-hint.is-gone { opacity: 0; }
@keyframes flipHintDrift { 0%, 100% { transform: scaleY(0.35); opacity: 0.4; } 50% { transform: scaleY(1); opacity: 1; } }
.flip-index {
  position: absolute; left: 28px; top: 118px; z-index: 4;
  display: flex; align-items: center; gap: 8px;
  font-family: 'Inter', sans-serif; font-size: 11px; letter-spacing: 0.18em;
  color: rgba(255,255,255,0.7);
}
@media (min-width: 1024px) { .flip-index { left: 64px; } }
.flip-index-current { color: #fff; }

/* Slide dots, quick wayfinding, and a click-to-jump control. Bottom
   center, below the "Scroll" hint, clear of the project info panel
   on the right at every viewport width. */
.hero-jack-dots { position: absolute; left: 50%; bottom: 30px; top: auto; transform: translateX(-50%); z-index: 4; display: flex; flex-direction: row; gap: 14px; }
.hero-jack-dots button {
  width: 7px; height: 7px; border-radius: 50%; padding: 0; cursor: pointer;
  border: 1px solid rgba(255,255,255,0.55); background: transparent; -webkit-appearance: none;
  transition: background 0.3s ease, transform 0.3s ease;
}
.hero-jack-dots button.is-active { background: #fff; transform: scale(1.35); }

/* Skip control, its own dedicated corner, bottom right, well clear of
   the project text panel and the dots. Jumps straight past the pinned
   showcase to Curated Collections via a plain anchor link, no JS
   needed beyond that. */
.hero-jack-skip {
  position: absolute; right: 28px; bottom: 30px; z-index: 4;
  width: 46px; height: 46px; border-radius: 50%; border: 1px solid rgba(255,255,255,0.4);
  background: rgba(23,24,15,0.24); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  color: rgba(255,255,255,0.9); text-decoration: none;
  transition: background 0.35s ease, border-color 0.35s ease, transform 0.35s ease;
}
@media (min-width: 1024px) { .hero-jack-skip { right: 64px; } }
.hero-jack-skip:hover { background: rgba(23,24,15,0.5); border-color: #fff; transform: translateY(4px); }
.hero-jack-skip svg { width: 16px; height: 16px; display: block; }

@media (max-width: 700px) {
  .flip-info { position: absolute; left: 0; right: 0; top: auto; bottom: 56px; transform: none; text-align: left; max-width: none; padding: 0 28px; }
}

/* ---------- Sage / white rhythm ----------
   Both are transparent now: the single continuous wash on <body> (see
   above) already carries the white-to-green progression down the
   whole page, so a section no longer needs to paint its own patch of
   colour. Painting each section's own 0-to-peak-to-0 hump independently
   used to read as a distinct, fairly saturated blob wherever a
   .tone-sage section was short, with a visible seam at the boundary
   between two neighbours; letting the shared backdrop show through
   everywhere removes both problems at once. These classes still exist
   as hooks for section-local tweaks (e.g. the mounted-print image
   shadow below) and to keep every page's markup meaningful. */
.tone-sage { position: relative; }
.tone-white { position: relative; }

/* ---------- Project CTA: soft blended tint ----------
   Used for the "Imagining something similar" section on individual
   project pages instead of .tone-sage: that class's fixed 32%/68%
   fade band is tuned for tall sections, so in a compact CTA it reads
   as a flat, fairly present block rather than a blend. A quiet radial
   wash centered behind the text, fading out on every side instead, so
   it feels like a soft tint bleeding into the page rather than a
   banner sitting on it. */
.project-cta {
  position: relative;
  background: radial-gradient(70% 170% at 50% 50%, rgba(139,110,78,0.16) 0%, rgba(139,110,78,0) 72%), var(--c-bg);
}

/* Mounted-print frame: photos on a sage field sit slightly lifted,
   like a print on a mat board, rather than flush color-block. */
.tone-sage .img-block:not(.port-item):not(.collection-card):not(.curated-item) {
  box-shadow: 0 28px 60px -20px rgba(23,24,15,0.28), 0 2px 0 rgba(255,255,255,0.4) inset;
  outline: 1px solid rgba(255,255,255,0.5); outline-offset: -1px;
}

.hero-collections { padding-top: 68px; }

/* ---------- Home Final CTA: auto-rotating background ----------
   A slow crossfade between a small set of images rather than one
   static frame (see js/site.js). The dark overlay is a separate
   sibling layer sitting above all of them, so it darkens whichever
   photo happens to be active identically, rather than needing to be
   reapplied per image. */
.home-cta-bg { position: absolute; inset: 0; }
.home-cta-bg-img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  opacity: 0; transition: opacity 2.2s ease;
}
.home-cta-bg-img.is-active { opacity: 1; z-index: 1; }
@media (prefers-reduced-motion: reduce) { .home-cta-bg-img { transition: none; } }

/* ---------- Slowed reveal (Final CTA) ----------
   Same fade/rise system as .reveal / .split-line, just stretched out
   for a more unhurried, premium settle. */
.reveal.reveal-slow { transition: opacity 2.4s cubic-bezier(0.22,0.61,0.36,1), transform 2.4s cubic-bezier(0.22,0.61,0.36,1); }
.split-line.split-line-slow .sl-line > span { transition-duration: 1.7s; transition-timing-function: cubic-bezier(0.16,1,0.3,1); }

/* ---------- About: statistics ----------
   Now sits inline under the Our Philosophy paragraph rather than in
   its own panel with a reserved visual half. */
.stats-content { display: flex; gap: 56px; flex-wrap: wrap; }
.stat-item { text-align: left; }
.stat-num { font-size: 56px; display: block; color: var(--c-ink); }

/* ---------- About: press mentions (auto-scrolling carousel) ----------
   A real, natively-scrollable strip (overflow-x:auto) rather than a
   pure CSS keyframe animation: js/site.js drives a slow, continuous
   auto-scroll via requestAnimationFrame, but the moment the visitor
   touches it (wheel, touch-drag, or a trackpad swipe in either
   direction) that auto-scroll steps aside completely and native
   scrolling takes over; once they stop, auto-scroll picks back up
   from wherever they left it rather than resetting anywhere. The set
   of cards is tripled (see about.html) rather than doubled, so there's
   always a full run of real cards to scroll into on either side —
   scrolling left never runs out of content the way a doubled strip
   would. Scrollbar hidden (site-wide ::-webkit-scrollbar rule +
   scrollbar-width:none here for Firefox); the edge mask fades the
   first/last cards rather than cutting them off sharply.

   Every card frame is the same fixed aspect ratio regardless of the
   source image's own shape (a magazine scan, a mobile screenshot, a
   photo of an award — all different proportions): the image uses
   object-fit:contain against the frame's own soft background fill, so
   it's scaled down to fit whole rather than cropped, with the fill
   showing as quiet letterboxing on whichever axis doesn't match. */
.press-carousel {
  width: 100%; overflow-x: auto; overflow-y: hidden; scrollbar-width: none;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}
.press-track { display: flex; align-items: flex-start; gap: 40px; width: max-content; }
.press-card { flex: 0 0 auto; width: clamp(160px, 15vw, 216px); display: flex; flex-direction: column; align-items: center; gap: 12px; }
.press-card-frame {
  display: flex; align-items: center; justify-content: center; width: 100%; aspect-ratio: 3/4;
  border: 1px solid var(--c-line); border-radius: 2px; background: rgba(168, 143, 115,0.07);
  text-align: center; padding: 0; overflow: hidden;
}
/* Frames without a real page to link to (the Straits Times clipping)
   stay a plain, non-interactive div — everything else is wrapped in a
   real <a>, which gets the browser's own pointer cursor for free. */
.press-card-frame img { width: 100%; height: 100%; object-fit: contain; display: block; }
.press-card-frame span {
  font-family: var(--font-accent); font-style: normal; font-weight: 400; letter-spacing: 0.03em;
  font-size: 17px; line-height: 1.4; color: var(--c-ink-soft); opacity: 0.6;
}
.press-card-name {
  font-family: 'Inter', sans-serif; font-size: 11px; font-weight: 500;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--c-ink);
}
/* Wraps the name in its own link (same href as the frame and the
   "Click Here" line) so clicking the outlet's name works exactly like
   clicking its image — three separate anchors sharing one URL rather
   than nesting <a> inside <a>, which isn't valid HTML. */
.press-card-name-link { text-decoration: none; }
.press-card-name-link:hover .press-card-name, .press-card-name-link:focus-visible .press-card-name { color: var(--c-ink-soft); }
.press-link {
  font-family: 'Inter', sans-serif; font-size: 10px; font-weight: 500;
  letter-spacing: 0.16em; text-transform: uppercase; text-decoration: none;
  color: var(--c-ink-soft); border-bottom: 1px solid transparent;
  transition: border-color 0.3s ease, color 0.3s ease;
}
.press-link:hover { color: var(--c-ink); border-color: var(--c-ink); }
@media (max-width: 700px) {
  .press-carousel { -webkit-mask-image: linear-gradient(90deg, transparent, #000 3%, #000 97%, transparent); mask-image: linear-gradient(90deg, transparent, #000 3%, #000 97%, transparent); }
  .press-track { gap: 28px; }
  .press-card { width: 132px; }
}
@media (prefers-reduced-motion: reduce) {
  .press-carousel { -webkit-mask-image: none; mask-image: none; }
}

/* ---------- About: Our Studio ----------
   Roughly two thirds of the viewport, centered, rather than a narrow
   fixed-width column, so the statement has room to breathe without
   running edge to edge. Widens back out below 860px, where two thirds
   of the screen is too cramped for the copy to sit comfortably. */
.studio-wrap { max-width: 66%; margin-left: auto; margin-right: auto; }
@media (max-width: 860px) { .studio-wrap { max-width: 100%; } }

/* ---------- About: Our Design Ethos + Client Experience Journey ----------
   Two halves of one section rather than two stacked blocks: the ethos
   copy on the left, the four-step journey running top-to-bottom as a
   plain numbered list on the right, each row divided by a hairline.
   Stacks to a single column (ethos, then journey) below 860px. Each
   row now carries a small line-art icon, the number/title it always
   had, and a one-line description of what actually happens at that
   step, so the journey reads as information rather than just labels. */
.ethos-journey { display: grid; grid-template-columns: 1fr 1fr; gap: 72px; align-items: start; }
.journey-list { display: flex; flex-direction: column; }
.journey-row { display: flex; align-items: flex-start; gap: 20px; padding: 24px 0; border-top: 1px solid rgba(23,24,15,0.12); }
.journey-row:first-child { border-top: none; padding-top: 0; }
.journey-icon { flex: 0 0 auto; width: 32px; height: 32px; color: var(--c-sage-deep); }
.journey-icon svg { width: 100%; height: 100%; }
.journey-text { display: flex; flex-direction: column; gap: 6px; }
.journey-num { font-family: var(--font-accent); font-style: normal; font-weight: 400; font-size: 20px; color: var(--c-ink); }
.journey-title { font-family: 'Inter', sans-serif; font-size: 14px; font-weight: 500; letter-spacing: 0.02em; color: var(--c-ink); line-height: 1.5; }
.journey-desc { font-family: 'Inter', sans-serif; font-weight: 300; font-size: 13px; letter-spacing: 0.01em; color: var(--c-ink-soft); line-height: 1.6; }
@media (max-width: 860px) {
  .ethos-journey { grid-template-columns: 1fr; gap: 48px; }
}

/* ---------- Press feature page ----------
   Standard template for every individual "as featured in" page: name
   and back link above, one contained frame reserved for the actual
   tearsheet/clipping (a placeholder until Steve supplies the real
   image), and a quiet CTA beneath, mirroring the project detail pages. */
.press-artifact-slot {
  min-height: 360px; border: 1px dashed var(--c-line); border-radius: 2px;
  background: rgba(168, 143, 115,0.07);
  display: flex; align-items: center; justify-content: center; text-align: center;
}
.press-artifact-hint { font-family: 'Inter', sans-serif; font-size: 10px; letter-spacing: 0.22em; text-transform: uppercase; color: var(--c-ink-soft); opacity: 0.55; padding: 0 24px; }

/* ---------- Contact: map ----------
   Compact strip rather than a near-full-screen block, enough to place
   the studio clearly without dominating the page. */
.map-frame { position: relative; height: 320px; overflow: hidden; filter: grayscale(0.35) contrast(1.02); outline: 1px solid var(--c-line); }
.map-frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
@media (max-width: 700px) { .map-frame { height: 260px; } }

/* ---------- Breadcrumbs ----------
   Quiet, small-caps trail, standing in for the old bare "‹ All
   Projects" back-link on project pages and added fresh to every other
   inner page, so there's always a clear, crawlable path back up the
   site (paired with matching BreadcrumbList structured data in each
   page's head). */
.breadcrumbs { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; font-family: 'Inter', sans-serif; font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--c-ink-soft); margin-bottom: 18px; }
.breadcrumbs a { color: var(--c-ink-soft); text-decoration: none; border-bottom: 1px solid transparent; transition: color 0.3s ease, border-color 0.3s ease; }
.breadcrumbs a:hover { color: var(--c-ink); border-color: var(--c-line); }
.breadcrumbs .crumb-current { color: var(--c-ink); }
.breadcrumbs .crumb-sep { opacity: 0.5; }

/* ---------- Footer credit line ---------- */
.foot-credit { color: var(--c-ink-soft); letter-spacing: 0.1em; text-transform: uppercase; font-size: 10px; }
.foot-credit a { color: var(--c-ink-soft); text-decoration: none; border-bottom: 1px solid var(--c-line); transition: border-color 0.3s ease, color 0.3s ease; }
.foot-credit a:hover { color: var(--c-ink); border-color: var(--c-ink); }


/* ---------- Project detail: header + gallery ----------
   The standard template for every individual project page.

   Header (.project-header): All Projects / location, year / name /
   description, sits in its own section above the gallery in normal
   document flow, no fixed height, so however many lines the copy
   wraps to, it simply takes the space it needs, nothing else on the
   page can be pushed under or behind it. A rule + generous padding
   separates it clearly from the gallery below.

   Gallery (.project-gallery): the full set of a project's photos runs
   vertically down a thumbnail column on the left; one wide, dominant
   stage image cycles through the same photos on the right. The whole
   block is given a fixed pixel height (deliberately NOT a
   viewport-height unit, which was the root of the earlier overlap bug:
   vh is only reliable once you know the actual viewport height, and
   percentage-height children chained under it are fragile). Below
   ~960px it drops to a simple stacked flow instead. The stage cycles
   on its own via a slow crossfade (see js/site.js); clicking a
   thumbnail jumps straight to that image and resets the timer.

   The thumbnail column itself is a plain natively-scrollable grid
   (overflow-y:auto + overscroll-behavior:contain) of 2 columns,
   multiple rows: hovering it and using the wheel/trackpad scrolls only
   through the project photos, nothing to click or focus first, and
   overscroll-behavior:contain stops that scroll from ever "chaining"
   into the page once the grid hits its top/bottom edge, so the rest of
   the page stays put while the column is being scrolled. Move the
   mouse off it and the page scrolls normally again — this needs no JS
   at all, it's what overflow:auto + overscroll-behavior:contain do by
   default.

   The main stage image uses object-fit:contain rather than cover: a
   full-bleed crop was cutting off the edges of the room on anything
   that wasn't already a 16:9 shot, so the frame instead always shows
   the complete, uncropped photograph (letterboxed on whichever axis
   doesn't match) against the stage's own background fill.

   Below ~960px the two panes stack, and the stage is reordered ahead
   of the thumbnails (order:-1) so the large image always reads first
   without touching the HTML — thumbnails follow underneath as a
   horizontally-scrolling, 2-row strip (grid-auto-flow:column) rather
   than the desktop's vertically-scrolling 2-column grid: swiping
   moves through the project photos left-to-right, two at a time per
   swipe distance, which reads as a compact editorial filmstrip instead
   of a long vertical list a thumb has to stack for. */
.project-header { max-width: 620px; margin-left: 0; }
/* Project name sits well below the site's default h1 size (tuned for
   full-bleed hero headlines, not a compact detail-page title), both so
   short names like "Sage & Stone" never wrap onto a second line, and
   so the header takes up noticeably less vertical space overall. */
.project-header h1.headline { font-size: clamp(24px, 2.6vw, 34px); line-height: 1.2; white-space: nowrap; }
.project-gallery { display: flex; flex-direction: column; gap: 14px; }
@media (min-width: 960px) {
  .project-gallery {
    display: grid;
    grid-template-columns: minmax(360px, 460px) 1fr;
    /* An explicit single-row track (rather than leaving grid-template-rows
       on its default 'auto') is what actually makes the height below
       definite for the children's height:100% to resolve against; auto
       plus align-items:stretch was letting the row grow to fit the
       thumbnails' own aspect-ratio content instead of the reverse. */
    grid-template-rows: 460px;
    gap: 16px; align-items: stretch;
    max-width: 1200px;
    height: 460px;
    overflow: hidden;
  }
}
/* The scroll container: fixed height, hidden scrollbar (site-wide
   ::-webkit-scrollbar rule + scrollbar-width:none here for Firefox),
   overscroll-behavior:contain so scrolling it never bleeds into the
   page behind it. The 2-column grid of thumbnails lives directly
   inside it so the whole grid scrolls as one block. */
.project-thumbs {
  overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain;
  scrollbar-width: none;
}
@media (min-width: 960px) { .project-thumbs { height: 100%; } }
.project-thumbs-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; align-content: start; }
.project-thumb {
  position: relative; width: 100%; padding: 0; margin: 0;
  border: 1px solid transparent; background: none; overflow: hidden; cursor: pointer;
  aspect-ratio: 16/9;
  opacity: 0.5; transition: opacity 0.35s ease, border-color 0.35s ease;
}
.project-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; filter: none !important; transform: none !important; }
.project-thumb:hover, .project-thumb:focus-visible { opacity: 0.8; }
.project-thumb.is-active { opacity: 1; border-color: var(--c-sage); }
.project-stage { position: relative; overflow: hidden; background: var(--c-bg-alt); aspect-ratio: 4/3; }
@media (min-width: 960px) { .project-stage { height: 100%; aspect-ratio: auto; } }
@media (max-width: 959.98px) {
  /* Main image first, thumbnails follow — pure visual reorder, the
     thumbnails still come first in the HTML/DOM (so the gallery's own
     tab order and screen-reader order are unaffected). */
  .project-stage { order: -1; }
  .project-thumbs { overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain; }
  .project-thumbs-grid {
    grid-auto-flow: column; grid-template-columns: none;
    grid-template-rows: repeat(2, 1fr);
    grid-auto-columns: 132px;
    height: 204px;
  }
  .project-thumb { aspect-ratio: auto; }
}
.project-stage-img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain;
  opacity: 0; transition: opacity 1.3s ease;
}
.project-stage-img.is-active { opacity: 1; z-index: 1; }
@media (prefers-reduced-motion: reduce) { .project-stage-img { transition: none; } }
