/* The Outback Experience - components.
   The plate and the departure row are each built once here and used everywhere. */

/* =============================================================== THE PLATE
   Her frame, as she exported it. Native aspect ratio, uncropped, seated
   directly on the near-black ground. No border, no radius, no shadow, no card
   wrapper, and nothing overlaid on it anywhere on the site, including the fold.
   The only crop in the build is the authored 4:5 mobile source, wired through
   <picture> so no phone is served the desktop ratio.                          */

.plate {
  margin: 0;
  display: block;
}

.plate img {
  display: block;
  width: 100%;
  height: auto;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  background: none;
  outline: 0;
}

.plate__cap {
  display: block;
  transition: color .15s ease;
  margin-top: .75rem;
  font-family: var(--body);
  font-size: .8125rem;
  line-height: 1.45;
  letter-spacing: .005em;
  color: var(--ink-quiet);
  max-width: 46ch;
}

.plate__place {
  color: var(--accent);
  transition: color .15s ease;
  font-weight: 640;
  letter-spacing: .07em;
  text-transform: uppercase;
  font-size: .6875rem;
  display: block;
  margin-bottom: .2rem;
}

/* A plate that links to its own full frame. The link is the plate; still
   nothing is drawn on top of the photograph. */
.plate a {
  display: block;
  text-decoration: none;
}

.plate a:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }

/* No photograph on this site opens larger (her ask, chg-a0c1ee68: "Visitors
   should not be able to click images to open a larger view or lightbox"), so a
   gallery plate holds its picture directly, with no link round it. The picture
   takes the block box the link used to give it. */
.plate > picture { display: block; }

/* Her photographs are her product. Right-click on an image is stopped in site.js
   (her ask, chg-a0c1ee68); this is the same for a long press on a phone (no
   save-image sheet) and for dragging a photo out of the page. It discourages,
   it cannot prevent: a screenshot still works, and she knows that. */
img {
  -webkit-touch-callout: none;
  -webkit-user-drag: none;
  -webkit-user-select: none;
  user-select: none;
}

/* A plate that links somewhere (the home page's doors) has to say so, and the
   plate rule settles where the feedback may go rather than whether there is any.
   (No photograph opens its own full frame any more: her ask, chg-a0c1ee68.) The plate rule settles where
   the feedback may go rather than whether there is any - a dim, a tint, a zoom
   or a scrim on the photograph is out, so the CAPTION answers instead. It sits
   below the frame, it is already the place the eye goes after the picture, and
   it moves from the quiet stone to the full stone while the place label takes
   the turquoise lift. The photograph itself is untouched in every state. */
.plate:has(a:hover) .plate__cap,
.plate:has(a:focus-visible) .plate__cap { color: var(--ink); }

.plate:has(a:hover) .plate__place,
.plate:has(a:focus-visible) .plate__place { color: var(--accent-hover); }

.plate:has(a:active) .plate__place { color: var(--ink); }

.plates {
  display: grid;
  gap: var(--s4) var(--s3);
  grid-template-columns: 1fr;
}

@media (min-width: 640px) {
  .plates--2, .plates--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1000px) {
  .plates--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* ============================================================ SWIPE GALLERY
   Every gallery on the site is a swipeable strip, "like Instagram" in her
   words. It began as the phone layout only; since chg-82453913 she asked for the
   same browsing on desktop too, so a gallery never turns into a long page. The
   phone strip is unchanged: 82% frames that bleed to the screen edge. */
@media (max-width: 700px) {
  .plates--swipe {
    display: flex;
    gap: var(--s3);
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-padding-left: var(--gutter);
    margin-inline: calc(var(--gutter) * -1);
    padding: 0 var(--gutter) var(--s2);
    -webkit-overflow-scrolling: touch;
  }
  .plates--swipe .plate {
    flex: 0 0 82%;
    scroll-snap-align: start;
  }
  .plates--swipe::-webkit-scrollbar { display: none; }
  .plates--swipe { scrollbar-width: none; }
}

/* Above the swipe width the strip stays on the content grid (never a bleed) and
   every frame shares one height, taking its width from its own shape: a portrait
   is simply narrower, nothing is cropped and there is no object-fit. The next
   frame always peeks past the edge, which is what says "there is more". A
   trackpad or a shift-wheel scrolls it; for a mouse, site.js adds a pair of
   arrows above the strip (never on a photograph), and a thin scrollbar stays
   visible as the plain fallback. */
@media (min-width: 701px) {
  .plates--swipe {
    display: flex;
    gap: var(--s3);
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;
    padding-bottom: var(--s3);
    scrollbar-width: thin;
    scrollbar-color: var(--rule-strong) transparent;
  }
  .plates--swipe .plate {
    flex: 0 0 auto;
    scroll-snap-align: start;
    margin: 0;
  }
  .plates--swipe .plate img {
    height: clamp(240px, 26vw, 400px);
    width: auto;
    max-width: none;
  }
  .plates--swipe .plate__cap {
    width: 0;
    min-width: 100%;
  }
  .plates--swipe::-webkit-scrollbar { height: 6px; }
  .plates--swipe::-webkit-scrollbar-thumb { background: var(--rule-strong); }
  .plates--swipe::-webkit-scrollbar-track { background: transparent; }
}

/* The arrows site.js puts above a strip on desktop. Type-only controls on the
   ground in the body stone; nothing is drawn on a photograph. */
.strip-nav { display: none; }
.strip-nav[hidden] { display: none !important; }

@media (min-width: 701px) {
  .strip-nav {
    display: flex;
    justify-content: flex-end;
    gap: var(--s2);
    margin-bottom: var(--s2);
  }
  .strip-nav button {
    font: inherit;
    font-size: 1.125rem;
    line-height: 1;
    min-width: 44px;
    min-height: 44px;
    padding: 0 .75rem;
    color: var(--ink);
    background: none;
    border: 1px solid var(--rule-strong);
    border-radius: 0;
    cursor: pointer;
  }
  .strip-nav button:hover { border-color: var(--accent); color: var(--accent-hover); }
  .strip-nav button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
  .strip-nav button:disabled { color: var(--ink-faint); border-color: var(--rule); cursor: default; }
}

/* On a phone the full gallery is the swipe strip, and one portrait frame at the
   strip's 82% width would set the height of every frame in it, leaving a tall
   empty band under each landscape one. So no frame in the strip is taller than
   a 3:2 landscape at 82%, and each takes its width from its own shape: a
   portrait is simply narrower. Still native ratio, still uncropped, no
   object-fit; the caption wraps to the frame's width instead of widening it. */
@media (max-width: 700px) {
  .plates--gallery.plates--swipe .plate {
    flex: 0 0 auto;
    max-width: 82%;
  }
  .plates--gallery.plates--swipe .plate img {
    width: auto;
    max-width: 100%;
    height: auto;
    max-height: 54vw;
  }
  .plates--gallery.plates--swipe .plate__cap {
    width: 0;
    min-width: 100%;
  }
}

/* =============================================================== HEADER */

.site-header {
  position: sticky;
  top: 0;
  z-index: 60;
  background: var(--ground);
  border-bottom: 1px solid var(--rule);
}

.site-header > .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  min-height: var(--header-h-sm);
}

.brand {
  display: flex;
  align-items: center;
  min-width: 0;
  flex: 0 1 auto;
  text-decoration: none;
  padding-block: .55rem;
}

.brand img {
  display: block;
  height: var(--logo-h-sm);
  width: auto;
}

.nav__toggle {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 2px;
  cursor: pointer;
  padding: 0;
}

.nav__toggle .icon-bar {
  display: block;
  width: 20px;
  height: 1.5px;
  background: var(--ink);
  transition: background-color .15s ease;
}

/* The one control on a phone that opens the whole site, and it had no hover and
   no press - the only feedback was the focus ring, which a touch or a mouse
   never sees. The border and the bars take the same turquoise the rest of the
   nav takes, and the open state holds it so the button reads as latched. */
.nav__toggle { transition: border-color .15s ease; }
.nav__toggle:hover { border-color: var(--rule-strong); }
.nav__toggle:hover .icon-bar { background: var(--accent); }
.nav__toggle:active,
.nav__toggle[aria-expanded="true"] { border-color: var(--accent); }
.nav__toggle:active .icon-bar,
.nav__toggle[aria-expanded="true"] .icon-bar { background: var(--accent); }

.nav__list {
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: none;
}

.nav__list a {
  display: block;
  font-family: var(--body);
  font-size: 13px;
  font-weight: 620;
  letter-spacing: .085em;
  text-transform: uppercase;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
}

.nav__list a { transition: color .15s ease; }
.nav__list a:hover { color: var(--accent-hover); }
.nav__list a:active { color: var(--ink); }

.nav__list a[aria-current="page"] {
  color: var(--accent);
}

/* The current page is already turquoise, so an attribute selector of equal
   weight but later position silently ate its hover: pointing at the item you
   are on did nothing. It is still a link - it scrolls you back to the top - so
   it keeps the same two states as its siblings. */
.nav__list a[aria-current="page"]:hover { color: var(--accent-hover); }
.nav__list a[aria-current="page"]:active { color: var(--ink); }

/* mobile drawer */
.nav__panel {
  display: none;
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  background: var(--ground);
  border-bottom: 1px solid var(--rule);
  box-shadow: 0 18px 40px rgba(0, 0, 0, .5);
}

.nav__panel.is-open { display: block; }

.nav__panel .nav__list {
  padding: var(--s2) var(--gutter) var(--s4);
}

.nav__panel .nav__list li + li { border-top: 1px solid var(--rule); }

.nav__panel .nav__list a { padding: .95rem 0; }

@media (min-width: 1200px) {
  .site-header > .wrap { min-height: var(--header-h); }
  .brand img { height: var(--logo-h); }
  .nav__toggle { display: none; }
  .nav__panel { display: none !important; }

  .site-nav { display: block; }

  .site-nav .nav__list {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: clamp(1rem, 1.55vw, 1.6rem);
  }

  .site-nav .nav__list a { padding-block: .35rem; }

  .site-nav .nav__list a[aria-current="page"] {
    box-shadow: inset 0 -1px 0 0 var(--accent);
  }
}

@media (max-width: 1199px) {
  .site-nav { display: none; }
}

/* =============================================================== THE FOLD
   One top-aligned two-column row: the type block beside the plate. Never a
   headline over a photograph. */

/* The fold hands over to the next section rather than running out into empty
   ground. With the old 72+72 there were 144px of nothing between the last
   thing in the fold and the first thing after it, which on the short-copy
   pages put the whole bottom band of the first screen beyond any content and
   made the space under the left column's action read as an accident. At 48+48
   the hairline and the next section's eyebrow sit inside the first screen, so
   the fold ends on something. */
.fold { padding-block: var(--s5) var(--s5); }
.fold + .sec { padding-top: var(--s5); }

.fold__row {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s4);
  align-items: start;
}

.fold__type { align-self: start; min-width: 0; }
.fold__type > :last-child { margin-bottom: 0; }

/* The measure stops growing before the type does, and that is what strands
   words. `.wrap` caps at 1280px, so the fold's type column stops widening:
   measured at 531px at 1366 and 530px at 1440, 1600 and 1920 alike - while the
   global h1 clamp keeps climbing on 4.4vw to a 64px ceiling. Every extra pixel
   of viewport past that point makes
   the headline bigger in a column that is not getting any wider, which is how
   "Junior Wildlife / Photographer / Camp" and "Private-Guided / Tours" arrive
   at 1440 having been perfectly well set at 1366. Measured: "Photographer
   Camp" needs 531.9px in 530 at 1440 and "Private-Guided Tours" needs 555.7px.
   So the display stops where the measure stops. 58px is inside the brief's
   56-72px band at 1440 (PITCH-PLAN decisions item 23, which says the exact
   value is set against the real plate width at build time), it is the value
   the real 530px column will hold, and below a 1318px viewport nothing changes
   at all - the fluid curve is still the one in charge. */
.fold h1 { font-size: clamp(2.2rem, 4.4vw, 3.625rem); }

/* A headline the measure cannot hold at the full display size steps down
   rather than being cut. Two carry it.

   index.html: her Position is 128 characters, and at 63px in the type column it
   sets as EIGHT lines, which strands the plate with a void beneath it and trips
   the layout-balance gate at 1.96x.

   kids-camps-workshops.html: "The Best Camps & Workshops Guaranteed!" stranded
   "Guaranteed!" on its own line at four of the five checked widths, and unlike
   the other two that is NOT a break that can be moved. The only arrangement
   with more than one word on the last line needs "Workshops Guaranteed!" to fit
   on one, which measures 630px in a 530px column - it wants 53px type, below
   the band floor. The step-down is the instrument; the words are untouched.

   Fraunces has an optical-size axis for exactly this. */
.fold h1.is-long {
  font-size: clamp(1.95rem, 3.35vw, 3.1rem);
  line-height: 1.08;
  /* No letter-spacing here on purpose. The fixed -.028em that used to sit on this
     rule overrode the fluid optical curve on base.css h1 for the one headline that
     steps down the most, which is exactly the line that needed it. The curve gives
     -.0286em at this rule's 48.2px maximum (what the fixed value shipped) and
     -.0031em at the 28.5px mobile step-down, where -.028em was cramped. */
}

.fold__plate { align-self: start; min-width: 0; }

.fold__pos {
  font-size: clamp(1.05rem, 1.35vw, 1.22rem);
  line-height: 1.55;
  color: var(--ink);
  max-width: 44ch;
  margin-bottom: var(--s3);
}

.fold__cats {
  font-family: var(--body);
  font-size: .75rem;
  font-weight: 640;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 var(--s3);
  max-width: none;
}

/* Each separator sits INSIDE the label it FOLLOWS, so the pair breaks as one
   unit and a wrapped line can never OPEN on a separator. A vertical line is
   UAX#14 class BA - a wrap is allowed straight after it - so neither a no-break
   space nor any property on the separator itself can hold it; only being inside
   the nowrap run can. The word space therefore lives between the runs, which
   is to say after the separator, and the asymmetric padding below puts the
   measured gap back where it was (11.5px left, 10.4px right at 1440).

   This binding was the other way round until 2026-09-20, on the reasoning that
   a line must never END on a dangling pipe. Both cannot be had: the strip wraps
   at 390px, every break lands next to a separator, and the only choice is which
   end of which line it sits on. It goes at the end, because a trailing
   separator reads as "this continues" - the same convention as a trailing
   comma - while a leading one reads as debris the page forgot to clear. The
   slop gate called the leading form on the homepage a tell. Do not flip it back
   without re-reading this paragraph; the alternative is not orphan-free. */
.fold__cats span { white-space: nowrap; }
.fold__cats i { font-style: normal; color: var(--rule-strong); padding: 0 .38rem 0 .72rem; }

/* About ends on her own full stop, in the house style she uses for
   "Explore . Capture . Rejoice .". Set as a separator - grey, with a
   separator's gap on both sides and nothing after it - it read as a dangling
   one, since every other page ends on a word. It is punctuation, so it takes
   the type colour, sits a plain word space after "Experience", and rides
   inside the last nowrap run rather than trailing the line. */
.fold__cats i.fold__stop { color: inherit; padding: 0; }

@media (min-width: 880px) {
  .fold__row {
    grid-template-columns: minmax(0, 0.94fr) minmax(0, 1.06fr);
    gap: clamp(2rem, 4vw, 3.5rem);
  }
  /* The plate steps down a touch from the headline's first line: an editorial
     step-down, and it keeps the image top from floating above the copy. */
  .fold__plate { margin-top: clamp(.75rem, 1.6vw, 1.5rem); }
}

/* A portrait frame in the fold (the home page's Kabini tiger in the rain, 2:3,
   chg-6e774e3d) is sized by HEIGHT, not by the column: at the column's width it
   stood 900px tall and put the tiger below the first screen. Here the whole
   frame, uncropped, and its caption clear a 1280x800 screen, and the type
   column takes the width the photo does not need. Phones keep the 4:5 crop. */
@media (min-width: 880px) {
  .fold__row:has(.fold__plate--portrait) { grid-template-columns: minmax(0, 1fr) auto; }
  .fold__plate--portrait .plate { width: min-content; }
  .fold__plate--portrait .plate img {
    width: auto;
    height: clamp(26rem, calc(100svh - 14rem), 44rem);
    max-width: none;
  }
}

/* The Photo Tours fold's carousel (her ask, chg-d59a226c): her featured
   photographs, one at a time, sliding on to the next by themselves every three
   seconds. Hers to say: no swipe, no arrows, no way for a visitor to change the
   photograph, so the track is clipped rather than scrollable and nothing here
   takes a touch, and no photograph opens full size (her ask, chg-62fe2cb5).
   On desktop every frame is her 3:2 at its native ratio, uncropped; on phones
   every frame is a hand-placed 4:5 crop in plates/m/, 900x1125 (her ask,
   chg-a0c1ee68, replacing the 4:3 of chg-62fe2cb5), the same shape as every
   tour page's lead, and all one shape so the page never jumps between
   photographs. Every slide is
   as tall as the tallest caption so nothing below it moves either. */
.fold__plate:has(.carousel) { align-self: stretch; }
.carousel { min-width: 0; overflow: hidden; overflow: clip; }
.carousel__track { display: flex; }
.carousel__track.is-moving { transition: transform .7s cubic-bezier(.4, 0, .2, 1); }
.carousel__slide { flex: 0 0 100%; min-width: 0; }
.carousel__slide picture { display: block; }

/* The home fold's carousel (her ask, chg-a076e472: "the same featured-image
   carousel settings as the Photo Tours page"): her 2:3 portraits, sized by
   height as the single tiger was. A carousel has no intrinsic width, so the
   frame is given the width a 2:3 photograph of that height takes, and each
   photograph fills it. Every one she sent is 2:3, so nothing is cropped. Her
   Amboseli caption runs three lines where the tiger's ran two, and every slide
   is as tall as the tallest caption, so the photographs stand 2.5rem shorter
   than the tiger did and the captions take the photograph's full width: frame
   and caption still clear a 1280x800 screen. */
@media (min-width: 880px) {
  .fold__plate--portrait .carousel { width: calc(clamp(26rem, calc(100svh - 16.5rem), 44rem) * 2 / 3); }
  .fold__plate--portrait .carousel .plate { width: auto; }
  .fold__plate--portrait .carousel .plate img { width: 100%; height: auto; }
  .fold__plate--portrait .carousel .plate__cap { max-width: none; }
}

/* Larger on desktop (her ask, chg-a2e94fd1: "show the image larger than it is
   right now for desktop only ... If reducing the caption a little bit helps in
   doing so, then please do that too. On mobile its perfect!"). Until now the
   photograph AND its caption had to clear the first screen, which held a
   1280x800 frame to 357px wide. Now the photograph alone fills the first
   screen's height, starting level with the category strip (no editorial
   step-down), and its caption sits just under it, a short scroll away, which
   is how the phone layout she calls perfect already works. The caption steps
   down a size; her words and her place-above-text layout are untouched.
   11.25rem is the header plus the fold's top padding plus the place line, so
   the place shows whole under the photograph. The vw bound keeps a tall,
   narrow desktop window from squeezing the headline column. Phones (below
   880px) are not touched. */
@media (min-width: 880px) {
  .fold__plate--portrait:has(.carousel) { margin-top: 0; }
  .fold__plate--portrait .carousel { width: calc(min(clamp(26rem, calc(100svh - 11.25rem), 56rem), 67.5vw) * 2 / 3); }
  .fold__plate--portrait .carousel .plate__cap { margin-top: .5rem; font-size: .75rem; line-height: 1.4; }
  .fold__plate--portrait .carousel .plate__place { font-size: .625rem; margin-bottom: .1rem; }
}

/* --- a fold whose type block is too long for a phone screen ----------------
   Two pages carry more type in the fold than a 390x844 screen can hold above
   the photograph. The homepage has her 128-character Position as its H1 and
   her longest lede, so in source order the plate began at y=658 of 844: 20% of
   the first screen on the one page whose whole argument is her photography,
   against 26-47% everywhere else. The contact page now carries her three
   role-labelled phone lines in the same block. Nothing is cut, nothing is
   hidden and nothing is laid over the photograph. Below the two-column
   breakpoint the fold's own blocks reorder so the category strip, the headline
   and the one action come first and the plate follows immediately, with the
   supporting copy under it. The two focusable things above the plate (the
   button, then the plate's own link) keep their source order, so tab order
   still matches what the eye sees. */
@media (max-width: 879.98px) {
  .fold--lead { padding-block: var(--s4) var(--s5); }
  /* align-items:start is right for the two-column grid and wrong here: in a
     column flex container it is the CROSS axis, so every block shrink-wraps to
     its own content and the links block's hairlines stop short of the measure. */
  .fold--lead .fold__row { display: flex; flex-direction: column; gap: 0; align-items: stretch; }
  .fold--lead > .wrap > .fold__row > .fold__type { display: contents; }
  .fold--lead .fold__cats { order: 1; }
  .fold--lead h1 { order: 2; margin-bottom: var(--s3); }
  .fold--lead .fold__type .fold__cta { order: 3; margin: 0 0 var(--s4); }
  /* Everything focusable keeps its source order relative to the plate's own
     link, so tab order still matches what the eye sees: the action, then a
     links block if the page has one, then the photograph. Ordering the links
     block after the plate reads fine and tabs wrong, which is why it sits
     here; measured with taborder.mjs. */
  .fold--lead .fold__type .fold__lines { order: 4; margin: 0 0 var(--s4); }
  .fold--lead .fold__plate { order: 5; }
  .fold--lead .fold__type .fold__pos { order: 6; margin: var(--s4) 0 0; }

  /* Fraunces has an optical-size axis for exactly this: a long line steps down
     a size rather than the sentence being cut. Set against the real plate
     height so her Position, the action and the whole 4:5 plate clear the fold
     together on a 390x844 screen. */
  .fold--lead h1.is-long { font-size: 1.78rem; line-height: 1.1; }
}

/* The contact fold's phone lines: her own three role-labelled numbers, in the
   first screen, because the page's one job is to let someone act. The shared
   .creds row right-aligns its label against a 1fr column, which is right in a
   full-width band and wraps the longest label onto two ragged lines inside the
   fold's narrower type column - so here the number takes only the width it
   needs and the label runs on from it. */
.fold__lines { margin-top: var(--s4); }
.fold__lines .creds { max-width: 34rem; }

/* One line per row at every width, including phones, where the shared .creds
   stacks its label under its value and doubles the block's height - enough on
   the 404 to push the photograph off the first screen. The name takes only the
   width it needs and its note runs on from it, rather than right-aligning
   against a 1fr column and wrapping inside the fold's narrower type column. */
.fold__lines .creds li {
  padding-block: .6rem;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: .2rem .9rem;
  align-items: baseline;
}

.fold__lines .creds span { text-align: left; }

@media (min-width: 720px) { .fold__lines .creds li { gap: .2rem 1.25rem; } }

/* =============================================================== DOORS */

.doors {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s4) var(--s3);
}

@media (min-width: 620px) { .doors { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1000px) { .doors { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.door { min-width: 0; }

/* Every door's desktop frame is 3:2 except the Photo Tours puffin (3024x1964,
   her export). The frame stays whole and uncropped; the slot keeps 3:2 so the
   labels in a row still share a line, and the few spare pixels fall below the
   photo on the ground. Phones get the 4:5 sources, which already match. */
@media (min-width: 701px) { .door .plate { aspect-ratio: 3 / 2; } }

.door a {
  display: block;
  text-decoration: none;
  color: inherit;
}

.door a:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }

.door__label {
  display: block;
  margin-top: .85rem;
  font-family: var(--display);
  font-size: 1.28rem;
  font-weight: 440;
  letter-spacing: -.018em;
  line-height: 1.2;
  color: var(--ink);
}

.door__label { transition: color .15s ease; }
.door a:hover .door__label,
.door a:focus-visible .door__label { color: var(--accent-hover); }
.door a:active .door__label { color: var(--ink); }

.door__note {
  display: block;
  margin-top: .3rem;
  font-size: .875rem;
  line-height: 1.45;
  color: var(--ink-quiet);
}

/* =============================================================== DEPARTURES
   One row per departure, fixed field order, tabular figures down the column.
   The whole row is the click target; the state chip is a label inside it. */

.dep-list {
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: none;
  border-top: 1px solid var(--rule);
}

.dep-list li { margin: 0; }

.dep {
  display: grid;
  grid-template-columns: 1fr;
  gap: .3rem 1.25rem;
  padding: 1.35rem 0;
  border-bottom: 1px solid var(--rule);
  text-decoration: none;
  color: inherit;
  align-items: baseline;
}

.dep { transition: background-color .15s ease; }
.dep:hover,
.dep:focus-visible { background: var(--ground-lift); }
.dep:hover .dep__name,
.dep:focus-visible .dep__name { color: var(--accent-hover); }
.dep:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* The row is the click target and the state chip is a label inside it, so the
   press has to read on the ROW, not on the chip. One more step up the ground
   (11.20:1 for the stone on it) plus the title going to the stone: pressed-in
   rather than lit-up, and no colour is spent that the chips need. */
.dep:active { background: var(--ground-press); }
.dep:active .dep__name { color: var(--ink); }

/* The press lifts the surface and the quiet text on the row did not go with it.
   --ink-faint was chosen against --ground, where it measures 5.07:1; on
   --ground-press it is 4.27:1, under the 4.5 AA floor, on the three shapes that
   wear it - the note, the extension line and the unfilled TOUR FULL label
   (measured in a browser with the press forced, 66 readings, 2026-09-20). It is
   a mismatch rather than a bad token, so the token is untouched and these three
   join the press behaviour the title already has: the row presses as ONE thing.
   The chip's rule moves with its text - text and outline are one mark, and a
   stone label inside a #8c8c8c box would read as a rendering fault. At rest and
   at hover the faint grey is unchanged (5.07:1 and 4.62:1, both over the floor);
   only the pressed state moves. */
.dep:active .dep__note,
.dep:active .dep__ext { color: var(--ink); }
.dep:active .dep__state--full { color: var(--ink); border-color: var(--ink); }

.dep__note, .dep__ext, .dep__state--full { transition: color .15s ease, border-color .15s ease; }

.dep__name {
  display: block;
  transition: color .15s ease;
  font-family: var(--display);
  font-size: 1.22rem;
  font-weight: 450;
  letter-spacing: -.018em;
  line-height: 1.2;
  color: var(--ink);
}

.dep__place, .dep__note {
  display: block;
  font-size: .8125rem;
  line-height: 1.45;
  color: var(--ink-quiet);
}

.dep__place { margin-top: .22rem; }
.dep__note { color: var(--ink-faint); }

.dep__dates { font-size: .9375rem; color: var(--ink); }
.dep__cap { font-size: .8125rem; color: var(--ink-quiet); white-space: nowrap; }

.dep__price {
  font-size: 1.0625rem;
  font-weight: 620;
  color: var(--ink);
  white-space: nowrap;
}

.dep__main { display: block; min-width: 0; }

.dep__ext {
  display: block;
  font-size: .75rem;
  letter-spacing: .01em;
  color: var(--ink-faint);
}

.dep__state {
  justify-self: start;
  display: inline-block;
  font-family: var(--body);
  font-size: .6875rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  white-space: nowrap;
  padding: .38rem .7rem;
  border-radius: 2px;
  border: 1px solid transparent;
}

.dep__state--view { background: var(--action); color: var(--on-state); }
.dep__state--limited { background: var(--state-limited); color: var(--on-state); }
.dep__state--wait { background: var(--state-wait); color: var(--on-state); }

/* A full departure is not an action, so it does not wear the shape of one. */
.dep__state--full {
  background: transparent;
  color: var(--state-full);
  border-color: var(--state-full);
  font-weight: 620;
}

.dep__note:empty, .dep__ext:empty { display: none; }

/* ONE grid for the whole ledger, not twenty-eight grids that happen to share a
   declaration. Each row used to resolve its own tracks, and the last one was
   content-sized, so a row whose chip read TOUR FULL sized that track narrower
   than one reading VIEW TOUR and the three fr tracks beside it divided a
   different leftover: dates, price and the guest cap wandered down the list by
   19px, 28px and 35px, with a visible step where the full departures gave way.
   That defeats the point of a catalogue - 28 departures exist here to be
   compared at a glance.

   So the track list lives on the list and every row resolves against it. The
   row keeps its own box (the background, the press, the rule beneath it, the
   padding) by subgridding rather than by dissolving into `display: contents`,
   and the li between them subgrids too, since a subgrid only reaches one level.
   Measured 2026-09-20: a subgrid uses its OWN column-gap and re-splits the
   parent's gutters when they differ, so all three levels must declare the same
   one or the columns shear apart again. */
@media (min-width: 760px) {
  /* The state track carries a floor, and that is a consequence of sharing the
     grid rather than a tidying-up. A content-sized last track is now sized by
     whichever chips are VISIBLE, so filtering to a set with no JOIN WAIT LIST
     in it narrowed the track and slid dates, price and cap 12px sideways -
     the one thing the filter's own transition promises not to do. The floor is
     the widest chip measured on the page (JOIN WAIT LIST, 127.5px) rounded up;
     `auto` still lets it grow if a label ever gets longer. */
  .dep-list {
    display: grid;
    grid-template-columns: minmax(0, 2.15fr) minmax(0, 1.15fr) minmax(0, .8fr) minmax(8.25rem, auto);
  }
  .dep-list > li,
  .dep {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: subgrid;
  }
  .dep-list,
  .dep-list > li,
  .dep { column-gap: 1.5rem; }
  .dep {
    row-gap: .35rem;
    align-items: center;
  }
  /* row / column, explicitly: grid-template-areas cannot name tracks it does
     not itself define, and in a subgridded axis it does not define them. */
  .dep__main  { grid-row: 1; grid-column: 1; }
  .dep__dates { grid-row: 1; grid-column: 2; }
  .dep__price { grid-row: 1; grid-column: 3; text-align: right; }
  .dep__state { grid-row: 1; grid-column: 4; justify-self: end; }
  .dep__cap   { grid-row: 2; grid-column: 1; }
  .dep__note  { grid-row: 2; grid-column: 2 / -1; }
  .dep__ext   { grid-row: 3; grid-column: 1 / -1; }
  .dep__note:empty, .dep__ext:empty { display: none; }
}

@media (min-width: 1040px) {
  .dep-list {
    grid-template-columns: minmax(0, 2.3fr) minmax(0, 1.1fr) minmax(0, .75fr) minmax(0, .62fr) minmax(8.25rem, auto);
  }
  .dep__cap   { grid-row: 1; grid-column: 4; text-align: right; }
  .dep__state { grid-row: 1; grid-column: 5; }
  .dep__note  { grid-row: 2; grid-column: 1 / -1; }
}

/* filter chips */

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  list-style: none;
  margin: 0 0 var(--s4);
  padding: 0;
  max-width: none;
}

.chips li { margin: 0; }

.chip {
  display: inline-block;
  font-family: var(--body);
  font-size: .75rem;
  font-weight: 620;
  letter-spacing: .07em;
  text-transform: uppercase;
  white-space: nowrap;
  padding: .45rem .8rem;
  color: var(--ink-quiet);
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 2px;
  cursor: pointer;
  transition: color .15s ease, border-color .15s ease;
}

.chip:hover { color: var(--ink); border-color: var(--rule-strong); }

/* Pressing a filter previews what it is about to become: the chip takes the
   selected colour under the finger, then keeps it. */
.chip:active { color: var(--accent); border-color: var(--accent); }

.chip[aria-pressed="true"] {
  color: var(--accent);
  border-color: var(--accent);
}

/* An attribute selector of equal weight, later in the file, was beating
   .chip:hover - so the one chip you are most likely to point at, the one
   already selected, was the only chip in the row that answered to nothing. */
.chip[aria-pressed="true"]:hover { color: var(--accent-hover); border-color: var(--accent-hover); }
.chip[aria-pressed="true"]:active { color: var(--ink); border-color: var(--ink); }

.dep-list li[hidden] { display: none; }

/* PITCH-PLAN decisions item 22: "one restrained transition on the catalogue
   filter is welcome at the polish step". This is it, and it is the whole of the
   motion on this site. Pressing a chip used to swap twenty-eight rows for four
   with no acknowledgement at all beyond a line of counting text, so the only
   rows that move are the ones that were NOT there a moment ago: they fade up
   over 220ms, staggered barely enough to read as a list arriving rather than a
   block appearing. Rows that survive the filter do not move, the result is never
   delayed, and nothing reflows - opacity only. Written inside `no-preference`
   rather than left to the reduce blanket, so under reduced motion the animation
   does not exist instead of being raced to nothing. */
@media (prefers-reduced-motion: no-preference) {
  .dep-list li.is-in { animation: dep-in .22s cubic-bezier(.22, 1, .36, 1) both; }
  .dep-list li.is-in:nth-child(2) { animation-delay: .02s; }
  .dep-list li.is-in:nth-child(3) { animation-delay: .04s; }
  .dep-list li.is-in:nth-child(4) { animation-delay: .06s; }
  .dep-list li.is-in:nth-child(n+5) { animation-delay: .08s; }

  @keyframes dep-in { from { opacity: 0; } to { opacity: 1; } }
}

.chips__count {
  font-size: .8125rem;
  color: var(--ink-quiet);
  margin: 0 0 var(--s3);
}

/* ============================================================ TOUR CARDS
   A photo-card catalogue, requested alongside the row-based .dep-list above
   (which stays as-is for the homepage teaser). Same underlying data - name,
   place, dates, price, cap, state, tags - a different shape for it, plus a
   search box that ANDs with the existing chip filter rather than replacing
   it: a chip narrows by category, the box narrows by what she typed. */

/* ============================================================ FILTER BAR
   The search box and category chips, held to the top of the viewport as she
   scrolls a long catalogue - added at the customer's request so narrowing
   the list never means scrolling all the way back up first. Sits on its own
   layer below the (already sticky) header, and takes the page's own ground
   so nothing shows through as content slides underneath it. */
.filter-bar {
  position: sticky;
  top: var(--header-h-sm);
  z-index: 50;
  background: var(--ground);
  padding-top: var(--s3);
}

@media (min-width: 1200px) {
  .filter-bar { top: var(--header-h); }
}

.tour-search {
  display: flex;
  align-items: center;
  gap: .6rem;
  max-width: 28rem;
  margin: 0 0 var(--s3);
  padding-left: .95rem;
  border: 1px solid var(--rule);
  border-radius: 3px;
  background: var(--ground-lift);
  transition: border-color .15s ease;
}
.tour-search:focus-within { border-color: var(--accent); }
.tour-search svg { flex: none; color: var(--ink-faint); }
.tour-search input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  padding: .7rem .2rem;
  font-family: var(--body);
  font-size: .9375rem;
  color: var(--ink);
}
.tour-search input::placeholder { color: var(--ink-faint); }
/* Our own clear button already does this job - the native one would sit
   right beside it as a second, unstyled affordance doing the same thing. */
.tour-search input::-webkit-search-cancel-button { display: none; }
.tour-search input::-webkit-search-decoration { display: none; }
.tour-search input::-ms-clear { display: none; }
.tour-search input:focus { outline: none; }
.tour-search__clear {
  flex: none;
  border: 0;
  border-left: 1px solid var(--rule);
  background: transparent;
  color: var(--ink-faint);
  font-size: .8125rem;
  letter-spacing: .04em;
  cursor: pointer;
  padding: .5rem .9rem;
}
.tour-search__clear:hover { color: var(--ink); }
.tour-search__clear[hidden] { display: none; }

.tour-grid__empty {
  font-size: .9375rem;
  color: var(--ink-quiet);
  padding: var(--s3) 0;
}

.tour-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: none;
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.75rem var(--gutter);
}
.tour-grid li { margin: 0; }
.tour-grid li[hidden] { display: none; }

@media (min-width: 640px) {
  .tour-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1000px) {
  .tour-grid { grid-template-columns: repeat(3, 1fr); }
}

/* The home page features three of each on a wide screen and two below it: the
   third card would sit alone on the two-column row, and on a phone it is one
   more card to scroll past. */
@media (max-width: 999px) {
  .tour-grid li.tour-grid__wide-only { display: none; }
}

.tour-card {
  display: flex;
  flex-direction: column;
  height: 100%;
  text-decoration: none;
  color: inherit;
  background: var(--ground-lift);
  border: 1px solid var(--rule);
  border-radius: 6px;
  overflow: hidden;
  transition: border-color .15s ease, transform .15s ease;
}
.tour-card:hover,
.tour-card:focus-visible { border-color: var(--rule-strong); }
.tour-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

@media (prefers-reduced-motion: no-preference) {
  .tour-card:hover { transform: translateY(-2px); }
}

.tour-card__media {
  position: relative;
  aspect-ratio: 4 / 3;
  background: var(--ground-press);
  overflow: hidden;
}
.tour-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform .3s ease;
}
@media (prefers-reduced-motion: no-preference) {
  .tour-card:hover .tour-card__media img { transform: scale(1.035); }
}
/* Legibility scrim behind the place mark - not every photo is naturally dark
   at the top, and it is skipped outright on a card with no photo. */
.tour-card__media::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(0,0,0,.55) 0%, rgba(0,0,0,0) 55%);
  pointer-events: none;
}
.tour-card__media--flat::after { display: none; }
/* Phillip's own card artwork, as on his original site: a 3:2 photo with the
   place name already printed across the top, each one framed to leave room
   for it. Shown whole - the frame matches the file, so nothing is cropped -
   and nothing is laid over it. Our place mark stays in the markup for the
   search box to read, hidden, so the name is never drawn twice. */
.tour-card__media--art { aspect-ratio: 3 / 2; }
.tour-card__media--art::after { display: none; }
.tour-card__place-mark[hidden] { display: none; }
.tour-card__place-mark {
  position: absolute;
  z-index: 1;
  top: .9rem;
  left: 1.1rem;
  right: 1rem;
  margin: 0;
  font-family: var(--display);
  font-weight: 600;
  font-size: 1.4rem;
  line-height: 1.05;
  letter-spacing: -.01em;
  color: #fff;
  text-transform: uppercase;
}
/* No real photo of this departure exists yet - an honest flat panel, never a
   borrowed photo of somewhere else. */
.tour-card__media--flat .tour-card__place-mark {
  position: static;
  display: flex;
  align-items: center;
  height: 100%;
  color: var(--ink-quiet);
  padding: 0 1.2rem;
}

.tour-card__body {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding: 1.1rem 1.2rem 1.2rem;
}
.tour-card__name {
  font-family: var(--display);
  font-size: 1.15rem;
  font-weight: 500;
  letter-spacing: -.015em;
  line-height: 1.25;
  color: var(--ink);
  margin: 0 0 .7rem;
}
.tour-card:hover .tour-card__name,
.tour-card:focus-visible .tour-card__name { color: var(--accent-hover); }

.tour-card__meta {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
  display: grid;
  gap: .38rem;
}
.tour-card__meta li {
  display: flex;
  align-items: flex-start;
  gap: .5rem;
  margin: 0;
  font-size: .8438rem;
  color: var(--ink-quiet);
}
.tour-card__meta svg { flex: none; margin-top: .15rem; color: var(--ink-faint); }
.tour-card__price { color: var(--ink); font-weight: 620; font-size: .9375rem; }
.tour-card__note { font-size: .78rem; color: var(--ink-faint); margin: -.4rem 0 1rem; }

.tour-card__cta {
  margin-top: auto;
  display: block;
  text-align: center;
  font-family: var(--body);
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  padding: .75rem 1rem;
  border-radius: 3px;
  border: 1px solid transparent;
  transition: background-color .15s ease;
}
.tour-card__cta--view { background: var(--action); color: var(--on-state); }
.tour-card__cta--limited { background: var(--state-limited); color: var(--on-state); }
/* The owner asked for these two to swap colours (2026-09-27): "New Dates Out
   Soon - Register Interest" now wears the filled blue, and "Tour Full - Add to
   Waitlist" the grey outline. Both still lead to the tour's form. */
.tour-card__cta--full {
  background: transparent;
  color: var(--state-full);
  border-color: var(--state-full);
}
.tour-card__cta--tba { background: var(--state-wait); color: var(--on-state); }
.tour-card:hover .tour-card__cta--view,
.tour-card:focus-visible .tour-card__cta--view { background: var(--action-hover); }

.tour-card__ext {
  display: block;
  text-align: center;
  font-size: .6875rem;
  letter-spacing: .02em;
  color: var(--ink-faint);
  margin-top: .5rem;
}

/* The row-fade-in on filter/search stays shared with .dep-list above - same
   substance, same restraint, now applying to two markups instead of one. */
@media (prefers-reduced-motion: no-preference) {
  .tour-grid li.is-in { animation: dep-in .22s cubic-bezier(.22, 1, .36, 1) both; }
  .tour-grid li.is-in:nth-child(2) { animation-delay: .02s; }
  .tour-grid li.is-in:nth-child(3) { animation-delay: .04s; }
  .tour-grid li.is-in:nth-child(n+4) { animation-delay: .06s; }
}

/* =============================================================== DAY SPINE */

.days { counter-reset: day; }

.day {
  display: grid;
  grid-template-columns: 1fr;
  gap: .4rem var(--s4);
  padding-block: var(--s4);
  border-top: 1px solid var(--rule);
  align-items: start;
}

.day__no {
  font-family: var(--display);
  font-size: clamp(1.6rem, 3vw, 2.6rem);
  font-weight: 380;
  line-height: 1;
  letter-spacing: -.03em;
  color: var(--accent);
  align-self: start;
}

.day__no small {
  display: block;
  font-family: var(--body);
  font-size: .6875rem;
  font-weight: 640;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: .35rem;
}

.day__body { min-width: 0; max-width: 66ch; }
.day__body > :last-child { margin-bottom: 0; }

.day__head {
  font-family: var(--display);
  font-size: 1.15rem;
  font-weight: 460;
  letter-spacing: -.015em;
  margin: 0 0 .6rem;
  color: var(--ink);
}

@media (min-width: 760px) {
  .day { grid-template-columns: 8.5rem minmax(0, 1fr); }
}

/* ======================================================= PRICE & TERMS PANEL
   The block a buyer compares numbers in, and the block she screenshots and
   forwards. Originally a deliberate light card (see tokens.css); the customer
   asked, in as many words, to avoid the white background and black text and
   have it match the rest of the site, so the --panel-* tokens now resolve to
   the same raised-dark surface used elsewhere rather than a light insert. */

.panel {
  background: var(--panel);
  color: var(--panel-ink);
  padding: clamp(1.5rem, 3.5vw, 2.75rem);
}

.panel h2, .panel h3, .panel h4 { color: var(--panel-head); }
.panel p { color: var(--panel-ink); }
.panel strong { color: var(--panel-head); }
.panel li::marker { color: var(--panel-quiet); }
.panel a { color: var(--accent); }

.panel__head {
  font-family: var(--body);
  font-size: .75rem;
  font-weight: 640;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--panel-quiet);
  margin: 0 0 var(--s3);
}

.tiers {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s3);
  margin-bottom: var(--s4);
}

@media (min-width: 720px) { .tiers { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.tier {
  border-top: 2px solid var(--panel-head);
  padding-top: .85rem;
}

.tier__name {
  font-family: var(--body);
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--panel-quiet);
  margin: 0 0 .35rem;
}

.price__amount {
  font-size: clamp(1.5rem, 2.4vw, 1.9rem);
  font-weight: 650;
  line-height: 1.1;
  color: var(--panel-head);
  letter-spacing: -.01em;
  display: block;
}

.tier__qual {
  display: block;
  font-size: .8125rem;
  line-height: 1.45;
  color: var(--panel-quiet);
  margin-top: .3rem;
}

.panel__cols {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s4);
}

@media (min-width: 860px) { .panel__cols { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* The sub-heads inside the price and terms panel. They are h3, not h4: the
   block they sit in is introduced by "How much does it cost?" as an h2, and
   jumping a level strands them for anyone reading by heading. The treatment is
   a small uppercase label rather than the body h3, because they are column
   labels over tables and lists, not section headings. */
.panel__cols h3 {
  font-family: var(--body);
  font-size: .75rem;
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--panel-quiet);
  border-top: 1px solid var(--panel-rule);
  padding-top: .8rem;
  margin-top: 0;
  margin-bottom: var(--s2);
}

.panel ul, .panel ol, .panel p { max-width: 62ch; }

.panel .fine {
  font-size: .875rem;
  line-height: 1.55;
  color: var(--panel-quiet);
}

.rate {
  width: 100%;
  border-collapse: collapse;
  font-size: .9375rem;
  margin: 0 0 1rem;
}

.rate th, .rate td {
  text-align: left;
  padding: .55rem .75rem .55rem 0;
  border-bottom: 1px solid var(--panel-rule);
  vertical-align: top;
}

.rate td:last-child {
  text-align: right;
  white-space: nowrap;
  font-weight: 650;
  color: var(--panel-head);
  padding-right: 0;
}

.rate th {
  font-family: var(--body);
  font-size: .6875rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--panel-quiet);
}

/* the same table, on the dark ground (the farm add-ons) */
.rate--dark { color: var(--ink); }
.rate--dark th, .rate--dark td { border-bottom-color: var(--rule); }
.rate--dark th { color: var(--ink-faint); }
.rate--dark td:last-child { color: var(--ink); }

/* =============================================================== TARIFF */

.tariff {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s3);
}

@media (min-width: 760px) { .tariff { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.tariff__card {
  background: var(--panel);
  color: var(--panel-ink);
  padding: clamp(1.35rem, 3vw, 2rem);
}

.tariff__card h3 { color: var(--panel-head); margin-bottom: .4rem; }

.tariff__amount {
  display: block;
  font-size: 1.85rem;
  font-weight: 650;
  line-height: 1.1;
  color: var(--panel-head);
}

.tariff__per {
  display: block;
  font-size: .8125rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--panel-quiet);
  margin: .25rem 0 1rem;
}

.tariff__card ul { padding-left: 1.05rem; font-size: .9375rem; }
.tariff__card li::marker { color: var(--panel-quiet); }
.tariff__card ul + ul { margin-top: -.3rem; }

/* Replaces the old ASCII bullet-and-diamond divider row - the customer asked
   for the costing section to "reduce visual clutter"; a quiet label reads
   cleaner than a line of punctuation doing the same job. */
.tariff__sub {
  font-family: var(--body);
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--panel-quiet);
  border-top: 1px solid var(--panel-rule);
  padding-top: .8rem;
  margin: .9rem 0 .5rem;
}

/* =============================================================== FEATURES */

.feat {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s3) var(--s4);
  align-items: start;
  padding-block: var(--s4);
  border-top: 1px solid var(--rule);
}

.feat__body { min-width: 0; }
.feat__body > :last-child { margin-bottom: 0; }

@media (min-width: 820px) {
  /* Deliberate 5/7 editorial split: the plate holds its native ratio and the
     prose gets the room it needs, rather than two equal columns with one a
     wall of text beside a short peer. */
  .feat { grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr); }
  .feat--flip { grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); }
  .feat--flip > .plate { order: 2; }
  .feat--flip > .feat__media { order: 2; }
  .feat--flip .feat__body { order: 1; }
}

/* A room's photographs as a small swipe strip in the plate's column (her ask,
   chg-80760160: "a swipeable gallery" for each room on Farm). One shared caption
   under the strip, as the single plate had. On a phone it is the site's swipe
   strip as is; above it each frame takes most of the column at its own shape, so
   the next one peeks, and the strip's arrows step one frame at a time. */
.feat__media { min-width: 0; }

@media (min-width: 701px) {
  .plates--room { scroll-snap-type: x mandatory; }
  .plates--room .plate { flex: 0 0 88%; }
  .plates--room .plate img {
    width: 100%;
    height: auto;
    max-width: 100%;
  }
}

.cols {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s4);
}

@media (min-width: 820px) {
  .cols--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cols--75 { grid-template-columns: minmax(0, 1.45fr) minmax(0, 1fr); }
  /* A plate beside a long body of prose tracks it rather than leaving a void
     below itself. No animation, no scroll listener: it is one sticky rule. */
  .cols--75 > .plate { position: sticky; top: calc(var(--header-h) + 2rem); }
}

.cols > * { min-width: 0; }

/* =============================================================== SAY / TRUST */

.says {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s4);
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: none;
}

@media (min-width: 820px) { .says { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.say { margin: 0; border-top: 1px solid var(--rule); padding-top: var(--s3); }

.say blockquote {
  margin: 0 0 .8rem;
  font-family: var(--display);
  font-size: 1.12rem;
  font-weight: 380;
  line-height: 1.4;
  letter-spacing: -.012em;
  color: var(--ink);
}

.say figcaption {
  font-size: .8125rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-quiet);
  font-weight: 620;
}

/* A partner promotion, set apart from the plain testimonial it shares a row
   with (client request) - a tinted card in the accent family rather than
   another --action fill, since each page already spends its one --action
   button elsewhere. */
.promo-panel {
  background: var(--accent-subtle);
  border: 1px solid var(--accent);
  border-radius: 4px;
  padding: var(--s4);
}

.say figcaption span {
  display: block;
  letter-spacing: .02em;
  text-transform: none;
  font-weight: 400;
  color: var(--ink-faint);
}

.stats {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s4);
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: none;
}

@media (min-width: 720px) { .stats { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.stat { border-top: 1px solid var(--rule); padding-top: var(--s3); }

.stat__figure {
  display: block;
  font-family: var(--display);
  font-size: clamp(2.6rem, 5vw, 3.75rem);
  font-weight: 340;
  line-height: 1;
  letter-spacing: -.035em;
  color: var(--ink);
}

.stat__unit {
  display: block;
  font-family: var(--body);
  font-size: .75rem;
  font-weight: 640;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--accent);
  margin: .5rem 0 .55rem;
}

.stat p { font-size: .9375rem; color: var(--ink-quiet); margin: 0; }

/* credentials */

.creds {
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: none;
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
}

.creds li {
  margin: 0;
  padding: .85rem 0;
  border-top: 1px solid var(--rule);
  font-size: .9375rem;
  line-height: 1.5;
  color: var(--ink);
  display: grid;
  grid-template-columns: 1fr;
  gap: .15rem;
}

.creds b {
  font-family: var(--body);
  font-weight: 650;
  color: var(--ink);
}

.creds span { color: var(--ink-quiet); font-size: .875rem; }

@media (min-width: 720px) {
  .creds li { grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); gap: 1.5rem; align-items: baseline; }
  .creds span { text-align: right; }
}

/* A creds row whose second cell LABELS the value beside it, rather than being a
   ledger column of its own. The default right-aligns that cell against a 1fr
   column, which is right for award-and-year (the eye runs along the rule to a
   column of dates) and wrong for a single short word: "Email" landed 235px from
   the address it names and read as a stray word, while the phone rows in the
   fold above sit beside their numbers. The value takes the width it needs and
   the label runs on from it - the same shape, and the same reasoning, as
   `.fold__lines .creds` above. Must stay after the media query it overrides:
   the two selectors carry equal specificity, so order is what decides. */
.creds--run-on li {
  grid-template-columns: max-content minmax(0, 1fr);
  gap: .2rem .9rem;
  align-items: baseline;
}
.creds--run-on span { text-align: left; }

@media (min-width: 720px) { .creds--run-on li { gap: .2rem 1.25rem; } }

/* bios */

.bio { padding-block: var(--s5); border-top: 1px solid var(--rule); }

.bio__role {
  font-family: var(--body);
  font-size: .75rem;
  font-weight: 640;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-quiet);
  margin: 0 0 var(--s3);
}

.bio h2 { margin-bottom: .35rem; }

/* ================================================================= AVATAR
   A small circular personal photo - Phillip beneath his credentials, a
   testimonial author beside her quote, a family member beside their bio -
   added at the customer's request so a visitor can put a face to the name.
   Every source photo here is a landscape original rather than a pre-cropped
   headshot, so the crop is done live with object-fit rather than baked into
   a second file: object-position favours the top third, where a portrait
   subject's face sits in an uncropped frame. */
.avatar {
  display: block;
  flex: none;
  border-radius: 50%;
  overflow: hidden;
  background: var(--ground-lift);
}

.avatar img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 20%;
}

.avatar--sm { width: 3.25rem; height: 3.25rem; }
.avatar--md { width: 4.75rem; height: 4.75rem; }

/* Name + face together, above the role line - used for Phillip's credential
   strip on the home page and for each family member's heading on About Us. */
.person {
  display: flex;
  align-items: center;
  gap: var(--s2);
  margin-bottom: var(--s2);
}

.person .avatar { margin: 0; }

/* A testimonial's face sits beside her words, not above them: the quote
   still leads, matching every .say on the site. */
.say__who {
  display: flex;
  align-items: center;
  gap: .6rem;
  margin-top: .8rem;
}

.say__who .avatar { margin: 0; }

.say figcaption { margin: 0; }

/* Four founders read down this page, and four identical text-left/plate-right
   rows read as a template rather than as four people. Three things vary the
   rhythm, and none of them touches a photograph: the plate column may lead
   instead of follow (`.cols--flip`, a visual reorder only - the DOM keeps the
   heading first, so the reading order is unchanged); a long bio's column holds
   TWO plates rather than one 355px frame beside 900px of prose; and one bio
   drops the split entirely for a stacked band, which is what keeps the page
   under the rubric's cap of two consecutive split rows. Every plate still
   sits at its native ratio, uncropped, on the ground, with nothing over it. */

.bio__body { min-width: 0; }

/* The column beside the prose. Two stacked plates on a long bio: it tracks the
   text instead of leaving a void, and on a wildlife photographer's own page the
   thing that fills the space is more of the work. */
.bio__aside {
  display: grid;
  gap: var(--s4);
  align-content: start;
  min-width: 0;
}

@media (min-width: 820px) {
  .cols--75 > .bio__aside { align-self: start; }
  /* A single plate tracks a long read; a pair already fills the column, and a
     sticky block taller than the viewport only fights the scroll. */
  .cols--75 > .bio__aside:has(> .plate:only-child) {
    position: sticky;
    top: calc(var(--header-h) + 2rem);
  }

  /* Mirror the split. Order is visual only; source order is body-then-plates. */
  .cols--75.cols--flip { grid-template-columns: minmax(0, 1fr) minmax(0, 1.45fr); }
  .cols--75.cols--flip > .bio__aside { order: 1; }
  .cols--75.cols--flip > .bio__body { order: 2; }
}

/* The band: the row that breaks the run of splits. The prose sets to its own
   measure across the full width, and the plates follow beneath it as a pair. */
.bio--band > .bio__body { max-width: 68ch; }
.bio--band > .plates { margin-top: var(--s4); }

.pull {
  margin: 0 0 var(--s3);
  font-family: var(--display);
  font-size: clamp(1.2rem, 1.9vw, 1.5rem);
  font-weight: 340;
  font-style: italic;
  line-height: 1.35;
  letter-spacing: -.015em;
  color: var(--accent);
  max-width: 34ch;
  border-left: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  padding-left: 1.1rem;
}

/* =============================================================== FAQ
   Two accordions share one dress. `.se-faq` is the injected, generated,
   zero-JS accordion (build-found-ready.mjs, which pairs it with FAQPage
   JSON-LD on contact.html). `.faq` is HER OWN page FAQ, carried verbatim on
   the tour, camp and farm pages.

   Those four pages now carry BOTH class sets. `.faq` was originally chosen to
   keep them away from the found-ready gate, since a `.se-faq` on a page with
   no FAQPage block is a structured-data mismatch the gate refuses, and
   rightly - but the answer to that was the schema, not the hiding: 34 of her
   questions were rendering with no structured data at all, on a site whose
   product promise includes being found. Each of the four now carries a
   FAQPage block built from its own questions, and wears `se-faq` so the gate
   pairs the two and fails if they ever stop matching. */

.se-faq, .faq { list-style: none; margin: 0; padding: 0; max-width: none; }

.se-faq__title { margin-bottom: var(--s3); }

.se-faq__item, .faq__item {
  border-top: 1px solid var(--rule);
  margin: 0;
}

.se-faq__item:last-of-type, .faq__item:last-of-type { border-bottom: 1px solid var(--rule); }

.se-faq__q, .faq__q {
  display: flex;
  align-items: baseline;
  gap: .9rem;
  cursor: pointer;
  list-style: none;
  padding: 1.1rem 0;
  font-family: var(--display);
  font-size: 1.08rem;
  font-weight: 450;
  letter-spacing: -.012em;
  line-height: 1.3;
  color: var(--ink);
}

.se-faq__q::-webkit-details-marker, .faq__q::-webkit-details-marker { display: none; }

.se-faq__q::before, .faq__q::before {
  content: "+";
  font-family: var(--body);
  font-weight: 400;
  font-size: 1.2rem;
  line-height: 1;
  color: var(--accent);
  flex: 0 0 auto;
  width: .9rem;
}

.se-faq__item[open] > .se-faq__q::before,
.faq__item[open] > .faq__q::before { content: "\2212"; }

.se-faq__q, .faq__q { transition: color .15s ease; }
.se-faq__q:hover, .faq__q:hover { color: var(--accent-hover); }
.se-faq__q:active, .faq__q:active { color: var(--ink); }

.se-faq__a, .faq__a {
  padding: 0 0 1.35rem 1.8rem;
  font-size: 1rem;
  line-height: 1.6;
  color: var(--ink-quiet);
  max-width: 68ch;
}

.se-faq__a p, .faq__a p { color: inherit; max-width: none; }
.se-faq__a p:last-child, .faq__a p:last-child { margin-bottom: 0; }
.se-faq__a strong, .faq__a strong { color: var(--ink); }
.faq__a ul, .faq__a ol { max-width: none; color: inherit; }

/* =============================================================== FORM
   The markup is generated by build-contact-form.mjs; this is the direction's
   dress for it. */

.cf-form {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s3);
  max-width: 44rem;
}

/* The markup is generated: every row is a <p>, the submit sits inside one, and
   the Turnstile mount is an empty div until go-live stamps the real key in. */
.cf-form > p { margin: 0; }
.cf-form > .cf-turnstile:empty { display: none; }

@media (min-width: 720px) {
  .cf-form { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* The generated row order is name, email, phone, message, then the submit.
     Addressed by position as well as by :has(), so the layout does not depend
     on one selector being supported. */
  .cf-form > p:nth-last-of-type(1),
  .cf-form > p:nth-last-of-type(2),
  .cf-form > p:has(.cf-textarea),
  .cf-form > p:has(.cf-submit),
  .cf-form > .cf-turnstile,
  .cf-form > .cf-status,
  .cf-field--wide { grid-column: 1 / -1; }
}

.cf-field { display: block; min-width: 0; }

.cf-label {
  display: block;
  font-family: var(--body);
  font-size: .6875rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-quiet);
  margin-bottom: .45rem;
}

.cf-input, .cf-textarea, .cf-field select {
  display: block;
  width: 100%;
  font-family: var(--body);
  font-size: 1rem;
  line-height: 1.5;
  color: var(--ink);
  background: var(--ground-lift);
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: .75rem .85rem;
}

.cf-input:hover, .cf-textarea:hover { border-color: var(--rule-strong); }
.cf-input:focus, .cf-textarea:focus { border-color: var(--accent); }

.cf-textarea { min-height: 9rem; resize: vertical; }

.cf-input::placeholder, .cf-textarea::placeholder { color: var(--ink-faint); }

/* Event Registration only: a hint under a field, and two tick boxes. */
.cf-hint { display: block; margin-top: .4rem; font-size: .8438rem; color: var(--ink-faint); }
.cf-check { display: flex; align-items: flex-start; gap: .7rem; }
.cf-check input { flex: none; width: 1.15rem; height: 1.15rem; margin: .2rem 0 0; accent-color: var(--accent); }
.cf-check label { font-size: .9375rem; line-height: 1.5; color: var(--ink); }
/* The upload field (Event Registration): the native picker on the site's field ground, and the
   line the handler fills when a chosen file is the wrong kind, too big or one too many. */
.cf-file { padding: .6rem .85rem; cursor: pointer; }
.cf-file::file-selector-button { margin-right: .8rem; padding: .45rem .9rem; font: inherit; font-size: .875rem; font-weight: 650; color: var(--ink); background: var(--ground); border: 1px solid var(--rule-strong); border-radius: 2px; cursor: pointer; }
.cf-file-error { display: block; margin-top: .4rem; font-size: .875rem; color: var(--state-limited); }
.cf-file-error[hidden] { display: none; }
/* A row is a <p>, which the base caps at reading width; a full-width row on this
   form spans both columns instead of stopping short of them. */
.cf-form > .cf-field--wide { max-width: none; }

/* Terms & Conditions, Privacy Policy: a divider as wide as the text it divides. */
.legal .prose hr { max-width: var(--maxw-read); }

.cf-submit {
  justify-self: start;
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  padding: .82rem 1.6rem;
  background: var(--action);
  color: var(--on-action);
  font-family: var(--body);
  font-size: .9375rem;
  font-weight: 680;
  letter-spacing: .02em;
  white-space: nowrap;
  border: 0;
  border-radius: 2px;
  cursor: pointer;
}

.cf-submit { transition: background-color .18s ease; }
.cf-submit:hover { background: var(--action-hover); }
.cf-submit:active { background: var(--action-active); }

/* This rule already existed and nothing ever set the attribute: the generated
   handler posts for up to eight seconds and changes nothing on screen while it
   does, so a visitor presses Send again. site.js now wires the state the dress
   was waiting for. :disabled as well as [disabled] so the styling cannot drift
   from whichever the script uses. */
.cf-submit[disabled], .cf-submit:disabled { opacity: .6; cursor: default; }
.cf-submit[disabled]:hover, .cf-submit:disabled:hover { background: var(--action); }

.cf-status {
  font-size: .9375rem;
  line-height: 1.55;
  color: var(--ink);
  max-width: 60ch;
}

.cf-status a { color: var(--accent); text-decoration: underline; text-underline-offset: .18em; }

/* =============================================================== DOCK */

.dock {
  position: fixed;
  right: clamp(.75rem, 2vw, 1.5rem);
  bottom: clamp(.75rem, 2vw, 1.5rem);
  z-index: 70;
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  padding: .7rem 1.05rem;
  background: var(--action);
  color: var(--on-action);
  font-family: var(--body);
  font-size: .9375rem;
  font-weight: 700;
  letter-spacing: .01em;
  text-decoration: none;
  white-space: nowrap;
  border-radius: 999px;
  box-shadow: 0 10px 28px rgba(0, 0, 0, .45);
}

.dock { transition: background-color .18s ease; }
.dock:hover { background: var(--action-hover); color: var(--on-action); }
.dock:active { background: var(--action-active); color: var(--on-action); }
.dock svg { flex: 0 0 auto; }
.dock__num { white-space: nowrap; font-variant-numeric: tabular-nums lining-nums; }

/* The dock is fixed, so at first paint it lands on the hero plate of all eleven
   pages - measured at 390x844, eleven of eleven. That is a straight collision
   between two things the brief holds: the signature move's second clause says
   no badge or button sits on top of a photograph ANYWHERE, including the fold,
   and the contact dock is a standing convention that is on whenever the crawl
   found a phone number. The dock stays and is held back instead: it is absent
   from the fold and arrives the moment she starts reading. Past the fold it is
   a fixed affordance and will cross photographs, which is the accepted cost of
   having one at all.

   `display: none`, not a hidden-but-laid-out state: a visibility:hidden element
   still returns a box, so it would still be measured as sitting on the plate,
   and it would still be a thing that is there and cannot be used.

   The polarity is the safe way round. CSS keeps the dock VISIBLE and this class
   is what hides it, so a visitor with JavaScript off keeps her call button
   rather than losing it for good. */
.dock-stow .dock { display: none; }

/* The reveal is an animation rather than a transition, because the stow is the
   page's starting state and not a gesture: a transition would run in BOTH
   directions and play a fade-out at load that nobody asked for. Written inside
   `no-preference`, as the catalogue filter's entrance is, so under reduced
   motion it does not exist at all rather than being raced to zero - the dock
   simply appears. */
@media (prefers-reduced-motion: no-preference) {
  .dock.is-revealing { animation: dock-in .26s cubic-bezier(.22, 1, .36, 1) both; }

  @keyframes dock-in {
    from { opacity: 0; transform: translateY(.6rem); }
    to { opacity: 1; transform: none; }
  }
}

/* =============================================================== FOOTER */

.site-footer {
  border-top: 1px solid var(--rule);
  padding-block: var(--s5) var(--s4);
  margin-top: var(--s6);
}

.foot {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s4);
}

@media (min-width: 760px) { .foot { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1040px) { .foot { grid-template-columns: 1.25fr 1fr 1fr 1fr; } }

.foot__brand { display: flex; flex-direction: column; align-items: flex-start; gap: var(--s2); }
.foot__brand img { display: block; height: 52px; width: auto; }

.foot address {
  font-style: normal;
  font-size: .9375rem;
  line-height: 1.6;
  color: var(--ink-quiet);
}

.foot h2 {
  font-family: var(--body);
  font-size: .6875rem;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 var(--s2);
}

.foot ul { list-style: none; margin: 0; padding: 0; max-width: none; }
.foot li { margin: 0 0 .5rem; }

.foot a {
  font-size: .9375rem;
  color: var(--ink-quiet);
  text-decoration: none;
  white-space: nowrap;
}

.foot a { transition: color .15s ease; }
.foot a:hover { color: var(--accent-hover); }
.foot a:active { color: var(--ink); }

.foot__role {
  display: block;
  font-size: .6875rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-faint);
  white-space: normal;
  margin-bottom: .1rem;
}

.foot__num {
  display: block;
  white-space: nowrap;
  font-variant-numeric: tabular-nums lining-nums;
  color: var(--ink);
}

.foot__mail { white-space: normal; overflow-wrap: anywhere; }

/* Follow Us and Quick Links fold away on a phone, where the footer stacks into
   one long column; from 760px up they are ordinary columns, always open, and
   the summary is only their heading. site.js closes them on a phone and keeps
   them open above it, so with JavaScript off every link is still showing. */
.foot__group > summary { list-style: none; }
.foot__group > summary::-webkit-details-marker { display: none; }
.foot__group > summary h2 { margin: 0 0 var(--s2); }

@media (max-width: 759px) {
  .foot__group { border-top: 1px solid var(--rule); }
  .foot__group + .foot__group { margin-top: calc(-1 * var(--s4)); }
  .foot__group > summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    min-height: 48px;
    cursor: pointer;
  }
  .foot__group > summary h2 { margin: 0; }
  .foot__group > summary::after {
    content: "+";
    font-size: 1.2rem;
    line-height: 1;
    color: var(--accent);
    width: .9rem;
    text-align: center;
  }
  .foot__group[open] > summary::after { content: "\2212"; }
  .foot__group > ul { padding-bottom: var(--s2); }
}

@media (min-width: 760px) {
  .foot__group > summary { pointer-events: none; }
}

.foot-base {
  margin-top: var(--s5);
  padding-top: var(--s3);
  padding-bottom: 3.5rem;
  border-top: 1px solid var(--rule);
  display: flex;
  flex-wrap: wrap;
  gap: .5rem 1.5rem;
  align-items: baseline;
  justify-content: space-between;
  font-size: .8125rem;
  color: var(--ink-faint);
}

.foot-base a { color: var(--ink-quiet); text-decoration: none; transition: color .15s ease; }
.foot-base a:hover { color: var(--accent-hover); }
.foot-base a:active { color: var(--ink); }

.credit a {
  color: var(--ink-quiet);
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--ink-quiet) 45%, transparent);
  text-underline-offset: .18em;
}

.credit a { transition: color .15s ease; }
.credit a:hover { color: var(--accent-hover); text-decoration-color: var(--accent-hover); }
.credit a:active { color: var(--ink); text-decoration-color: var(--ink); }

/* The 404 uses the standard fold, plate and all: it was the one page in the
   build with no photograph and an empty right half, which on a photography
   site turns a dead end into a blank wall rather than a way back in. */

/* Partner logos on a tour run with others (Premium Kanha, chg-30401a93): each
   mark kept in its own colours on the ground her poster set it on, never
   recoloured for the dark page. */
.partners {
  list-style: none;
  margin: var(--s3) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 13rem));
  gap: clamp(.5rem, 2vw, var(--s3));
  max-width: none;
}
.partners li { margin: 0; }
.partners img { display: block; width: 100%; height: auto; border-radius: 2px; }
