/* =============================================================
   Indigo Interiors — style.css
   Brand: Indigo #4f3f68 · Gold #ccbb92 · White
   Type:  Bodidota (display roman) · Bodoni Moda (italic) · Poppins (body)

   Two Didones, deliberately paired:

   - ROMAN — Bodidota, self-hosted from assets/Bodidota.woff. Chosen over
     Bodoni Moda because its hairlines and serifs carry more weight; Bodoni
     Moda's thins get so fine at heading sizes they start to break up.
     Single weight (400), roman only, no optical-size axis.
   - ITALIC — Bodoni Moda, from Google Fonts (SIL OFL). Bodidota ships no
     italic, and a synthesised oblique on a Didone looks wrong: it shears
     the hairlines instead of redrawing them. The site leans on italic for
     voice (pull quote, testimonials, footer tagline, hero typewriter
     word), so those keep a real italic via --serif-italic.

   Neither is Linotype Didot, for which we hold only a desktop licence —
   see README "Fonts & licensing". The one place the real Didot survives
   is the logo reveal, as static SVG outlines (tools/build-wordmark.js),
   which is artwork rather than font software.

   Because Bodidota is a single weight with no opsz axis, the weight bumps
   that existed to keep Bodoni Moda's hairlines alive are mostly gone. Its
   sturdier strokes hold up unaided; where a nudge remains (small gold on
   white, light-on-dark) it is commented individually below.
   ============================================================= */

/* ---------- Fonts ---------- */
@font-face {
  font-family: 'Bodidota';
  src: url('../assets/Bodidota.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* ---------- Tokens ---------- */
:root {
  --indigo: #4f3f68;
  --indigo-deep: #3b2f50;
  --gold: #ccbb92;
  --gold-soft: #e7dfcd;
  --lavender: #8b7fa0;
  --paper: #ffffff;
  --mist: #f5f2f9;      /* off-white lavender — the single tinted-section colour */
  --sand: #f5f2f9;      /* alias of --mist (kept for markup compat) */
  --body: #5a5468;
  --hairline: rgba(79, 63, 104, 0.14);

  /* Roman: Bodidota (self-hosted). Italic: Bodoni Moda (Google Fonts) —
     apply --serif-italic wherever font-style:italic is used, or the
     browser will synthesise an oblique from Bodidota's roman.
     Fallbacks are other Didones so the look survives a font failure. */
  --serif: 'Bodidota', 'Bodoni Moda', 'Didot', 'Bodoni MT', Georgia, serif;
  --display: 'Bodidota', 'Bodoni Moda', 'Didot', 'Bodoni MT', Georgia, serif;
  --serif-italic: 'Bodoni Moda', 'Didot', 'Bodoni MT', Georgia, serif;
  --sans: 'Poppins', 'Segoe UI', Helvetica, Arial, sans-serif;

  --ease: cubic-bezier(.45, 0, .2, 1);
  --header-h: 84px;
}

/* ---------- Type trials ----------
   Scratch space for comparing display faces in situ. Put
   `data-type-trial="<name>"` on any section and everything inside it that
   uses --serif/--display switches to that face, while the rest of the page
   stays on the site default — the only honest way to judge a face is beside
   the one it would replace. Add a @font-face plus a rule setting --serif /
   --display / --serif-italic, then point the attribute at it and run
   `node tools/shoot-trial.js` for a before/after pair.

   Trials are EXPERIMENTS, not shipped type: promote a winner into :root and
   remove the attribute. Any self-hosted trial font must clear licensing
   first — see README "Fonts & licensing".

   (No trial is active. Bodidota was trialled here and promoted to :root.) */

/* ---------- Custom cursor ----------
   Gold scalene triangle (solid fill + spaced outline stroke) that morphs
   into a circle of the same construction over clickables. Implemented as a
   JS-driven follower element (static cursor: urls can't animate); main.js
   adds .has-cursor on fine-pointer devices, which hides the native cursor.
   Fallback below: static triangle for fine pointers if JS never runs. */
@media (pointer: fine) {
  html, body {
    cursor: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M2.5 1.5 L11 29 L29 16.5 Z" fill="none" stroke="%23ccbb92" stroke-width="1.6"/><path d="M7.8 8.4 L12.6 23.7 L22.6 16.7 Z" fill="%23ccbb92"/></svg>') 2 1, auto;
  }
}
html.has-cursor, html.has-cursor * { cursor: none !important; }
.cursor {
  position: fixed;
  left: 0; top: 0;
  width: 34px; height: 34px;
  z-index: 9999;
  pointer-events: none;
  will-change: transform;
  opacity: 0;
  transition: opacity .25s var(--ease);
}
.cursor.is-visible { opacity: 1; }
.cursor svg {
  position: absolute;
  inset: 0;
  transition: opacity .28s var(--ease), transform .28s var(--ease);
}
/* triangle: tip sits at the pointer position (top-left of the box) */
.cursor__tri { opacity: 1; transform: scale(1); transform-origin: 8% 5%; }
/* circle: centered on the pointer position */
.cursor__circ {
  opacity: 0;
  transform: translate(-50%, -50%) scale(.55);
  transform-origin: 0 0;
  left: 0; top: 0;
}
.cursor.is-hover .cursor__tri { opacity: 0; transform: scale(.45); }
.cursor.is-hover .cursor__circ { opacity: 1; transform: translate(-50%, -50%) scale(.75); }

/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--paper);
  color: var(--body);
  font-family: var(--sans);
  font-weight: 300;
  font-size: 16px;
  line-height: 1.75;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* let Bodoni Moda's opsz axis respond to size (see header note) */
  font-optical-sizing: auto;
}
img, svg, video { max-width: 100%; display: block; }
a { color: var(--indigo); text-decoration: none; transition: color .3s var(--ease); }
a:hover { color: var(--gold); }
button { font-family: inherit; }

h1, h2, h3, h4 {
  font-family: var(--serif);
  font-weight: 400;
  color: var(--indigo);
  line-height: 1.15;
  margin: 0 0 .5em;
}
/* Bodoni Moda sets a little wider than Didot at the same size, so the
   large sizes come back very slightly and carry a touch less tracking. */
h1 { font-size: clamp(2.5rem, 5.7vw, 4.2rem); letter-spacing: .01em; }
h2 { font-size: clamp(1.95rem, 3.9vw, 2.8rem); letter-spacing: .01em; }
/* Bodidota has a single 400 weight, so asking for 500 makes the browser
   synthesise a fake bold — smeared, uneven strokes. Its serifs are sturdy
   enough at this size not to need the bump Bodoni Moda did. */
h3 { font-size: 1.35rem; letter-spacing: .015em; }
p  { margin: 0 0 1.2em; }

::selection { background: var(--indigo); color: #fff; }

/* ---------- Utilities ---------- */
.container { width: min(1180px, 92vw); margin-inline: auto; }
.container--narrow { width: min(820px, 92vw); margin-inline: auto; }

.eyebrow {
  font-family: var(--sans);
  font-size: .72rem;
  font-weight: 500;
  letter-spacing: .32em;
  text-transform: uppercase;
  color: var(--lavender);
  margin-bottom: 1.1em;
}
.rule {
  width: 64px; height: 1px;
  background: var(--gold);
  border: 0; margin: 1.6rem 0;
}
.rule--center { margin-inline: auto; }
.text-center { text-align: center; }

.section { padding: clamp(4.5rem, 9vw, 8rem) 0; }
.section--mist { background: var(--mist); }
.section--sand { background: var(--sand); }
.section--indigo { background: var(--indigo); }
.section--indigo h2, .section--indigo h3 { color: #fff; }
.section--indigo p { color: rgba(255, 255, 255, .78); }
.section--indigo .eyebrow { color: var(--gold); }

/* ---------- Patterned section ----------
   A faint, repeating tile of the logo mark laid over a section's
   background. Painted on ::before rather than the section itself so the
   opacity applies to the pattern alone and not to the content above it;
   the section needs a stacking context, and content sits at z-index 1.

   Tune: --pattern-size (tile width) and --pattern-opacity. Currently used
   on the home page testimonials as a trial — remove .section--pattern
   from the markup to drop it. */
.section--pattern {
  position: relative;
  isolation: isolate;
  /* Small enough to read as texture rather than as repeated logos, but
     still legible as the mark. Opacity is the sensitive control on the
     indigo ground: below ~.08 it vanishes, much above .16 it starts
     competing with the cards in front of it. */
  --pattern-size: 175px;
  --pattern-opacity: .05;
}
.section--pattern::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: url('../assets/pattern-mark.svg');
  background-repeat: repeat;
  background-size: var(--pattern-size) auto;
  background-position: center;
  opacity: var(--pattern-opacity);
  pointer-events: none;
  z-index: 0;
}
.section--pattern > * { position: relative; z-index: 1; }
@media (max-width: 720px) {
  .section--pattern { --pattern-size: 128px; }
}

.section-head { max-width: 640px; margin-bottom: clamp(2.5rem, 5vw, 4rem); }
.section-head--center { margin-inline: auto; text-align: center; }

/* ---------- Buttons ---------- */
.btn {
  display: inline-block;
  font-family: var(--sans);
  font-size: .78rem;
  font-weight: 500;
  letter-spacing: .22em;
  text-transform: uppercase;
  padding: 1em 2.4em;
  border: 1px solid var(--indigo);
  color: var(--indigo);
  background: transparent;
  cursor: pointer;
  transition: background .35s var(--ease), color .35s var(--ease), border-color .35s var(--ease);
}
.btn:hover { background: var(--indigo); color: #fff; }
.btn--solid { background: var(--indigo); color: #fff; }
.btn--solid:hover { background: var(--indigo-deep); border-color: var(--indigo-deep); color: #fff; }
.btn--gold { border-color: var(--gold); color: var(--indigo); }
.btn--gold:hover { background: var(--gold); border-color: var(--gold); color: var(--indigo-deep); }
.btn--light { border-color: rgba(255,255,255,.6); color: #fff; }
.btn--light:hover { background: #fff; border-color: #fff; color: var(--indigo); }

/* ---------- Header / Nav (centered logo mark) ---------- */
.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 600;
  height: var(--header-h);
  display: flex;
  align-items: center;
  background: rgba(255, 255, 255, .96);
  backdrop-filter: blur(8px);
  box-shadow: 0 1px 0 var(--hairline);
}
.header-grid {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
}
.brand { display: flex; align-items: center; gap: .9rem; }
.brand__mark {
  width: 42px; height: auto;
  transition: transform .4s var(--ease);
}
.header-grid .brand:hover .brand__mark { transform: translateY(-2px); }
.brand__name {
  font-family: var(--display);
  /* Bodidota is single-weight; a synthesised bold would distort the
     wordmark, which sits right beside the logo mark. Its strokes hold at
     this tracking unaided. */
  font-weight: 400;
  font-size: 1.05rem;
  letter-spacing: .18em;
  color: var(--indigo);
  /* the wordmark stacks over two lines (INDIGO / INTERIORS), so nowrap
     would fight the <br>; the tight leading is set on the footer rule */
  white-space: normal;
}

.nav { display: flex; align-items: center; gap: 2.8rem; }
.nav--left { justify-content: flex-end; padding-right: 3.4rem; }
.nav--right { justify-content: flex-start; padding-left: 3.4rem; }
.nav a {
  position: relative;
  font-size: .78rem;
  font-weight: 400;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--indigo);
  padding: .4em 0;
}
.nav a::after {
  content: '';
  position: absolute;
  left: 0; bottom: 0;
  width: 100%; height: 1px;
  background: var(--gold);
  transform: scaleX(0);
  transform-origin: right;
  transition: transform .35s var(--ease);
}
.nav a:hover::after, .nav a[aria-current="page"]::after {
  transform: scaleX(1);
  transform-origin: left;
}
.nav a[aria-current="page"] { color: var(--indigo); }

.nav-toggle {
  display: none;
  position: relative;
  z-index: 2;
  background: none;
  border: 0;
  width: 40px; height: 40px;
  cursor: pointer;
  padding: 8px;
}
.nav-toggle span {
  display: block;
  height: 1.5px;
  background: var(--indigo);
  margin: 6px 0;
  transition: transform .35s var(--ease), opacity .3s;
}
.site-header.is-open .nav-toggle span:nth-child(1) { transform: translateY(7.5px) rotate(45deg); }
.site-header.is-open .nav-toggle span:nth-child(2) { opacity: 0; }
.site-header.is-open .nav-toggle span:nth-child(3) { transform: translateY(-7.5px) rotate(-45deg); }

/* Full-screen mobile menu.

   Sizing note, and it is not optional. .site-header sets
   `backdrop-filter: blur(8px)`, and an element with a backdrop-filter
   becomes the containing block for its fixed-position descendants —
   exactly as `transform` and `filter` do. So `inset: 0` on this element
   resolved against the 84px header box rather than the viewport: the
   overlay painted 375x84, "Home" landed at y=-99 (completely above the
   screen), and the lower links sat on top of page content with no
   background behind them at all.

   The header is itself pinned to the viewport's top-left
   (`position: fixed; inset: 0 0 auto 0`) and spans its full width, so
   top/left/right already resolve where we want them — only the height
   was wrong. Setting it explicitly fixes the overlay without moving the
   markup out of the header on all six pages.

   dvh rather than vh: on phones vh means the *largest* possible
   viewport, so with the address bar on screen the bottom of the menu
   would sit underneath it. The plain vh line above is the fallback for
   browsers that do not know dvh. */
.mobile-nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 100vh;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2.4rem;
  /* Clears the header so the links never collide with the logo or the
     close button, and keeps every link reachable on a short landscape
     viewport. */
  padding: var(--header-h) 1.5rem 2rem;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: rgba(255, 255, 255, .98);
  opacity: 0;
  pointer-events: none;
  transition: opacity .35s var(--ease);
}
.site-header.is-open .mobile-nav { opacity: 1; pointer-events: auto; }
/* The overlay is a sibling of the brand inside the header, so without
   this the white background covers the logo while the menu is open and
   the header reads as empty apart from the close button. */
.site-header.is-open .brand { position: relative; z-index: 2; }
.mobile-nav a {
  font-family: var(--serif);
  /* single-weight face — 400 avoids a synthesised bold */
  font-weight: 400;
  font-size: 1.5rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--indigo);
  opacity: 0;
  transform: translateY(12px);
  transition: opacity .45s var(--ease), transform .45s var(--ease), color .3s var(--ease);
}
.mobile-nav a[aria-current="page"] { color: var(--gold); }
.site-header.is-open .mobile-nav a { opacity: 1; transform: none; }
.site-header.is-open .mobile-nav a:nth-child(1) { transition-delay: .08s; }
.site-header.is-open .mobile-nav a:nth-child(2) { transition-delay: .16s; }
.site-header.is-open .mobile-nav a:nth-child(3) { transition-delay: .24s; }
.site-header.is-open .mobile-nav a:nth-child(4) { transition-delay: .32s; }

@media (max-width: 860px) {
  .nav--left, .nav--right { display: none; }
  .nav-toggle { display: block; grid-column: 3; justify-self: end; }
  .header-grid .brand { grid-column: 2; }
  .header-grid .brand__mark { width: 36px; }
}

/* Short viewports — a phone held in landscape. At 320px tall the header
   alone is a quarter of the screen, and the menu at full size needed
   283px for 204px of room, which pushed "Contact" below the fold: the
   overlay scrolled, but a four-item menu should never ask for a scroll.
   Tightened so all four links fit without one. The top padding still
   clears the close button, which sits 22-62px down inside the header. */
@media (max-width: 860px) and (max-height: 480px) {
  .mobile-nav {
    gap: 1.1rem;
    padding: 64px 1.5rem 1rem;
  }
  .mobile-nav a { font-size: 1.15rem; }
}

/* ---------- Hero (home) ---------- */
.hero {
  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  align-items: center;
  overflow: hidden;
}
.hero__media {
  position: absolute;
  inset: 0;
  z-index: -1;
}
/* Placeholder “photograph” — replace .hero__ph with a real <img> or <video>
   of an interior; keep .hero__scrim above it for text legibility. */
.hero__ph {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse 90% 70% at 78% 22%, rgba(204, 187, 146, .35), transparent 60%),
    radial-gradient(ellipse 70% 90% at 12% 85%, rgba(79, 63, 104, .28), transparent 65%),
    linear-gradient(160deg, #f6f2ea 0%, #ece6f0 48%, #ded5e6 100%);
  animation: heroDrift 26s var(--ease) infinite alternate;
}
/* Low-opacity interior slideshow over the placeholder wash. The reveal
   overlay is transparent, so this is also the backdrop of the logo
   animation. The strip itself fades in ~3s after load (js/main.js adds
   .slides-on to the section), then slides crossfade every ~7s. */
.hero__slides {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 1.8s var(--ease);
  animation: heroDrift 26s var(--ease) infinite alternate;
}
.hero.slides-on .hero__slides { opacity: 1; }
.hero__slide {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 2.5s var(--ease);
}
/* While the logo reveal plays, slides are ghosted so the indigo animation
   reads against white; once the site content fades in (body.revealed) they
   go full-opacity and the white scrim swaps for a dark shade. */
.hero__slide.is-active { opacity: .3; }
body.revealed .hero__slide.is-active { opacity: 1; }
.hero__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(255,255,255,.5) 0%, rgba(255,255,255,.12) 45%, rgba(255,255,255,.6) 100%);
  transition: opacity 2s var(--ease);
}
body.revealed .hero__scrim { opacity: 0; }
.hero__shade {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse 90% 65% at 50% 52%, rgba(30, 22, 44, .42), rgba(30, 22, 44, .12) 70%),
    linear-gradient(180deg, rgba(30, 22, 44, .25), transparent 30%, transparent 70%, rgba(30, 22, 44, .35));
  opacity: 0;
  transition: opacity 2s var(--ease);
}
body.revealed .hero__shade { opacity: 1; }
@keyframes heroDrift {
  from { transform: scale(1); }
  to   { transform: scale(1.08) translateY(-1.5%); }
}

.hero__inner {
  margin-inline: auto;
  text-align: center;
  max-width: 980px;
  padding: var(--header-h) 0 0;
}
.hero__title {
  font-family: var(--sans);
  font-weight: 300;
  letter-spacing: .04em;
  color: #fff;
  font-size: clamp(2rem, 4.4vw, 3.4rem);
  line-height: 1.3;
  margin: 0;
  text-shadow: 0 2px 28px rgba(30, 22, 44, .35);
}
.hero__sub {
  font-family: var(--sans);
  font-weight: 300;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .85);
  font-size: clamp(.8rem, 1.3vw, .95rem);
  margin: 1.4rem 0 0;
  text-shadow: 0 2px 20px rgba(30, 22, 44, .35);
}
/* Typewriter word — Bodoni Moda italic, white, with a blinking caret.
   White-on-photo is the worst case for a Didone: thin strokes get eaten by
   the image behind them. 500 plus the inherited text-shadow keeps the word
   solid without making it look bold beside the 300-weight Poppins around it. */
.hero__typed {
  font-family: var(--serif-italic);
  font-style: italic;
  font-weight: 500;
  color: #fff;
  white-space: nowrap;
}
.hero__caret {
  display: inline-block;
  width: 2px;
  height: .9em;
  margin-left: .06em;
  vertical-align: -.08em;
  background: #fff;
  animation: caretBlink 1.1s step-end infinite;
}
@keyframes caretBlink { 50% { opacity: 0; } }

/* Hero slide arrows — tablet/desktop only */
.hero__arrow {
  position: absolute;
  top: 50%;
  margin-top: -24px;
  z-index: 5;
  width: 48px;
  height: 48px;
  display: none;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  padding: 0;
  color: #fff;
  opacity: .65;
  filter: drop-shadow(0 1px 10px rgba(30, 22, 44, .45));
  transition: opacity .3s var(--ease), transform .3s var(--ease);
}
.hero__arrow--prev { left: clamp(.8rem, 2.5vw, 2.4rem); }
.hero__arrow--next { right: clamp(.8rem, 2.5vw, 2.4rem); }
.hero__arrow:hover { opacity: 1; transform: scale(1.12); }
@media (min-width: 768px) {
  .hero__arrow { display: flex; }
}

.scroll-hint {
  position: absolute;
  /* centered via auto margins, NOT translateX(-50%): this element is also a
     .fade-on-reveal, whose revealed state sets transform:none and would wipe
     out a transform-based centering (it sat ~half its width right of center) */
  left: 0;
  right: 0;
  margin-inline: auto;
  width: max-content;
  bottom: 2.2rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .7rem;
  font-size: .62rem;
  letter-spacing: .34em;
  /* letter-spacing adds a phantom trailing space after the last glyph —
     indent the text by the same amount so the visible glyphs sit on the
     true center line (text-indent doesn't move the ::after line) */
  text-indent: .34em;
  text-transform: uppercase;
  color: #fff;
  text-shadow: 0 1px 14px rgba(30, 22, 44, .45);
}
.scroll-hint::after {
  content: '';
  width: 1px; height: 44px;
  background: linear-gradient(var(--gold), transparent);
  animation: hintPulse 2.4s var(--ease) infinite;
}
@keyframes hintPulse {
  0%, 100% { transform: scaleY(.4); transform-origin: top; opacity: .4; }
  50% { transform: scaleY(1); opacity: 1; }
}

/* ---------- Post-reveal fade-in ---------- */
/* While the logo reveal is active, flagged elements wait invisible;
   body.revealed lets them rise in with per-element delays (--d). */
html.reveal-active .fade-on-reveal {
  opacity: 0;
  transform: translateY(14px);
}
html.reveal-active body.revealed .fade-on-reveal {
  opacity: 1;
  transform: none;
  transition: opacity 1s var(--ease) var(--d, 0s), transform 1s var(--ease) var(--d, 0s);
}

/* Navbar enters early in the reveal, as the opening text glide slows
   (body.nav-revealed is added by reveal.js at ~3s on the timeline). */
html.reveal-active .site-header {
  opacity: 0;
  transform: translateY(-14px);
}
html.reveal-active body.nav-revealed .site-header {
  opacity: 1;
  transform: none;
  transition: opacity 1.1s var(--ease), transform 1.1s var(--ease);
}

/* Reveal overlay shell (stage markup lives in index.html) */
#ovl { display: none; }
html.reveal-active #ovl { display: flex; }

/* ---------- Scroll animations ---------- */
[data-reveal] {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity .9s var(--ease), transform .9s var(--ease);
  transition-delay: var(--d, 0s);
}
[data-reveal].in-view { opacity: 1; transform: none; }

/* ---------- Intro / About strip (home) ---------- */
.about-grid {
  display: grid;
  grid-template-columns: 1fr 1.15fr;
  gap: clamp(2.5rem, 6vw, 6rem);
  align-items: center;
}
.portrait-frame {
  position: relative;
  aspect-ratio: 4 / 5;
  background: linear-gradient(150deg, #e9e3f0, #d7cfe2 55%, #cfc4dd);
  display: flex;
  align-items: center;
  justify-content: center;
}
.portrait-frame::after {
  content: '';
  position: absolute;
  inset: 18px;
  border: 1px solid var(--gold);
  pointer-events: none;
}
.portrait-frame img { width: 42%; opacity: .5; }
.ph-note {
  position: absolute;
  bottom: 8%;
  font-size: .62rem;
  letter-spacing: .3em;
  text-transform: uppercase;
  color: var(--lavender);
}
@media (max-width: 820px) {
  .about-grid { grid-template-columns: 1fr; }
}

blockquote.pull {
  font-family: var(--serif-italic);
  font-style: italic;
  /* body sets 300, but the loaded italic range starts at 400 — be explicit
     so the browser isn't left to pick */
  font-weight: 400;
  font-size: 1.35rem;
  color: var(--indigo);
  border-left: 1px solid var(--gold);
  margin: 2rem 0;
  padding-left: 1.6rem;
  line-height: 1.5;
}

/* ---------- Cards (services preview + services page) ---------- */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(255px, 1fr));
  gap: 1.5rem;
}
.card {
  background: var(--paper);
  border: 1px solid var(--hairline);
  padding: 2.6rem 2.2rem;
  transition: transform .45s var(--ease), box-shadow .45s var(--ease), border-color .45s var(--ease);
}
.card:hover {
  transform: translateY(-6px);
  box-shadow: 0 24px 48px -28px rgba(79, 63, 104, .35);
  border-color: var(--gold);
}
.card__num {
  font-family: var(--serif);
  /* Small gold-on-white is the lowest-contrast type on the site. Bodidota
     is single-weight, so instead of a synthesised bold the numeral gets a
     slightly larger size and the deeper gold — same legibility, real strokes. */
  font-weight: 400;
  font-size: 1.02rem;
  color: #b9a473;
  letter-spacing: .1em;
  display: block;
  margin-bottom: 1.2rem;
}
.card h3 { margin-bottom: .6rem; }
.card p { font-size: .92rem; margin: 0; }

/* ---------- Service detail rows (services page) ---------- */
.service-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(2rem, 6vw, 5rem);
  align-items: center;
  padding: clamp(2.5rem, 5vw, 4rem) 0;
}
.service-row + .service-row { border-top: 1px solid var(--hairline); }
.service-row:nth-child(even) .service-row__media { order: 2; }
.service-row__media {
  aspect-ratio: 4 / 3;
  position: relative;
  overflow: hidden;
}
.service-row__media .ph-note { left: 50%; transform: translateX(-50%); white-space: nowrap; }
.service-row h3 { font-size: 1.7rem; }
.service-row ul {
  margin: 1.2rem 0 0;
  padding: 0;
  list-style: none;
  font-size: .92rem;
}
.service-row li {
  padding: .5em 0 .5em 1.6em;
  position: relative;
}
.service-row li::before {
  content: '';
  position: absolute;
  left: 0; top: 1.15em;
  width: 14px; height: 1px;
  background: var(--gold);
}
@media (max-width: 820px) {
  .service-row { grid-template-columns: 1fr; }
  .service-row:nth-child(even) .service-row__media { order: 0; }
}

/* Placeholder art for media boxes — swap for real photography */
.ph {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.ph--a { background: linear-gradient(155deg, #efe9f4, #dcd2e6 60%, #cbbfd9); }
.ph--b { background: linear-gradient(155deg, #f5efe3, #e9dfc9 60%, #d9cbaa); }
.ph--c { background: linear-gradient(155deg, #e8e4ef, #d4cee2 55%, #b9aecd); }
.ph--d { background: linear-gradient(155deg, #f3eee6, #e2d8c6 55%, #cfc0a2); }
.ph--e { background: linear-gradient(155deg, #ece7f2, #d8d0e4 55%, #c2b6d4); }
.ph--f { background: linear-gradient(155deg, #f6f1e8, #e5dcc8 55%, #d4c6a6); }
.ph img { width: 26%; opacity: .35; }
/* Real photography (temporary stock for now) fills the tile; the gradient
   above remains as the loading backdrop. */
.ph img.ph__photo,
.portrait-frame img.ph__photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 1;
}

/* ---------- Process strip ---------- */
.process-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 2.5rem;
  counter-reset: step;
}
.process-step { position: relative; padding-top: 1.4rem; }
.process-step::before {
  counter-increment: step;
  content: counter(step, decimal-leading-zero);
  font-family: var(--serif);
  /* large display numeral — this is where a Didone's stroke contrast
     should show, so it stays at 400 and gains a little tracking */
  font-weight: 400;
  font-size: 2.8rem;
  letter-spacing: .02em;
  color: var(--gold);
  opacity: .9;
  display: block;
  margin-bottom: .6rem;
}
/* All caps, a size up from the default h3. Caps need more tracking than
   mixed case to breathe — without it the letterforms crowd each other. */
.process-step h3 {
  font-size: 1.32rem;
  text-transform: uppercase;
  letter-spacing: .13em;
}
.process-step p { font-size: .88rem; margin: 0; }

/* ---------- Gallery ---------- */
.gallery-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 1.4rem;
  /* projects.js renders the tiles after load, so the grid is 0px tall to
     begin with and everything below it jumps up the page when they land —
     measured as a 0.155 layout shift, nearly all of the gallery's CLS.
     projects.js sets --rows from the real project count before rendering,
     so the space is reserved at the correct height from the first paint.
     Each row is a 4/5 tile on a 300px min column, plus the 1.4rem gap. */
  min-height: calc(var(--rows, 1) * 375px + (var(--rows, 1) - 1) * 1.4rem);
}
.gallery-item {
  position: relative;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  cursor: pointer;
  border: 0;
  padding: 0;
  background: none;
  transition: opacity .5s var(--ease), transform .5s var(--ease);
}
.gallery-item.is-hidden { display: none; }
.gallery-item .ph { transition: transform .8s var(--ease); }
.gallery-item:hover .ph { transform: scale(1.05); }
.gallery-item__label {
  position: absolute;
  inset: auto 0 0 0;
  padding: 1.4rem 1.5rem;
  background: linear-gradient(transparent, rgba(59, 47, 80, .55));
  color: #fff;
  text-align: left;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity .45s var(--ease), transform .45s var(--ease);
}
.gallery-item:hover .gallery-item__label { opacity: 1; transform: none; }
.gallery-item__label strong {
  font-family: var(--serif);
  /* over photography; single-weight face, and the label's dark scrim
     already carries the contrast */
  font-weight: 400;
  font-size: 1.15rem;
  display: block;
  letter-spacing: .04em;
}
.gallery-item__label span {
  font-size: .68rem;
  letter-spacing: .26em;
  text-transform: uppercase;
  color: var(--gold-soft);
}

/* Lightbox */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: rgba(38, 30, 52, .94);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity .4s var(--ease);
}
.lightbox.is-open { opacity: 1; pointer-events: auto; }
.lightbox__stage {
  width: min(880px, 88vw);
  aspect-ratio: 4 / 3;
  position: relative;
  overflow: hidden;
}
.lightbox__caption {
  text-align: center;
  color: #fff;
  margin-top: 1.4rem;
}
.lightbox__caption strong {
  font-family: var(--serif);
  /* white on the lightbox's near-black backdrop; single-weight face */
  font-weight: 400;
  font-size: 1.3rem;
  letter-spacing: .05em;
  display: block;
}
.lightbox__caption span {
  font-size: .68rem;
  letter-spacing: .28em;
  text-transform: uppercase;
  color: var(--gold);
}
.lightbox__close, .lightbox__nav {
  position: absolute;
  background: none;
  border: 0;
  color: #fff;
  cursor: pointer;
  font-size: 1.6rem;
  line-height: 1;
  padding: .6em;
  opacity: .7;
  transition: opacity .3s;
}
.lightbox__close:hover, .lightbox__nav:hover { opacity: 1; }
.lightbox__close { top: 20px; right: 26px; font-size: 2rem; }
.lightbox__nav--prev { left: 12px; top: 50%; transform: translateY(-50%); }
.lightbox__nav--next { right: 12px; top: 50%; transform: translateY(-50%); }

/* ---------- Testimonials ---------- */
.testimonial-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 2rem;
}
.testimonial {
  position: relative;
  border: 1px solid rgba(255, 255, 255, .18);
  padding: 2.4rem 2.2rem;
  overflow: hidden;
}
/* A gold light that loops around the card border — same idea as the
   scroll-hint's pulsing line. A thin conic-gradient ring, revealed only in
   the 1px border zone via two stacked rects (mask), rotating forever. */
.testimonial::before {
  content: '';
  position: absolute;
  inset: -75%;
  background: conic-gradient(
    transparent 0deg,
    transparent 300deg,
    rgba(204, 187, 146, .9) 335deg,
    #e8dcbe 348deg,
    rgba(204, 187, 146, .9) 353deg,
    transparent 360deg);
  animation: borderOrbit 7s linear infinite;
  pointer-events: none;
}
/* cover the interior so only the border zone shows the moving gradient */
.testimonial::after {
  content: '';
  position: absolute;
  inset: 1px;
  background: var(--indigo);
  pointer-events: none;
}
.testimonial > * { position: relative; z-index: 1; }
@keyframes borderOrbit {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}
/* stagger so the three cards don't pulse in sync */
.testimonial-grid .testimonial:nth-child(2)::before { animation-delay: -2.3s; }
.testimonial-grid .testimonial:nth-child(3)::before { animation-delay: -4.6s; }
.testimonial p {
  font-family: var(--serif-italic);
  font-style: italic;
  /* light-on-indigo: optical thinning is strongest here, so 500 */
  font-weight: 500;
  font-size: 1.12rem;
  line-height: 1.65;
}
.testimonial cite {
  font-style: normal;
  font-size: .72rem;
  letter-spacing: .26em;
  text-transform: uppercase;
  color: var(--gold);
}

/* ---------- Page hero (inner pages) ---------- */
.page-hero {
  position: relative; /* anchors .project-back-top on project pages */
  padding: calc(var(--header-h) + clamp(4rem, 9vw, 7rem)) 0 clamp(3rem, 6vw, 5rem);
  text-align: center;
  background:
    radial-gradient(ellipse 70% 100% at 50% 0%, rgba(204, 187, 146, .13), transparent 70%),
    var(--paper);
}
/* Single-word page titles at display size — the one place to let Bodoni's
   stroke contrast run at full 400 with generous tracking. */
.page-hero h1 { font-family: var(--display); font-weight: 400; letter-spacing: .07em; }
.page-hero .lede { max-width: 38em; margin-inline: auto; }

/* ---------- Project page (project.html, rendered by js/projects.js) ---------- */
/* Back-to-gallery arrow, top left of the project header */
.project-back-top {
  position: absolute;
  left: clamp(1rem, 4vw, 3rem);
  top: calc(var(--header-h) + 1.6rem);
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  font-size: .72rem;
  font-weight: 500;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--lavender);
  transition: color .3s var(--ease), transform .3s var(--ease);
}
.project-back-top:hover { color: var(--indigo); transform: translateX(-3px); }
.project-back-top svg { flex: 0 0 auto; }
@media (max-width: 640px) {
  .project-back-top span { display: none; } /* just the arrow on phones */
}

.project-brief {
  padding: clamp(2rem, 4vw, 3.5rem) 0 0;
}
.project-brief p {
  font-size: 1.05rem;
  max-width: 46em;
}
.project-photos { padding-top: clamp(2.5rem, 5vw, 4rem); }
.project-row {
  display: grid;
  grid-template-columns: 1.35fr 1fr;
  gap: clamp(1.8rem, 4vw, 3.5rem);
  align-items: center;
  margin-bottom: clamp(3rem, 7vw, 5.5rem);
}
.project-row:last-child { margin-bottom: 0; }
/* alternate photo left / photo right */
.project-row:nth-child(even) .project-row__media { order: 1; }
.project-row__media img {
  width: 100%;
  height: auto;
  display: block;
}
.project-row__text h3 {
  font-size: 1.5rem;
  margin-bottom: .6em;
}
.project-row__text p { margin-bottom: 0; }
.project-back { padding-top: 0; }
@media (max-width: 760px) {
  .project-row { grid-template-columns: 1fr; gap: 1.2rem; }
  .project-row:nth-child(even) .project-row__media { order: 0; }
}

/* ---------- Contact ---------- */
.contact-grid {
  display: grid;
  grid-template-columns: 1fr 1.3fr;
  gap: clamp(2.5rem, 6vw, 5.5rem);
  align-items: start;
}
@media (max-width: 820px) {
  .contact-grid { grid-template-columns: 1fr; }
}
.contact-info h3 { font-size: 1.05rem; letter-spacing: .06em; margin: 2rem 0 .4rem; }
.contact-info h3:first-of-type { margin-top: 0; }
.contact-info p { font-size: .95rem; margin-bottom: .4em; }

.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1.4rem; }
.form-field { display: flex; flex-direction: column; gap: .45rem; }
.form-field--full { grid-column: 1 / -1; }
.form-field label {
  font-size: .68rem;
  font-weight: 500;
  letter-spacing: .26em;
  text-transform: uppercase;
  color: var(--indigo);
}
.form-field input,
.form-field select,
.form-field textarea {
  font-family: var(--sans);
  font-weight: 300;
  font-size: .95rem;
  color: var(--indigo-deep);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--hairline);
  padding: .7em 2px;
  outline: none;
  border-radius: 0;
  transition: border-color .3s var(--ease);
  resize: vertical;
}
.form-field input:focus,
.form-field select:focus,
.form-field textarea:focus { border-bottom-color: var(--gold); }
.form-field input.is-invalid,
.form-field textarea.is-invalid { border-bottom-color: #b3554e; }
.form-note { font-size: .8rem; color: var(--lavender); margin-top: 1.2rem; }
/* honeypot — remove from human view without display:none (many bots skip
   hidden fields); also unfocusable via tabindex=-1 */
.form-field--trap {
  position: absolute !important;
  left: -9999px !important;
  width: 1px; height: 1px;
  overflow: hidden;
}
.form-captcha { margin-top: 1.4rem; }
.form-captcha:empty { margin-top: 0; }
.form-captcha.is-missing { outline: 1px solid #b3554e; outline-offset: 4px; }
.form-error {
  display: none;
  border: 1px solid #b3554e;
  background: #fbf3f2;
  color: #7c3a35;
  padding: 1.4rem 1.6rem;
  font-size: .95rem;
  margin-top: 1.4rem;
}
.form-error.is-visible { display: block; }
.form-success {
  display: none;
  border: 1px solid var(--gold);
  background: var(--sand);
  color: var(--indigo);
  padding: 1.4rem 1.6rem;
  font-size: .95rem;
  margin-top: 1.4rem;
}
.form-success.is-visible { display: block; }

@media (max-width: 620px) {
  .form-grid { grid-template-columns: 1fr; }
}

/* ---------- CTA band ---------- */
.cta-band { text-align: center; }
.cta-band h2 { font-family: var(--display); font-weight: 400; letter-spacing: .045em; }
.cta-band .btn { margin-top: 1.4rem; }

/* ---------- Footer ---------- */
.site-footer {
  background: var(--indigo-deep);
  color: rgba(255, 255, 255, .72);
  padding: clamp(3.5rem, 7vw, 5.5rem) 0 2rem;
  font-size: .88rem;
}
.footer-grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr;
  gap: 3rem;
  padding-bottom: 3rem;
  border-bottom: 1px solid rgba(255, 255, 255, .12);
}
@media (max-width: 720px) {
  .footer-grid { grid-template-columns: 1fr; gap: 2.2rem; }
}
/* Footer wordmark stacks INDIGO over INTERIORS. Tight leading keeps the
   two lines reading as one block, and the flex row centres that block
   against the logo mark beside it. The trailing letter-spacing on each
   line would push the text visually left of centre, so it is trimmed back
   with a matching negative margin. */
.site-footer .brand__name {
  color: #fff;
  line-height: 1.08;
  margin-right: -.18em;
}
.site-footer .brand__mark { filter: brightness(0) invert(1); opacity: .9; }
.site-footer h4 {
  color: var(--gold);
  font-family: var(--sans);
  font-size: .7rem;
  font-weight: 500;
  letter-spacing: .3em;
  text-transform: uppercase;
  margin-bottom: 1.1rem;
}
.site-footer ul { list-style: none; margin: 0; padding: 0; }
.site-footer li { margin-bottom: .55em; }
.site-footer a { color: rgba(255, 255, 255, .72); }
.site-footer a:hover { color: var(--gold); }
.footer-tagline {
  font-family: var(--serif-italic);
  font-style: italic;
  /* light-on-indigo, same reasoning as the testimonials */
  font-weight: 500;
  font-size: 1.08rem;
  color: rgba(255, 255, 255, .85);
  margin-top: 1.2rem;
  max-width: 26em;
}
.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 1rem;
  padding-top: 1.8rem;
  font-size: .74rem;
  letter-spacing: .06em;
  color: rgba(255, 255, 255, .45);
}

/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
  /* frozen border-light would show as a static smear — hide it */
  .testimonial::before { display: none; }
  [data-reveal], html.reveal-active .fade-on-reveal,
  html.reveal-active .site-header {
    opacity: 1;
    transform: none;
  }
}
