/* Villa Il Mulino, catalogue system.
   Brand tokens preserved from v1: cool gallery paper, deep Ionian ink, one Aegean blue.
   Dual mode. Radius 0 throughout. No beige, no brass. */

/* ---------- light and dark ----------

   The reader can override the system, so three states have to be expressible:
   follow the system, force light, force dark. An inline script in <head> writes
   data-theme on <html> when, and only when, a choice has been stored, so with no
   choice, or with JavaScript off, none of it applies and the system decides
   through the media query below, as it always did.

   Every colour is written ONCE. The dark values live in --d-* on :root, inert
   until something points at them, and switching a theme re-points the names
   rather than restating the colours. That way a change to a dark colour is a
   change in one place, and the two ways of arriving at dark cannot drift apart.
   Only the mapping is repeated, and a mapping that goes stale shows up as an
   obviously missing colour rather than as a subtly wrong one.

   'color-scheme' is set alongside, so form controls, scrollbars and the like
   follow the chosen theme instead of the system's. */

:root {
  color-scheme: light dark;

  --paper:   #F4F4F0;
  --paper-2: #EAEBE5;
  --ink:     #14191E;
  --muted:   #4B5660;
  --line:    rgba(20, 25, 30, 0.18);
  --line-2:  rgba(20, 25, 30, 0.09);
  --accent:  #2A5C8A;
  --frame:   #DFE1DA;

  --d-paper:   #101519;
  --d-paper-2: #161C21;
  --d-ink:     #E9EDEA;
  --d-muted:   #9AA7B0;
  --d-line:    rgba(233, 237, 234, 0.20);
  --d-line-2:  rgba(233, 237, 234, 0.10);
  --d-accent:  #8FB8DC;
  --d-frame:   #1D242A;
  --d-sea:     #16242E;
  --d-land:    #232B31;
  --d-coast:   #4A6376;

  /* Two voices, and only two: Cormorant Garamond sets the display, Hanken
     Grotesk sets everything else. GFS Didot is Greek-only (its unicode-range
     never touches Latin), so it colours one ornament word and nothing more.
     IBM Plex Mono is gone: it was a third voice doing a job Hanken already
     does, because every Hanken digit is 560/1000 em wide, tabular by
     construction. Cormorant's figures are proportional, so where numerals must
     line up in a serif they ask for its real 'tnum' feature. */
  --serif: 'Cormorant Garamond', Georgia, serif;
  --sans:  'Hanken Grotesk', -apple-system, 'Segoe UI', sans-serif;
  --greek: 'GFS Didot', 'Cormorant Garamond', Georgia, serif;

  /* one type scale, rather than thirty ad hoc clamps */
  --fs-index: 0.74rem;   /* the caption number, the counter, the coordinates */
  --fs-cap:   0.9rem;    /* the frame caption. --cap-h on .strip depends on it */
  --fs-micro: 0.78rem;   /* eyebrows, map labels, the scale bar */
  --fs-note:  0.95rem;   /* captions, footnotes, form labels */
  --fs-body:  1.18rem;   /* running copy */
  --fs-lede:  clamp(1.16rem, 1.35vw, 1.36rem);
  --fs-h3:    clamp(1.55rem, 2.5vw, 2.2rem);
  --fs-h2:    clamp(2.05rem, 3.7vw, 3.15rem);
  /* the map's own three voices: the estate, the harbour it is measured from, and
     the islands written across their own land */
  --fs-map-here: 1.3rem;
  --fs-map-key:  0.92rem;
  --fs-map-area: 0.86rem;

  --gutter: clamp(20px, 5vw, 76px);
  --pad:    clamp(4.5rem, 9vw, 8.5rem);
  --maxw:   1360px;

  /* the map: sea reads as a wash of the one brand blue, land as the page */
  /* the fallback ratio only; the real one arrives per view as --fr-ar, because
     the island and bay views are no longer the same shape */
  --map-ar: 1.4;
  --sea:    #CFDEEA;
  --land:   #F8F8F4;
  --coast:  #8AA6BC;

  --on-photo: #F5F5F1;
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
}

/* the system's choice, unless the reader has overridden it */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper:   var(--d-paper);
    --paper-2: var(--d-paper-2);
    --ink:     var(--d-ink);
    --muted:   var(--d-muted);
    --line:    var(--d-line);
    --line-2:  var(--d-line-2);
    --accent:  var(--d-accent);
    --frame:   var(--d-frame);
    --sea:     var(--d-sea);
    --land:    var(--d-land);
    --coast:   var(--d-coast);
  }
}

/* the reader's own choice, whatever the system says */
:root[data-theme="dark"] {
  color-scheme: dark;
  --paper:   var(--d-paper);
  --paper-2: var(--d-paper-2);
  --ink:     var(--d-ink);
  --muted:   var(--d-muted);
  --line:    var(--d-line);
  --line-2:  var(--d-line-2);
  --accent:  var(--d-accent);
  --frame:   var(--d-frame);
  --sea:     var(--d-sea);
  --land:    var(--d-land);
  --coast:   var(--d-coast);
}
:root[data-theme="light"] { color-scheme: light; }

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

body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 1.15rem;
  line-height: 1.66;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: clip;
}

img { display: block; max-width: 100%; height: auto; }
a { color: inherit; text-decoration: none; }
p { text-wrap: pretty; }
::selection { background: var(--accent); color: var(--paper); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

h1, h2, h3 {
  font-family: var(--serif);
  font-weight: 400;
  line-height: 1.1;
  letter-spacing: -0.004em;
}

.container { max-width: var(--maxw); margin-inline: auto; padding-inline: var(--gutter); }
[id] { scroll-margin-top: 80px; }

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

/* ---------- nav ---------- */

.nav {
  position: fixed; top: 0; left: 0; right: 0; z-index: 40;
  height: 64px;
  display: flex; align-items: center; justify-content: space-between;
  gap: 24px;
  padding-inline: var(--gutter);
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  border-bottom: 1px solid var(--line-2);
  transition: background 0.45s var(--ease), border-color 0.45s var(--ease);
}

/* while the hero fills the screen the masthead rides on the photograph */
.nav.over-hero {
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-bottom-color: transparent;
}
.nav.over-hero .nav-brand svg,
.nav.over-hero .nav-brand span,
.nav.over-hero .nav-theme,
.nav.over-hero .nav-links a { color: var(--on-photo); }
.nav.over-hero .nav-links a.nav-cta { border-bottom-color: var(--on-photo); }
.nav.over-hero .nav-theme { border-color: color-mix(in srgb, var(--on-photo) 42%, transparent); }

.nav-right { display: flex; align-items: center; gap: clamp(14px, 2.2vw, 30px); flex: none; }

/* The theme switch. Half a disc, which is the plainest way to say "this is the
   light and dark control" without a sun, a moon or a toggle track: it simply
   turns to show how much of the page is dark. It is a real button with a label
   that says what pressing it WILL do, not what the page currently is, because
   that is the question someone reaching for it is actually asking. */
.nav-theme {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px; height: 36px;
  padding: 0;
  font: inherit;
  color: var(--muted);
  background: none;
  border: 1px solid var(--line);
  cursor: pointer;
  transition: color 0.25s var(--ease), border-color 0.25s var(--ease),
              background 0.25s var(--ease);
}
.nav-theme svg { transition: transform 0.5s var(--ease); }
.nav-theme:hover { color: var(--ink); border-color: var(--ink); }
:root[data-mode="dark"] .nav-theme svg { transform: rotate(180deg); }
/* Without scripting there is nothing to store a choice in and nothing to apply
   it with, and the media query above is already following the system correctly,
   so the control would be a button that does nothing. Better absent than dead.
   Written as the negative case on purpose: styling the button through a
   :root.js prefix would have out-specified '.nav.over-hero .nav-theme' and left
   it grey on the photograph. */
:root:not(.js) .nav-theme { display: none; }

@media (prefers-reduced-motion: reduce) {
  .nav-theme svg { transition: none; }
}

.nav-brand { display: inline-flex; align-items: center; gap: 10px; flex: none; }
.nav-brand svg { color: var(--ink); flex: none; }
.nav-brand span {
  font-family: var(--serif);
  font-size: 1.24rem;
  letter-spacing: 0.005em;
  white-space: nowrap;
}

.nav-links { display: flex; align-items: center; gap: clamp(14px, 2.2vw, 30px); flex: none; min-width: 0; }
.nav-links a {
  font-size: 0.82rem;
  letter-spacing: 0.02em;
  color: var(--muted);
  white-space: nowrap;
  transition: color 0.25s var(--ease);
}
.nav-links a:hover { color: var(--ink); }
.nav-links a.nav-cta {
  color: var(--ink);
  border-bottom: 1px solid var(--ink);
  padding-bottom: 2px;
}

@media (max-width: 720px) {
  .nav-links a:not(.nav-cta) { display: none; }
}

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

/* Full bleed. The photo sits behind the fixed masthead and fills the screen. */
.hero {
  position: relative;
  isolation: isolate;
  min-height: 100dvh;
  display: grid;
  align-items: end;
}

.hero-media { position: absolute; inset: 0; z-index: -2; }
.hero-media img {
  width: 100%;
  height: 100%;
  max-width: none;
  object-fit: cover;
  background-color: var(--ph, var(--frame));
}

.hero-scrim {
  position: absolute; inset: 0; z-index: -1;
  background:
    linear-gradient(to bottom, rgba(9, 14, 18, 0.48) 0%, rgba(9, 14, 18, 0) 22%),
    linear-gradient(to top, rgba(9, 14, 18, 0.84) 0%, rgba(9, 14, 18, 0.62) 16%,
                            rgba(9, 14, 18, 0.34) 34%, rgba(9, 14, 18, 0) 62%);
}

.hero-copy {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding: 0 var(--gutter) clamp(46px, 9vh, 112px);
  color: var(--on-photo);
  /* the scrim alone cannot be trusted: the copy can land on bright stone */
  text-shadow: 0 1px 2px rgba(9, 14, 18, 0.42), 0 2px 28px rgba(9, 14, 18, 0.38);
}
.hero-copy > * + * { margin-top: 1rem; }

.hero-place {
  font-size: 0.79rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(245, 245, 241, 0.94);
}

.hero h1 {
  /* "Villa Il Mulino" is 5.787em in Cormorant Garamond, so at the old 7.4rem
     ceiling it filled 57 per cent of the measure. 8.6rem takes it to 66 per cent.
     It could not wrap before 13.05rem, so the ceiling is a taste limit, not a fit
     limit, and the 3.2rem floor still renders 296px into a 320px viewport. */
  font-size: clamp(3.2rem, 9.2vw, 8.6rem);
  line-height: 0.94;
  letter-spacing: -0.018em;
}

.hero-sub {
  font-size: clamp(1.08rem, 1.75vw, 1.5rem);
  color: rgba(245, 245, 241, 0.94);
  /* The deck is 26.5em, which is 47.4ch in Hanken Grotesk and 48.1ch in the
     Segoe UI fallback, so any cap of 50ch or more can never force a break in any
     of the faces. The old 36ch gave 20.2em of box to 26.5em of text, which is
     exactly why "private sea access" was orphaned onto a second line.
     Deliberately not text-wrap: balance: balance would move "Ionian Sea," down
     to even the two lines, splitting the proper noun. */
  max-width: 54ch;
}

/* ---------- price line ---------- */

.price-line {
  display: flex; flex-direction: column; gap: 4px;
  margin: 1.25rem 0 1.15rem;
}
.price-line b {
  font-family: var(--serif);
  font-size: clamp(2rem, 3.4vw, 3rem);
  font-weight: 400;
  letter-spacing: -0.012em;
}
/* A flex item of a column flex container, so it is blockified and text-wrap
   applies. Balance keeps the two lines of the sub-line even where the box is
   too narrow to hold it on one, at desktop widths 861 to 1050 and on phones. */
.price-line span { font-size: 1rem; color: var(--muted); text-wrap: balance; }

/* ---------- section furniture ---------- */

.section { padding-block: var(--pad); }
.section + .section { padding-top: 0; }
/* a neighbour's bottom padding is already enough separation */
.section + .gallery, .gallery + .section, .gallery + .gallery { padding-top: 0; }
/* the figures band belongs to the hero, not to the page below it */
.hero + .section { padding-top: clamp(2.6rem, 5vw, 5rem); }

/* The cap used to sit on the wrapper, where 52ch resolved against the wrapper's
   inherited 1.15rem and handed the same 536px to both children. For the paragraph
   that is fine; for a 16.3em serif headline it is only 10.8em of box, so "Two
   buildings, four residences, one estate" broke mid phrase. Each child now
   carries its own cap, measured in its own font. */
.lede { max-width: none; }
.lede h2 {
  font-size: var(--fs-h2);
  /* 1ch in Cormorant Garamond is 0.477em, so 38ch clears the longest headline
     here at every viewport from 580px up, and in the serif fallbacks too. */
  max-width: 38ch;
  text-wrap: balance;
}
.lede p {
  margin-top: 1.05rem;
  color: var(--muted);
  font-size: var(--fs-lede);
  max-width: 54ch;
}

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

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

.gallery { padding-block: clamp(2.4rem, 4.4vw, 4.4rem); }
.hero + .gallery { padding-top: clamp(5rem, 7vw, 7rem); }

.gal-head {
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
  margin-bottom: clamp(14px, 1.8vw, 24px);
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 20px 32px;
}
.gal-title h2 { font-size: clamp(1.5rem, 2.4vw, 2.1rem); text-wrap: balance; }
.gal-title p {
  margin-top: 0.45rem;
  color: var(--muted);
  max-width: 48ch;
  font-size: clamp(1.04rem, 1.15vw, 1.16rem);
  /* two line decks, which is what balance is for; the cap is in ch so the
     chosen breaks do not move when the type scales */
  text-wrap: balance;
}
.gal-nav { display: flex; align-items: center; gap: clamp(12px, 1.4vw, 20px); flex: none; }

/* The arrow overlay is anchored to this box, so the caption band has to be
   stated: 14px of margin plus one line of --fs-cap text at the inherited 1.66.
   Two preconditions make that exact, and both are load bearing: the caption is
   one line (the longest, "The main living room, towards the terrace", still fits
   the narrowest plate), and .track keeps align-items: center on desktop so every
   photograph's mid line falls at the same height whatever its shape. Change
   either and the arrows drift off the pictures by half a line. */
.strip {
  position: relative;
  --cap-h: calc(14px + var(--fs-cap) * 1.66);
}

/* The loop closes with a cut, not a slide. This scrim covers the frames for the
   moment the track is repositioned, so the wrap reads as a dissolve rather than
   a flicker. It is one strip sized rectangle: fading the track itself would ask
   the compositor for a layer about forty five thousand pixels wide. The 0.13s
   below is mirrored by the 130 in catalogue-v8.js and the two must stay equal. */
.strip::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: var(--paper);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.22s var(--ease);
}
.strip.wrapping::after { opacity: 1; transition-duration: 0.13s; }

.track {
  /* What a photograph shares the viewport with: the 64px fixed masthead, this
     track's own 8px of block padding, and the caption under the frame (14px of
     margin plus one --fs-cap line at 1.66). That is absolute pixels, not a
     proportion, which is why a flat 88vh overflowed on every screen shorter than
     about 940px while leaving 179px unused on a tall desktop.
     svh, never dvh: the small viewport is a constant, so a mobile toolbar
     collapsing mid scroll can never resize a frame that is already inside a
     mandatory scroll-snap track. */
  --furniture: calc(64px + 8px + 14px + var(--fs-cap) * 1.66);
  --frame-h: min(calc(100svh - var(--furniture)), 1240px);
  --frame-w: 94vw;
  display: flex;
  align-items: center;
  gap: clamp(14px, 1.8vw, 30px);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: var(--gutter);
  /* inset from the viewport edge so an iOS back-swipe is not stolen by the track */
  padding-inline: var(--gutter);
  padding-block: 4px;
  scrollbar-width: none;
}
.track::-webkit-scrollbar { display: none; }
.track.dragging { cursor: grabbing; scroll-snap-type: none; }
.track.dragging img { pointer-events: none; }

.plate {
  flex: none;
  scroll-snap-align: center;
  position: relative;
}

/* The height is stated, never left to the image's intrinsic size, so a frame
   that has not loaded yet still occupies its full box. Leaving it auto let every
   lazy plate collapse to the width of its caption; when the strip scrolled into
   view the photos loaded, the track grew, and mandatory snap dropped the reader
   partway along it. min() reproduces exactly what max-width plus max-height used
   to compute once loaded, so nothing is ever distorted or wider than the screen. */
.plate img {
  aspect-ratio: var(--ar, 1.5);
  height: min(var(--frame-h), calc(var(--frame-w) / var(--ar, 1.5)));
  width: auto;
  max-width: var(--frame-w);
  background-color: var(--ph, var(--frame));
}

.plate figcaption {
  display: flex; align-items: baseline; gap: 10px;
  margin-top: 14px;
  font-size: var(--fs-cap);
  color: var(--muted);
  opacity: 0;
  transition: opacity 0.35s var(--ease);
}
.plate.is-current figcaption { opacity: 1; }
.plate figcaption b {
  font-size: var(--fs-index);
  font-weight: 500;
  color: var(--accent);
  letter-spacing: 0.09em;
  font-variant-numeric: tabular-nums;
  flex: none;
}

/* empty slots waiting on the owner's four remaining photographs */
.plate.pending .slot {
  height: var(--frame-h);
  aspect-ratio: 3 / 2;
  border: 1px solid var(--line);
  display: grid;
  place-content: center;
  gap: 6px;
  text-align: center;
  padding: 20px;
  background: var(--paper-2);
}
.plate.pending .slot b {
  font-size: var(--fs-index);
  font-weight: 500;
  letter-spacing: 0.09em;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
}
.plate.pending .slot span { font-size: 0.82rem; color: var(--muted); }

.gal-count {
  font-size: var(--fs-index);
  color: var(--muted);
  letter-spacing: 0.07em;
  flex: none;
  font-variant-numeric: tabular-nums;
}

.gal-bar { width: clamp(70px, 12vw, 190px); height: 1px; background: var(--line-2); position: relative; }
.gal-bar i {
  position: absolute; inset-block: 0; left: 0;
  display: block; background: var(--accent);
  transform-origin: left center;
  transform: scaleX(0);
  transition: transform 0.2s linear;
  width: 100%;
}

/* ---------- carousel arrows, overlaid on the frames ---------- */

/* The box is the photographic band only: the track's 4px of block padding at the
   top, and at the bottom that padding plus the caption line, so align-items
   center puts the buttons on the mid line of the pictures.

   Horizontally they sit on the page gutter, the same margin the heading, the
   counter and every other edge use, pushed a little further in so they overlap
   the frame rather than graze it. A fixed position, not the current frame's own
   edge: shapes here run from 16:9 to portrait, so an edge tracking button would
   slide about every time the shape changed, which looks broken during a drag. */
.gal-btns {
  position: absolute;
  inset: 4px 0 calc(4px + var(--cap-h)) 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-inline: calc(var(--gutter) + clamp(8px, 1vw, 20px));
  pointer-events: none;
}
/* a drag must not light up the arrows it passes under */
.track.dragging ~ .gal-btns { pointer-events: none; }

/* One control system: this is the same frosted plate as the lightbox buttons,
   which sit on photographs too. See .lb-btn, which shares it. */
.gal-btn {
  pointer-events: auto;
  font-family: inherit;
  width: 46px; height: 46px;
  display: grid; place-items: center;
  border: 1px solid var(--line);
  background: color-mix(in srgb, var(--paper) 84%, transparent);
  backdrop-filter: blur(10px) saturate(130%);
  -webkit-backdrop-filter: blur(10px) saturate(130%);
  box-shadow: 0 2px 18px rgba(9, 14, 18, 0.18);
  color: var(--ink);
  cursor: pointer;
  font-size: 1.05rem;
  line-height: 1;
  transition: background 0.22s var(--ease), border-color 0.22s var(--ease), transform 0.1s var(--ease);
}
.gal-btn:hover {
  background: color-mix(in srgb, var(--paper-2) 92%, transparent);
  border-color: var(--ink);
}
.gal-btn:active { transform: scale(0.96); }

@media (max-width: 899.98px) {
  .gal-head { flex-direction: column; align-items: stretch; }
  .gal-nav { justify-content: flex-start; }
  /* --frame-h is not overridden here any more: viewport minus furniture is
     already the right answer on a handset, where the same masthead and the same
     caption have to fit, and the 1240px cap never binds. --frame-w stays at
     96vw, whose clear edge either side keeps an iOS back-swipe off the
     photograph. */
  .track { --frame-w: 96vw; align-items: flex-start; }
  .plate.pending .slot { height: auto; width: 96vw; }
  .plate figcaption { opacity: 1; }
  .gal-btns { display: none; }
}

/* ---------- the two villas ---------- */

/* stacked, so each villa is shown at the same scale as a carousel frame
   instead of two thumbnails side by side */
.pair {
  display: grid;
  gap: clamp(46px, 7vw, 100px);
  margin-top: clamp(38px, 5vw, 70px);
}
.villa-name {
  font-size: var(--fs-h3);
  text-wrap: balance;
  padding-bottom: clamp(10px, 1.2vw, 16px);
  margin-bottom: clamp(14px, 1.6vw, 22px);
  border-bottom: 1px solid var(--line);
}
.villa figure img { width: 100%; background-color: var(--ph, var(--frame)); }
.villa-note {
  margin-top: clamp(14px, 1.8vw, 24px);
  color: var(--muted);
  /* 62ch of its own 1.32rem measured 78.5 characters, past the 60 to 75 band.
     54ch is 68 characters, the same measure as .lede p. */
  max-width: 54ch;
  font-size: var(--fs-lede);
}

/* ---------- key figures band ---------- */

.figures {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.fig {
  padding: clamp(20px, 2.4vw, 32px) clamp(10px, 1.6vw, 24px);
}
.fig + .fig { border-left: 1px solid var(--line-2); }
.fig b {
  display: block;
  font-family: var(--serif);
  font-size: clamp(1.8rem, 3.2vw, 3.2rem);
  font-weight: 400;
  line-height: 1;
  letter-spacing: -0.012em;
  font-variant-numeric: tabular-nums;
}
.fig span {
  display: block;
  margin-top: 0.55rem;
  font-size: clamp(0.83rem, 1.05vw, 1rem);
  letter-spacing: 0.05em;
  color: var(--muted);
}

@media (max-width: 899.98px) {
  .figures { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .fig:nth-child(n + 4) { border-top: 1px solid var(--line-2); }
  .fig:nth-child(3n + 1) { border-left: 0; }
}
@media (max-width: 520px) {
  .figures { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .fig:nth-child(n + 3) { border-top: 1px solid var(--line-2); }
  .fig:nth-child(odd) { border-left: 0; }
  .fig:nth-child(even) { border-left: 1px solid var(--line-2); }
}

/* ---------- particulars schedule ---------- */

.schedule {
  display: grid;
  grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr);
  gap: clamp(32px, 5vw, 84px);
  align-items: start;
}
.schedule-copy h2 { font-size: clamp(1.9rem, 3.4vw, 2.9rem); text-wrap: balance; }
/* 42ch set this as three lines ending on the single word "programme." */
.schedule-copy p { margin-top: 1.05rem; color: var(--muted); max-width: 46ch; font-size: 1.06rem; }

.schedule-list { display: grid; gap: clamp(24px, 3vw, 40px); }
.sched-group h4 {
  font-family: var(--sans);
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  padding-bottom: 0.8rem;
  border-bottom: 1px solid var(--line);
  margin-bottom: 1.05rem;
}
.sched-group dl > div { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
.sched-group dl > div + div { margin-top: 0.8rem; }
.sched-group dt { color: var(--muted); font-size: 1.03rem; }
.sched-group dd {
  font-size: var(--fs-note);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

@media (min-width: 861px) {
  .schedule-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .sched-group:first-child { grid-column: 1 / -1; }
  /* keep the spanning group's rows aligned with the column beneath it instead
     of stretching label and value to opposite ends of the page */
  .sched-group:first-child dl { max-width: calc(50% - clamp(12px, 1.5vw, 20px)); }
}
@media (max-width: 860px) { .schedule { grid-template-columns: 1fr; } }

/* ---------- setting ---------- */

.setting-cols {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(26px, 3.4vw, 56px);
  margin-top: clamp(34px, 4vw, 58px);
}
.setting-cols h4 {
  font-family: var(--sans);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  padding-bottom: 0.85rem;
  border-bottom: 1px solid var(--line);
  margin-bottom: 1.15rem;
}
.setting-cols ul { list-style: none; }
.setting-cols li { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
.setting-cols li + li { margin-top: 0.85rem; }
.setting-cols li span { color: var(--muted); font-size: 1.03rem; }
.setting-cols li b {
  font-size: var(--fs-note);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

/* The .fineprint rules lived here and went with the Golden Visa and non-dom
   footnote the owners asked to be taken off the page, 5 August 2026. Nothing
   on the page uses the class now. If any small print ever comes back, it wants
   its own rules rather than these: they were written for a block of legal
   citations sitting under the price, which is a particular problem. */

.setting-note {
  margin-top: clamp(30px, 3.6vw, 48px);
  padding-top: 1.4rem;
  border-top: 1px solid var(--line-2);
  color: var(--muted);
  font-size: 1.04rem;
  /* 66ch measured 83.5 characters, the widest line on the page */
  max-width: 58ch;
}
.setting-note .coords {
  display: block;
  margin-top: 0.7rem;
  font-size: var(--fs-index);
  letter-spacing: 0.09em;
  font-variant-numeric: tabular-nums;
}

@media (max-width: 860px) { .setting-cols { grid-template-columns: 1fr; } }

/* ---------- the map ---------- */

/* The drawing is inline SVG built from OpenStreetMap coastline; the place names,
   the estate marker and the scale bar are HTML laid over it. SVG text scales with
   the viewBox, so a label sized for a desktop map would be about five pixels on a
   phone. As HTML they hold an ordinary type size at every width. That alignment
   only works while the plate matches the viewBox exactly, hence --fr-ar, which
   each view supplies from its own viewBox.

   The boats ARE inside the SVG, and that is the right way round: a name is
   typography and must hold its size, a boat is geography and should shrink with
   the water it sits on. */

/* The map is NOT in the text column, and is deliberately the only thing on the
   page that is not. Inside it, the plate stopped at 1206 px however large the
   screen, because the column caps at 1360 px, so a 27 inch monitor showed the
   same small map as a laptop. A chart of 87 kilometres wants the room. It keeps
   its own tighter gutter for the same reason, and its own cap so it does not run
   the full width of a very wide display and become unreadable at the other end.
   build.py closes the container before it and reopens it after, rather than
   pulling it out with negative margins: 100vw includes the scrollbar, and that
   is a horizontal overflow waiting to happen. */
.map {
  margin-top: clamp(30px, 4vw, 52px);
  max-width: 1680px;
  margin-inline: auto;
  padding-inline: clamp(12px, 2.5vw, 44px);
}

/* The hint on the left, the two scale buttons on the plate's right edge like a
   figure's own control. There used to be a paragraph here instead, which said
   again what the section's opening lines had already said twenty lines above,
   and read as neither a title nor a caption because it was neither. */
.map-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 10px 32px;
  margin-bottom: clamp(12px, 1.5vw, 18px);
}

/* NOTHING ABOUT A MARKER SAYS IT OPENS A PHOTOGRAPH. A reader who never hovers,
   or who is on a touch screen and cannot, sees a map of dots and never learns
   there is anything behind them. So the map says so once, and stops saying it
   the moment it has been understood.

   The wording follows the input, because "tap" is wrong on a laptop and "hover"
   is impossible on a phone; @media (hover) answers that without scripting. It is
   aria-hidden because a screen reader is already told: every marker is a real
   button carrying the place's name and its note. */
.map-hint {
  font-size: var(--fs-index);
  letter-spacing: 0.09em;
  color: var(--muted);
  opacity: 0.85;
  transition: opacity 0.5s var(--ease), visibility 0.5s;
}
.hint-touch { display: none; }
@media (hover: none) {
  .hint-pointer { display: none; }
  .hint-touch { display: inline; }
}
.map.is-taught .map-hint { opacity: 0; visibility: hidden; }


/* Coordinates and the way out, under the plate. The link is an ordinary
   outbound anchor, NOT an embedded map: an embed would fetch tiles for every
   viewer on load and hand a third party their IP and this property's
   coordinates, unasked, on a deliberately gated listing. This sends nothing
   until someone chooses to follow it. */
.map-cap {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px 24px;
  margin-top: clamp(10px, 1.2vw, 15px);
  font-size: var(--fs-index);
  letter-spacing: 0.09em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.map-link {
  letter-spacing: 0.06em;
  color: var(--muted);
  border-bottom: 1px solid var(--line);
  padding-bottom: 2px;
  transition: color 0.22s var(--ease), border-color 0.22s var(--ease);
}
.map-link:hover { color: var(--ink); border-bottom-color: var(--ink); }

/* The two scales are the map's only control, so they are sized to be seen and
   read as a pair of choices rather than as a footnote. */
.map-switch { display: flex; gap: 0; flex: none; border: 1px solid var(--line); }
.map-btn {
  font-family: inherit;
  font-size: 0.95rem;
  letter-spacing: 0.05em;
  line-height: 1;
  padding: 14px 26px;
  min-height: 50px;
  border: 0;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.2s var(--ease), color 0.2s var(--ease);
}
.map-btn + .map-btn { border-left: 1px solid var(--line); }
.map-btn:hover { background: var(--paper-2); color: var(--ink); }
.map-btn.is-on { background: var(--ink); color: var(--paper); }

/* The plate takes its shape from whichever view is showing, because the two are
   no longer the same shape: the island view was extended south to reach Ithaca
   and Kefalonia rather than zoomed out, so it is taller than the bay. The ratio
   arrives as --fr-ar on each frame, written by build.py from that view's own
   viewBox, and the visible frame is the one in flow, so the plate simply follows
   it. This must keep matching the viewBox exactly: the names are positioned as a
   percentage of the plate, so letterboxing the drawing inside it would leave
   every one of them floating off the coast it names. */
/* THE PLATE FILLS THE COLUMN. It must, and this was got wrong once: bounding it
   by viewport height so the near-square island view would fit on screen drew that
   view 750 px wide instead of 1214 px, and since the type is fixed-size and the
   drawing is not, the whole map came out small and coarse. The coastline detail
   was identical; it was simply rendered at 62 per cent. The island view is
   allowed to be deeper than the window instead, and the reader scrolls, which is
   what a large map is for. Scale stays exactly what it was before Ithaca and
   Kefalonia were added: same width, same kilometres across, so the same metres
   per pixel, and the extra ground arrives as extra height.

   What must NOT change is the ratio: the names are positioned as a percentage of
   the plate, so letterboxing the drawing inside it would slide every one of them
   off the coast it names. */
.map-plate {
  position: relative;
  border: 1px solid var(--line);
  overflow: hidden;
  /* so how many names the map carries can be asked of the PLATE rather than of
     the window: the plate is bounded by viewport height, so on a wide short
     screen it is far narrower than the page and a window-width query would
     cheerfully draw sixteen names into 750 px */
  container-type: inline-size;
}
.map-frame { position: relative; aspect-ratio: var(--fr-ar, 1.4); }

/* TWO FRAMES ANSWER TO "the island": a wide one and an upright one, and the
   plate's width picks between them. A map has to be composed for its frame. The
   wide view holds 87 km across, so at 364 px each fixed-size name covers 14 km of
   ground and four of seventeen can be drawn; the upright view holds 44 km in the
   one dimension a phone has, which halves that and carries eleven.

   The query is on inline-size only, which is what makes this safe: the plate's
   WIDTH does not depend on which frame is in flow, only its height does, so
   there is no loop between the choice and the thing it is chosen by. */
.map-frame[data-shape="tall"] { display: none; }
@container (max-width: 559.98px) {
  .map-frame[data-shape="wide"] { display: none; }
  .map-frame[data-shape="tall"] { display: block; }
}
/* last, so a hidden frame stays hidden whichever shape it is */
.map-frame[hidden] { display: none; }

.map-svg { width: 100%; height: 100%; display: block; }
.map-sea  { fill: var(--sea); }
.map-land path {
  fill: var(--land);
  stroke: var(--coast);
  stroke-width: 1;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

/* ---- what is drawn on the map, as opposed to written over it ----

   Boats under sail, and nothing else. Olives on the hillsides were tried and
   cut: at map scale a tree is a blob, and scattered over a chart they read as
   clip art rather than as anything about the place.

   These are inside the SVG, unlike the names, and that is the right way round: a
   name is typography and has to hold its size at every width, but a boat is
   geography and should shrink with the water it sits on. Positions come from the
   generator, which proves every one is in open water and fails the build rather
   than guess. The strokes do not scale, so the line stays a hairline on a phone
   and on a desktop alike, and they are drawn in the coastline's own tone so they
   read as part of the map's line work. */
.map-orns { color: var(--coast); }
.map-orn { fill: none; stroke: currentColor; stroke-width: 1.1;
           stroke-linecap: round; stroke-linejoin: round;
           vector-effect: non-scaling-stroke; }
.map-orn .orn-hull { fill: color-mix(in srgb, currentColor 30%, transparent); }
.map-orn .orn-sail { fill: color-mix(in srgb, var(--land) 88%, transparent); }
.map-orn .orn-wake { opacity: 0.5; }
.map-orn-boat  { opacity: 0.78; }

.map-marks { position: absolute; inset: 0; pointer-events: none; }

.map-place, .map-here, .map-area {
  position: absolute;
  left: calc(var(--xn) * 1%);
  top: calc(var(--yn) * 1%);
  display: flex;
  align-items: center;
  gap: 7px;
  white-space: nowrap;
  transform: translate(-50%, -50%);
}
/* anchor tells the label which side of its dot to sit on, so a name never runs
   off the plate or lies over the coast it is naming */
.map-place.map-e { transform: translate(-100%, -50%); }
.map-place.map-e .map-pin > i { margin-left: 7px; }
.map-place.map-w { transform: translate(0, -50%); }
/* 's': the dot on top and the name hanging below it, centred, over two lines if
   the name is given two. This exists for one situation, which no left or right
   choice can fix: two marks close enough that a name on either side of one lands
   on the other. Lefkada town and the Agia Mavra bridge are 1.2 km apart, about
   twenty pixels on the wide view, so the town drops its name out of the way and
   the bridge keeps the open water to the east that the town's name used to fill.
   The 2.5px is half the dot: with the pin stacked as a column its box starts at
   the dot's TOP, and what has to sit on the point is the dot's centre. */
.map-place.map-s { transform: translate(-50%, -2.5px); align-items: flex-start; }
.map-place.map-s .map-pin { flex-direction: column; align-items: center; gap: 6px; }
.map-place.map-s .map-pin > b { text-align: center; }
/* 'n' is the same idea upside down: the name sits ABOVE its dot, centred on it.
   Vasiliki uses it. Its dot is on a stretch of coast running north east, and a
   name set beside it lay along the shore rather than against it, which read as
   labelling the water. The box grows upward from the dot, so it is pinned by its
   BOTTOM edge, and the same half dot correction applies at that edge instead. */
.map-place.map-n { transform: translate(-50%, calc(-100% + 2.5px)); align-items: flex-end; }
.map-place.map-n .map-pin { flex-direction: column-reverse; align-items: center; gap: 6px; }
.map-place.map-n .map-pin > b { text-align: center; }
/* The one place the line-height 1 rule below is deliberately not wanted: it is
   there because a map label is a single line against a coastline, and this label
   is two. At 1 the two lines touch. */
.map-place.map-s .map-pin > b { line-height: 1.26; }

.map-place .map-pin > i {
  width: 5px; height: 5px;
  flex: none;
  background: var(--ink);
  opacity: 0.5;
  border-radius: 50%;
}
/* line-height 1 on every map label, and it is load bearing. Inheriting the body's
   1.66 gives a 12.5 px name a 21 px box, so names that are a comfortable 18 px
   apart on the drawing overlap as boxes and have to be pushed around or dropped.
   A label on a map is a single line against a coastline, not running copy. */
/* THE NAMES GROW WITH THE PLATE, above the size they used to be fixed at.
   cqw is one per cent of the plate, so past the width the map used to be capped
   at the type keeps its old proportion to the drawing instead of shrinking into
   it: on a 27 inch screen the map is now half as wide again, and fixed 12 px
   names would read as an afterthought on it. The clamp floor holds the old size
   on everything smaller, so nothing below a large desktop changes at all. */
.map-place .map-pin > b {
  font-weight: 400;
  font-size: clamp(var(--fs-micro), 1.03cqw, 1.05rem);
  line-height: 1;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink);
  opacity: 0.66;
}

/* Syvota: the harbour every distance on this page is measured from, so it is
   said louder than the beaches and the villages around it. */
.map-place.map-key .map-pin > i { width: 6px; height: 6px; opacity: 0.72; }
.map-place.map-key .map-pin > b {
  font-size: clamp(var(--fs-map-key), 1.21cqw, 1.24rem);
  letter-spacing: 0.1em;
  opacity: 0.9;
  text-shadow: 0 1px 0 color-mix(in srgb, var(--land) 75%, transparent);
}

/* Where two marks are unavoidably close, the NAME moves and the dot never does:
   the dot is the thing the map is asserting, and a map that shifts a harbour to
   make room for a word is not telling the truth. A name sitting a little above
   or below its mark is ordinary cartography and still reads as belonging to it.
   The amount comes from the geometry, per view, since the same two names can be
   comfortable at one scale and touching at another. */
.map-place.map-nudge .map-pin > b { transform: translateY(var(--nudge, 0px)); }

/* the estate: a ring, not a pin-drop */
.map-here { transform: translate(0, -50%); }
.map-here .map-pin > i {
  position: relative;
  width: 15px; height: 15px;
  flex: none;
  border: 2px solid var(--accent);
  border-radius: 50%;
  background: var(--paper);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--paper) 62%, transparent);
}
.map-here .map-pin > i::after {
  content: "";
  position: absolute; inset: 3px;
  background: var(--accent);
  border-radius: 50%;
}

/* A ring travels out from the estate, three times, and then the map is quiet.
   It is on the estate's marker and no other because one demonstration is enough:
   what it teaches is that the marks are live, and the reader generalises. It
   stops the instant any marker is hovered, focused or tapped, since by then it
   is telling someone something they have just worked out for themselves.

   Three iterations rather than a loop: an animation that never stops is a
   fidget, and this one has a job that is finished within a few seconds. The
   delay lets the section's own reveal settle first. */
.map-here .map-pin > i::before {
  content: "";
  position: absolute;
  inset: -4px;
  border-radius: 50%;
  border: 2px solid var(--accent);
  opacity: 0;
  animation: map-ping 2.4s var(--ease) 1.1s 3;
}
@keyframes map-ping {
  0%   { transform: scale(0.9); opacity: 0.62; }
  70%  { transform: scale(2.5); opacity: 0; }
  100% { transform: scale(2.5); opacity: 0; }
}
.map.is-taught .map-here .map-pin > i::before {
  animation: none;
  opacity: 0;
}
/* with motion turned down the hint carries the whole job on its own */
@media (prefers-reduced-motion: reduce) {
  .map-here .map-pin > i::before { animation: none; }
  .map-hint { transition: none; }
}
.map-here .map-pin > b {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(var(--fs-map-here), 1.71cqw, 1.75rem);
  line-height: 1.05;
  letter-spacing: 0.005em;
  color: var(--ink);
  text-shadow: 0 1px 0 color-mix(in srgb, var(--paper) 80%, transparent),
               0 0 12px color-mix(in srgb, var(--paper) 92%, transparent);
}

/* An island is not a point. Its name is written ACROSS the landmass, letterspaced
   the way a chart names a coast, with no dot, because there is no single spot on
   Kefalonia that the word Kefalonia is about. The generator finds the position:
   the point of the drawn shape furthest from any of its own edges, so the name
   lands in open ground and, where the window cuts an island, inside the part
   that is actually on screen. */
.map-area .map-pin > b {
  font-weight: 400;
  font-size: clamp(var(--fs-map-area), 1.13cqw, 1.16rem);
  line-height: 1;
  /* AREA_CHAR_W in tools/export_map_data.py is measured against this tracking.
     Loosen it and the generator's idea of how wide ITHACA is stops matching the
     browser's, which is how a name ends up hanging over the water. */
  letter-spacing: 0.22em;
  text-indent: 0.22em;
  text-transform: uppercase;
  color: var(--ink);
  opacity: 0.42;
}
.map-area:hover .map-pin > b,
.map-area:focus-within .map-pin > b,
.map-area.is-open .map-pin > b { opacity: 0.72; }
/* a name written across the land does not need a mark under it as well */
.map-area .map-pin > i { display: none; }

/* BELOW THIS WIDTH AN ISLAND NAME BECOMES AN ORDINARY MARKER, and it has to.
   A name written across a landmass only works while the landmass is wider than
   the word, and the word does not shrink past the clamp floor above: measured on
   a 364 px plate, ITHACA comes out 147 viewBox units against an island 75 units
   across, so it lies half in the sea, which is the exact fault this whole
   approach was meant to avoid. A dot in the middle of the island with its name
   beside it says the same thing and is true at any size. The switch is set well
   clear of the point where the name stops fitting rather than at it. */
@container (max-width: 1019.98px) {
  .map-area { transform: translate(0, -50%); }
  .map-area .map-pin { display: flex; }
  .map-area .map-pin > i {
    display: block;
    width: 5px; height: 5px;
    flex: none;
    background: var(--ink);
    opacity: 0.5;
    border-radius: 50%;
  }
  .map-area .map-pin > b {
    font-size: var(--fs-micro);
    letter-spacing: 0.06em;
    text-indent: 0;
    opacity: 0.66;
  }
}

/* ---- the markers open a card ---- */

/* .map-marks is pointer-events: none so the drawing beneath stays inert, so each
   marker has to opt back in. The card shows on hover, on keyboard focus, and on
   click, which is the only one of the three a touch screen has. */
.map-place, .map-here, .map-area { pointer-events: auto; }
.map-place:hover, .map-here:hover, .map-area:hover,
.map-place:focus-within, .map-here:focus-within, .map-area:focus-within,
.map-place.is-open, .map-here.is-open, .map-area.is-open { z-index: 3; }
/* the estate sits above the names around it, so nothing can obscure the one
   marker the map exists to place */
.map-here { z-index: 2; }

.map-pin {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: inherit;
  white-space: nowrap;
}
.map-place.map-e .map-pin { flex-direction: row-reverse; }
.map-place.map-s .map-pin { flex-direction: column; }
.map-place.map-n .map-pin { flex-direction: column-reverse; }
/* no dot to sit beside, so nothing to align against */
.map-area .map-pin { display: block; }

.map-card {
  position: absolute;
  width: 230px;
  display: block;
  padding: 10px;
  background: color-mix(in srgb, var(--paper) 96%, transparent);
  backdrop-filter: blur(12px) saturate(130%);
  -webkit-backdrop-filter: blur(12px) saturate(130%);
  border: 1px solid var(--line);
  box-shadow: 0 8px 34px rgba(9, 14, 18, 0.22);
  opacity: 0;
  visibility: hidden;
  transform: translateY(4px);
  transition: opacity 0.2s var(--ease), transform 0.2s var(--ease), visibility 0.2s;
  white-space: normal;
  text-align: left;
  z-index: 4;
}
.map-card img {
  width: 100%;
  height: 140px;
  object-fit: cover;
  margin-bottom: 8px;
  background: var(--frame);
}
.map-card b {
  display: block;
  font-family: var(--serif);
  font-size: 1.12rem;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink);
  opacity: 1;
}
.map-card > span {
  display: block;
  margin-top: 3px;
  font-size: var(--fs-note);
  line-height: 1.45;
  letter-spacing: 0;
  text-transform: none;
  color: var(--muted);
  text-wrap: pretty;
}

/* which way the card opens is decided in build.py from the point's own position,
   so a marker near an edge can never open its card off the plate */
.map-down .map-card { top: calc(100% + 9px); }
.map-up   .map-card { bottom: calc(100% + 9px); }
.map-c-l  .map-card { left: 0; }
.map-c-r  .map-card { right: 0; }
.map-c-c  .map-card { left: 50%; transform: translate(-50%, 4px); }

.map-place:hover .map-card, .map-here:hover .map-card, .map-area:hover .map-card,
.map-place:focus-within .map-card, .map-here:focus-within .map-card,
.map-area:focus-within .map-card,
.map-place.is-open .map-card, .map-here.is-open .map-card,
.map-area.is-open .map-card {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
.map-c-c:hover .map-card, .map-c-c:focus-within .map-card, .map-c-c.is-open .map-card {
  transform: translate(-50%, 0);
}

.map-pin:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
/* the whole marker is a target now, so give the dot a little more presence */
.map-place .map-pin:hover > i { opacity: 0.95; }

@media (max-width: 899.98px) {
  .map-card { width: 190px; }
  .map-card img { height: 112px; }
}

/* A card taller than the map it opens on is clipped by the plate, and the reader
   gets half a sentence: standing up, the Syvota card is 263 px on a plate 249 px
   tall. So on a small plate it lies down. The photograph goes beside the words
   instead of above them, and the note is held to three lines, so no card can
   outgrow the map whatever its text says. 240 px wide is set by the worst case,
   which is not the middle of the plate but a card anchored to a marker near the
   edge: at 250 px the Afteli card ran off the side of the bay view. */
@container (max-width: 559.98px) {
  .map-card {
    /* Wide rather than tall. The constraint here is the plate's WIDTH, 348 px on
       a phone, and the clamp below slides a card back inside rather than letting
       it hang off an edge, so the card can afford most of that width. Height is
       what the plate cannot spare, and every pixel given to the text column is a
       line the note does not have to wrap onto. */
    --cw: 280px;
    width: var(--cw);
    padding: 8px;
    display: grid;
    grid-template-columns: 76px 1fr;
    column-gap: 9px;
    align-items: center;
  }
  .map-card img {
    grid-column: 1;
    grid-row: 1 / span 2;
    width: 100%;
    /* a fixed height, NOT 100% of the row span: a percentage height on a grid
       item whose rows are sized by content is the cyclic case the browser drops,
       which is exactly how the lightbox once rendered a photograph at three
       times the height of the window */
    height: 66px;
    align-self: center;
    margin: 0;
  }
  .map-card b {
    grid-column: 2;
    grid-row: 1;
    align-self: end;
    font-size: 0.95rem;
  }
  .map-card > span {
    grid-column: 2;
    grid-row: 2;
    align-self: start;
    margin-top: 2px;
    font-size: 0.78rem;
    line-height: 1.34;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    /* four lines takes the longest note here, about ninety characters, without
       cutting a word in half; it is a backstop against a future note running
       long rather than something any current one hits */
    -webkit-line-clamp: 4;
    overflow: hidden;
  }
  /* the places with no photograph of their own get the whole width for words */
  .map-card:not(:has(img)) { --cw: 230px; grid-template-columns: 1fr; }
  .map-card:not(:has(img)) b,
  .map-card:not(:has(img)) > span { grid-column: 1; }

  /* On a plate this narrow a card cannot simply sit under its marker: 240 px of
     card against 348 px of map runs off the side. Nor is centring on the marker
     enough, because a marker is a dot AND its name, so "centred" on the estate
     put the card 12 px past the edge, pushed there by the width of its own
     label. So each card is centred on its marker's ANCHOR POINT, and slid back
     inside when there is not room, against bounds given in cqw, which is one per
     cent of the plate.

     Which edge to measure from differs by anchor, and this is the part that has
     to be got right: each family of markers is shifted onto its point by its own
     transform, so the edge of the box that actually sits at --xn is not the same
     edge in all three. A name to the WEST of its dot starts at the point, a name
     to the EAST ends at it, and an island's name straddles it. */

  /* the point is the box's left edge */
  .map-place.map-w .map-card, .map-here .map-card {
    left: clamp(calc(-1 * var(--xn) * 1cqw),
                calc(var(--cw) / -2),
                calc(100cqw - var(--cw) - var(--xn) * 1cqw));
    right: auto;
    transform: translateY(4px);
  }
  /* the point is the box's right edge, so the card is placed from the right */
  .map-place.map-e .map-card {
    right: clamp(calc((var(--xn) - 100) * 1cqw),
                 calc(var(--cw) / -2),
                 calc(var(--xn) * 1cqw - var(--cw)));
    left: auto;
    transform: translateY(4px);
  }
  /* The point is the box's centre, so the card shares it and is then nudged back
     inside if that puts it over an edge. A margin rather than a different left,
     because here the box's own width is unknown to the stylesheet and only the
     centre can be relied on: the shift is zero wherever there is room, and
     exactly the overhang where there is not. */
  .map-area .map-card {
    --cw: 260px;
    left: 50%;
    right: auto;
    margin-left: clamp(calc(var(--cw) / 2 - var(--xn) * 1cqw),
                       0px,
                       calc(100cqw - var(--xn) * 1cqw - var(--cw) / 2));
    transform: translate(-50%, 4px);
  }
  /* 's' and 'n' straddle their point like an island name does, so they are kept
     inside the plate the same way. Their own width, not the island name's wider
     one. Vertically they need nothing: map-up and map-down place the card
     against the whole box, and the box is what grew, so the card clears the name
     and the dot in both directions without knowing which way round they are. */
  .map-place.map-s .map-card, .map-place.map-n .map-card {
    left: 50%;
    right: auto;
    margin-left: clamp(calc(var(--cw) / 2 - var(--xn) * 1cqw),
                       0px,
                       calc(100cqw - var(--xn) * 1cqw - var(--cw) / 2));
    transform: translate(-50%, 4px);
  }

  .map-place.map-w:hover .map-card, .map-here:hover .map-card,
  .map-place.map-e:hover .map-card,
  .map-place.map-w:focus-within .map-card, .map-here:focus-within .map-card,
  .map-place.map-e:focus-within .map-card,
  .map-place.map-w.is-open .map-card, .map-here.is-open .map-card,
  .map-place.map-e.is-open .map-card {
    transform: translateY(0);
  }
  .map-area:hover .map-card, .map-area:focus-within .map-card,
  .map-area.is-open .map-card,
  .map-place.map-s:hover .map-card, .map-place.map-s:focus-within .map-card,
  .map-place.map-s.is-open .map-card,
  .map-place.map-n:hover .map-card, .map-place.map-n:focus-within .map-card,
  .map-place.map-n.is-open .map-card {
    transform: translate(-50%, 0);
  }
}
@media (prefers-reduced-motion: reduce) {
  .map-card { transition: none; }
}

.map-credit {
  margin-top: 6px;
  font-size: var(--fs-index);
  letter-spacing: 0.02em;
  color: var(--muted);
  opacity: 0.8;
  max-width: 66ch;
}

.map-bar {
  position: absolute;
  left: clamp(12px, 2%, 22px);
  bottom: clamp(12px, 2.4%, 22px);
  width: var(--len);
  display: block;
  border-left: 1px solid var(--ink);
  border-right: 1px solid var(--ink);
  border-bottom: 1px solid var(--ink);
  height: 6px;
  opacity: 0.55;
}
.map-bar b {
  position: absolute;
  left: 0; bottom: 9px;
  font-weight: 400;
  font-size: var(--fs-micro);
  letter-spacing: 0.05em;
  color: var(--ink);
  white-space: nowrap;
}

.map-north {
  position: absolute;
  right: clamp(12px, 2%, 22px);
  /* room for the tick, which is drawn ABOVE the letter and was being cut off by
     the top of the plate on the taller island view */
  top: clamp(22px, 3.4%, 30px);
  font-size: var(--fs-micro);
  letter-spacing: 0.12em;
  color: var(--ink);
  opacity: 0.55;
}
.map-north::before {
  content: "";
  position: absolute;
  left: 50%; bottom: 100%;
  width: 1px; height: 13px;
  margin-bottom: 3px;
  background: currentColor;
}

@media (max-width: 899.98px) {
  /* the buttons take the full width and split it, so each is a proper target,
     and the hint sits on its own line above them rather than being squeezed */
  .map-switch { width: 100%; }
  .map-btn { flex: 1 1 0; padding-inline: 8px; }
  .map-hint { width: 100%; }
  /* the plate's ratio must stay equal to the viewBox ratio at every width:
     letterboxing the SVG would leave the HTML labels, which are positioned as a
     percentage of the plate, floating away from the coast they name. So the
     names shrink and the drawing does not, which is the whole reason they are
     HTML over the SVG rather than text inside it. */
  :root {
    --fs-map-here: 0.98rem;
    --fs-map-key:  0.72rem;
    --fs-map-area: 0.66rem;
  }
  .map-place .map-pin > b { font-size: 0.66rem; }
  /* by here an island name is an ordinary marker, so it takes the ordinary size */
  .map-area .map-pin > b { font-size: 0.66rem; }
  .map-place.map-key .map-pin > b { font-size: var(--fs-map-key); }
  /* the names are smaller here, so they need less parting than on a full plate */
  .map-place.map-nudge .map-pin > b { transform: translateY(calc(var(--nudge, 0px) * 0.75)); }
  .map-here .map-pin > i { width: 12px; height: 12px; }
  .map-btn { font-size: 0.85rem; padding: 12px 18px; min-height: 46px; }
  .map-plate { width: 100%; }
}

/* How many names the map carries, decided by how much room it has to carry them
   in. The drawing scales with the plate and the type does not, so the island view
   holds sixteen names at full width and about five on a phone; left alone the
   rest simply pile on top of one another. Asked of the plate, not the window,
   because the two part company as soon as the viewport is short.

   Tier 1 is the estate, the harbour every distance here is measured from, and
   the three islands, so the map still answers "where is this, and what is around
   it" at any size. */
@container (max-width: 699.98px) { .map-t3 { display: none; } }
@container (max-width: 449.98px) { .map-t2 { display: none; } }

@media (prefers-reduced-motion: reduce) {
  .map-btn { transition: none; }
}

/* ---------- ornament ---------- */

.ornament {
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  padding-block: clamp(3.4rem, 6vw, 6rem);
  color: var(--muted);
}
.ornament-word {
  font-family: var(--greek);
  font-size: 1.06rem;
  letter-spacing: 0.34em;
  text-indent: 0.34em;
}

/* ---------- enquire ---------- */

.enquire {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.92fr);
  gap: clamp(34px, 5vw, 84px);
  align-items: start;
  padding-top: clamp(38px, 5vw, 64px);
  border-top: 1px solid var(--line);
}
.enquire-copy h2 { font-size: clamp(1.9rem, 3.2vw, 2.8rem); text-wrap: balance; }
.enquire-copy p { margin-top: 1.05rem; color: var(--muted); max-width: 46ch; font-size: 1.08rem; }
.enquire-mail { margin-top: 1.5rem; overflow-wrap: anywhere; }
.enquire-mail a {
  font-family: var(--serif);
  font-size: clamp(1.15rem, 1.9vw, 1.5rem);
  border-bottom: 1px solid var(--line);
  padding-bottom: 3px;
  transition: border-color 0.25s var(--ease);
}
.enquire-mail a:hover { border-color: var(--ink); }

.field + .field { margin-top: 1.1rem; }
.field label {
  display: block;
  font-size: 0.78rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.5rem;
}
.field input, .field textarea {
  width: 100%;
  font-family: inherit;
  font-size: 1rem;
  color: var(--ink);
  background: var(--paper-2);
  border: 1px solid var(--line);
  padding: 0.72rem 0.85rem;
  transition: border-color 0.2s var(--ease);
}
.field input:focus, .field textarea:focus { border-color: var(--accent); outline: none; }
.field textarea { resize: vertical; min-height: 118px; }

.btn {
  margin-top: 1.4rem;
  font-family: inherit;
  font-size: 0.88rem;
  letter-spacing: 0.05em;
  color: var(--paper);
  background: var(--ink);
  border: 1px solid var(--ink);
  padding: 0.82rem 1.9rem;
  cursor: pointer;
  white-space: nowrap;
  transition: opacity 0.22s var(--ease), transform 0.1s var(--ease);
}
.btn:hover { opacity: 0.86; }
.btn:active { transform: translateY(1px); }

.form-note { margin-top: 0.95rem; font-size: 0.82rem; color: var(--muted); }
.form-note.err { color: #B3453B; }
@media (prefers-color-scheme: dark) { .form-note.err { color: #E88A80; } }

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

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

.footer {
  padding-block: clamp(30px, 3.6vw, 46px);
  border-top: 1px solid var(--line-2);
}
.footer-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px 30px;
  justify-content: space-between;
}
.footer-row p { font-size: 0.8rem; color: var(--muted); }

/* The reservation of rights. Worth having in plain sight rather than buried in a
   terms page nobody opens: it removes any "I assumed it was free to use" from an
   agent who lifts the photographs, and it is what a takedown request points at.
   The real protection is still the gate and the noindex header, not this line.
   The Commons photographs are carved out deliberately, because claiming rights
   over CC BY and CC BY-SA work would be both untrue and a breach of the licence
   the owners are relying on to use it at all. */
.rights {
  margin-top: clamp(20px, 2.4vw, 30px);
  padding-top: 1.1rem;
  border-top: 1px solid var(--line-2);
  max-width: 86ch;
  font-size: 0.76rem;
  line-height: 1.6;
  color: var(--muted);
  opacity: 0.82;
}
.footer-brand { display: inline-flex; align-items: center; gap: 9px; color: var(--ink); }
.footer-brand span { font-family: var(--serif); font-size: 1.06rem; }

/* ---------- layout switch (temporary, remove once a layout is chosen) ---------- */

.switch {
  position: fixed;
  right: clamp(12px, 2vw, 26px);
  bottom: clamp(12px, 2vw, 26px);
  z-index: 50;
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 5px;
  background: color-mix(in srgb, var(--paper) 92%, transparent);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  border: 1px solid var(--line);
  box-shadow: 0 8px 30px rgba(9, 14, 18, 0.16);
}
.sw-label {
  font-size: 0.65rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 0 10px 0 7px;
}
.switch a, .switch .sw-on {
  font-size: 0.8rem;
  line-height: 1;
  padding: 8px 14px;
  white-space: nowrap;
  transition: background 0.2s var(--ease), color 0.2s var(--ease);
}
.switch a { color: var(--muted); }
.switch a:hover { background: var(--paper-2); color: var(--ink); }
.switch .sw-on { background: var(--ink); color: var(--paper); }

@media print { .switch, .gal-btns { display: none; } }
@media (max-width: 520px) { .sw-label { display: none; } }

/* ---------- lightbox ---------- */

/* The photograph is sized from the viewport, never from itself. --lb-h is the
   height it may have, 100cqw is the width the figure actually offers, and the
   ratio the script writes into --lb-ar turns one into the other. Percentages are
   the trap: max-height: 100% on an item whose grid row is sized by its own
   content is cyclic, so the browser drops it, the row grows to the full height
   of the photograph and the dialog scrolls. That is why a 1600 by 900 window
   used to render the tall frame at 1492 by 2652 and show only its top third.
   Same idiom as .plate img, and for the same reason. */
#lightbox {
  --lb-pad: clamp(16px, 4vw, 54px);
  --lb-h: calc(100dvh - 2 * var(--lb-pad));
  width: 100%; max-width: 100%;
  height: 100dvh; max-height: 100dvh;
  border: 0; padding: 0;
  overflow: hidden;
  background: var(--paper);
  color: var(--ink);
}
#lightbox::backdrop { background: rgba(10, 14, 17, 0.9); }
#lightbox figure {
  /* 100cqw is this box's own content width, so it excludes the scrollbar the
     page behind may still be showing. 100vw would not. */
  container-type: inline-size;
  height: 100%;
  display: flex; align-items: center; justify-content: center;
  padding: var(--lb-pad);
}
#lightbox img {
  aspect-ratio: var(--lb-ar, 1.5);
  height: min(var(--lb-h), calc(100cqw / var(--lb-ar, 1.5)));
  width: auto;
  max-width: 100%;
  max-height: var(--lb-h);
  object-fit: contain;
  background-color: var(--ph, var(--frame));
}

/* same frosted plate as the carousel arrows: both sit on photographs */
.lb-btn {
  position: absolute;
  font-family: inherit;
  width: 46px; height: 46px;
  display: grid; place-items: center;
  border: 1px solid var(--line);
  background: color-mix(in srgb, var(--paper) 84%, transparent);
  backdrop-filter: blur(10px) saturate(130%);
  -webkit-backdrop-filter: blur(10px) saturate(130%);
  box-shadow: 0 2px 18px rgba(9, 14, 18, 0.18);
  color: var(--ink);
  font-size: 1.05rem; line-height: 1;
  cursor: pointer;
  transition: background 0.2s var(--ease), transform 0.1s var(--ease);
}
.lb-btn:hover { background: color-mix(in srgb, var(--paper-2) 92%, transparent); }
.lb-btn:active { transform: scale(0.96); }

/* Both control sets land on photographs, where a thin accent ring can vanish
   into a blue sea, so the focus state carries a paper halo behind it. */
.gal-btn:focus-visible,
.lb-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  box-shadow: 0 0 0 5px color-mix(in srgb, var(--paper) 84%, transparent),
              0 2px 18px rgba(9, 14, 18, 0.18);
}
.lb-prev  { left: clamp(10px, 2vw, 24px); top: 50%; transform: translateY(-50%); }
.lb-next  { right: clamp(10px, 2vw, 24px); top: 50%; transform: translateY(-50%); }
.lb-prev:active { transform: translateY(-50%) scale(0.96); }
.lb-next:active { transform: translateY(-50%) scale(0.96); }
.lb-close { right: clamp(10px, 2vw, 24px); top: clamp(10px, 2vw, 24px); }

/* ---------- reveal ---------- */

.reveal { opacity: 0; transform: translateY(16px); }
.js .reveal { transition: opacity 0.7s var(--ease), transform 0.7s var(--ease); }
.reveal.in { opacity: 1; transform: none; }
html:not(.js) .reveal { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
  .reveal { opacity: 1; transform: none; }
  .track { scroll-behavior: auto; }
  .plate figcaption { opacity: 1; }
}
