/* =========================================================
   William Strutin — NYC commercial site
   Design intent: dark gallery canvas. The photographs
   carry all the colour; the interface stays out of the way.
   ========================================================= */

:root {
  /* Dark gallery canvas. Photographs sit on near-black so saturated
     fashion work pushes forward, the way prints read on a gallery wall.
     "ink" is now the light foreground; "paper" is the dark ground. */
  --ink:        #F2EFEA;   /* primary text, warm off-white */
  --ink-soft:   #B3ADA5;   /* body copy */
  --muted:      #7C756C;   /* labels, tertiary */
  --line:       #2C2825;   /* hairlines and borders */
  --paper:      #0B0A09;   /* page ground */
  --paper-warm: #141210;   /* lifted panel sections */
  --paper-deep: #000000;   /* deepest, for full-contrast bands */

  --sans: "Helvetica Neue", Helvetica, Arial, sans-serif;
  --display: "Helvetica Neue", Helvetica, Arial, sans-serif; /* headline face — same family as body, distinguished by weight & tracking, not by switching fonts */

  --gut: clamp(20px, 5vw, 72px);
  --stack: clamp(64px, 9vw, 140px);

  --label: 400 11px/1 var(--sans);
  --track: 0.18em;
}

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* The canvas must never be bare, or the browser paints its own default
     white for a frame before the site background appears. */
  background-color: var(--paper);
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.65;
  font-weight: 300;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, video { max-width: 100%; display: block; }
a { color: inherit; }

h1, h2, h3, h4 { margin: 0; font-weight: 400; line-height: 1.08; letter-spacing: -0.02em; }

/* ---------- shared atoms ---------- */

.wrap { max-width: 1360px; margin: 0 auto; padding-inline: var(--gut); }
.wrap--narrow { max-width: 780px; }
.wrap--mid { max-width: 1080px; }

.label {
  font: var(--label);
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 22px;
}

.lede {
  font-size: clamp(17px, 1.7vw, 21px);
  line-height: 1.55;
  color: var(--ink-soft);
  max-width: 62ch;
}

.rule { height: 1px; background: var(--line); border: 0; margin: 0; }

.section { padding-block: var(--stack); }
.section--warm { background: var(--paper-warm); }
/* On a dark site this is no longer "the dark section" — it is the
   deepest one, used for full-contrast CTA bands. */
.section--dark { background: var(--paper-deep); color: var(--ink); }
.section--dark .label { color: var(--muted); }
.section--dark .lede { color: var(--ink-soft); }

/* ---------- buttons ---------- */

.btn {
  display: inline-block;
  /* 600, not 400. These are 11px uppercase with wide letter-spacing, and
     the filled ones put dark text on a near-white ground where 400 reads
     thin and washed out. 600 is also the first step that changes anything
     in this font stack: Helvetica and Arial carry no medium weight, so 500
     silently falls back to 400 and looks identical.

     Applied to every button rather than only the filled ones so the weight
     never changes on hover, when a ghost button inverts to filled. A
     weight change there would reflow the label under the cursor. */
  font: 600 11px/1 var(--sans);
  letter-spacing: var(--track);
  text-transform: uppercase;
  text-decoration: none;
  text-align: center;
  padding: 17px 34px;
  min-width: 254px;
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--paper);
  cursor: pointer;
  transition: background .25s ease, color .25s ease, border-color .25s ease;
}
.btn:hover { background: transparent; color: var(--ink); }

.btn--ghost { background: transparent; color: var(--ink); }
.btn--ghost:hover { background: var(--ink); color: var(--paper); }

.btn--light { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.btn--light:hover { background: transparent; color: var(--ink); }

/* On a phone every full-size button should be the same length and sit in
   the middle, wherever it happens to live. Two were not:

   The quote panel is a grid, and a grid item stretches by default, so
   Request a Quote came out 292px against everyone else's 254.
   The form's submit is an inline-block in a plain block, so it sat at its
   natural width hard against the left edge.

   min() rather than a flat 254px so neither can overflow its container on
   a narrow phone. */
@media (max-width: 760px) {
  /* min-width has to be released as well as width set. The base .btn has a
     254px minimum, which on a very narrow phone is wider than the quote
     panel's inner width — the button then overflowed its own container and
     could not centre inside it. */
  .quote > .btn {
    justify-self: center;
    width: min(254px, 100%);
    min-width: 0;
  }
  form .btn {
    display: block;
    width: min(254px, 100%);
    min-width: 0;
    margin-inline: auto;
  }
}

/* The same line-and-chevron mark used on the carousel controls, reused as
   the site's link affordance. Drawn in CSS off currentColor so it inherits
   whatever colour the link sits in, and needs no markup on any page. */
.tlink {
  position: relative;
  display: inline-block;
  font: 400 11px/1 var(--sans);
  letter-spacing: var(--track);
  text-transform: uppercase;
  text-decoration: none;
  padding-right: 66px;
  line-height: 1.2;
  transition: opacity .25s ease;
}
.tlink::before {
  content: "";
  position: absolute; right: 0; top: 50%;
  width: 46px; height: 1px;
  background: currentColor;
}
.tlink::after {
  content: "";
  position: absolute; right: 0; top: calc(50% - 4px);
  width: 7px; height: 7px;
  border-top: 1px solid currentColor;
  border-right: 1px solid currentColor;
  transform: rotate(45deg);
}
.tlink:hover { opacity: .55; }

/* ---------- header ---------- */

.hdr {
  position: sticky; top: 0; z-index: 60;
  background: rgba(11,10,9,.92);
  backdrop-filter: saturate(160%) blur(14px);
  border-bottom: 1px solid var(--line);
}
.hdr__in {
  max-width: 1360px; margin: 0 auto;
  padding: 0 var(--gut);
  height: 74px;
  display: flex; align-items: center; justify-content: space-between; gap: 24px;
}
.brand {
  font-family: var(--display);
  font-weight: 700;
  letter-spacing: -0.02em;
  font-size: 22px;
  text-decoration: none;
  white-space: nowrap;
}
/* Sentence case, matching the landing page. */
.brand span {
  display: block;
  font: 400 12px/1 var(--sans);
  letter-spacing: .01em;
  color: var(--muted);
  margin-top: 4px;
}

.nav { display: flex; align-items: center; gap: clamp(22px, 2.6vw, 40px); }
.nav a {
  font: 400 15px/1 var(--sans);
  letter-spacing: .01em;
  text-decoration: none;
  color: var(--ink-soft);
  white-space: nowrap;
  padding-bottom: 3px;
  border-bottom: 1px solid transparent;
  transition: color .2s ease, border-color .2s ease;
}
/* :not(.btn) matters — Book a Shoot is also a nav link, and this rule would
   otherwise outrank the button's own colour, painting light text on the
   light button and making it invisible on the contact page itself. */
.nav a:not(.btn):hover,
.nav a:not(.btn)[aria-current="page"],
.nav a:not(.btn).is-current { color: var(--ink); border-color: var(--ink); }
.nav .btn { padding: 13px 24px; min-width: 0; border-bottom-color: var(--ink); color: var(--paper); }
.nav .btn:hover { color: var(--ink); }

.burger {
  display: none; width: 42px; height: 42px; margin-right: -10px;
  background: none; border: 0; cursor: pointer; padding: 0;
}
.burger i { display: block; width: 21px; height: 1px; background: var(--ink); margin: 5px auto; transition: transform .3s ease, opacity .3s ease; }
.burger[aria-expanded="true"] i:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.burger[aria-expanded="true"] i:nth-child(2) { opacity: 0; }
.burger[aria-expanded="true"] i:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

/* Breakpoint is 1040px, not 900px: between roughly 900 and 1040 the five
   desktop nav items technically fit but sit too cramped to read. Keep this
   number in sync with the resize handler in site.js. */
@media (max-width: 1040px) {
  .burger { display: block; }
  .nav {
    position: fixed; inset: 74px 0 auto 0;
    background: var(--paper);
    border-bottom: 1px solid var(--line);
    flex-direction: column; align-items: flex-start; gap: 0;
    padding: 12px var(--gut) 32px;
    transform: translateY(-12px); opacity: 0; pointer-events: none;
    transition: opacity .25s ease, transform .25s ease;
    max-height: calc(100dvh - 74px); overflow-y: auto;
  }
  .nav[data-open="true"] { opacity: 1; transform: none; pointer-events: auto; }
  .nav a { width: 100%; padding: 17px 0; border-bottom: 1px solid var(--line); font-size: 12px; }
  .nav .btn { margin-top: 20px; width: 100%; text-align: center; padding: 17px 24px; }
}

/* ---------- hero ---------- */

.hero { position: relative; background: var(--paper-deep); }
.hero__media { position: relative; height: clamp(460px, 78vh, 780px); overflow: hidden; }
.hero__media img, .hero__media video { width: 100%; height: 100%; object-fit: cover; }
/* The ramp gets heavy from ~55% down because the headline sits there and
   fashion frames are often light in the middle. Without it, white type over
   pale fabric or skin drops below readable contrast. */
/* Two layers: the horizontal one darkens the left side, where the
   headline sits, and fades to neutral on the right, where the desktop
   crop shifts the model's face — so the photo stays visible without
   fighting the text. The vertical one keeps the bottom readable
   regardless of what's behind it. */
.hero__media::after {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(90deg,
      rgba(0,0,0,.60) 0%,
      rgba(0,0,0,.28) 40%,
      rgba(0,0,0,0) 72%),
    linear-gradient(180deg,
      rgba(0,0,0,.30) 0%,
      rgba(0,0,0,.10) 28%,
      rgba(0,0,0,.46) 58%,
      rgba(0,0,0,.84) 100%);
}
/* Always white, never themed: this text sits ON a photograph, so it has to
   contrast with the image, not with the page ground. */
.hero__copy {
  position: absolute; inset: auto 0 0 0; z-index: 2;
  padding-bottom: clamp(38px, 6vw, 74px);
  color: #fff;
}
.hero__copy .label { color: rgba(255,255,255,.72); }
.hero h1 {
  font-family: var(--display); font-weight: 700; letter-spacing: -0.03em;
  font-size: clamp(38px, 6.4vw, 82px);
  line-height: 1.02;
  max-width: 17ch;
  text-wrap: balance;
}
.hero p {
  margin: 22px 0 0;
  max-width: 54ch;
  font-size: clamp(15px, 1.5vw, 18px);
  color: rgba(255,255,255,.86);
  line-height: 1.6;
}
.hero__cta { margin-top: 34px; display: flex; flex-wrap: wrap; gap: 14px; }

/* compact hero for interior pages */
.phero { padding-block: clamp(60px, 8vw, 118px) clamp(40px, 5vw, 70px); border-bottom: 1px solid var(--line); }
.phero h1 {
  font-family: var(--display); font-weight: 700; letter-spacing: -0.03em;
  font-size: clamp(34px, 5.4vw, 68px);
  max-width: 20ch;
  text-wrap: balance;
}
.phero .lede { margin-top: 26px; }
.phero__cta { margin-top: 36px; display: flex; flex-wrap: wrap; gap: 14px; }

/* ---------- section headings ---------- */

.shead { margin-bottom: clamp(38px, 4.5vw, 62px); }
.shead h2 {
  font-family: var(--display); font-weight: 700; letter-spacing: -0.03em;
  font-size: clamp(30px, 4vw, 50px);
  max-width: 22ch;
  text-wrap: balance;
}
.shead .lede { margin-top: 20px; }

/* ---------- service cards ---------- */

.cards { display: grid; gap: 1px; background: var(--line); border-block: 1px solid var(--line); }
@media (min-width: 640px) { .cards { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1040px) { .cards { grid-template-columns: repeat(4, 1fr); } }

.card {
  background: var(--paper);
  padding: clamp(30px, 3.2vw, 46px);
  display: flex; flex-direction: column;
  text-decoration: none;
  transition: background .3s ease;
}
.card:hover { background: var(--paper-warm); }
.card h3 { font-size: clamp(20px, 2vw, 26px); margin-bottom: 14px; }
.card p { color: var(--ink-soft); font-size: 15px; margin: 0 0 26px; flex: 1; }
.card .tlink { align-self: flex-start; }
.card__num { font: var(--label); letter-spacing: var(--track); color: var(--muted); margin-bottom: 26px; }

/* ---------- discipline list (fashion/beauty page) ---------- */

.disc { border-top: 1px solid var(--line); }
.disc__row {
  display: grid; gap: clamp(14px, 2vw, 48px);
  padding-block: clamp(34px, 4vw, 54px);
  border-bottom: 1px solid var(--line);
}
@media (min-width: 900px) { .disc__row { grid-template-columns: 4fr 7fr; align-items: start; } }
.disc__row h3 { font-size: clamp(22px, 2.4vw, 32px); font-family: var(--display); font-weight: 700; letter-spacing: -0.03em; }
.disc__row p { margin: 0; color: var(--ink-soft); max-width: 62ch; }
.disc__row p + p { margin-top: 16px; }
.disc__tags { margin: 20px 0 0; padding: 0; list-style: none; display: flex; flex-wrap: wrap; gap: 8px; }
.disc__tags li {
  font: var(--label); letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-soft);
  border: 1px solid var(--line); border-radius: 999px;
  padding: 8px 14px;
}

/* ---------- packages ---------- */

.pkgs { display: grid; gap: 24px; }
@media (min-width: 900px)  { .pkgs { grid-template-columns: repeat(3, 1fr); } }

/* Every card reserves the flag row whether or not it has a flag, so the
   three headings sit on one baseline instead of the featured card
   dropping ~34px below its neighbours. */
.pkg {
  position: relative;
  border: 1px solid var(--line);
  padding: clamp(28px, 3vw, 42px);
  padding-top: calc(clamp(28px, 3vw, 42px) + 30px);
  display: flex; flex-direction: column;
  background: var(--paper);
}
.pkg--feat { border-color: var(--ink); }
.pkg__flag {
  position: absolute;
  top: clamp(28px, 3vw, 42px);
  left: clamp(28px, 3vw, 42px);
  margin: 0;
  font: var(--label); letter-spacing: var(--track); text-transform: uppercase;
  color: var(--ink);
}
.pkg h3 { font-family: var(--display); font-weight: 700; letter-spacing: -0.03em; font-size: clamp(24px, 2.4vw, 31px); }
.pkg__for { color: var(--muted); font-size: 14px; margin: 10px 0 26px; }
.pkg__price { font-family: var(--display); font-weight: 700; letter-spacing: -0.03em; font-size: 34px; margin: 0 0 26px; }
.pkg ul { list-style: none; margin: 0 0 32px; padding: 0; flex: 1; }
.pkg li {
  padding: 13px 0 13px 22px;
  border-top: 1px solid var(--line);
  font-size: 15px; color: var(--ink-soft);
  position: relative;
}
.pkg li::before { content: ""; position: absolute; left: 0; top: 21px; width: 9px; height: 1px; background: var(--muted); }
/* The 254px minimum on .btn is sized for a standalone CTA sitting in open
   space. Inside a card it is wider than the card's own content box at every
   width from the 3-column breakpoint up to about 1200px, which pushed the
   whole grid past the viewport and put a horizontal scrollbar on the page.
   In a card the button should simply fill the column. */
.pkg .btn { text-align: center; min-width: 0; width: 100%; }

/* ---------- horizontal rate rows ---------- */
/* The alternative to a package card, for options that are shorter and
   cheaper than a full session. Laid out as wide rows on purpose: as
   vertical cards they sat in a row of four or five equal boxes and read
   as more session tiers, which they are not. */

.rates { border-top: 1px solid var(--line); }
.rate {
  display: grid; gap: clamp(16px, 2vw, 34px);
  padding-block: clamp(28px, 3.4vw, 44px);
  border-bottom: 1px solid var(--line);
}
/* The last column is a fixed width, not `auto`. Each .rate is its own grid,
   so an auto column is sized by that row's own content: a row reading
   $1,050 got a wider price column than one reading $750, which pushed its
   price and its tags to a different x. Every row shares the same geometry
   only if the column does not depend on the row. */
@media (min-width: 940px) {
  .rate {
    /* 300px = widest price (98px) + gap (28px) + button (150px), plus room
       for a five-figure price later. Below this the button wraps under the
       price on the dearer rows only, which breaks the button alignment. */
    grid-template-columns: minmax(0, 5fr) minmax(0, 4fr) 300px;
    align-items: center;
    gap: clamp(24px, 3vw, 52px);
  }
}
.rate h3 {
  font-family: var(--display); font-weight: 700; letter-spacing: -0.03em;
  font-size: clamp(24px, 2.4vw, 31px);
}
.rate__for { margin: 9px 0 0; color: var(--muted); font-size: 14px; max-width: 34ch; }

/* Plain bullet list, using the same short dash mark the package cards
   used. Replaced the rounded pill tags: as pills the spec read as a row
   of decorative chips rather than as a list of what you get, and a long
   item wrapped inside its own bubble. */
.bullets { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.bullets li {
  position: relative; padding-left: 20px;
  font-size: 15px; color: var(--ink-soft); line-height: 1.5;
}
.bullets li::before {
  content: ""; position: absolute; left: 0; top: 11px;
  width: 9px; height: 1px; background: var(--muted);
}
@media (min-width: 760px) {
  .bullets--3 { grid-template-columns: repeat(3, 1fr); column-gap: clamp(20px, 2.5vw, 40px); }
}

/* space-between at every width, not just on desktop. Packed to the left,
   the button starts wherever that row's price happens to end, so a $250
   row and a $1,050 row put their buttons 26px apart on mobile. */
.rate__buy {
  display: flex; align-items: center; justify-content: space-between;
  gap: clamp(18px, 2.4vw, 32px); flex-wrap: wrap;
}
.rate__price {
  font-family: var(--display); font-weight: 700; letter-spacing: -0.03em;
  font-size: 34px; margin: 0; white-space: nowrap;
}
/* The full 254px button is built for a card column; in a row it crowds
   the price and forces an early wrap. */
.rate__buy .btn { min-width: 150px; }

/* ---------- quote panel ---------- */
/* Carries the same border weight as the featured package card so the
   unpriced commercial work reads as a peer of the priced sections
   rather than as a footnote under them. */

.quote {
  border: 1px solid var(--ink);
  padding: clamp(28px, 3.6vw, 52px);
  display: grid; gap: clamp(26px, 3vw, 48px);
}
@media (min-width: 900px) { .quote { grid-template-columns: 1fr auto; align-items: center; } }

/* ---------- add-ons ---------- */

.addons { display: grid; gap: 0; border-top: 1px solid var(--line); }
@media (min-width: 760px)  { .addons { grid-template-columns: 1fr 1fr; column-gap: clamp(30px, 4vw, 70px); } }
/* Three add-ons, so at full container width they get a column each. In two
   columns each row stretched to ~580px, leaving ~370px of dead space
   between the name and its price. */
@media (min-width: 1100px) { .addons { grid-template-columns: repeat(3, 1fr); } }
.addon {
  display: flex; justify-content: space-between; align-items: baseline; gap: 20px;
  padding: 20px 0; border-bottom: 1px solid var(--line);
}
.addon span:first-child { color: var(--ink); }
.addon span:last-child { color: var(--muted); font-size: 14px; white-space: nowrap; }

/* ---------- work grid ---------- */

.grid { display: grid; gap: clamp(10px, 1.2vw, 18px); grid-template-columns: repeat(2, 1fr); }
@media (min-width: 860px) { .grid { grid-template-columns: repeat(4, 1fr); } }
.grid figure { margin: 0; position: relative; overflow: hidden; background: var(--paper-warm); }
.grid img { width: 100%; height: 100%; object-fit: cover; aspect-ratio: 4 / 5; transition: transform .7s cubic-bezier(.2,.6,.2,1); }
.grid figure:hover img { transform: scale(1.035); }
.grid .tall { grid-row: span 2; }
.grid .tall img { aspect-ratio: 4 / 10; }
@media (max-width: 859px) { .grid .tall { grid-row: auto; } .grid .tall img { aspect-ratio: 4 / 5; } }

/* ---------- steps ---------- */

.steps { display: grid; gap: clamp(28px, 3vw, 44px); counter-reset: s; }
@media (min-width: 820px) { .steps { grid-template-columns: repeat(3, 1fr); } }
.step { border-top: 1px solid var(--ink); padding-top: 22px; }
.step::before {
  counter-increment: s; content: counter(s, decimal-leading-zero);
  display: block; font: var(--label); letter-spacing: var(--track); color: var(--muted); margin-bottom: 18px;
}
.step h3 { font-size: 20px; margin-bottom: 12px; }
.step p { margin: 0; color: var(--ink-soft); font-size: 15px; }

/* ---------- faq ---------- */

.faq { border-top: 1px solid var(--line); }
.faq details { border-bottom: 1px solid var(--line); }
.faq summary {
  cursor: pointer; list-style: none;
  padding: 26px 52px 26px 0;
  font-size: clamp(16px, 1.7vw, 19px);
  position: relative;
  transition: color .25s ease;
}
.faq summary::-webkit-details-marker { display: none; }

/* The marker is two hairlines stacked on the same spot, one turned upright.
   Together they read as a plus; opening turns the upright one flat so the
   two overlap into a minus. A swapped "+"/"-" text glyph cannot animate,
   and the two characters have different widths so the mark also shifted
   sideways as it changed. */
.faq summary::before,
.faq summary::after {
  content: "";
  position: absolute; right: 4px; top: 50%;
  width: 15px; height: 1px;
  background: var(--muted);
  transition: transform .34s cubic-bezier(.4, 0, .2, 1), background-color .25s ease;
}
.faq summary::after { transform: rotate(90deg); }
.faq details[open] summary::after { transform: rotate(0deg); }

.faq summary:hover { color: var(--ink); }
.faq summary:hover::before,
.faq summary:hover::after,
.faq details[open] summary::before,
.faq details[open] summary::after { background: var(--ink); }

/* Height is animated from faq.js. This only has to clip the answer while
   it is mid-slide. */
.faq__body { overflow: hidden; }
.faq__body p { margin: 0; padding-bottom: 28px; color: var(--ink-soft); max-width: 70ch; }

/* ---------- credits strip ---------- */

.credits { display: flex; flex-wrap: wrap; gap: clamp(24px, 4vw, 58px); align-items: center; }
.credits span {
  font: var(--label); letter-spacing: var(--track); text-transform: uppercase; color: var(--ink-soft);
  font-size: 13px;
}

/* ---------- cta band ---------- */

.band { text-align: center; }
.band h2 { font-family: var(--display); font-weight: 700; letter-spacing: -0.03em; font-size: clamp(30px, 4.4vw, 56px); max-width: 18ch; margin-inline: auto; text-wrap: balance; }
.band p { margin: 22px auto 0; max-width: 52ch; opacity: .8; }
.band .btn { margin-top: 38px; }

/* ---------- contact ---------- */

.cgrid { display: grid; gap: clamp(34px, 5vw, 78px); }
@media (min-width: 900px) { .cgrid { grid-template-columns: 5fr 6fr; } }
.field { display: block; margin-bottom: 26px; }

/* Labels are white and full size. They were 11px uppercase in the muted
   grey, which is fine for a section eyebrow but unreadable as a form label. */
.field span {
  display: block;
  font: 400 14px/1.3 var(--sans);
  letter-spacing: .01em;
  color: var(--ink);
  margin-bottom: 9px;
}
.field input, .field select, .field textarea {
  width: 100%;
  font-family: var(--sans); font-size: 16px; font-weight: 400;
  color: var(--ink);
  background-color: transparent;
  border: 0; border-bottom: 1px solid var(--line);
  padding: 12px 0; border-radius: 0;
}
.field textarea { resize: vertical; min-height: 120px; max-height: 240px; }
.field input:focus, .field select:focus, .field textarea:focus { outline: 0; border-bottom-color: var(--ink); }
.field__hint {
  display: block; margin-top: 8px;
  font: var(--label); letter-spacing: normal; text-transform: none;
  color: var(--muted); font-size: 12px; text-align: right;
}
.field__hint.is-near-limit { color: var(--ink); }
.dl { margin: 0; }
.dl dt { font: var(--label); letter-spacing: var(--track); text-transform: uppercase; color: var(--muted); margin-top: 30px; }
.dl dd { margin: 8px 0 0; font-size: 17px; }
.dl dd a { text-decoration: none; border-bottom: 1px solid var(--line); }
.dl dd a:hover { border-color: var(--ink); }

/* ---------- footer ---------- */

.ftr { background: var(--paper-deep); color: var(--ink); padding-block: clamp(56px, 7vw, 92px) 34px; border-top: 1px solid var(--line); }
.ftr__grid { display: grid; gap: clamp(32px, 4vw, 60px); }
@media (min-width: 820px) { .ftr__grid { grid-template-columns: 2fr 1fr 1fr; } }
.ftr .brand { color: var(--ink); }
.ftr h4 { font: var(--label); letter-spacing: var(--track); text-transform: uppercase; color: #8F8880; margin-bottom: 20px; }
.ftr ul { list-style: none; margin: 0; padding: 0; }
.ftr li { margin-bottom: 13px; }
.ftr a { color: var(--ink); text-decoration: none; font-size: 15px; opacity: .82; transition: opacity .2s ease; }
.ftr a:hover { opacity: 1; }
.ftr__area { margin: 22px 0 0; color: #A49D95; font-size: 14px; max-width: 46ch; line-height: 1.6; }
.ftr__base {
  margin-top: clamp(44px, 6vw, 72px); padding-top: 26px; border-top: 1px solid rgba(255,255,255,.14);
  display: flex; flex-wrap: wrap; gap: 14px; justify-content: space-between;
  font-size: 12px; color: #8F8880;
}

/* =========================================================
   PHOTO-FORWARD COMPONENTS
   The site sells photography, so photographs carry the layout
   rather than decorating it. Every page opens on an image,
   full-bleed bands break up long text, and images stay pinned
   beside copy while it scrolls.
   ========================================================= */

/* ---------- interior image hero ---------- */
/* Shorter than the homepage hero so interior pages still show
   the start of real content above the fold. */
.hero--short .hero__media { height: clamp(380px, 62vh, 580px); }

/* Phones only, and the reason is the address bar. It retracts the moment
   you scroll, which grows the viewport; `vh` tracks that, so the banner
   grew mid-scroll and the photo re-cropped inside it — the image looking
   like it jumped forward. `svh` is measured with the browser chrome
   showing and never changes, so the banner holds still through the whole
   scroll.

   The percentage is higher than 62 because svh is the smaller measure, so
   it takes more of it to land on the same height as before. Left off
   desktop deliberately: there is no retracting chrome there, and svh
   would simply make the banner 64px taller for no reason. Browsers
   without svh keep the vh rule above. */
@media (max-width: 760px) {
  .hero--short .hero__media { height: clamp(380px, 70svh, 580px); }
}

/* Banner crops are very wide (1920x580). A phone shows a tall slice cut
   from the middle of that, which loses anyone standing off to one side.
   Same control as the homepage carousel's slide--right, and it runs the
   same way round: LOWER moves the photo right, HIGHER moves it left. */
@media (max-width: 760px) {
  .hero__media--right img { object-position: 75% center; }
}

/* ---------- full-bleed image band ---------- */
/* Edge to edge, no wrap, no padding. Used between text sections
   so the page never runs more than roughly a screen without a
   photograph. */
.imgband {
  position: relative;
  height: clamp(300px, 58vh, 600px);
  overflow: hidden;
  background: var(--paper-deep);
}
.imgband img { width: 100%; height: 100%; object-fit: cover; }
.imgband--tall { height: clamp(380px, 78vh, 760px); }

/* Same address-bar fix as the hero, for the full-bleed image bands. */
@media (max-width: 760px) {
  .imgband      { height: clamp(300px, 64svh, 600px); }
  .imgband--tall { height: clamp(380px, 86svh, 760px); }
}

/* Optional caption sitting on the band */
.imgband__cap {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: clamp(20px, 4vw, 44px) var(--gut);
  background: linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,.72) 100%);
  color: #fff; /* sits on a photo — must not follow the page theme */
}
.imgband__cap p {
  margin: 0; max-width: 46ch;
  font: 400 11px/1.6 var(--sans);
  letter-spacing: var(--track); text-transform: uppercase;
  color: rgba(255,255,255,.86);
}

/* ---------- sticky split: image pinned, text scrolls ---------- */
.split { display: grid; gap: clamp(26px, 4vw, 60px); align-items: start; }
@media (min-width: 900px) {
  .split { grid-template-columns: 1fr 1fr; }
  .split__media { position: sticky; top: 104px; }
  /* Media on the right instead of the left */
  .split--flip .split__media { order: 2; }
}
.split__media img {
  width: 100%; aspect-ratio: 3 / 4; object-fit: cover;
  background: var(--paper-warm);
}
.split__body > * + * { margin-top: clamp(26px, 3vw, 40px); }

/* ---------- asymmetric two-up composition ---------- */
.duo { display: grid; gap: clamp(10px, 1.4vw, 20px); }
@media (min-width: 820px) { .duo { grid-template-columns: 1.4fr 1fr; } }
.duo figure { margin: 0; overflow: hidden; background: var(--paper-warm); }
.duo img {
  width: 100%; height: 100%; object-fit: cover; aspect-ratio: 4 / 5;
  transition: transform .7s cubic-bezier(.2,.6,.2,1);
}
.duo figure:hover img { transform: scale(1.03); }
@media (min-width: 820px) { .duo .duo__tall img { aspect-ratio: 3 / 4; } }

/* ---------- image-backed CTA band ---------- */
.band--img { position: relative; overflow: hidden; background: var(--paper-deep); }
.band--img > img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; opacity: .42;
}
.band--img .wrap { position: relative; z-index: 1; }

/* ---------- a11y ---------- */

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--ink); color: var(--paper); padding: 14px 22px; font-size: 13px;
}
.skip:focus { left: 0; }

:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* ---------- dark-canvas form corrections ---------- */

/* Placeholders are hint text, so they sit below the white input text, but
   well above the old muted grey which was effectively unreadable. They are
   also cleared on focus by form.js, so they never fight with what you type. */
.field ::placeholder { color: #9C948B; opacity: 1; }

/* The select needs an explicit dark ground of its own — a transparent
   control inherits the OS light theme in some browsers and renders as
   near-white text on near-white. The popup options are drawn by the OS and
   need the same treatment separately. */
.field select {
  background-color: var(--paper);
  color: var(--ink);
  padding-right: 28px;
  cursor: pointer;
  /* Native arrow is unreliable on a dark control; draw our own chevron. */
  appearance: none; -webkit-appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--ink) 50%),
                    linear-gradient(135deg, var(--ink) 50%, transparent 50%);
  background-position: calc(100% - 13px) calc(50% + 1px),
                       calc(100% - 8px)  calc(50% + 1px);
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
.field select option,
.field select optgroup { background-color: #171412; color: var(--ink); }

/* Group headings inside the popup, so the list reads as two audiences
   rather than one long undifferentiated run of options. */
.field select optgroup {
  font-weight: 700;
  font-style: normal;
  color: #9C948B;
}

/* =========================================================
   BROKEN GRID SYSTEM
   Fashion work does not sit in tidy equal columns. Images take
   deliberately uneven spans and get pushed off the baseline so
   the eye travels diagonally instead of scanning rows.
   ========================================================= */

/* Full bleed: escapes .wrap and touches both screen edges. */
.fb { width: 100vw; margin-left: calc(50% - 50vw); }

.mosaic {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: clamp(6px, 0.9vw, 14px);
  align-items: start;
}
.mosaic figure { margin: 0; overflow: hidden; background: var(--paper-warm); grid-column: span 6; }
.mosaic img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform .8s cubic-bezier(.2,.6,.2,1), filter .5s ease;
}
.mosaic figure:hover img { transform: scale(1.04); }

/* Span variants */
.mosaic .m-xl   { grid-column: span 8; }
.mosaic .m-lg   { grid-column: span 7; }
.mosaic .m-md   { grid-column: span 5; }
.mosaic .m-sm   { grid-column: span 4; }
.mosaic .m-xs   { grid-column: span 3; }
.mosaic .m-full { grid-column: 1 / -1; }

/* Aspect variants so heights never line up */
.a-tall  img { aspect-ratio: 2 / 3; }
.a-port  img { aspect-ratio: 4 / 5; }
.a-sq    img { aspect-ratio: 1 / 1; }
.a-wide  img { aspect-ratio: 16 / 10; }
.a-pano  img { aspect-ratio: 21 / 9; }

/* Break the baseline */
.m-drop { margin-top: clamp(30px, 7vw, 130px); }
.m-drop-sm { margin-top: clamp(16px, 3vw, 56px); }

@media (max-width: 760px) {
  .mosaic { grid-template-columns: repeat(6, 1fr); gap: 6px; }
  .mosaic .m-xl, .mosaic .m-lg, .mosaic .m-md { grid-column: span 6; }
  .mosaic .m-sm, .mosaic .m-xs { grid-column: span 3; }
  .m-drop, .m-drop-sm { margin-top: 0; }
}

/* ---------- oversized display type ---------- */

/* Runs to the edge, tight and enormous. The point is scale contrast:
   10px labels against 150px headlines with nothing in between. */
.mega {
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(44px, 11vw, 168px);
  line-height: .84;
  letter-spacing: -0.05em;
  margin: 0;
  text-wrap: balance;
}
.mega--tight { letter-spacing: -0.06em; }

/* Type sitting over a photograph, inverting against whatever is behind it. */
.mega--blend {
  mix-blend-mode: difference;
  color: #fff;
  position: relative;
  z-index: 2;
}

/* Pulls the following image up so the type overlaps it. */
.overlap { margin-bottom: clamp(-70px, -6vw, -20px); }

/* Tiny index marker, paired with the mega type. */
.idx {
  font: 400 11px/1 var(--sans);
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--muted);
  display: flex; gap: 14px; align-items: baseline;
  margin: 0 0 clamp(14px, 2vw, 26px);
}
.idx b { font-weight: 400; color: var(--ink); }

/* ---------- edge-to-edge marquee strip ---------- */
.strip { display: flex; gap: clamp(6px, .8vw, 12px); overflow-x: auto; scrollbar-width: none; }
.strip::-webkit-scrollbar { display: none; }
.strip figure { margin: 0; flex: 0 0 clamp(220px, 26vw, 420px); overflow: hidden; }
.strip img { width: 100%; aspect-ratio: 3 / 4; object-fit: cover; display: block; }

/* =========================================================
   PHOTO-FIRST OVERRIDES
   No scrim over the work, no copy competing with it. The
   photograph is the page; the interface gets out of the way.
   ========================================================= */

/* Kill the darkening overlay. The image is the point — it does not get
   dimmed so text can sit on top of it. */
.hero--clean .hero__media::after { display: none; }

/* Full viewport, edge to edge. */
.hero--full .hero__media { height: 100vh; height: 100dvh; }

/* Header floats over the image with no bar and no background. */
.hdr--over {
  position: absolute; top: 0; left: 0; right: 0;
  background: transparent;
  backdrop-filter: none;
  border-bottom: 0;
}
.hdr--over .brand,
.hdr--over .nav a { color: #fff; text-shadow: 0 1px 12px rgba(0,0,0,.55); }
.hdr--over .brand span { color: rgba(255,255,255,.65); }
.hdr--over .nav a:not(.btn) { color: rgba(255,255,255,.88); }
.hdr--over .nav a:not(.btn):hover { color: #fff; border-color: #fff; }
.hdr--over .nav .btn {
  background: transparent; color: #fff; border-color: rgba(255,255,255,.7);
  text-shadow: none;
}
.hdr--over .nav .btn:hover { background: #fff; color: #000; border-color: #fff; }
.hdr--over .burger i { background: #fff; }
@media (max-width: 1040px) {
  .hdr--over .nav { background: rgba(0,0,0,.94); }
  .hdr--over .nav a { text-shadow: none; }
}

/* Dense contact-sheet grid, edge to edge, hairline gaps. Static — no
   scroll or hover motion. */
.dense { display: grid; grid-template-columns: repeat(4, 1fr); gap: 2px; }
@media (max-width: 900px) { .dense { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 560px) { .dense { grid-template-columns: repeat(2, 1fr); } }
.dense figure { margin: 0; overflow: hidden; background: var(--paper-warm); }
.dense img { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; display: block; }

/* =========================================================
   GRID HOVER
   Pointer-driven only — nothing here reacts to scrolling, so the
   page is still while you read it and only responds when you
   reach for something. Hovering dims the grid and leaves the frame
   under the cursor lit, with the site's arrow mark fading in.
   ========================================================= */

.mosaic figure,
.duo figure,
.dense figure { position: relative; }

.mosaic figure img,
.duo figure img,
.dense figure img {
  transition: transform .75s cubic-bezier(.2,.6,.2,1), filter .45s ease;
}

/* No arrow mark on image grids. The arrow means "go somewhere" — it belongs
   on links, the carousel and the lightbox controls. These images are not
   links, so an arrow on them points at nothing.

   Only on real pointers; touch devices have no hover state to leave. */
@media (hover: hover) and (pointer: fine) {
  .mosaic figure:hover img,
  .duo figure:hover img,
  .dense figure:hover img { transform: scale(1.03); }
}

/* Respect the system setting — no scale, no fade, nothing. */
@media (prefers-reduced-motion: reduce) {
  .mosaic figure img, .duo figure img, .dense figure img,
  .mosaic figure, .duo figure, .dense figure { transition: none !important; }
  .mosaic figure:hover img,
  .duo figure:hover img,
  .dense figure:hover img { transform: none !important; }
}

/* =========================================================
   HOMEPAGE: ONE SCREEN, NO SCROLL, CORNER CHROME
   The page is one photograph. Type sits lower-left and uses
   difference blending so it inverts against whatever is behind
   it. No scrim, no shadow, no dimming of the image.
   ========================================================= */

body.oneScreen { overflow: hidden; height: 100dvh; }
body.oneScreen .hero__media { height: 100dvh; }

.chrome {
  position: fixed;
  left: clamp(20px, 3.4vw, 52px);
  bottom: clamp(20px, 3.4vw, 46px);
  z-index: 60;
  mix-blend-mode: difference;
  color: #fff;
}
.chrome__name {
  display: block; text-decoration: none;
  font-family: var(--display); font-weight: 700;
  font-size: clamp(21px, 2.5vw, 36px);
  letter-spacing: -0.035em; line-height: 1;
  margin: 0 0 6px;
}
.chrome__loc {
  font: var(--label); letter-spacing: var(--track); text-transform: uppercase;
  color: #fff; opacity: .62;
  margin: 0 0 clamp(22px, 3.2vw, 38px);
}
.chrome__nav { display: flex; flex-direction: column; gap: 9px; }
.chrome__nav a {
  font: 400 12px/1 var(--sans);
  letter-spacing: .16em; text-transform: uppercase;
  text-decoration: none; color: #fff;
  transition: opacity .2s ease;
}
.chrome__nav a:hover { opacity: .5; }

.chrome__nav a.is-book {
  margin-top: 12px; padding-top: 12px;
  border-top: 1px solid rgba(255,255,255,.45);
  align-self: flex-start;
}

@media (max-width: 700px) {
  .chrome__name { font-size: 19px; }
  .chrome__nav a { font-size: 11px; }
}

/* ---------- grouped corner nav ---------- */
/* Three audiences, three columns. Talent, brands and studio each get
   their own route in rather than sharing one flat list. */
.chrome__groups { display: flex; gap: clamp(18px, 3vw, 56px); flex-wrap: wrap; }
.chrome__col { display: flex; flex-direction: column; gap: 8px; }
.chrome__cat {
  font: var(--label); letter-spacing: var(--track); text-transform: uppercase;
  color: #fff; opacity: .45; margin: 0 0 3px;
}
.chrome__col a {
  font: 400 12px/1 var(--sans);
  letter-spacing: .14em; text-transform: uppercase;
  text-decoration: none; color: #fff;
  transition: opacity .2s ease;
}
.chrome__col a:hover { opacity: .5; }

/* Credit line: the part that says this is not a first website. */
.chrome__credit {
  font: 400 11px/1.6 var(--sans);
  letter-spacing: .1em; text-transform: uppercase;
  color: #fff; opacity: .5;
  margin: clamp(18px, 2.4vw, 28px) 0 0; max-width: 52ch;
}

@media (max-width: 700px) {
  .chrome { right: clamp(20px, 3.4vw, 52px); }
  .chrome__groups { gap: 16px; }
  .chrome__col a { font-size: 11px; }
  .chrome__credit { display: none; }
}

/* =========================================================
   UNIFIED HEADER
   Identical on every page: links left, name upper right.
   Nothing shifts position between pages — only the background
   changes (transparent over the landing photograph, solid
   elsewhere) and the name is absent on the landing page,
   where it is the headline instead.
   ========================================================= */

.topnav {
  position: fixed; top: 0; left: 0; right: 0; z-index: 60;
  display: flex; align-items: center; justify-content: space-between;
  padding: clamp(26px, 3.4vw, 46px) clamp(24px, 4vw, 60px);
  background: rgba(11,10,9,.9);
  backdrop-filter: saturate(160%) blur(12px);
  /* Clips the name while it is parked past the right edge; without this
     it widens the document and puts a horizontal scrollbar on the page. */
  overflow: hidden;
}

/* Used on the pages whose first section is a banner photo (About, Pricing,
   Book) so the nav sits above the photo instead of overlapping its top.
   Sticky rather than fixed: it still stays pinned once you scroll past it,
   but it also occupies real space at the top of the document, which pushes
   whatever comes after it — the banner — down by the nav's own rendered
   height. That height changes with viewport width and at the mobile
   breakpoint, so this pushes it down by exactly the right amount at every
   size without a hardcoded number to keep in sync.

   Left off the homepage on purpose — its hero is a full-screen photo with
   the nav floating over it by design — and off Gallery, which has no
   banner for the nav to cover. */
.topnav--push { position: sticky; }
.topnav__links { display: flex; gap: clamp(20px, 2.6vw, 40px); align-items: center; }

/* Instagram, last in the inline nav. Drawn as strokes off currentColor so
   it picks up the same white the nav links use and dims with them on
   hover, instead of being a coloured logo bolted onto a monochrome bar.
   The bottom padding and transparent border match .topnav a so it sits on
   the same optical line as the words. */
.topnav__ig {
  display: flex; align-items: center;
  padding-bottom: 6px;
  border-bottom: 1px solid transparent;
}
.topnav__ig svg {
  width: 19px; height: 19px;
  fill: none; stroke: currentColor; stroke-width: 1.9;
}
.topnav__ig svg .ig-dot { fill: currentColor; stroke: none; }
.topnav a {
  font: 400 15px/1 var(--sans);
  letter-spacing: .01em;
  color: #fff; text-decoration: none;
  padding-bottom: 6px;
  border-bottom: 1px solid transparent;
  white-space: nowrap;
  transition: border-color .25s ease, opacity .25s ease;
}
.topnav a:hover { opacity: .6; }
.topnav a.is-current { border-bottom-color: #fff; }

.topnav__brand {
  font-family: var(--display); font-weight: 700;
  font-size: 17px; letter-spacing: -0.02em;
  padding-bottom: 0; border-bottom: 0;
}

/* Deliberately over-qualified. The name is an <a> inside the bar, so the
   generic `.topnav a` rule above also matches it, and that rule is the
   more specific of the two — it was replacing this transition list with
   its own. Since that list happens to include opacity but not transform,
   the name faded in while snapping into position instead of sliding.
   Matching its specificity puts transform back under transition. */
.topnav .topnav__brand {
  /* Even easing, not a hard ease-out. cubic-bezier(.22,1,.36,1) put most
     of the travel into the first ~100ms, so the movement was over before
     the eye caught it and what registered was the fade. The fade is also
     slower than the slide now, so it cannot finish first and leave the
     last of the movement happening on an already-solid name. */
  transition: transform .6s cubic-bezier(.4, 0, .2, 1),
              opacity   .55s cubic-bezier(.4, 0, .2, 1);
}

/* Arriving from the homepage, which has no name in the bar: the inline
   script in <head> sets this pre-paint, so the name renders off to the
   right on the very first frame and brand.js drops the class once there
   is something worth watching it against.

   A transition rather than a keyframe animation on purpose: if animation
   is unavailable or frozen, removing the class still resolves to the
   correct final style, so the name simply appears rather than being left
   parked off-screen. */
.brand-in .topnav__brand {
  transform: translateX(calc(100% + 48px));
  opacity: 0;
}

/* The reverse trip, and it has to be built the other way round.

   Animating the name out on the page being left does not work: the tap
   navigates immediately, so the browser tears that page down a few frames
   in and the exit is cut off — it visibly starts and then vanishes. So
   the homepage plays it instead, on arrival. This stand-in sits exactly
   where the name would be, is shown from the first frame, and then slides
   out to the right and hands the slot to the progress ring.

   Absolutely positioned so it overlays the ring's place in the bar rather
   than becoming a third flex item and pushing the layout around. */
.topnav__exit {
  position: absolute;
  right: clamp(24px, 4vw, 60px);
  top: 50%;
  /* Must match what .topnav__brand actually renders as, which is not what
     its own rule says: the `.topnav a` shorthand above outranks it and
     sets 400 15px. Copying the winning values rather than the intended
     ones is what keeps the name the same size and weight across the
     handoff — otherwise it visibly changes at the moment of navigation. */
  font: 400 15px/1 var(--sans);
  letter-spacing: .01em;
  color: #fff;
  white-space: nowrap;
  pointer-events: none;

  /* Reproduces the real name's box, not just its type. `.topnav a` gives
     that link a 6px bottom padding and a 1px transparent bottom border
     (again outranking .topnav__brand's own zeroes), and it is centred
     inside a 30px min-height flex box. Those 7px of bottom spacing lift
     its glyphs 3.5px above the box centre. Without matching all of it the
     stand-in lines up by its box but its text sits 3.5px lower, so the
     name drops as it crosses onto the homepage. */
  min-height: 30px;
  display: flex;
  align-items: center;
  padding-bottom: 6px;
  border-bottom: 1px solid transparent;
  transform: translate(calc(100% + 48px), -50%);
  opacity: 0;
  visibility: hidden;
  transition: transform .6s cubic-bezier(.4, 0, .2, 1),
              opacity   .55s cubic-bezier(.4, 0, .2, 1),
              visibility 0s linear .6s;
}
.brand-exit .topnav__exit {
  transform: translate(0, -50%);
  opacity: 1;
  visibility: visible;
  transition: transform .6s cubic-bezier(.4, 0, .2, 1),
              opacity   .55s cubic-bezier(.4, 0, .2, 1),
              visibility 0s;
}
/* Only the offset changes on mobile, tracking the bar's smaller padding.
   The size deliberately does not: the real name stays 15px at every width
   too, for the same specificity reason noted above. */
@media (max-width: 760px) { .topnav__exit { right: 20px; } }

/* Whatever sits on the right — the name on interior pages, the progress
   ring on the landing page — occupies the same 30px box. Without this the
   row height changes between pages and the nav links shift a few pixels
   vertically when you navigate, which is exactly what must not happen. */
.topnav__brand,
.topnav .progress {
  min-height: 30px;
  display: flex;
  align-items: center;
}

/* ---------- mobile menu ---------- */
/* Sits first in the header so flex puts it hard left, with the name still
   hard right. Hidden on desktop, where the five links show inline. */

.navtoggle {
  display: none;
  position: relative;
  width: 32px; height: 24px;
  flex: 0 0 auto;
  padding: 0; margin: 0;
  background: none; border: 0; cursor: pointer;
}
.navtoggle i {
  position: absolute; left: 0; right: 0;
  height: 2px; background: #fff;
  transition: transform .38s cubic-bezier(.4, 0, .2, 1), opacity .2s ease;
}
.navtoggle i:nth-child(1) { top: 3px; }
.navtoggle i:nth-child(2) { top: 11px; }
.navtoggle i:nth-child(3) { top: 19px; }

/* Bars → X. The outer two slide to the middle bar's line (8px each) and
   cross; the middle one fades out underneath them. Driven off
   aria-expanded, which the script already maintains, so the visual state
   cannot drift from the state screen readers are told about. */
.navtoggle[aria-expanded="true"] i:nth-child(1) { transform: translateY(8px) rotate(45deg); }
.navtoggle[aria-expanded="true"] i:nth-child(2) { opacity: 0; }
.navtoggle[aria-expanded="true"] i:nth-child(3) { transform: translateY(-8px) rotate(-45deg); }

/* The panel is a full page, not a dropdown. It starts one full screen to
   the left and slides right into place, so closing reverses that and takes
   it back out to the left.

   `visibility` is what keeps it from being reachable while parked
   offscreen: transform alone leaves it hittable and can widen the
   document. It is delayed by the length of the slide on the way out so the
   panel stays on screen for the whole close animation, and switches
   instantly on the way in. */
/* z-index sits *below* the header's 60 on purpose. The panel fills the
   screen and slides under the bar, so the name stays exactly where it was
   and the toggle stays put and turns into the X — nothing in the header
   moves or is replaced. --navh is measured by nav.js and keeps the links
   clear of the bar without a hardcoded header height to fall out of sync. */
.mobilenav {
  position: fixed; inset: 0; z-index: 50;
  display: flex; flex-direction: column;
  padding-top: var(--navh, 66px);
  background: var(--paper);
  transform: translateX(-100%);
  visibility: hidden;
  transition: transform .45s cubic-bezier(.4, 0, .2, 1), visibility 0s linear .45s;
}
.mobilenav.is-open {
  transform: translateX(0);
  visibility: visible;
  transition: transform .45s cubic-bezier(.4, 0, .2, 1);
}

/* Set by a tiny inline script in <head> while the page is still parsing,
   on a page arrived at from a menu tap. nav.js is deferred, so it cannot
   run until parsing finishes — and the browser is free to paint before
   that. The result was a flash of the uncovered new page a frame or two
   before the menu appeared over it. Putting the covering state on <html>
   during head parsing means the very first paint already has it.

   Transitions are off here so nothing slides or morphs into place;
   nav.js drops the class once it has taken over, which restores them
   without changing where anything sits. */
.nav-arriving .mobilenav {
  transform: translateX(0);
  visibility: visible;
  transition: none;
}
.nav-arriving .navtoggle i { transition: none; }
.nav-arriving .navtoggle i:nth-child(1) { transform: translateY(8px) rotate(45deg); }
.nav-arriving .navtoggle i:nth-child(2) { opacity: 0; }
.nav-arriving .navtoggle i:nth-child(3) { transform: translateY(-8px) rotate(-45deg); }

/* Big enough to hit with a thumb without looking. Each row is its own
   tap target across the full width, not just the width of the word. */
.mobilenav__links {
  flex: 1 1 auto;
  display: flex; flex-direction: column; justify-content: center;
  padding: 0 clamp(24px, 7vw, 44px) 10vh;
  overflow-y: auto;
}
.mobilenav__links a {
  font-family: var(--display); font-weight: 700;
  letter-spacing: -0.03em; line-height: 1.1;
  font-size: clamp(30px, 9vw, 46px);
  color: var(--ink); text-decoration: none;
  padding: 20px 0;
  border-bottom: 1px solid var(--line);
}
.mobilenav__links a:last-child { border-bottom: 0; }

/* Instagram row in the full-page menu. Icon and word together, because at
   this size a bare glyph in a list of words reads as something gone
   wrong. Sized off the row's own font so it scales with the rest. */
.mobilenav__ig { display: flex; align-items: center; gap: .5em; }
.mobilenav__ig svg {
  width: .82em; height: .82em; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.9;
}
.mobilenav__ig svg .ig-dot { fill: currentColor; stroke: none; }
/* The page you are already on: dimmed, and inert — tapping it does
   nothing at all, handled in nav.js. */
.mobilenav__links a.is-current { color: var(--muted); cursor: default; }

/* Held for a beat after a tap, before the panel slides away: the row you
   picked stays lit and the others drop back, so the pause reads as the
   menu acknowledging the choice rather than as the site hanging. */
.mobilenav__links a { transition: opacity .18s ease; }
.mobilenav__links.is-choosing a { opacity: .3; }
.mobilenav__links.is-choosing a.is-tapped { opacity: 1; color: var(--ink); }

@media (max-width: 760px) {
  .topnav { padding: 18px 20px; }
  .topnav__brand { font-size: 14px; }
  .topnav__links { display: none; }
  .navtoggle { display: block; }
}

/* Never leave the panel mounted on desktop, where the inline links are
   back and the toggle that opens it is gone. */
@media (min-width: 761px) {
  .mobilenav { display: none; }
}



/* ---------- gallery grid ---------- */

.gal { display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(4px, .6vw, 10px); }
@media (min-width: 1100px) { .gal { grid-template-columns: repeat(4, 1fr); } }
@media (max-width: 640px)  { .gal { grid-template-columns: repeat(2, 1fr); } }
.gal figure { margin: 0; overflow: hidden; background: var(--paper-warm); position: relative; }
.gal img { width: 100%; aspect-ratio: 4 / 5; object-fit: cover; display: block; }

/* =========================================================
   GALLERY HOVER
   Pointer only, and only the frame under the cursor: a slow
   scale up. No arrow — that mark means "navigate", and these
   open a lightbox rather than going anywhere. The zoom-in
   cursor already states what the click does.
   ========================================================= */

.gal figure { cursor: zoom-in; }
.gal img {
  transition: transform .85s cubic-bezier(.2,.6,.2,1);
}

@media (hover: hover) and (pointer: fine) {
  .gal figure:hover img { transform: scale(1.05); }
}

/* =========================================================
   LIGHTBOX
   Same arrow mark as the landing carousel. Opens on click,
   closes on Escape, backdrop click or the close mark.
   ========================================================= */

.lb {
  position: fixed; inset: 0; z-index: 200;
  display: flex; align-items: center; justify-content: center;
  background: rgba(4,4,4,.97);
  opacity: 0; visibility: hidden;
  transition: opacity .35s cubic-bezier(.4,0,.2,1), visibility 0s linear .35s;
}
.lb.is-open { opacity: 1; visibility: visible; transition: opacity .35s cubic-bezier(.4,0,.2,1); }

/* Two stacked frames that slide horizontally, the same push used on the
   landing carousel. Frames never fade — cross-fading swings the screen
   luminance down and back on every step, which is hard on the eyes over
   a run of images. Sliding holds brightness constant. */
.lb__stage {
  position: relative;
  width: min(92vw, 1500px);
  height: 86vh;
  overflow: hidden;
}
.lb__frame {
  position: absolute; inset: 0; margin: 0;
  display: flex; align-items: center; justify-content: center;
  transform: translateX(100%);
  transition: transform .72s cubic-bezier(.76, 0, .24, 1);
  will-change: transform;
}
.lb__frame.is-active { transform: translateX(0); }
.lb__frame img {
  max-width: 100%; max-height: 86vh;
  width: auto; height: auto;
  object-fit: contain; display: block;
}

.lb__count {
  position: absolute; left: clamp(24px, 4vw, 60px); bottom: clamp(26px, 3.4vw, 46px);
  margin: 0; display: flex; align-items: center; gap: 14px;
  font: 400 14px/1 var(--sans); letter-spacing: .04em; color: #fff;
}
.lb__count s { display: block; width: 38px; height: 1px; background: rgba(255,255,255,.6); text-decoration: none; }

/* Arrow marks, identical construction to the landing carousel. */
.lb__nav {
  position: absolute; bottom: clamp(26px, 3.4vw, 46px); right: clamp(24px, 4vw, 60px);
  display: flex; gap: clamp(20px, 3vw, 40px);
}
.lb__nav button, .lb__close {
  background: none; border: 0; padding: 0; cursor: pointer;
  transition: opacity .25s ease;
}
.lb__nav button { width: 44px; height: 20px; position: relative; }
.lb__nav button:hover, .lb__close:hover { opacity: .55; }
.lb__nav button::before {
  content: ""; position: absolute; left: 0; right: 0; top: 50%;
  height: 1px; background: #fff;
}
.lb__nav button::after {
  content: ""; position: absolute; top: calc(50% - 4px);
  width: 8px; height: 8px; border-top: 1px solid #fff; border-right: 1px solid #fff;
}
.lb__nav .lb-prev::after { left: 0; transform: rotate(-135deg); }
.lb__nav .lb-next::after { right: 0; transform: rotate(45deg); }

/* Close: the same two strokes, crossed. */
.lb__close {
  position: absolute; top: clamp(26px, 3.4vw, 46px); right: clamp(24px, 4vw, 60px);
  width: 26px; height: 26px;
}
.lb__close::before, .lb__close::after {
  content: ""; position: absolute; left: 0; top: 50%;
  width: 26px; height: 1px; background: #fff;
}
.lb__close::before { transform: rotate(45deg); }
.lb__close::after  { transform: rotate(-45deg); }

/* Deliberately no `overflow: hidden` here. Hiding it removes the
   scrollbar, which narrows the viewport and shifts the whole page
   sideways when the lightbox opens and again when it closes. Scroll is
   blocked in lightbox.js instead, so the scrollbar stays put. */

@media (prefers-reduced-motion: reduce) {
  .lb, .lb__frame, .gal img, .gal figure { transition: none !important; }
  .gal figure:hover img { transform: none !important; }
}

/* The lightbox takes focus on open so Escape and the arrow keys have
   somewhere to land, but it is a container, not a control — it must not
   draw a focus ring. Focus used to go to the Next button instead, which
   painted an outline around the arrow the moment any image was clicked
   and made it look permanently selected. */
.lb:focus, .lb:focus-visible { outline: none; }

/* =========================================================
   IMAGE PROTECTION
   Not a lock — anything on screen can be captured, and a
   determined person will always get the file. This is the
   ordinary layer: no right-click save, no drag-to-desktop,
   no accidental text dragging. Paired with the contextmenu
   handler in protect.js.
   ========================================================= */

img {
  -webkit-user-drag: none;
  -webkit-touch-callout: none;  /* stops the iOS long-press "Save Image" sheet */
  user-select: none;
  -webkit-user-select: none;
}

body {
  user-select: none;
  -webkit-user-select: none;
}

/* ---------- form confirmation ---------- */
.form-modal[hidden] { display: none; }
.form-modal {
  position: fixed; inset: 0; z-index: 1000;
  display: grid; place-items: center;
  padding: 24px;
  background: rgba(0,0,0,.82);
  opacity: 0;
  transition: opacity .25s ease;
}
.form-modal.is-open { opacity: 1; }
.form-modal__panel {
  position: relative;
  width: min(100%, 620px);
  padding: clamp(42px, 7vw, 82px);
  border: 1px solid var(--line);
  background: var(--paper);
  transform: translateY(12px);
  transition: transform .25s ease;
}
.form-modal.is-open .form-modal__panel { transform: none; }
.form-modal__panel h2 {
  margin-top: 14px;
  font-family: var(--display); font-weight: 700;
  font-size: clamp(44px, 7vw, 76px); line-height: 1;
  letter-spacing: -.04em;
}
.form-modal__panel > p:last-of-type {
  margin: 24px 0 0;
  max-width: 42ch;
  color: var(--ink-soft);
}
.form-modal__done { margin-top: 34px; }
.form-modal__close {
  position: absolute; top: 24px; right: 24px;
  width: 26px; height: 26px;
  padding: 0; border: 0; background: none; cursor: pointer;
}
.form-modal__close::before, .form-modal__close::after {
  content: ""; position: absolute; left: 0; top: 50%;
  width: 26px; height: 1px; background: var(--ink);
}
.form-modal__close::before { transform: rotate(45deg); }
.form-modal__close::after { transform: rotate(-45deg); }
.form-modal__close:hover { opacity: .55; }
body.form-modal-open { overflow: hidden; }

@media (prefers-reduced-motion: reduce) {
  .form-modal, .form-modal__panel { transition: none; }
}

/* Anything the visitor has to type in, or may legitimately need to copy,
   stays selectable. Without this the booking form cannot be corrected and
   the email address cannot be copied, which would cost real enquiries. */
input, textarea, select,
[contenteditable="true"],
.dl dd, .ftr a[href^="mailto:"] {
  user-select: text;
  -webkit-user-select: text;
}
