/* ============================================================================
   The Pearl — design system and styles
   ----------------------------------------------------------------------------
   Mobile-first. No framework, no build step, no preprocessor.

   Sections
     00  Fonts
     01  Tokens
     02  Reset and base
     03  Typography
     04  Layout primitives
     05  Announcement bar
     06  Header and navigation
     06b Flush header (homepage)
     07  Hero
     08  Teaser strip
     09  Vision
     11  Location
     12  Updates
     12b Amenities
     12c Gallery grid
     12d Floor plans
     13  Page hero (interior pages)
     14  Draft / placeholder states
     15  Footer
     16  Lightbox
     17  Motion preferences
   ========================================================================= */

/* ---------------------------------------------------------------------------
   00  FONTS
   ----------------------------------------------------------------------------
   The display face is self-hosted from assets/fonts/. Body/UI type (Inter) still
   comes from Google Fonts.

   ONE WEIGHT ONLY. The file contains a single regular; there is no light and no
   bold. So every display element must ask for 400 and nothing else — a request
   for 600 or 700 makes the browser smear a synthetic bold, which on a
   high-contrast serif thickens the hairlines unevenly and looks like a printing
   fault. If a heading ever needs more presence, give it size, not weight.

   Shipped as .otf rather than .woff2 because there is no font compiler on this
   machine. At 29KB the difference is roughly 12KB, which is not worth blocking
   on; convert it if a build step ever appears.

   Licence: the file's own metadata reads "Copr. Castcraft Software, Inc.
   1990-1991". See the note in the README before launch.
   ------------------------------------------------------------------------ */
@font-face {
  font-family: "OPTI Baker Danmark One";
  src: url("../assets/fonts/opti-baker-danmark-one.otf") format("opentype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* ---------------------------------------------------------------------------
   01  TOKENS
   ------------------------------------------------------------------------ */
:root {
  /* Brand — sampled from the logo artwork. See assets/images/logo/README.md.
     THE CONTRAST RULE: --gold is for dark backgrounds only (8.5:1 on
     near-black, 2.1:1 on ivory — unusable). --gold-deep is the light-background
     counterpart at ~4.5:1, which sits exactly on the WCAG AA line, so it is
     permitted for large text and accents but never for body copy. Do not swap
     these to "match the logo" — the swap breaks accessibility. */
  --ink:        #1C1B19;
  --near-black: #0A0A0A;
  --ivory:      #F7F4EF;
  --gold:       #C6A47C;
  --gold-deep:  #8F6B3E;

  /* Derived neutrals — not brand colours, mixed from ink and ivory.
     --muted is 5.3:1 on ivory, so it passes AA for body text. */
  --muted:      #6B655D;
  --muted-dark: rgba(247, 244, 239, 0.72);
  --rule:       rgba(28, 27, 25, 0.14);
  --rule-dark:  rgba(247, 244, 239, 0.18);
  --surface:    #FFFFFF;

  /* Type families */
  --font-display: "OPTI Baker Danmark One", "Iowan Old Style", Georgia, serif;
  --font-body:    "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;

  /* Wordmark only — the nav lockup, nothing else. These come from the owner's
     wordmark design (assets/images/logo/the-pearl-wordmark-reference.svg), which
     is typeset rather than outlined, so the nav reproduces it as live text in the
     same two faces. Do not use them for headings or UI. */
  --font-wordmark: "Playfair Display", "Iowan Old Style", Georgia, serif;
  --font-subline:  "Jost", "Inter", "Helvetica Neue", Arial, sans-serif;

  /* Fluid type scale. Cormorant runs optically small, so display steps are
     deliberately larger than a 1.25 ratio would suggest. */
  --text-xs:   0.75rem;
  --text-sm:   0.875rem;
  --text-base: 1rem;
  --text-md:   1.0625rem;
  --text-lg:   clamp(1.125rem, 0.4vw + 1.03rem, 1.25rem);
  --display-sm: clamp(1.75rem, 1.5vw + 1.4rem, 2.25rem);
  --display-md: clamp(2.125rem, 2.6vw + 1.5rem, 3.25rem);
  --display-lg: clamp(2.5rem, 4.4vw + 1.4rem, 4.75rem);

  /* Spacing — 0.25rem base */
  --space-1:  0.25rem;
  --space-2:  0.5rem;
  --space-3:  0.75rem;
  --space-4:  1rem;
  --space-5:  1.5rem;
  --space-6:  2rem;
  --space-7:  2.5rem;
  --space-8:  3rem;
  --space-9:  4rem;
  --space-10: 5.5rem;
  --space-11: 7rem;

  /* Tracking. The wide-tracked "APARTMENTS" in the wordmark is echoed by every
     all-caps micro-label on the site. */
  --track-label: 0.18em;
  --track-tight: -0.015em;

  /* Header height as a token, because the homepage hero is pulled up underneath
     the header by exactly this amount. The two must stay in step. */
  --header-h: 5.5rem;

  --measure: 62ch;      /* readable line length */
  --shell:   76rem;     /* max content width */
  --shell-narrow: 48rem;

  --radius: 2px;        /* deliberately minimal — no soft-cornered cards */
  --ease:   cubic-bezier(0.4, 0, 0.2, 1);
  --dur:    240ms;
}

/* ---------------------------------------------------------------------------
   02  RESET AND BASE
   ------------------------------------------------------------------------ */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--ivory);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--text-md);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

img, svg, iframe { display: block; max-width: 100%; }
img { height: auto; }

/* The `hidden` attribute only gets `display: none` from the browser's own
   stylesheet, which ANY author `display` rule outranks. The floor plan cards set
   `display: flex`, so hiding them from JavaScript silently did nothing and the
   bedroom filter appeared broken. !important is load-bearing here: the whole
   point is to beat whatever a component declares. */
[hidden] { display: none !important; }

a { color: inherit; text-decoration: none; }

button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }

/* Visible focus everywhere. Never remove this. */
:focus-visible {
  outline: 2px solid var(--gold-deep);
  outline-offset: 3px;
}
.on-dark :focus-visible { outline-color: var(--gold); }

.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.skip-link {
  position: absolute; left: var(--space-4); top: -100%;
  z-index: 200;
  padding: var(--space-3) var(--space-5);
  background: var(--near-black); color: var(--ivory);
  font-size: var(--text-sm); letter-spacing: 0.04em;
  transition: top var(--dur) var(--ease);
}
.skip-link:focus { top: var(--space-4); }

/* ---------------------------------------------------------------------------
   03  TYPOGRAPHY
   ------------------------------------------------------------------------ */
h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 400;
  line-height: 1.12;
  letter-spacing: var(--track-tight);
  margin: 0;
  text-wrap: balance;
}

/* Pinned to 400 on dark as well as light. The display face ships one weight, so
   this is not a stylistic choice — anything else triggers a synthetic bold. Kept
   as an explicit rule rather than deleted, so a future edit to the base heading
   weight cannot silently drag the dark sections along with it. */
.on-dark h1, .on-dark h2, .on-dark h3 { font-weight: 400; }

h1 { font-size: var(--display-lg); }
h2 { font-size: var(--display-md); }
h3 { font-size: var(--display-sm); }

p { margin: 0 0 var(--space-4); max-width: var(--measure); }
p:last-child { margin-bottom: 0; }

/* All-caps micro-label. Inter only — the display face is never used this small. */
.label {
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--gold-deep);
  margin: 0 0 var(--space-4);
}
.on-dark .label { color: var(--gold); }

.lede { font-size: var(--text-lg); color: var(--muted); }
.on-dark .lede { color: var(--muted-dark); }

/* ---------------------------------------------------------------------------
   04  LAYOUT PRIMITIVES
   ------------------------------------------------------------------------ */
.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--space-5);
}
@media (min-width: 48rem) { .shell { padding-inline: var(--space-7); } }

.shell-narrow { max-width: var(--shell-narrow); margin-inline: auto; }

.section { padding-block: var(--space-9); }
@media (min-width: 48rem) { .section { padding-block: var(--space-11); } }

.section--dark { background: var(--near-black); color: var(--ivory); }
.section--tight { padding-block: var(--space-8); }

.center { text-align: center; }
.center p { margin-inline: auto; }

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

/* Arrow link — used on teaser cards and inline calls to read more. */
.arrow-link {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-size: var(--text-sm); font-weight: 500;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--gold-deep);
}
.arrow-link::after {
  content: "→";
  transition: transform var(--dur) var(--ease);
}
.arrow-link:hover::after, a:hover .arrow-link::after { transform: translateX(4px); }
.on-dark .arrow-link { color: var(--gold); }

/* ---------------------------------------------------------------------------
   05  ANNOUNCEMENT BAR
   ------------------------------------------------------------------------ */
/* Deliberately loud. With no CTA anywhere on the site, this bar and the
   homepage update block are the only two elements that signal the project is
   active, so the bar is sized to be read rather than skimmed past. */
.announce {
  background: var(--near-black);
  color: var(--ivory);
  text-align: center;
  padding-block: var(--space-4);
  border-bottom: 1px solid rgba(198, 164, 124, 0.3);
}
.announce p {
  margin: 0; max-width: none;
  font-size: var(--text-sm);
  font-weight: 700;
  letter-spacing: 0.1em;
  line-height: 1.5;
  text-transform: uppercase;
}
/* Tracking opens up with the type size — tight tracking on small caps looks
   cramped, wide tracking on larger caps is what ties back to the wordmark. */
@media (min-width: 48rem) {
  .announce { padding-block: var(--space-5); }
  .announce p { font-size: var(--text-md); letter-spacing: 0.14em; }
}
.announce strong { color: var(--gold); font-weight: 700; }

/* ---------------------------------------------------------------------------
   06  HEADER AND NAVIGATION
   ------------------------------------------------------------------------ */
.site-header {
  position: sticky; top: 0; z-index: 100;
  background: var(--ivory);
  /* Hairline drawn as a shadow rather than a border. The homepage header moves
     between a transparent and a solid state, and a border would add and remove
     1px of layout on every transition, nudging the whole page as you scroll. */
  box-shadow: 0 1px 0 var(--rule);
  transition: background-color var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease),
              color var(--dur) var(--ease);
}
.site-header__inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-5);
  min-height: var(--header-h);
}

/* Wordmark, set as live text rather than an image.

   The owner's wordmark is a typeset design, not outlines. An SVG loaded through
   an <img> cannot reach the page's webfonts, so its Playfair Display would fall
   back to Times New Roman almost everywhere. Reproducing it as text renders
   correctly, scales without a second file, stays selectable, gives the header a
   real accessible name, and — the reason it matters most here — takes its colour
   from a token, so the transparent and solid header states are a one-line swap
   rather than two more image variants.

   The horizontal lockup also suits a nav bar in a way the stacked one never did:
   no 160px-wide minimum forcing a tall header. */
.brand {
  display: flex; flex-direction: column; align-items: center;
  gap: 0.34rem;

  /* Two-colour wordmark: light name over a gold subline. Each line reads its own
     local custom property, so switching the whole lockup between the transparent
     and solid header states stays a single two-line override below rather than a
     rule per element.

     Light state is the artwork's own scheme. Solid state cannot be — a white name
     on ivory is 1.06:1, i.e. invisible — so it drops to Ink over deep gold, which
     is exactly what the supplied logo kit's own on-light variant does. */
  --wordmark:     var(--ink);
  --wordmark-sub: var(--gold-deep);
}

.brand__name {
  color: var(--wordmark);
  transition: color var(--dur) var(--ease);
  font-family: var(--font-wordmark);
  font-weight: 500;
  font-size: clamp(1.05rem, 0.9vw + 0.8rem, 1.375rem);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  line-height: 1;
  white-space: nowrap;
  /* Letter-spacing is applied after the last character too, which pushes the
     optical centre left of the real one. Pulling back by exactly one tracking
     step re-centres it against the subline below. */
  margin-right: -0.18em;
}

.brand__sub {
  /* The flanking hairlines below inherit this via currentColor. */
  color: var(--wordmark-sub);
  transition: color var(--dur) var(--ease);
  display: flex; align-items: center;
  gap: 0.5rem;
  font-family: var(--font-subline);
  font-weight: 400;
  font-size: 0.5625rem;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  line-height: 1;
  white-space: nowrap;
  margin-right: -0.34em;   /* same trailing-tracking correction */
}
/* The hairlines flanking APARTMENTS in the source artwork. */
.brand__sub::before,
.brand__sub::after {
  content: ""; flex: none;
  width: 18px; height: 1px;
  background: currentColor;
}

.nav-toggle {
  display: inline-flex; flex-direction: column; justify-content: center;
  gap: 5px; width: 44px; height: 44px; padding: 0 10px;
}
.nav-toggle span {
  display: block; height: 1.5px; background: var(--ink);
  transition: transform var(--dur) var(--ease), opacity var(--dur) var(--ease);
}
.site-header[data-open="true"] .nav-toggle span:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
.site-header[data-open="true"] .nav-toggle span:nth-child(2) { opacity: 0; }
.site-header[data-open="true"] .nav-toggle span:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }

.nav {
  display: none;
  position: absolute; left: 0; right: 0; top: 100%;
  background: var(--ivory);
  border-bottom: 1px solid var(--rule);
  padding: var(--space-3) var(--space-5) var(--space-6);
}
.site-header[data-open="true"] .nav { display: block; }

.nav ul { list-style: none; margin: 0; padding: 0; }
.nav a {
  display: block;
  padding-block: var(--space-3);
  font-size: var(--text-sm); font-weight: 500;
  letter-spacing: 0.1em; text-transform: uppercase;
  border-bottom: 1px solid var(--rule);
  transition: color var(--dur) var(--ease);
}
.nav a:hover { color: var(--gold-deep); }
.nav a[aria-current="page"] { color: var(--gold-deep); }

@media (min-width: 56rem) {
  .nav-toggle { display: none; }
  .nav {
    display: block; position: static;
    border: 0; padding: 0; background: none;
  }
  .nav ul { display: flex; gap: var(--space-6); }
  .nav a { padding-block: 0; border: 0; }
}

/* ---------------------------------------------------------------------------
   06b  FLUSH HEADER (all pages)
   ----------------------------------------------------------------------------
   Every page's hero runs the full height of the page, up behind the navigation,
   so the header floats over it rather than sitting on a band above it. The
   announcement bar stays solid above all of it.

   Works for the near-black floor-plans hero too, which has no image — ivory
   links on near-black are the strongest pairing on the site.

   The header remains sticky and in normal flow; the hero is pulled up by
   exactly the header's height instead. Taking the header out of flow with
   absolute positioning would have meant hard-coding an offset for the
   announcement bar, whose height changes with breakpoint and text wrap.

   `data-transparent` is set by js/main.js, never in the markup. It resolves
   three inputs: the page has a flush hero, the hero is still under the header,
   and the mobile menu is closed. Without JavaScript the attribute never
   appears, the header stays solid, and the hero keeps its offset — the image
   simply starts below the bar, exactly as it did before. Quieter than intended,
   never broken.
   ------------------------------------------------------------------------ */
/* Interior heroes also add the header's height back to their own — see the note
   at the end of section 13. */
.hero--flush,
.page-hero--flush { margin-top: calc(-1 * var(--header-h)); }

.site-header[data-transparent="true"] {
  background: transparent;
  box-shadow: none;
  color: var(--ivory);
}
/* Over the image the wordmark takes the artwork's own colours: light name, true
   gold subline. Ivory rather than pure #fff, to match the nav links and the hero
   ring beside it — a cooler pure white next to ivory reads as a mismatch rather
   than an accent. The contrast rule still decides the golds: true gold here, deep
   gold on the solid bar where true gold is 2.1:1. */
.site-header[data-transparent="true"] .brand {
  --wordmark:     var(--ivory);
  --wordmark-sub: var(--gold);
}
.site-header[data-transparent="true"] .nav-toggle span { background: var(--ivory); }
.site-header[data-transparent="true"] .nav a:hover,
.site-header[data-transparent="true"] .nav a[aria-current="page"] { color: var(--gold); }
/* The default focus ring is deep gold, which is a light-background colour. */
.site-header[data-transparent="true"] :focus-visible { outline-color: var(--gold); }

/* ---------------------------------------------------------------------------
   07  HERO
   ------------------------------------------------------------------------ */
.hero { position: relative; isolation: isolate; background: var(--near-black); }

.hero__media {
  position: absolute; inset: 0; z-index: -2;
}
.hero__media img {
  width: 100%; height: 100%;
  object-fit: cover; object-position: center 50%;
}

/* Renderings are bright at the top and busy at the bottom, so a flat grey veil
   carries the baseline dimming and a two-stop gradient adds contrast where the
   type actually sits.

   z-index: 1, NOT -1. This layer is a child of .hero__media, which sets
   z-index: -2 and therefore opens its own stacking context. A negative z-index
   here does not put the scrim behind the hero — it puts it behind the <img>
   inside it, where it does nothing at all. It must be positive to sit over the
   image; .hero__inner still paints above the whole group. */
.hero__media::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background:
    linear-gradient(180deg, rgba(10,10,10,0.42) 0%, rgba(10,10,10,0.20) 45%, rgba(10,10,10,0.62) 100%),
    rgba(28, 27, 25, 0.3);
}

.hero__inner {
  position: relative;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center;
  /* Height tracks viewport WIDTH, not height. The renderings are 16:9, and
     `cover` crops whatever the container does not match — so a height driven by
     vh cropped hard on short windows and left the hero looking zoomed in. At
     52vw the container stays near the source ratio (56.25vw would be exact), so
     roughly 92% of the image is visible at any width. The floor keeps the type
     from crushing on phones; the ceiling stops a full-height hero on very large
     monitors, at the cost of some crop returning above ~1850px. */
  min-height: clamp(28rem, 52vw, 60rem);
  padding-block: var(--space-10);
  color: var(--ivory);
}
/* Ring only, not the stacked lockup — the nav carries the name now, so repeating
   "THE PEARL APARTMENTS" here would say it twice on one screen. Sized well above
   the mark's 28px brand minimum: the ring is a traced brush stroke and its
   hairlines need room to draw. */
.hero__mark { width: clamp(96px, 9.5vw, 132px); height: auto; margin-bottom: var(--space-6); }
.hero h1 { max-width: 22ch; margin-bottom: var(--space-5); }
/* Overrides the standard .on-dark .lede (72% ivory). That value is tuned for
   flat near-black panels; over a rendering it goes soft, and this line carries
   the address. */
.hero .lede { max-width: 46ch; color: rgba(247, 244, 239, 0.92); }

/* ---------------------------------------------------------------------------
   08  TEASER STRIP
   ------------------------------------------------------------------------ */
.teasers { display: grid; gap: var(--space-6); }
@media (min-width: 44rem) { .teasers { grid-template-columns: repeat(3, 1fr); gap: var(--space-5); } }

.teaser { display: flex; flex-direction: column; gap: var(--space-4); }
.teaser__media {
  position: relative; overflow: hidden;
  aspect-ratio: 4 / 3; background: var(--near-black);
  border-radius: var(--radius);
}
.teaser__media img {
  width: 100%; height: 100%; object-fit: cover;
  transition: transform 600ms var(--ease);
}
.teaser:hover .teaser__media img { transform: scale(1.035); }

/* Card for a page with no imagery yet — mark on near-black instead of a
   grey placeholder box. */
.teaser__media--mark {
  display: grid; place-items: center;
}
.teaser__media--mark img { width: 34%; height: auto; object-fit: contain; }
.teaser:hover .teaser__media--mark img { transform: none; }

.teaser h3 { font-size: var(--display-sm); }
.teaser__meta {
  font-size: var(--text-sm); color: var(--muted);
  margin: calc(var(--space-2) * -1) 0 var(--space-2);
}

/* ---------------------------------------------------------------------------
   09  VISION
   ------------------------------------------------------------------------ */
.vision h2 { margin-bottom: var(--space-6); max-width: 30ch; }
.vision .lede { margin-bottom: var(--space-5); }
.vision .center h2 { margin-inline: auto; }

/* Left-aligned running text in a centred column. `.measure` caps the line length;
   `margin-inline: auto` centres the block itself, so the copy is not stranded
   against the left edge of a wider shell. */
.prose { max-width: var(--measure); margin-inline: auto; }
.prose p + p { margin-top: var(--space-5); }

/* ---------------------------------------------------------------------------
   11  LOCATION
   ------------------------------------------------------------------------ */
.location__grid { display: grid; gap: var(--space-7); }
@media (min-width: 56rem) {
  .location__grid { grid-template-columns: 0.85fr 1.15fr; gap: var(--space-8); align-items: start; }
}

.address { font-style: normal; }
/* Inter, not the display face — the only place on the site where display type
   would meet a run of digits, and it cannot handle them. The display face has
   old-style figures with no digit kerning and no GSUB table, so there is no
   lining-figures feature to switch on: "74120" renders as "74 1 20", which reads
   as a broken page rather than a typographic choice. Set in Inter at a display
   size so the address keeps its presence. Do not put numerals back into the
   display face anywhere. */
.address__line {
  font-family: var(--font-body);
  font-size: clamp(1.25rem, 0.9vw + 1.05rem, 1.5rem);
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: 0;
  margin-bottom: var(--space-5);
}

/* Deliberately larger than body copy. It sits directly beneath a 20–24px address
   line and beside a big map, and at the base 17px it read as fine print rather
   than as the paragraph that actually sells the neighborhood. Ink, not muted —
   it was hard to read, so lightening it would be the wrong direction. */
.location__note {
  margin-top: var(--space-6);
  font-size: clamp(1.125rem, 0.5vw + 1rem, 1.3125rem);
  line-height: 1.7;
  color: var(--ink);
}

/* Second address block in the location column — the adjacent garage. Ruled off so
   it reads as a distinct place rather than more detail about the apartments, and
   its supporting copy stays at body size so it does not compete with the
   neighborhood paragraph above it. */
.place-block {
  margin-top: var(--space-7);
  padding-top: var(--space-6);
  border-top: 1px solid var(--rule);
}
.address__detail {
  margin: var(--space-3) 0 0;
  color: var(--ink);
}
.place-block__note { margin-top: var(--space-5); }

.place { padding-block: var(--space-5); border-top: 1px solid var(--rule); }
.place:last-of-type { border-bottom: 1px solid var(--rule); }
.place__name {
  font-size: var(--text-sm); font-weight: 600;
  letter-spacing: 0.06em; text-transform: uppercase;
  margin: 0 0 var(--space-1);
}
.place__detail { font-size: var(--text-base); color: var(--muted); margin: 0 0 var(--space-3); }

.map {
  position: relative;
  aspect-ratio: 4 / 3;
  background: #e8e4dd;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  overflow: hidden;
}
@media (min-width: 56rem) { .map { aspect-ratio: 1 / 1; } }
.map iframe { width: 100%; height: 100%; border: 0; }

/* ---------------------------------------------------------------------------
   12  UPDATES
   ------------------------------------------------------------------------ */
.update { border-top: 1px solid var(--rule-dark); padding-top: var(--space-5); }
.update__date {
  font-size: var(--text-xs); font-weight: 500;
  letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--gold); margin: 0 0 var(--space-3);
}
.update h3 { margin-bottom: var(--space-3); }

/* ---------------------------------------------------------------------------
   12b  AMENITIES
   ----------------------------------------------------------------------------
   Plain two-column lists, no icon cards — icons would add decoration without
   adding information, and the brief ruled them out.
   ------------------------------------------------------------------------ */
.amenity-group + .amenity-group { margin-top: var(--space-9); }

.amenity-group__head {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-4);
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--rule);
  margin-bottom: var(--space-6);
}
.amenity-group__head h2 { font-size: var(--display-sm); }

/* CSS columns, not a two-column grid. A grid fills across — item 1, item 2 on the
   first row — which would scramble the owner's amenity sheet, where the list reads
   all the way down the left column and then down the right. Columns flow in that
   order and balance the halves automatically, so the page matches the source
   document without hard-coding a row count. */
.amenity-list { list-style: none; margin: 0; padding: 0; }
@media (min-width: 40rem) {
  .amenity-list { columns: 2; column-gap: var(--space-7); }
}

.amenity-list li {
  position: relative;
  padding-left: var(--space-5);
  margin-bottom: var(--space-3);
  font-size: var(--text-base);
  line-height: 1.55;
  break-inside: avoid;   /* never split an item across the column break */
}
.amenity-list li:last-child { margin-bottom: 0; }
/* Round bullet in deep gold. Drawn as a pseudo-element rather than a real
   list-style marker because the <ul> is a grid container, where native markers
   sit inconsistently across browsers. `top` centres the dot on the first line
   box (0.55em of a 1.55 line-height), so it holds if the item wraps. */
.amenity-list li::before {
  content: ""; position: absolute; left: 2px; top: 0.55em;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--gold-deep);
}

.amenity-empty { color: var(--muted); font-size: var(--text-base); }

/* ---------------------------------------------------------------------------
   12c  GALLERY GRID
   ----------------------------------------------------------------------------
   Five renderings: the first runs full width as the feature, the remaining four
   fall into a 2x2 below it. Every tile is 16:9 — the source ratio — so the grid
   crops nothing. These are architectural drawings and the composition is the
   content; cropping them into square tiles would cut the building in half.
   ------------------------------------------------------------------------ */
.gallery-grid {
  display: grid; gap: var(--space-4);
  grid-template-columns: 1fr;
}
@media (min-width: 44rem) {
  .gallery-grid { grid-template-columns: repeat(2, 1fr); gap: var(--space-5); }
  .gallery-grid > :first-child { grid-column: 1 / -1; }
}

/* A button, not a link — it opens a dialog rather than navigating. */
.gallery-item {
  position: relative; display: block; width: 100%;
  padding: 0; border: 0; overflow: hidden;
  background: var(--near-black);
  border-radius: var(--radius);
  cursor: zoom-in;
  text-align: left;
}
.gallery-item img {
  width: 100%; height: auto; aspect-ratio: 16 / 9; object-fit: cover;
  transition: transform 600ms var(--ease);
}
.gallery-item:hover img { transform: scale(1.03); }

.gallery-item__caption {
  position: absolute; left: 0; right: 0; bottom: 0;
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-7) var(--space-4) var(--space-3);
  background: linear-gradient(180deg, rgba(10,10,10,0) 0%, rgba(10,10,10,0.78) 100%);
  color: var(--ivory);
  font-size: var(--text-xs); font-weight: 500;
  letter-spacing: var(--track-label); text-transform: uppercase;
}
.gallery-item__zoom { color: var(--gold); font-size: var(--text-md); line-height: 1; }

/* ---------------------------------------------------------------------------
   12d  FLOOR PLANS
   ----------------------------------------------------------------------------
   Filter bar plus a card grid. Cards are buttons — they open the viewer rather
   than navigating.

   Plans are NOT cropped. `object-fit: contain` inside a fixed-ratio tile, because
   the unit types are different shapes (some tall and narrow, some square, one
   angled corner) and cropping a floor plan cuts rooms off. The renders sit on
   white, so a white tile reads as continuous with the drawing.
   ------------------------------------------------------------------------ */
.filter-bar {
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  padding-bottom: var(--space-5);
  border-bottom: 1px solid var(--rule);
  margin-bottom: var(--space-7);
}

.filter-tab {
  display: inline-flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--rule);
  border-radius: 999px;
  font-size: var(--text-sm); font-weight: 500;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--muted);
  transition: color var(--dur) var(--ease),
              border-color var(--dur) var(--ease),
              background-color var(--dur) var(--ease);
}
.filter-tab:hover { color: var(--ink); border-color: var(--ink); }
/* aria-pressed carries the state, so the styling follows the accessibility
   attribute rather than a parallel class that could drift out of sync. */
.filter-tab[aria-pressed="true"] {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--ivory);
}
.filter-tab__count {
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  opacity: 0.65;
}

.filter-status {
  margin: var(--space-6) 0 0;
  font-size: var(--text-sm);
  color: var(--muted);
}

/* Two columns at most, never three. At three across, a tall studio plan was
   rendering about 250px wide inside its tile and was genuinely hard to read. Two
   columns roughly doubles the drawn size of every plan, and with nine of them the
   extra scrolling is what the bedroom filter is for. */
.plan-grid {
  display: grid; gap: var(--space-6);
  grid-template-columns: 1fr;
}
@media (min-width: 48rem) { .plan-grid { grid-template-columns: repeat(2, 1fr); } }

.plan-card {
  display: flex; flex-direction: column;
  padding: 0; border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--surface);
  text-align: left;
  overflow: hidden;
  cursor: zoom-in;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.plan-card:hover {
  border-color: var(--gold-deep);
  box-shadow: 0 2px 16px rgba(28, 27, 25, 0.07);
}

/* Fixed height rather than aspect-ratio. As a column flex item the media box
   takes its main size from content, so a tall plan stretched the tile and the
   name/spec rows in a row of cards stopped lining up. An explicit height is the
   same for every card, which is the whole point. */
/* Tall enough that a portrait plan is limited by the tile's height rather than
   shrinking to a sliver. Five of the nine types are portrait, four are landscape,
   so the tile has to serve both — at this height the landscape plans fill the
   width and the portrait ones fill the height. */
.plan-card__media {
  display: block;
  height: clamp(22rem, 36vw, 36rem);
  flex: none;
  background: #FFFFFF;
  padding: var(--space-4);
}
.plan-card__media img {
  width: 100%; height: 100%;
  object-fit: contain;
}

.plan-card__body {
  display: flex; flex-direction: column; gap: var(--space-1);
  padding: var(--space-4) var(--space-5) var(--space-5);
  border-top: 1px solid var(--rule);
}
/* Inter, NOT the display face. Every unit name is a letter followed by digits —
   S1, A11, B3 — and the display face has old-style figures with no lining
   alternate, so "S1" renders with a dropped, undersized 1 and "A11" looks
   broken. Same rule as the address block: no numerals in the display face. */
.plan-card__name {
  font-family: var(--font-body);
  font-size: 1.375rem;
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: 0.02em;
  font-variant-numeric: lining-nums tabular-nums;
}
.plan-card__spec {
  font-size: var(--text-sm);
  color: var(--muted);
}
/* Rent range. One line for the whole community rather than a price per card, so
   it is sized to be read as a headline figure. Inter, not the display face —
   it is almost entirely digits. */
.rent-range {
  margin: var(--space-7) 0 0;
  max-width: none;
  font-family: var(--font-body);
  font-size: clamp(1.125rem, 0.8vw + 0.95rem, 1.375rem);
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--gold-deep);
  font-variant-numeric: lining-nums tabular-nums;
}

/* ---------------------------------------------------------------------------
   13  PAGE HERO (interior pages)
   ------------------------------------------------------------------------ */
.page-hero {
  position: relative; isolation: isolate;
  background: var(--near-black); color: var(--ivory);
}
.page-hero__media { position: absolute; inset: 0; z-index: -2; }
.page-hero__media img { width: 100%; height: 100%; object-fit: cover; object-position: center 50%; }
/* Same z-index correction as the homepage hero — see the note there. */
.page-hero__media::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background:
    linear-gradient(180deg, rgba(10,10,10,0.34) 0%, rgba(10,10,10,0.74) 100%),
    rgba(28, 27, 25, 0.24);
}
.page-hero__inner {
  position: relative;
  /* Default is the typographic case — floor-plans.html has no imagery, so its
     hero is type on near-black and must stay compact. */
  min-height: min(42vh, 22rem);
  display: flex; flex-direction: column; justify-content: flex-end;
  padding-block: var(--space-8) var(--space-7);
}

/* Heroes that actually carry a rendering get a taller, width-driven band.
   The old height was min(42vh, 22rem) for these too — a ~4:1 letterbox fed a
   16:9 image, which threw away well over half its height. The courtyard hero
   was cropped so tightly that the pool, the subject of the picture, was out of
   frame. At 50vw the banner sits near 2:1 and keeps ~90% of the image.

   Scoped with :has() so no page needs a modifier class, and so an imageless
   hero added later gets the compact height by default rather than 700px of
   empty black. Where :has() is unsupported these heroes simply render at the
   compact height — quieter than intended, never broken. */
@supports selector(:has(*)) {
  .page-hero:has(.page-hero__media) .page-hero__inner {
    min-height: clamp(20rem, 50vw, 52rem);
  }
}

/* Oversized ring as a compositional device, for the one page hero with no
   photograph behind it. Deliberately cropped by the right edge so it reads as a
   placed mark rather than a centred logo, and sized off the viewport so it scales
   with the band.

   Flat opacity, not a blend mode. Blend modes need mid-tones in the surface to
   work with; this ground is flat near-black, where soft-light and overlay do
   almost nothing and screen is indistinguishable from plain opacity. Over
   photography the advice would be the opposite.

   `overflow: hidden` on the section is load-bearing — without it the negative
   `right` gives the whole page a horizontal scrollbar. */
/* Ink rather than near-black, owner's choice, and deliberately scoped to THIS
   hero only. Ink is already in the palette — it is the body-text colour — so this
   introduces no new brand colour, just a warmer and slightly lighter dark.

   Be aware of what that means on this page: the announcement bar above and the
   footer below are still near-black, so the page stacks three tones of dark. It
   was raised and the owner chose this scope. If it ever reads as an accident
   rather than a step, the fix is to move this to the `--near-black` token and let
   every dark surface follow. */
.page-hero--mark {
  overflow: hidden;
  background: var(--ink);
}
.page-hero--mark::before {
  content: "";
  position: absolute;
  z-index: 0;
  pointer-events: none;
  /* Centred horizontally and larger, cropped only by the bottom edge. Tucked into
     the corner it was too far out of the way to register at all.

     `top` starts it just below the navigation row, which sits over this hero
     while the header is transparent. That is the one thing it must stay clear of:
     behind the heading it reads as deliberate layering, behind the nav it just
     looks busy where the current page is marked in gold. */
  /* Sized by HEIGHT and positioned right below the nav, so the whole ring shows.

     The two terms are deliberately smaller than the band's own `min(46vh, 26rem)`
     — that expression IS the space below the nav row, so 42vh < 46vh and
     23rem < 26rem guarantee the circle is never clipped at any viewport, without
     needing to know the band's computed height.

     The 60vw term is the phone case: without it the ring went nearly edge to edge
     on a 390px screen and swamped the heading. Width follows from the aspect
     ratio; do not set both. */
  left: 50%;
  top: var(--header-h);
  translate: -50% 0;
  height: min(42vh, 23rem, 60vw);
  width: auto;
  aspect-ratio: 310.6 / 326;   /* the mark's own ratio, so it never distorts */

  /* The GOLD ring, not the ivory one. Ivory on near-black just reads as grey and
     disappeared at low opacity; gold is the brand's dark-background colour and
     carries warmth, so it registers while staying subtle. */
  background: url("../assets/images/logo/the-pearl-mark.svg") center / contain no-repeat;
  opacity: 0.18;
}
/* The heading sits in .page-hero__inner, which is positioned and comes later in
   the DOM, so it already paints above this. Stated because it is easy to break. */

/* A flush hero is pulled up under the header, so it gives back the header's
   height to keep the same visible depth as before. Without this every interior
   banner would silently lose 88px, and the compact floor-plans hero would end up
   crowding its own heading.

   These must come after the rules above: `:has()` takes the specificity of its
   argument, so the flush and non-flush selectors tie, and source order decides. */
.page-hero--flush .page-hero__inner {
  min-height: calc(min(42vh, 22rem) + var(--header-h));
}
@supports selector(:has(*)) {
  .page-hero--flush:has(.page-hero__media) .page-hero__inner {
    min-height: calc(clamp(20rem, 50vw, 52rem) + var(--header-h));
  }
}

/* Taller than the standard typographic hero, so the whole ring fits between the
   navigation row and the bottom edge. At the standard height a full circle would
   have had to shrink by about a third to fit, which undoes the point of it being
   visible. Must come after the --flush rule above: same specificity, so source
   order decides. */
.page-hero--mark .page-hero__inner {
  min-height: calc(min(46vh, 26rem) + var(--header-h));
}

/* ---------------------------------------------------------------------------
   14  DRAFT / PLACEHOLDER STATES
   ----------------------------------------------------------------------------
   Deliberately conspicuous. These mark content that is unconfirmed or missing
   and must never be mistaken for finished design.
   ------------------------------------------------------------------------ */
.draft-note {
  border: 1px dashed var(--gold-deep);
  border-radius: var(--radius);
  padding: var(--space-4) var(--space-5);
  background: rgba(143, 107, 62, 0.06);
  font-size: var(--text-sm);
  color: var(--ink);
  margin-bottom: var(--space-6);
}
.draft-note p { max-width: none; }
.draft-note strong {
  display: block;
  font-size: var(--text-xs); font-weight: 600;
  letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--gold-deep);
  margin-bottom: var(--space-2);
}
.on-dark .draft-note {
  border-color: var(--gold);
  background: rgba(198, 164, 124, 0.1);
  color: var(--ivory);
}
.on-dark .draft-note strong { color: var(--gold); }

.empty-state {
  border: 1px dashed var(--rule-dark);
  border-radius: var(--radius);
  padding: var(--space-7) var(--space-5);
  text-align: center;
  font-size: var(--text-sm);
  color: var(--muted-dark);
}

/* ---------------------------------------------------------------------------
   15  FOOTER
   ------------------------------------------------------------------------ */
.site-footer {
  background: var(--near-black);
  color: var(--ivory);
  padding-block: var(--space-9) var(--space-6);
}
.site-footer__top {
  display: grid; gap: var(--space-8);
  padding-bottom: var(--space-8);
}
@media (min-width: 56rem) {
  .site-footer__top { grid-template-columns: auto 1fr auto; align-items: start; gap: var(--space-9); }
}
.site-footer__lockup { width: 190px; height: auto; }

.footer-nav ul { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-3); }
@media (min-width: 44rem) { .footer-nav ul { grid-auto-flow: column; gap: var(--space-6); justify-content: start; } }
.footer-nav a {
  font-size: var(--text-sm); font-weight: 500;
  letter-spacing: 0.1em; text-transform: uppercase;
  transition: color var(--dur) var(--ease);
}
.footer-nav a:hover { color: var(--gold); }

.footer-info { font-size: var(--text-sm); color: var(--muted-dark); font-style: normal; }
.footer-info dt {
  font-size: var(--text-xs); font-weight: 600;
  letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--gold); margin-bottom: var(--space-1);
}
.footer-info dd { margin: 0 0 var(--space-4); }

.site-footer__legal {
  border-top: 1px solid var(--rule-dark);
  padding-top: var(--space-6);
  font-size: var(--text-xs);
  color: var(--muted-dark);
  line-height: 1.7;
}
.site-footer__legal p { max-width: none; margin-bottom: var(--space-3); }
.site-footer__legal a { text-decoration: underline; text-underline-offset: 2px; }

.compliance { display: flex; flex-wrap: wrap; gap: var(--space-5); align-items: center; margin-bottom: var(--space-5); }
.compliance p { margin: 0; font-size: var(--text-xs); }

/* ---------------------------------------------------------------------------
   16  LIGHTBOX
   ----------------------------------------------------------------------------
   Click-through viewer for the gallery. Markup is built by js/main.js and lives
   at the end of <body>. `contain` rather than `cover` — a rendering shown in a
   viewer should never be cropped.
   ------------------------------------------------------------------------ */
.lightbox {
  position: fixed; inset: 0; z-index: 300;
  display: none;
  /* Fully opaque. Earlier values let the page behind stay faintly legible, which
     read as a rendering bug rather than a scrim — and it is worse over the light
     floor plans page than it was over the gallery. A full-screen viewer has no
     reason to be translucent. */
  background: #0A0A0A;
  color: var(--ivory);
}
.lightbox[data-open="true"] {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
}

.lightbox__bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--rule-dark);
}
.lightbox__count {
  font-size: var(--text-xs); font-weight: 500;
  letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--gold);
}

/* 44px minimum on every control — thumb-sized targets. */
.lightbox__close,
.lightbox__nav {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px;
  color: var(--ivory);
  border: 1px solid var(--rule-dark);
  border-radius: var(--radius);
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.lightbox__close:hover,
.lightbox__nav:hover { color: var(--gold); border-color: var(--gold); }
.lightbox__close { font-size: 1.5rem; line-height: 1; }

/* Deliberately large. These are the controls people actually use, and at 44px
   they were a small target in a lot of empty space. 72px with a big glyph is
   easy to hit with a thumb or a rough mouse move. */
.lightbox__nav {
  min-width: 4.5rem; min-height: 4.5rem;
  font-size: 2.5rem; line-height: 1;
}

.lightbox__stage {
  position: relative;
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center; gap: var(--space-3);
  padding: var(--space-5);
  min-height: 0;
  overflow: hidden;   /* belt and braces: nothing escapes the stage */
}
/* height: 100% is load-bearing, not decoration. The stage centres its items, so
   the figure's height was auto — and a percentage max-height against an
   auto-height parent is indefinite, which browsers treat as `none`. The image
   therefore ignored max-height entirely. Wide 16:9 renderings never showed it
   because they were width-constrained, but a tall floor plan overflowed the
   viewer and pushed the caption off screen. Stretching the figure gives the
   percentage something definite to resolve against. */
.lightbox__figure {
  margin: 0; min-width: 0; min-height: 0;
  align-self: stretch;
  height: 100%;
  display: grid; place-items: center;
}
/* Capped against the VIEWPORT, not the parent. Percentage heights here depend on
   a chain of grid rows resolving to a definite height, and tall floor plans kept
   escaping it — the image would run past the bottom of the screen and push the
   caption out of view. Viewport units sidestep the chain entirely. The subtracted
   15rem reserves the top bar, the caption and the stage padding; the max() keeps
   it sane if someone opens the viewer in a very short window. */
.lightbox__figure img {
  max-width: 100%;
  max-height: max(12rem, calc(100vh - 15rem));
  width: auto; height: auto;
  object-fit: contain;
}

.lightbox__caption {
  padding: var(--space-4) var(--space-5) var(--space-6);
  border-top: 1px solid var(--rule-dark);
  text-align: center;
}

/* Attribute list in the side panel. Empty on the gallery, which passes no meta,
   and an empty <ul> takes no space. */
.lightbox__meta {
  list-style: none; margin: var(--space-5) 0 0; padding: 0;
  display: flex; flex-direction: column;
  font-size: var(--text-lg);
}
.lightbox__meta:empty { display: none; }
.lightbox__meta li {
  padding-block: var(--space-3);
  border-top: 1px solid var(--rule-dark);
}

/* Bigger title inside the side panel than the shared caption default. */
.lightbox--side .lightbox__title { font-size: 2rem; }
.lightbox--side .lightbox__hint { margin-top: var(--space-5); font-size: var(--text-sm); }

/* --- side panel layout ------------------------------------------------------
   Caption moves out of its own bottom row and into a fourth column of the stage,
   so it sits level with the image. Arrows stay hugging the image rather than
   being pushed to the far edge by the panel. */
.lightbox--side[data-open="true"] { grid-template-rows: auto minmax(0, 1fr); }

/* Every column sizes to its content and the whole group centres, so the arrows
   sit hard against the edges of the plan instead of out at the screen edges with
   a void between. Order is: ‹ plan › panel. */
.lightbox--side .lightbox__stage {
  grid-template-columns: auto auto auto auto;
  justify-content: center;
  gap: var(--space-5);
}
/* A FIXED BOX, identical for every plan — the arrows sit on its edges, so it must
   not depend on the image or they move as you click through.

   Sized to fill the space actually available, NOT square. A square was tried and
   is the wrong shape: it is bounded by the smaller of the two axes, which threw
   away most of the width and shrank every plan, wide ones badly.

   The reserves are measured, not guessed. Horizontally: two 72px arrows, three
   1.5rem gaps, the panel at up to 26rem, and the stage's own padding — 42rem
   covers it with room to spare. Vertically: the top bar plus stage padding is
   about 7.75rem, so 9rem is safe. The rem caps stop the box growing absurdly on a
   very large monitor. */
.lightbox--side .lightbox__figure {
  position: relative;
  width:  min(60rem, calc(100vw - 42rem));
  height: min(48rem, calc(100vh - 9rem));
}
/* Absolutely positioned with inset: 0, not width/height: 100%. On this element a
   percentage width resolved but the matching percentage height did not — the
   image kept its natural height and ran off the bottom of the box. `inset` takes
   its size from the containing block directly, with no percentage to resolve, so
   the image is exactly the box and `contain` letterboxes inside it. */
.lightbox--side .lightbox__figure img {
  position: absolute;
  inset: 0;
  /* Both needed. `auto` on an absolutely positioned replaced element means its
     intrinsic size and the offsets get ignored, so it drew at full resolution.
     Out of flow, these percentages resolve against the positioned ancestor's
     padding box, which is the fixed square. */
  width: 100%; height: 100%;
  max-width: none; max-height: none;
  object-fit: contain;
}
.lightbox--side .lightbox__info {
  width: clamp(20rem, 27vw, 26rem);
  align-self: center;
  padding: var(--space-6);
  border: 1px solid var(--rule-dark);
  border-radius: var(--radius);
  text-align: left;
}

/* --- light theme -----------------------------------------------------------
   Ivory ground for the floor plans: they are white line drawings and a dark
   surround fights them. Colours follow the contrast rule — deep gold on light,
   never true gold. */
.lightbox--light { background: var(--ivory); color: var(--ink); }
.lightbox--light .lightbox__bar { border-bottom-color: var(--rule); }
.lightbox--light .lightbox__caption { border-top-color: var(--rule); }
.lightbox--light .lightbox__count { color: var(--gold-deep); }
.lightbox--light .lightbox__close,
.lightbox--light .lightbox__nav {
  color: var(--ink);
  border-color: var(--rule);
}
.lightbox--light .lightbox__close:hover,
.lightbox--light .lightbox__nav:hover {
  color: var(--gold-deep);
  border-color: var(--gold-deep);
}
.lightbox--light .lightbox__info {
  background: var(--surface);
  border-color: var(--rule);
}
.lightbox--light .lightbox__meta li { border-top-color: var(--rule); }
.lightbox--light .lightbox__hint { color: var(--muted); }
.lightbox--light :focus-visible { outline-color: var(--gold-deep); }
/* max-width: none overrides the base `p` measure of 62ch. Without it the title's
   box starts at the left edge and the caption's text-align: center only centres
   the text inside that narrower box, so it sits noticeably off-centre. */
.lightbox__title {
  font-family: var(--font-display); font-size: var(--display-sm);
  margin: 0; max-width: none;
}
/* Floor plan titles lead with a unit name like "A11", and the display face's
   old-style figures mangle those — see .plan-card__name. Callers whose titles
   contain digits opt into the body face. */
.lightbox--numeric .lightbox__title {
  font-family: var(--font-body);
  font-size: var(--text-lg);
  font-weight: 600;
  letter-spacing: 0.02em;
  font-variant-numeric: lining-nums tabular-nums;
}
.lightbox__hint {
  margin: var(--space-2) 0 0;
  max-width: none;
  font-size: var(--text-xs);
  letter-spacing: 0.04em;
  color: var(--muted-dark);
}


/* On narrow screens the side arrows would squeeze the image to nothing, so
   they move below it into a row of their own. */
@media (max-width: 40rem) {
  .lightbox__stage { grid-template-columns: 1fr; padding: var(--space-3); }
  /* grid-column: 1 on all three is load-bearing. Row 2 holds two items
     (prev, next) with no explicit column, so auto-placement was giving each
     its own column — creating an implicit second column the 1fr explicit
     column then had to shrink to make room for. That squeezed the image
     into a narrower left-hand strip instead of the full screen width.
     Pinning all three to the single explicit column keeps that column at
     its full 1fr width; justify-self still spreads the two buttons to its
     left and right edges within that one shared column. */
  .lightbox__nav--prev { grid-row: 2; grid-column: 1; justify-self: start; }
  .lightbox__nav--next { grid-row: 2; grid-column: 1; justify-self: end; }
  .lightbox__figure { grid-row: 1; grid-column: 1; }

  .lightbox__nav { min-width: 3.25rem; min-height: 3.25rem; font-size: 2rem; }
}

/* Below 52rem there is no room for plan + two 72px arrows + panel on one line, so
   the panel drops underneath. Tablets keep the side-by-side layout: the plan gets
   narrower but stays legible, and the arrows stay next to it. */
@media (max-width: 52rem) {
  /* Content-sized rows, and the stage scrolls if the three of them do not fit.
     A 1fr row for the plan looked right but the image escaped it and the panel
     landed on top of the drawing; with auto rows an overlap is impossible by
     construction, and scrolling on a phone is expected anyway. */
  /* max-content rows with align-content: start, NOT auto rows. The stage has a
     definite height, and an `auto` row was still being compressed to fit it —
     the plan then overflowed its row and the panel drew on top of the drawing.
     max-content refuses to shrink and align-content: start stops the tracks
     being stretched, so the rows are exactly their content and the stage
     scrolls when the three of them do not fit. Scrolling on a phone is fine;
     a panel over the plan is not.

     Two columns so the arrows can sit next to each other in row 2 rather than
     being placed in the same cell. */
  .lightbox--side .lightbox__stage {
    grid-template-columns: 1fr 1fr;
    grid-template-rows: max-content max-content max-content;
    align-content: start;
    align-items: start;
    justify-content: stretch;
    overflow-y: auto;
  }
  /* Undo the fixed square box and the stretch the side-by-side layout needs. */
  .lightbox--side .lightbox__figure {
    grid-column: 1 / -1;
    grid-row: 1;
    width: 100%;
    height: auto;
    align-self: start;
  }

  /* A column each, meeting in the middle — a pair under the plan rather than one
     at each screen edge. */
  .lightbox--side .lightbox__nav--prev {
    grid-column: 1; grid-row: 2;
    justify-self: end; margin-right: var(--space-3);
  }
  .lightbox--side .lightbox__nav--next {
    grid-column: 2; grid-row: 2;
    justify-self: start; margin-left: var(--space-3);
  }

  .lightbox--side .lightbox__info {
    grid-column: 1 / -1;
    grid-row: 3;
    width: 100%;
    margin-top: var(--space-4);
  }

  /* position: static is the important one. The side-by-side layout takes the
     image out of flow to fill its fixed box, and that has to be undone here:
     an out-of-flow child contributes no height, so the figure collapsed to zero,
     its grid row collapsed with it, and the plan was drawn floating over both the
     arrows and the panel. Putting it back in flow gives the row its height.

     Height is then a plain share of the viewport. The panel's height is not
     knowable in advance — it grows with the number of attributes — so reserving
     space for it by formula was always a guess, and guessing low clipped the plan. */
  .lightbox--side .lightbox__figure img {
    position: static;
    inset: auto;
    max-width: 100%;
    max-height: 55vh;
    width: auto;
    height: auto;
    object-fit: contain;
  }
}

/* Scroll lock while the viewer is open. */
body[data-lightbox-open="true"] { overflow: hidden; }

/* ---------------------------------------------------------------------------
   17  MOTION PREFERENCES
   ------------------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .teaser:hover .teaser__media img { transform: none; }
  .gallery-item:hover img { transform: none; }
}
