/* ==========================================================================
   Nissa Safaris, stylesheet
   Design tokens + component classes, ported from the original single-page
   index.html cinematic design. Structure:
     1. Reset
     2. :root design tokens
     3. Base elements
     4. Keyframes
     5. Components (order follows the Task 10 contractual class list)
     6. Responsive breakpoints (1100 / 860 / 620)
     7. prefers-reduced-motion (last)
   ========================================================================== */

/* ==========================================================================
   1. Reset
   ========================================================================== */
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  background: #F3ECDC;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
img, svg, video { max-width: 100%; display: block; }
a { color: inherit; }
button { font: inherit; color: inherit; }
ul, ol { margin: 0; padding: 0; list-style: none; }
::selection { background: #B28A3F; color: #FBF7EF; }

/* ==========================================================================
   2. :root design tokens
   ========================================================================== */
:root {
  /* core palette */
  --ink: #241B12;
  --sand: #F3ECDC;
  --sand-2: #EBE1CD;
  --forest: #22291E;
  --cream: #FBF7EF;
  --cream-2: #F6EFDE;

  /* gold accents */
  --gold: #C9A24B;
  --gold-dark: #B28A3F;
  --gold-deep: #7A5A22;
  --gold-light: #E6C879;

  /* muted text tones */
  --muted: #8A7B66;
  --muted-2: #5A4A33;
  --muted-3: #A99C7E;
  --muted-4: #CFC5AE;

  /* type */
  --font-display: 'Cormorant Garamond', serif;
  --font-body: 'Mulish', sans-serif;
  --font-mono: 'DM Mono', monospace;

  /* layout */
  --wrap-w: 1320px;
  --wrap-narrow-w: 920px;

  /* motion */
  --ease-glass: cubic-bezier(.16,.7,.3,1);
  --ease-reveal: cubic-bezier(.16,.74,.24,1);

  /* nav geometry, shared by the bar, its scrim and the progress rule so the
     three cannot drift out of alignment */
  --nav-h: 64px;
  --nav-pad: 22px;
}

/* ==========================================================================
   3. Base elements
   ========================================================================== */
body {
  font-family: var(--font-body);
  color: var(--ink);
  background: var(--sand);
}
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  font-weight: 600;
  margin: 0;
  color: var(--ink);
}
p { margin: 0; }
a { text-decoration: none; }

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

/* ==========================================================================
   4. Keyframes, names kept exactly as app.js / legacy markup reference them
   ========================================================================== */
@keyframes nk_pan {
  0% { transform: scale(1.06); }
  100% { transform: scale(1.16); }
}
@keyframes nk_fade {
  from { opacity: 0; }
  to { opacity: 1; }
}
@keyframes nk_rise {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(8px); }
}
@keyframes nk_wa_pulse {
  0% { transform: scale(1); opacity: .55; }
  70%, 100% { transform: scale(1.35); opacity: 0; }
}

/* ==========================================================================
   5. Components
   ========================================================================== */

/* ---------- Layout ---------- */
.wrap {
  max-width: var(--wrap-w);
  margin: 0 auto;
  padding: 0 32px;
}
.wrap-narrow {
  max-width: var(--wrap-narrow-w);
  margin: 0 auto;
  padding: 0 32px;
}
.section {
  background: var(--sand);
  padding: clamp(48px, 6.5vw, 88px) 32px;
}
.section-alt {
  background: var(--sand-2);
  padding: clamp(48px, 6.5vw, 88px) 32px;
}
.section-ink {
  background: var(--ink);
  padding: clamp(48px, 6.5vw, 88px) 32px;
}
.section-forest {
  position: relative;
  background: var(--forest);
  padding: clamp(48px, 6.5vw, 88px) 32px;
  overflow: hidden;
}

/* ---------- Nav ----------

   The bar is driven by one scroll-linked custom property, --nav-lift, which
   app.js runs from 0 at the top of the page to 1 by 120px down. Everything
   below interpolates off it: the scrim fades out, the material fades in, the
   elevation builds, the logo compacts, the progress rule appears.

   It replaced a class toggle at scrollY > 60 that cross-faded those same
   properties over 500ms. Two things were wrong with that. 500ms is well past
   the point a UI transition stops reading as responsive, and because the
   trigger was a threshold rather than the scroll position itself, the bar
   changed state a beat after the gesture that caused it. Tying it to scroll
   removes the lag entirely and makes the bar feel like an object being lifted
   off the page rather than a state that switches. There is no transition on
   these properties now, and there should not be: the finger is the clock.

   With JavaScript off --nav-lift stays 0, which is exactly the resting state:
   full scrim, transparent bar, readable links. Nothing depends on the script
   to be legible. */
.nav {
  --nav-lift: 0;
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 60;
  /* Interpolated, not transitioned. calc() on a unitless custom property is
     how the whole bar compacts in step with the scroll.
     Both axes go to zero: the horizontal inset is what lets the bar sit as an
     inset band over the hero, and it has to be gone by the time the bar is
     full-bleed, or the fill stops 18px short of each edge and the thing reads
     as a floating slab rather than a bar. */
  padding: calc(var(--nav-pad) * (1 - var(--nav-lift))) calc(18px * (1 - var(--nav-lift)));
}

/* The scroll edge.

   .nav-link is var(--cream) and cannot take a per-page override (the nav is
   fixed, outside the section it overlaps, so no descendant selector reaches
   it), so over a pale hero the links would sit at ~1:1 against the photo.
   This gradient is what guarantees contrast, and its alpha is a measured
   number rather than a taste one: cream on black-over-white needs alpha 0.56
   to clear 4.5:1, so the ramp holds 0.60 across the band the link text
   actually occupies (y 44-64 at rest, text centred at y 54) and decays after.

   It was previously a 160px slab held at 0.65 solid to 68% and then cut to
   transparent. Two problems. The hard stop left a visible band edge across
   every hero, and 160px is far taller than the 108px bar it exists to serve,
   so it dimmed the top fifth of every photograph on the site for no gain: at
   0.65 the links were running 6.6:1, half again past the floor. Worse, it did
   not know about the scrolled state, so once the solid bar took over the
   scrim carried on painting ~96px of grey-brown wash down over the page
   content beneath it, which is what it looked like: a smear.

   Now it is 112px, eased over five stops so there is no edge to see, and it
   hands over to the bar as --nav-lift rises. pointer-events:none so it never
   intercepts clicks; z-index:-1 so it paints behind .nav-bar inside the
   stacking context .nav's own z-index:60 establishes. Do not remove the
   z-index:-1 without re-checking paint order: a positive or auto value here
   previously painted the scrim OVER the bar, the exact "content under an
   overlay" bug this project has shipped three times. */
.nav::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 112px;
  background: linear-gradient(to bottom,
    rgba(20,15,9,.62) 0%,
    rgba(20,15,9,.60) 40%,
    rgba(20,15,9,.58) 58%,
    rgba(20,15,9,.34) 74%,
    rgba(20,15,9,.12) 88%,
    rgba(20,15,9,0) 100%);
  opacity: calc(1 - var(--nav-lift));
  pointer-events: none;
  z-index: -1;
}

.nav-bar {
  position: relative;
  max-width: var(--wrap-w);
  margin: 0 auto;
  /* Full-bleed once lifted, but the content stays locked to the same 1320px
     column in both states, so nothing slides sideways as the bar solidifies:
     the padding grows by exactly what the max-width stops constraining. */
  max-width: calc(var(--wrap-w) + (100vw - var(--wrap-w)) * var(--nav-lift));
  padding: 0 calc(28px + max(0px, (100vw - var(--wrap-w)) / 2) * var(--nav-lift));
  height: var(--nav-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* A vertical gradient rather than a flat fill. The bar sits at the top of
     the viewport with the page's light coming from above, so the material
     reads as catching a little of it along its upper edge; flat --ink reads
     as a hole cut in the page. Both stops fade in together off --nav-lift. */
  background-image: linear-gradient(180deg,
    rgba(45,34,23, calc(var(--nav-lift) * .985)) 0%,
    rgba(36,27,18, calc(var(--nav-lift) * .985)) 100%);
  border-bottom: 1px solid rgba(201,162,75, calc(var(--nav-lift) * .26));
  /* Elevation, as a stack rather than a single blur.
     Depth in the real world is two things at once: a tight, dark contact
     shadow where the object nearly touches the surface, and a wide, faint
     ambient one cast by the mass above it. One 24px blur at .28 is neither,
     which is why the old bar read as a flat rectangle with a grey rectangle
     under it. The third layer is the far cast that gives the bar weight over
     a busy photograph without darkening the text beneath it. */
  box-shadow:
    0 1px 2px rgba(20,15,9, calc(var(--nav-lift) * .34)),
    0 6px 16px rgba(20,15,9, calc(var(--nav-lift) * .26)),
    0 22px 48px rgba(20,15,9, calc(var(--nav-lift) * .20));
}

/* The reading rule.

   The scroll progress indicator used to be a 3px bar fixed to the very top of
   the viewport with a 14px gold glow around it: a zero-offset halo, floating
   above the nav and belonging to nothing. It is now the bar's own bottom
   edge, so the hairline is its track and the gold is how far through the page
   you are. One element instead of two competing ones, and the hero gets its
   full frame back, since at the top of a page the progress is zero anyway and
   there is nothing worth drawing. */
#nk-progress {
  position: absolute;
  left: 0; bottom: -1px;
  height: 2px;
  width: 100%;
  background: linear-gradient(90deg, var(--gold-deep), var(--gold-dark) 45%, var(--gold) 100%);
  opacity: var(--nav-lift);
  transform: scaleX(0);
  transform-origin: left center;
  pointer-events: none;
}

.nav-logo {
  text-decoration: none;
  display: flex;
  flex-direction: column;
  line-height: 1;
}
/* the supplied logo file is roughly square, not a slim wordmark, constrain
   by height only (auto width) so it fits the fixed 64px nav bar */
.nav-logo img {
  height: 54px;
  width: auto;
  display: block;
  /* Compacts with the bar. transform rather than height: scaling the box
     would relayout the flex row on every scroll frame, where a transform
     stays on the compositor. Origin left so the mark stays pinned to the
     column edge instead of drifting inward as it shrinks. */
  transform-origin: left center;
  transform: scale(calc(1 - .16 * var(--nav-lift)));
}
.nav-logo strong,
.nav-logo .nav-logo-name {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 22px;
  letter-spacing: .02em;
  color: var(--cream);
}
.nav-logo span,
.nav-logo .nav-logo-tag {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: .34em;
  text-transform: uppercase;
  color: var(--gold-dark);
  margin-top: 5px;
}
.nav-links {
  display: flex;
  align-items: center;
  gap: 34px;
}
.nav-link {
  position: relative;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .18em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--cream);
  transition: color .2s ease;
}

/* The rule under a link: solid for the page you are on, drawn in for the ones
   you are not.

   The current-page rule is the part that was missing. templates/layout.js has
   emitted aria-current="page" on the active nav item since it was written, and
   nothing in this stylesheet ever read it, so the bar answered "where can I
   go" and never "where am I". A screen reader knew which page you were on and
   the screen did not.

   Inset by one tracking unit on the right because .18em of letter-spacing
   hangs off the final glyph, and a rule that runs the full inline box sits
   visibly wider than the word it belongs to.

   Excludes .nav-cta: the last nav item carries .nav-link *and* the button
   classes, and a pill does not take an underline. */
.nav-link:not(.nav-cta)::after {
  content: '';
  position: absolute;
  left: 0; right: .18em; bottom: -7px;
  height: 1px;
  background: var(--gold);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform .22s var(--ease-glass);
}
.nav-link[aria-current="page"]::after { transform: scaleX(1); }
.nav-link:not(.nav-cta):focus-visible::after { transform: scaleX(1); }
/* Gated: a touch device fires hover on tap, which would draw the rule on the
   link you are in the act of leaving. */
@media (hover: hover) and (pointer: fine) {
  .nav-link:not(.nav-cta):hover::after { transform: scaleX(1); }
}
/* Presses land. Nothing here scales, because a 1px settle is what a line of
   text does under a finger and 0.97 is what a button does. */
.nav-link:not(.nav-cta):active { transform: translateY(1px); }

@media (prefers-reduced-motion: reduce) {
  /* The affordance stays, the movement goes. */
  .nav-link:not(.nav-cta)::after { transition-duration: 0s; }
}
/* Compound selector, not `.nav-cta` alone: `.btn`/`.btn-ink` are declared
   later in this file (Buttons section) and share the `padding`/
   `border-radius` properties at equal (single-class) specificity, so a
   later-declared `.btn`/`.btn-ink` would otherwise win the cascade tie and
   this sizing would never apply. `.btn.nav-cta` outranks both regardless of
   declaration order. Always used paired with `.btn` in markup. */
.btn.nav-cta {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .2em;
  text-transform: uppercase;
  text-decoration: none;
  padding: 11px 20px;
  border-radius: 40px;
}

/* mobile full-screen menu */
.menu {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 80;
  background: rgba(28,22,14,.6);
  backdrop-filter: blur(22px) saturate(1.2);
  -webkit-backdrop-filter: blur(22px) saturate(1.2);
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 30px;
  animation: nk_fade .4s ease;
}
.menu-open {
  /* display: none at rest, switched to inline-flex (not block) at the 860px
     breakpoint below so this centering actually applies once the icon is
     shown. min-width/min-height give a real 44x44 tap target regardless of
     the 26px glyph's own box, measured via CDP after the change (44x44). */
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
}
.menu-open span {
  display: block;
  width: 26px;
  height: 2px;
  background: var(--cream);
  margin: 5px 0;
}
.menu-close {
  position: absolute;
  /* Anchors the box, not the glyph: min-width/min-height plus flex
     centering guarantee a real 44x44 tap target around the "x" glyph,
     which previously had no padding and measured well under 44px on
     either axis. */
  top: 14px; right: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  line-height: 1;
  background: none;
  border: none;
  color: var(--sand);
  font-size: 34px;
  cursor: pointer;
  font-family: var(--font-display);
}
.menu-link, .nk-mlink {
  font-family: var(--font-display);
  font-size: 30px;
  color: var(--sand);
  text-decoration: none;
  cursor: pointer;
  transition: opacity .3s ease;
}
/* Same restrained opacity dim used by .nav-link:hover, kept consistent
   between the desktop bar and the full-screen mobile menu. */
.menu-link:hover, .menu-link:focus-visible,
.nk-mlink:hover, .nk-mlink:focus-visible {
  opacity: .7;
}

/* ---------- Type ---------- */
.display {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(32px, 4.4vw, 58px);
  /* Cormorant Garamond has deep descenders; 1.04 clipped the "y" into the
     line beneath it wherever a heading was followed directly by meta text. */
  line-height: 1.14;
  letter-spacing: -.01em;
}
.display-lg {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(48px, 8.2vw, 118px);
  line-height: 1.02;
  letter-spacing: -.01em;
}
.h2 {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(32px, 4.4vw, 58px);
  /* Cormorant Garamond has deep descenders; 1.04 clipped the "y" into the
     line beneath it wherever a heading was followed directly by meta text. */
  line-height: 1.14;
}
.h3 {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(22px, 2vw, 27px);
  line-height: 1.15;
}
.h4 {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 20px;
  line-height: 1.2;
}
.lede {
  font-family: var(--font-body);
  font-weight: 300;
  font-size: clamp(15px, 1.7vw, 21px);
  letter-spacing: .03em;
  line-height: 1.55;
  /* Default reads on the light .section/.section-alt backgrounds most
     `.lede` instances land on (destinations/destination/safaris index
     intros). Dark sections override back to cream immediately below, 
     see fix-round-1 finding 1: --cream-2 on --sand was ~1.01:1 contrast,
     effectively invisible. */
  color: var(--muted-2);
}
.section-ink .lede,
.section-forest .lede,
.hero .lede {
  color: var(--cream-2);
}
/* Task 15: the base h1-h6 rule hard-codes `color: var(--ink)` (near-black),
   which is correct on the light .section/.section-alt backgrounds most
   headings render on, but unreadable on the dark .section-ink/.section-forest
   backgrounds (ctaBlock's <h2 class="h2"> on .section-forest, and the
   about page's philosophy heading, both land here). Same technique as the
   .lede override immediately above: swap to a light token in dark context.
   Task 16: `.hero` (the home page's cinematic header) added to this group, 
   same hazard, its <h1> sits over the photographic hero, not a light section. */
.section-ink h1, .section-ink h2, .section-ink h3, .section-ink h4,
.section-forest h1, .section-forest h2, .section-forest h3, .section-forest h4,
.hero h1, .hero h2, .hero h3, .hero h4 {
  color: var(--cream);
}
/* `.quote`/`.cite` (the attributed pull-quote pattern) default to colours
   tuned for the light .section/.section-alt backgrounds they render on
   (origins section, Task 15). The philosophy section's closing pull-quote
   is the first dark-background use, so it needs the same swap. */
.section-ink .quote, .section-forest .quote {
  color: var(--cream-2);
}
.section-ink .cite, .section-forest .cite {
  color: var(--muted-3);
}
.body {
  font-family: var(--font-body);
  font-size: 17px;
  line-height: 1.8;
  color: var(--muted-2);
}
.label {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .18em;
  text-transform: uppercase;
  /* fix-round-2: --muted on light backgrounds was 3.49:1, below WCAG AA's
     4.5:1 for text this small. --muted-2 is the same swap .lede and .body
     already use for readable copy on --sand/--sand-2. Not yet used by any
     live page/dark section, no override needed unless one lands here. */
  color: var(--muted-2);
}
.eyebrow {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 24px;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: .28em;
  text-transform: uppercase;
  color: var(--muted-2);
}
.eyebrow::before {
  content: "";
  height: 1px;
  width: 36px;
  background: var(--gold-dark);
}
.quote {
  font-family: var(--font-display);
  font-style: italic;
  font-size: clamp(20px, 2.6vw, 25px);
  line-height: 1.4;
  color: #33402E;
  margin: 0;
}
.cite {
  display: block;
  font-family: var(--font-mono);
  font-style: normal;
  font-size: 11px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--muted-2);
  margin-top: 16px;
}

/* ---------- Buttons ---------- */
.btn {
  position: relative;
  overflow: hidden;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: rgba(201,162,75,.22);
  -webkit-backdrop-filter: url(#nk-refract) blur(16px) saturate(180%);
  backdrop-filter: url(#nk-refract) blur(16px) saturate(180%);
  border: 1px solid rgba(255,255,255,.3);
  box-shadow: 0 4px 20px rgba(20,15,9,.18), inset 0 1px 0 rgba(255,255,255,.35);
  color: var(--cream);
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: .2em;
  text-transform: uppercase;
  text-decoration: none;
  padding: 14px 26px;
  border-radius: 40px;
  cursor: pointer;
  transition: transform .35s var(--ease-glass), background .35s ease, box-shadow .35s ease;
}
.btn:hover {
  transform: translateY(-2px);
  background: rgba(201,162,75,.34);
  box-shadow: 0 10px 28px rgba(20,15,9,.28), inset 0 1px 0 rgba(255,255,255,.45);
}
/* Dark sections only. `.btn` sets `color: var(--cream)`, so this translucent
   white fill leaves cream-on-near-white and is effectively invisible on the
   light --sand surfaces (.section, .section-alt). Use .btn-outline there. */
.btn-ghost {
  background: rgba(255,255,255,.16);
  border: 1px solid rgba(255,255,255,.32);
}
.btn-ghost:hover { background: rgba(255,255,255,.26); }
/* The light-section counterpart to .btn-ghost: --ink on --sand is ~13:1,
   so a secondary action stays readable where .btn-ghost would vanish. */
.btn-outline {
  background: transparent;
  border: 1px solid var(--gold-deep);
  color: var(--ink);
  box-shadow: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.btn-outline:hover {
  background: rgba(122,90,34,.1);
  box-shadow: none;
}
.btn-ink {
  background: rgba(28,22,14,.32);
  -webkit-backdrop-filter: url(#nk-refract) blur(16px) saturate(160%);
  backdrop-filter: url(#nk-refract) blur(16px) saturate(160%);
  border: 1px solid rgba(231,221,198,.28);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.18);
  transition: transform .35s ease, background .35s ease;
}
.btn-ink:hover {
  transform: translateY(-2px);
  background: rgba(28,22,14,.46);
}
.btn-gold {
  background: var(--gold);
  border: 1px solid var(--gold-light);
  color: var(--ink);
  box-shadow: 0 4px 20px rgba(20,15,9,.18);
}
.btn-gold:hover {
  transform: translateY(-2px);
  background: var(--gold-light);
}
@supports not (backdrop-filter: url(#nk-refract)) {
  .btn { background: #B0863C; }
  .btn-ink { background: #33402E; }
}

/* ---------- Cards ---------- */
.card {
  background: var(--cream);
  border: 1px solid rgba(36,27,18,.12);
  border-radius: 6px;
  overflow: hidden;
  transition: transform .5s var(--ease-glass), box-shadow .5s ease;
}
.card:hover {
  transform: translateY(-6px);
  box-shadow: 0 24px 50px rgba(20,15,9,.32);
}
.card-media {
  position: relative;
  aspect-ratio: 16/11;
  overflow: hidden;
  background: var(--ink);
}
.card-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform .6s var(--ease-glass);
}
/* Same restrained zoom-on-hover already used by .pkg-card-media img, applied
   here so every card type in the site (guide portrait, destination tiles,
   journal entries) shares one deliberate hover language rather than some
   cards feeling interactive and others inert. */
.card:hover .card-media img { transform: scale(1.05); }
.card-body { padding: 28px 26px; }

.pkg-card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--cream);
  border: 1px solid rgba(36,27,18,.12);
  border-radius: 6px;
  overflow: hidden;
  transition: transform .5s var(--ease-glass), box-shadow .5s ease;
}
.pkg-card:hover {
  transform: translateY(-6px);
  box-shadow: 0 24px 50px rgba(20,15,9,.32);
}
.pkg-card-media {
  position: relative;
  aspect-ratio: 4/3;
  overflow: hidden;
  background: var(--ink);
}
.pkg-card-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform .6s var(--ease-glass);
}
.pkg-card:hover .pkg-card-media img { transform: scale(1.06); }
.pkg-card-body {
  padding: 26px 26px 28px;
  display: flex;
  flex-direction: column;
  flex: 1;
}
.pkg-meta {
  margin-top: 10px;
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: .18em;
  text-transform: uppercase;
  /* fix-round-2: --muted on --cream/--sand was 3.85:1/3.49:1, below AA.
     .pkg-meta only ever renders on the light .pkg-card (--cream) / package
     header (--sand) backgrounds, no dark instance exists to preserve. */
  color: var(--muted-2);
  margin-bottom: 10px;
}
.badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--cream);
  background: rgba(28,22,14,.55);
  border: 1px solid rgba(255,255,255,.24);
  padding: 6px 12px;
  border-radius: 30px;
}
.badge-signature {
  background: linear-gradient(90deg, var(--gold-deep), var(--gold-dark) 40%, var(--gold-light) 70%, var(--gold));
  color: var(--ink);
  border: none;
  font-weight: 600;
}

/* ---------- Grids ---------- */
.grid { display: grid; gap: 24px; }
.grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 24px; }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 24px; }
.grid-auto {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 24px;
}

/* ---------- Safari filter bar (/safaris/) ---------- */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 28px;
}
.filter-field { display: flex; flex-direction: column; gap: 8px; }
.filter-label {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .18em;
  text-transform: uppercase;
  /* fix-round-2: was --muted (3.17:1 on this bar's --sand-2 background),
     below AA. Only ever renders on the light .section-alt filter bar. */
  color: var(--muted-2);
}
.filter-select {
  appearance: none;
  background: var(--cream) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='%238A7B66' d='M4 6l4 4 4-4'/%3E%3C/svg%3E") no-repeat right 14px center;
  border: 1px solid rgba(36,27,18,.18);
  border-radius: 6px;
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 15px;
  min-width: 220px;
  padding: 12px 40px 12px 16px;
  cursor: pointer;
}
.filter-select:hover { border-color: rgba(36,27,18,.32); }
.filter-count {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted-2);
  margin-left: auto;
}

/* ---------- Package detail ---------- */
.pkg-hero {
  position: relative;
  height: 60vh;
  min-height: 420px;
  width: 100%;
  overflow: hidden;
  background: var(--ink);
}
.pkg-hero img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.pkg-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(20,15,9,.45) 0%, rgba(20,15,9,.15) 32%, rgba(20,15,9,.32) 64%, rgba(20,15,9,.88) 100%);
  pointer-events: none;
}

.itinerary { display: flex; flex-direction: column; gap: 0; }
.itinerary-day {
  position: relative;
  display: grid;
  grid-template-columns: 64px 1fr;
  gap: 24px;
  padding: 28px 0;
  border-top: 1px solid rgba(36,27,18,.14);
}
.itinerary-day:first-child { border-top: none; }
.itinerary-num {
  font-family: var(--font-display);
  font-size: 34px;
  /* fix-round-2: --gold-dark on this list's --sand background was 2.70:1
   , fails even the large-text 3:1 floor. --gold-deep clears both
     thresholds (5.39:1) and stays in the same gold family. */
  color: var(--gold-deep);
  line-height: 1;
}

.incl-excl {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 40px;
}
.incl, .excl {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.incl li, .excl li {
  position: relative;
  padding-left: 26px;
  font-size: 15px;
  line-height: 1.6;
  color: var(--muted-2);
}
.incl li::before {
  content: "\2022";
  position: absolute;
  left: 0;
  /* fix-round-2: --gold-dark on --sand-2 was 2.45:1, below the 3:1
     graphical-object floor. --gold-deep clears it (4.88:1). */
  color: var(--gold-deep);
}
.excl li::before {
  content: "\2715";
  position: absolute;
  left: 0;
  font-size: 12px;
  top: .25em;
  /* fix-round-2: --muted-3 on --sand-2 was 2.09:1, below the 3:1
     graphical-object floor. --muted clears it (3.17:1); the "Not
     included" heading (visually-hidden, Task 12) already carries the
     meaning for assistive tech, so this glyph only needs to be
     perceivable, not maximally legible. */
  color: var(--muted);
}

.faq { display: flex; flex-direction: column; gap: 1px; background: rgba(36,27,18,.12); border: 1px solid rgba(36,27,18,.12); }
.faq-item {
  background: var(--sand);
  padding: 26px 28px;
}
.faq-item summary {
  cursor: pointer;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 20px;
  color: var(--ink);
  list-style: none;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item p {
  margin-top: 14px;
  font-size: 15px;
  line-height: 1.7;
  color: var(--muted-2);
}

.best-time {
  background: var(--sand-2);
  border-left: 2px solid var(--gold-dark);
  padding: 24px 28px;
  font-size: 15px;
  line-height: 1.7;
  color: var(--muted-2);
}

/* ---------- Destination ---------- */
.dest-hero {
  position: relative;
  height: 62vh;
  min-height: 440px;
  width: 100%;
  overflow: hidden;
  background: var(--ink);
}
.dest-hero img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  animation: nk_pan 22s ease-out forwards;
}
.dest-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(20,15,9,.5) 0%, rgba(20,15,9,.12) 32%, rgba(20,15,9,.3) 64%, rgba(20,15,9,.85) 100%);
  pointer-events: none;
}
.dest-facts {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1px;
  background: rgba(36,27,18,.12);
  border: 1px solid rgba(36,27,18,.12);
}
.dest-facts > div { background: var(--sand); padding: 28px 26px; }

.nissa-note {
  position: relative;
  background: var(--forest);
  border-radius: 8px;
  padding: 34px 36px;
  color: var(--cream-2);
}
.nissa-note::before {
  content: "Nissa's note";
  display: block;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 14px;
}
.nissa-note p {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 20px;
  line-height: 1.5;
  color: var(--cream-2);
}

/* ---------- About ---------- */
/* Hero holds only the portrait image, same contract as .dest-hero/.pkg-hero:
   no text content is ever nested inside it, since the ::after gradient
   overlay paints last and would sit on top of anything else in this box. */
.about-hero {
  position: relative;
  height: 56vh;
  min-height: 380px;
  width: 100%;
  overflow: hidden;
  background: var(--ink);
}
.about-hero img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 25%;
  animation: nk_pan 22s ease-out forwards;
}
.about-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(20,15,9,.45) 0%, rgba(20,15,9,.15) 32%, rgba(20,15,9,.32) 64%, rgba(20,15,9,.88) 100%);
  pointer-events: none;
}

.stat-row { display: flex; flex-wrap: wrap; gap: 30px; margin-top: 28px; }
.stat-num {
  font-family: var(--font-display);
  font-size: 42px;
  color: #33402E;
  line-height: 1;
}
.stat-label {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--muted-2);
  margin-top: 6px;
}

.pull-quote {
  margin: 36px 0 0;
  padding: 24px 0 24px 28px;
  border-left: 2px solid var(--gold-dark);
}
.pull-quote-center {
  margin: 56px auto 0;
  max-width: 920px;
  text-align: center;
}
.pull-quote-center .quote {
  font-size: clamp(24px, 3.4vw, 40px);
}

.career-timeline { display: flex; flex-direction: column; }
.career-item {
  padding: 28px 0;
  border-top: 1px solid rgba(36,27,18,.14);
}
.career-item:first-child { border-top: none; }
.career-period {
  display: block;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--gold-deep);
  margin-bottom: 10px;
}

.worked-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1px;
  background: rgba(36,27,18,.12);
  border: 1px solid rgba(36,27,18,.12);
}
.worked-item { background: var(--sand); padding: 24px 22px; }
.worked-name {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 19px;
  color: var(--ink);
  margin: 0 0 6px;
}
.worked-role {
  font-family: var(--font-body);
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--muted-2);
  margin: 0;
}

.expertise-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1px;
  background: rgba(36,27,18,.12);
  border: 1px solid rgba(36,27,18,.12);
}
.expertise-item { background: var(--sand); padding: 34px 30px; }
.expertise-num {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .16em;
  color: var(--gold-deep);
}

.services-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  background: rgba(36,27,18,.12);
  border: 1px solid rgba(36,27,18,.12);
}
.service-item {
  background: var(--sand-2);
  padding: 30px 24px;
  text-align: center;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 19px;
  color: var(--ink);
}

/* Decorative background photo behind the philosophy section's text,
   ported from index.html's #philosophy image treatment. Structured as two
   plain sibling elements (image, then gradient overlay) painted BEFORE the
   `.wrap` content div in DOM order, rather than as a ::after on the same
   container the content lives in, a ::after paints last within its own
   element and would cover the content instead of sitting behind it. */
.photo-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 60%;
  opacity: .30;
}
.photo-overlay {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, var(--forest) 0%, rgba(34,41,30,.72) 50%, var(--forest) 100%);
}
.photo-content { position: relative; }

.philosophy-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 48px; }
.philosophy-item { border-top: 1px solid rgba(201,162,75,.4); padding-top: 26px; }
.philosophy-item h3 {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 25px;
  color: var(--cream);
  margin: 0 0 10px;
}
.philosophy-item p {
  font-size: 16px;
  line-height: 1.75;
  color: var(--muted-4);
  margin: 0;
}

/* ---------- Gallery ----------
   The reel that used to live here is gone; templates/gallery.js renders a
   mosaic now (see "Gallery mosaic" further down). Its rules were deleted
   rather than overridden, because the old `.gal-item` set a fixed height
   and a transform:scale that the grid rules did not reset, which made the
   tiles overlap. `.galbtn` and `.lb*` below are NOT carousel leftovers:
   the lightbox still uses them. */
.galbtn {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 3;
  width: 54px;
  height: 54px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 26px;
  line-height: 1;
  color: var(--cream);
  cursor: pointer;
  background: rgba(28,22,14,.42);
  -webkit-backdrop-filter: blur(16px) saturate(160%);
  backdrop-filter: blur(16px) saturate(160%);
  border: 1px solid rgba(255,255,255,.28);
  box-shadow: 0 8px 24px rgba(20,15,9,.3), inset 0 1px 0 rgba(255,255,255,.25);
  transition: transform .3s ease, background .3s ease, opacity .3s ease;
}
.galbtn:hover { background: rgba(28,22,14,.58); transform: translateY(-50%) scale(1.07); }
.galbtn:active { transform: translateY(-50%) scale(.95); }
.galbtn[disabled] { opacity: .22; pointer-events: none; }
.galbtn-prev { left: 6px; }
.galbtn-next { right: 6px; }

/* Above .consent (130) and .nav (60). At the old z-index of 90 the
   privacy banner floated over an open lightbox on a phone, covering the
   caption and taking taps meant for the image. A modal has to sit above
   page furniture. */
.lb {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 140;
  background: rgba(15,11,6,.72);
  backdrop-filter: blur(20px) saturate(1.1);
  -webkit-backdrop-filter: blur(20px) saturate(1.1);
  align-items: center;
  justify-content: center;
  padding: 4vh 4vw;
  animation: nk_fade .35s ease;
}
.lb.is-open { display: flex; }
.lb-img {
  width: min(1100px, 90vw);
  height: 78vh;
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
  filter: drop-shadow(0 24px 60px rgba(0,0,0,.6));
}
.lb-meta {
  margin-top: 18px;
  text-align: center;
  max-width: 680px;
}
.lb-figure {
  margin: 0;
  max-width: 1100px;
  max-height: 88vh;
  display: flex;
  flex-direction: column;
  align-items: center;
}
/* z-index is load-bearing, not decoration. `.lb-figure` is a flex item, and a
   flex item paints like an inline block, so with `z-index: auto` on both it
   and this button the tie is broken by DOM order, which `.lb-figure` wins by
   coming second. Without this the image sits over the close button and
   swallows the tap: on viewports narrower than about 380px (iPhone SE,
   smaller Androids) and in landscape, .lb-img's `min(1100px, 90vw)` box
   reaches under `top: 24px; right: 30px` and the X stops closing the
   lightbox entirely. `.galbtn` (prev/next) already carries `z-index: 3` for
   exactly this reason; this matches it.
   min-width/min-height give a real 44x44 touch target: the glyph alone
   measured 27x40, under the minimum, which made near-misses common on a
   phone even where the stacking was fine. */
.lb-close {
  position: absolute;
  top: 24px;
  right: 30px;
  z-index: 3;
  min-width: 44px;
  min-height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  color: var(--cream-2);
  font-size: 38px;
  line-height: 1;
  cursor: pointer;
  font-family: var(--font-display);
}
.lb-cat {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--gold);
}
.lb-title {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 22px;
  color: var(--cream-2);
  margin-top: 4px;
}
.lb-story {
  font-size: 14.5px;
  line-height: 1.7;
  color: var(--muted-4);
  margin: 12px 0 0;
}

.gal-card-cat {
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--gold-light);
}
.gal-card-title {
  font-family: var(--font-display);
  font-size: 23px;
  color: var(--cream);
  margin: 5px 0 8px;
  line-height: 1.15;
}
.gal-card-story {
  font-size: 13px;
  line-height: 1.55;
  color: var(--muted-4);
  margin: 0 0 14px;
}

/* ---------- Form ---------- */
/* fix-round-3 (Task 17, defect 3): `.field-input`/`.field-textarea` etc.
   below set --cream-2 text with no background of their own, the same
   latent pattern that made `.lede` invisible at ~1.01:1 (see fix-round-1
   finding 1) once composed onto a light section. `.form` previously relied
   entirely on an ambient dark ancestor (the legacy `nk-glass nk-refract`
   frosted panel over `#contact`'s dark background) to stay readable; a
   `.form` rendered anywhere else would be near-invisible cream-on-cream.
   This now declares its own solid, guaranteed background so the pairing is
   correct regardless of context: --cream-2 (#F6EFDE) on --ink (#241B12) is
   ~14.8:1, far past WCAG AA's 4.5:1 floor. */
.form {
  background: var(--ink);
  border: 1px solid rgba(201,162,75,.25);
  box-shadow: 0 24px 60px rgba(0,0,0,.35);
  padding: 72px 48px 80px;
  border-radius: 16px;
}
.field { margin-bottom: 42px; }
.field-label {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--muted-3);
  display: block;
  margin-bottom: 8px;
}
.field-input {
  width: 100%;
  background: transparent;
  border: none;
  border-bottom: 1px solid rgba(207,197,174,.35);
  color: var(--cream-2);
  font-family: var(--font-body);
  font-size: 16px;
  padding: 14px 0;
  outline: none;
}
.field-textarea {
  width: 100%;
  background: transparent;
  border: none;
  border-bottom: 1px solid rgba(207,197,174,.35);
  color: var(--cream-2);
  font-family: var(--font-body);
  font-size: 16px;
  padding: 14px 0;
  outline: none;
  resize: vertical;
}
.field-check {
  display: flex;
  align-items: center;
  gap: 9px;
  margin-top: 14px;
  font-family: var(--font-body);
  font-size: 13.5px;
  color: var(--muted-3);
  cursor: pointer;
  /* The label (not just the 16px checkbox) is the click/tap target since
     the input is nested inside it; min-height keeps that target >=44px
     tall on touch devices without changing the row's visual density on
     desktop, where align-items: center just adds breathing room. */
  min-height: 44px;
}
.field-check input {
  width: 16px;
  height: 16px;
  accent-color: var(--gold);
  cursor: pointer;
}

/* native date input, ported from #nk-date in index.html so the
   calendar-picker icon reads against the dark form background under a
   CSP that forbids inline styles */
.field-date {
  color-scheme: dark;
  width: 100%;
  background: transparent;
  border: none;
  border-bottom: 1px solid rgba(207,197,174,.35);
  color: var(--cream-2);
  font-family: var(--font-body);
  font-size: 16px;
  padding: 13px 0;
  outline: none;
}
.field-date::-webkit-calendar-picker-indicator {
  filter: invert(74%) sepia(34%) saturate(560%) hue-rotate(2deg) brightness(95%);
  cursor: pointer;
  opacity: .95;
}
.field-date::-webkit-datetime-edit { color: var(--cream-2); }
.field-date:disabled { cursor: not-allowed; opacity: .45; }

/* "choose a safari" custom listbox, ported from #nk-pkg-toggle / #nk-pkg /
   .nk-pkg-opt / .nk-pkg-sel in index.html. Not in the Task 10 brief's
   contractual class list (a real gap: this widget has markup precedent but
   no assigned name), added here per fix-round-1 finding 4 so Task 17 can
   build the form under a CSP with no inline styles. */
.pkg-select-wrap { position: relative; }
.pkg-select {
  width: 100%;
  text-align: left;
  background: transparent;
  border: none;
  border-bottom: 1px solid rgba(207,197,174,.35);
  /* 3.72:1 on --ink and fails AA. This is the DEFAULT state of the enquiry
     form's package field, so it is what every visitor reads first.
     --muted-3 gives 6.24:1 and matches the sibling .field-label. */
  color: var(--muted-3);
  font-family: var(--font-body);
  font-size: 16px;
  padding: 14px 0;
  outline: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
}
.pkg-select.has-value { color: var(--cream-2); }
.pkg-select-icon {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--gold);
  letter-spacing: .1em;
  flex: none;
}
.pkg-select-panel {
  display: none;
  position: absolute;
  top: 56px;
  left: 0; right: 0;
  z-index: 21;
  max-height: 264px;
  overflow-y: auto;
  background: #20180F;
  border: 1px solid rgba(201,162,75,.35);
  box-shadow: 0 24px 60px rgba(0,0,0,.6);
  padding: 8px;
  border-radius: 14px;
  animation: nk_fade .25s ease;
}
.pkg-select-panel.is-open { display: block; }
.pkg-select-opt {
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  border: none;
  border-radius: 9px;
  color: #DCD2BC;
  font-family: var(--font-body);
  font-size: 14.5px;
  padding: 11px 14px;
  cursor: pointer;
  transition: background .18s ease;
}
.pkg-select-opt:hover,
.pkg-select-opt:focus-visible {
  background: rgba(201,162,75,.18);
  color: var(--cream-2);
  outline: none;
}
.pkg-select-opt.is-selected {
  background: rgba(201,162,75,.20);
  color: var(--cream-2);
}
.form-submit { width: 100%; }

/* the form's post-submit thank-you panel, replaces the form inside
   #nk-form-wrap via app.js. Built from static classes (no style="..."), and
   given the same guaranteed --ink background as .form for the same
   contrast reason. */
.form-success {
  background: var(--ink);
  border: 1px solid rgba(201,162,75,.4);
  padding: 48px 36px;
  text-align: center;
  border-radius: 16px;
}
.form-success-heading {
  font-family: var(--font-display);
  font-size: 30px;
  color: var(--gold);
  margin-bottom: 12px;
}
.form-success-body {
  font-size: 15px;
  color: var(--muted-4);
  line-height: 1.6;
  margin: 0;
}
.form-success-body a { color: var(--gold); }

/* ---------- Contact page ---------- */
.contact-info { display: flex; flex-direction: column; gap: 22px; }
.contact-row { display: flex; flex-direction: column; }
.contact-link {
  font-family: var(--font-display);
  font-size: 23px;
  color: var(--cream);
}
.contact-alt {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--muted-3);
  margin-top: 4px;
}
.contact-alt a { color: var(--muted-4); }
.contact-actions { display: flex; gap: 10px; margin-top: 12px; flex-wrap: wrap; }
.contact-social { display: flex; gap: 12px; margin-top: 6px; flex-wrap: wrap; }

/* ---------- Footer ---------- */
.footer {
  background: #1A1611;
  padding: 56px 32px 40px;
}
.footer-grid {
  max-width: var(--wrap-w);
  margin: 0 auto;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: flex-end;
  gap: 24px;
}
/* same square-logo constraint as .nav-logo img, sized up slightly since the
   footer isn't pinned to a fixed-height row */
.footer-logo {
  /* The full card is portrait (880x1194), so width drives height, keep it to
     a normal footer brand-block size rather than a hero. */
  height: auto;
  width: clamp(150px, 17vw, 200px);
  max-width: 100%;
  display: block;
}
.footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: 18px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: .16em;
  text-transform: uppercase;
}
.footer-links a { color: var(--muted-4); text-decoration: none; }
.footer-links a:hover { color: var(--gold); }
.footer-legal {
  max-width: var(--wrap-w);
  margin: 34px auto 0;
  border-top: 1px solid rgba(178,138,63,.18);
  padding-top: 24px;
  font-family: var(--font-body);
  font-size: 12.5px;
  line-height: 1.7;
  color: #6E6549;
}

/* ---------- Utility ---------- */
.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}
.skip-link {
  position: absolute;
  top: -100%;
  left: 12px;
  z-index: 200;
  background: var(--ink);
  color: var(--cream);
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: 12px 20px;
  border-radius: 8px;
  border: 1px solid var(--gold);
  text-decoration: none;
  transition: top .25s ease;
}
.skip-link:focus {
  top: 12px;
}

/* glass / refract, Apple-grade glassmorphism, ported verbatim from index.html.
   Used today only by the gallery caption (templates/gallery.js's
   `glass glass-dark refract gal-card-caption`). The scrolled nav used to
   reuse this via legacy `nk-` prefixed aliases app.js toggled on scroll;
   that navbar treatment has been replaced by the plain, opaque
   `.nav.nk-scrolled .nav-bar` rule below (no backdrop-filter, no SVG
   displacement), so the `nk-glass`/`nk-glass-dark`/`nk-refract` aliases are
   gone from these selectors, this component is unrelated to the nav now. */
.glass {
  position: relative;
  background: rgba(231,221,198,.10);
  -webkit-backdrop-filter: blur(30px) saturate(180%);
  backdrop-filter: blur(30px) saturate(180%);
  border-radius: 18px;
  box-shadow: 0 8px 40px rgba(20,15,9,.30), inset 0 1px 1px rgba(255,255,255,.22), inset 0 -1px 1px rgba(0,0,0,.10);
  isolation: isolate;
}
.glass::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  pointer-events: none;
  z-index: 2;
  background: linear-gradient(135deg, rgba(255,255,255,.65) 0%, rgba(255,255,255,.10) 30%, rgba(255,255,255,0) 55%, rgba(201,162,75,.30) 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
}
.glass::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  z-index: 0;
  background: linear-gradient(140deg, rgba(255,255,255,.14), rgba(255,255,255,0) 60%);
  mix-blend-mode: overlay;
}
.glass > * { position: relative; z-index: 1; }
.glass-dark { background: rgba(28,22,14,.34); }
.refract {
  -webkit-backdrop-filter: url(#nk-refract) blur(7px) saturate(190%) brightness(1.06);
  backdrop-filter: url(#nk-refract) blur(7px) saturate(190%) brightness(1.06);
}
@supports not (backdrop-filter: url(#nk-refract)) {
  .refract { backdrop-filter: blur(30px) saturate(180%); }
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .glass { background: rgba(36,27,18,.86); }
  .glass-dark { background: rgba(20,15,9,.9); }
}

/* cinematic reveal-on-scroll engine */
[data-reveal], .reveal {
  opacity: 0;
  transition: opacity 1.15s var(--ease-reveal) var(--d,0ms),
              transform 1.15s var(--ease-reveal) var(--d,0ms),
              clip-path 1.25s var(--ease-reveal) var(--d,0ms),
              filter 1.15s ease var(--d,0ms);
  will-change: transform, opacity, clip-path, filter;
}
[data-reveal].nk-in, .reveal.nk-in {
  opacity: 1;
  transform: none;
  filter: none;
  clip-path: inset(0 0 0 0);
}
[data-reveal=""], [data-reveal="up"], .reveal-up {
  transform: translateY(46px) scale(.98);
  filter: blur(6px);
}
[data-reveal="left"], .reveal-left {
  transform: translateX(-72px) rotate(-1.2deg);
  filter: blur(4px);
}
[data-reveal="right"], .reveal-right {
  transform: translateX(72px) rotate(1.2deg);
  filter: blur(4px);
}
[data-reveal="zoom"], .reveal-zoom {
  transform: scale(.78);
  filter: blur(10px);
}
[data-reveal="clip"], .reveal-clip {
  clip-path: inset(0 0 100% 0);
  transform: translateY(40px) scale(.97);
}
[data-reveal="wipe"], .reveal-wipe {
  clip-path: inset(0 100% 0 0);
}
[data-reveal="rise"], .reveal-rise {
  transform: translateY(120px);
  filter: blur(3px);
}
[data-reveal] [data-kenburns] {
  transform: scale(1.32);
  transition: transform 1.8s var(--ease-reveal) var(--d,0ms);
}
[data-reveal].nk-in [data-kenburns] {
  transform: scale(1);
}
[data-stagger] > *:nth-child(1) { --d: 0ms; }
[data-stagger] > *:nth-child(2) { --d: 110ms; }
[data-stagger] > *:nth-child(3) { --d: 220ms; }
[data-stagger] > *:nth-child(4) { --d: 330ms; }
[data-stagger] > *:nth-child(5) { --d: 440ms; }
[data-stagger] > *:nth-child(6) { --d: 550ms; }

/* ---------- Breadcrumb ---------- */
.crumbs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: .14em;
  text-transform: uppercase;
  /* fix-round-2: --muted on the header's --sand background was 3.49:1,
     below AA. breadcrumbNav only ever renders inside a light .section
     header, no dark instance exists to preserve. */
  color: var(--muted-2);
  padding: 18px 0;
}
.crumb { color: var(--muted-2); text-decoration: none; }
.crumb:hover { color: var(--gold-deep); }
.crumb:not(:last-child)::after {
  content: "/";
  margin-left: 8px;
  /* fix-round-2: --muted-3 on --sand was 2.30:1, below the 3:1
     graphical-object floor; --muted clears it (3.49:1) without going as
     heavy as the --muted-2 used for the crumb text itself. */
  color: var(--muted);
}
.crumb[aria-current="page"] { color: var(--ink); }

/* ---------- Miscellaneous carried-over pieces ---------- */
/* hidden container for the #nk-refract SVG filter def layout.js emits near
   the top of <body>; replaces index.html's
   style="position:absolute;pointer-events:none;" since inline style
   attributes are no longer permitted */
.filter-defs {
  position: absolute;
  pointer-events: none;
}


/* grain + wash overlays */
.nk-grain {
  position: fixed;
  inset: 0;
  z-index: 70;
  pointer-events: none;
  opacity: .07;
  mix-blend-mode: overlay;
  background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxNjAnIGhlaWdodD0nMTYwJz48ZmlsdGVyIGlkPSduJz48ZmVUdXJidWxlbmNlIHR5cGU9J2ZyYWN0YWxOb2lzZScgYmFzZUZyZXF1ZW5jeT0nMC44NScgbnVtT2N0YXZlcz0nMycgc3RpdGNoVGlsZXM9J3N0aXRjaCcvPjwvZmlsdGVyPjxyZWN0IHdpZHRoPScxMDAlJyBoZWlnaHQ9JzEwMCUnIGZpbHRlcj0ndXJsKCNuKScvPjwvc3ZnPg==');
}
.nk-wash {
  position: fixed;
  inset: 0;
  z-index: 69;
  pointer-events: none;
  mix-blend-mode: soft-light;
  background: rgba(206,142,52,0.50);
}

/* privacy / cookie consent banner (Task 11, genuine gap: the original
   single page positioned this with a style="..." attribute; the multipage
   shell needs a class since inline styles are no longer permitted) */
.consent {
  display: none;
  position: fixed;
  left: 16px; right: 16px; bottom: 16px;
  z-index: 130;
  max-width: 600px;
  margin: 0 auto;
  flex-direction: column;
  gap: 16px;
  background: rgba(26,22,14,.96);
  border: 1px solid rgba(201,162,75,.4);
  box-shadow: 0 20px 60px rgba(0,0,0,.5);
  border-radius: 16px;
  padding: 22px 24px;
}
.consent p {
  margin: 0;
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.65;
  color: var(--muted-4);
}
.consent p a { color: var(--gold); }
.consent-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

/* floating WhatsApp chat button */
.nk-wa {
  position: fixed;
  right: 24px; bottom: 24px;
  z-index: 75;
  display: flex;
  align-items: center;
  gap: 0;
  height: 58px;
  padding: 0;
  border-radius: 32px;
  background: #25D366;
  color: #fff;
  text-decoration: none;
  box-shadow: 0 10px 30px rgba(20,15,9,.32), 0 0 0 1px rgba(255,255,255,.18) inset;
  overflow: hidden;
  transition: gap .4s var(--ease-glass), padding .4s var(--ease-glass), transform .3s ease, box-shadow .3s ease;
  will-change: transform;
}
.nk-wa::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 32px;
  border: 2px solid #25D366;
  animation: nk_wa_pulse 2.6s ease-out infinite;
  pointer-events: none;
}
/* Task 10 gap (ported now, Task 11 round 2): icon-only by default, label
   reveals on hover/focus via max-width + opacity, matching index.html's
   original behaviour rather than showing the label permanently. */
.nk-wa-ico {
  flex: none;
  width: 58px; height: 58px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.nk-wa-txt {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .14em;
  text-transform: uppercase;
  white-space: nowrap;
  max-width: 0;
  opacity: 0;
  overflow: hidden;
  transition: max-width .4s var(--ease-glass), opacity .35s ease, padding .4s ease;
  padding: 0;
}
.nk-wa:hover .nk-wa-txt,
.nk-wa:focus-visible .nk-wa-txt {
  max-width: 180px;
  opacity: 1;
  padding-right: 22px;
}
.nk-wa:hover {
  transform: translateY(-2px);
  box-shadow: 0 16px 38px rgba(20,15,9,.40), 0 0 0 1px rgba(255,255,255,.22) inset;
}

/* hero (used by the homepage's cinematic header) */
.hero {
  position: relative;
  height: 100vh;
  min-height: 640px;
  width: 100%;
  overflow: hidden;
}
/* No inline style="..." is permitted (CSP), so the hero photograph, always
   lion.jpg on this one page, per site copy, is baked into the class rather
   than set per-instance the way index.html's background-image once was. */
.hero-media {
  position: absolute;
  inset: 0;
  background-image: url('/assets/cheetah-cubs.jpg');
  background-size: cover;
  background-position: center 42%;
  animation: nk_pan 22s ease-out forwards;
}
/* Golden-hour wash, ported from index.html's hero (a separate soft-light
   overlay layered above the dark vignette). Anchored to .hero-media itself
   (its own ::after) rather than another markup child of .hero, so the hero
   element still holds only the one real child the about/dest/pkg hero
   contract expects, see the "hero holds only the image" note this file's
   header points to. */
.hero-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(206,142,52,.40);
  mix-blend-mode: soft-light;
  pointer-events: none;
}
.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(20,15,9,.55) 0%, rgba(20,15,9,.15) 32%, rgba(20,15,9,.32) 64%, rgba(20,15,9,.85) 100%);
  pointer-events: none;
}
/* Title/tagline/CTAs for the cinematic hero. Unlike .about-hero/.dest-hero/
   .pkg-hero (image-only; title lives in a sibling .section), the home hero
   carries text directly over the photograph, matching index.html. Text
   would otherwise paint UNDER .hero-media (position:absolute, so it's a
   z-index:auto positioned box) and under .hero::after (a generated pseudo,
   always last in paint order regardless of markup position), this wrapper
   gets its own z-index so it always wins that stack, independent of DOM
   order relative to the ::after pseudo. */
.hero-content {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding-bottom: 9vh;
}
.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin-top: 34px;
}

/* ==========================================================================
   6. Responsive breakpoints
   ========================================================================== */
@media (max-width: 1100px) {
  .grid-4 { grid-template-columns: repeat(2, 1fr); }
  .dest-facts { grid-template-columns: repeat(2, 1fr); }
  .incl-excl { gap: 28px; }
  .worked-grid { grid-template-columns: repeat(2, 1fr); }
  .expertise-grid { grid-template-columns: repeat(2, 1fr); }
  .services-grid { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 860px) {
  .nav-links { display: none !important; }
  .menu-open { display: inline-flex; }
  .grid-3 { grid-template-columns: 1fr 1fr; }
  .grid-2 { grid-template-columns: 1fr; }
  .itinerary-day { grid-template-columns: 48px 1fr; gap: 16px; }
  .incl-excl { grid-template-columns: 1fr; gap: 32px; }
  .form { padding: 44px 22px 52px; }
  .philosophy-grid { grid-template-columns: 1fr; }
}

@media (max-width: 620px) {
  .wrap, .wrap-narrow, .section, .section-alt, .section-ink, .section-forest {
    padding-left: 20px;
    padding-right: 20px;
  }
  .grid-3, .grid-4 { grid-template-columns: 1fr; }
  .dest-facts { grid-template-columns: 1fr; }
  .worked-grid { grid-template-columns: 1fr; }
  .expertise-grid { grid-template-columns: 1fr; }
  .services-grid { grid-template-columns: 1fr; }
  .galbtn { width: 44px; height: 44px; font-size: 22px; }
  .galbtn-prev { left: 2px; }
  .galbtn-next { right: 2px; }
  .nk-wa { right: 16px; bottom: 16px; }
  .footer-grid { align-items: flex-start; }
}

/* ==========================================================================
   7. prefers-reduced-motion, disables all animation and transition
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
  [data-reveal], .reveal {
    transition-duration: .4s;
    filter: none !important;
    clip-path: none !important;
    transform: none !important;
  }
  [data-reveal] [data-kenburns] {
    transform: none !important;
  }
  .gal { scroll-behavior: auto; }
  .gal-item, .gal-item.is-active { transform: none; transition: opacity .25s ease; }
  .nk-wa::after { animation: none; }
}

/* Empty state for the safaris filter. 7 of the 36 destination/length
   combinations have no package, and without this the page renders blank. */
.filter-empty {
  font-family: var(--font-body);
  font-size: clamp(15px, 1.5vw, 18px);
  line-height: 1.6;
  color: var(--muted-2);
  max-width: 46ch;
  margin: clamp(40px, 6vw, 72px) auto;
  padding: 28px 32px;
  text-align: center;
  border: 1px solid rgba(36, 27, 18, .14);
  border-radius: 4px;
  background: var(--sand-2);
}
.filter-empty a { color: var(--gold-deep); text-underline-offset: .2em; }

/* Footer mark: the complete supplied lockup, emblem, wordmark, tagline and
   contact block, centred and given room, rather than the compact nav mark. */
.footer-mark {
  display: flex;
  justify-content: center;
  padding: 0 24px clamp(28px, 4vw, 44px);
}
.footer-mark .footer-logo,
.footer-mark picture { display: block; }

/* 404 */
.notfound-links {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin: clamp(28px, 4vw, 40px) 0 clamp(24px, 3vw, 32px);
}
.notfound-help { max-width: 54ch; }
.notfound-help a { color: var(--gold-deep); }
/* Every other page opens with a breadcrumb row that clears the fixed nav.
   The 404 has none, so it needs the offset explicitly. */
.section-notfound { padding-top: clamp(110px, 12vw, 150px); }

/* Whole-card click target. The title anchor stretches over the entire card
   via ::after, so one real link covers it without nesting anchors or
   wrapping the <article>. Text stays selectable because the overlay sits
   above the card but the anchor itself is not a block wrapper. */
.pkg-card { position: relative; }
.pkg-card-link::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
}
.pkg-card:hover { cursor: pointer; }
.pkg-card-link:focus-visible::after {
  outline: 2px solid var(--gold);
  outline-offset: 3px;
}

/* Photo credit fine print. Only rendered on pages using a licensed
   third-party photograph (data/journeys.js `heroCredit`); CC BY-SA requires
   the attribution to travel with the image. Muted, but --muted-2 keeps it
   above 4.5:1 rather than fading it out entirely. */
.photo-credit {
  max-width: 78rem;
  margin: 0 auto;
  padding: 1.75rem 1.5rem 2.5rem;
  font-size: 0.8125rem;
  line-height: 1.6;
  color: var(--muted-2);
}
.photo-credit a {
  color: var(--muted-2);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}
.photo-credit a:hover,
.photo-credit a:focus-visible {
  color: var(--gold-deep);
}

/* ============================================================
   Reviews & credentials (/reviews/)
   Every block here is data-gated in templates/reviews.js and renders
   nothing while its source data is empty, so these rules are inert on a
   site with no verified licences and no published reviews yet.
   ============================================================ */

.cred-group {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--muted-2);
  margin: 40px 0 18px;
}
.cred-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 20px;
}
.cred {
  border: 1px solid var(--line);
  border-left: 3px solid var(--gold-deep);
  background: var(--sand);
  padding: 24px 22px;
}
.cred-body {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--gold-deep);
  margin: 6px 0 12px;
}
.cred-body a { color: inherit; text-decoration: underline; text-underline-offset: .2em; }
/* --muted-2 rather than --muted: this is small text and must clear 4.5:1,
   the same correction applied across the stylesheet in the contrast pass. */
.cred-source {
  margin-top: 14px;
  font-size: 13px;
  line-height: 1.6;
  color: var(--muted-2);
}
.cred-source a { color: var(--muted-2); text-decoration: underline; text-underline-offset: .2em; }
.cred-source a:hover, .cred-source a:focus-visible { color: var(--gold-deep); }
.cred-note {
  margin-top: 36px;
  padding-top: 26px;
  border-top: 1px solid var(--line);
  max-width: 68ch;
}

.review-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 22px;
}
.review {
  margin: 0;
  border: 1px solid var(--line);
  background: var(--sand);
  padding: 26px 24px;
}
.review-stars {
  color: var(--gold-deep);
  font-size: 15px;
  letter-spacing: .16em;
}
.review-body {
  margin: 14px 0 0;
  font-size: 15.5px;
  line-height: 1.75;
  color: var(--ink);
}
.review-by {
  margin-top: 18px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--muted-2);
}
.review-trip { display: block; margin-top: 6px; color: var(--gold-deep); }

.platform-links,
.rating-row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 22px;
}
/* Radio group, not a star widget: a real <input type="radio"> keeps this
   operable by keyboard and screen reader without any JS, which matters
   because the CSP forbids inline handlers anyway. */
.rating-opt {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  min-height: 44px;
  border: 1px solid var(--line);
  background: var(--sand);
  font-size: 13.5px;
  color: var(--ink);
  cursor: pointer;
}
.rating-opt:has(input:checked) {
  border-color: var(--gold-deep);
  background: var(--sand-2);
}
.rating-opt input { accent-color: var(--gold-deep); }

@media (max-width: 720px) {
  .cred-grid, .review-grid { grid-template-columns: 1fr; }
}

/* ============================================================
   Borrowed motion, reworked for this stylesheet
   Two effects taken from a reference safari site and rebuilt in our own
   idiom rather than lifted: a parallax on photo-backed sections, and a
   panel that slides up over a trip card's photograph on hover.
   ============================================================ */

/* Parallax. The reference used `background-attachment: fixed`, which iOS
   Safari ignores outright and which forces a repaint on every scroll frame
   on the browsers that do honour it. This instead translates the existing
   <img class="photo-bg"> on the compositor via app.js, which is smooth on
   mobile and costs no repaint.

   The scale is what makes it safe: translating a full-bleed image would
   expose the section behind it at one end of the travel, so the image is
   oversized by more than the maximum shift app.js can apply. Keep
   --parallax-scale ahead of the travel distance if that shift is changed. */
.photo-bg {
  --parallax-scale: 1.14;
  transform: scale(var(--parallax-scale));
  will-change: transform;
}
/* Only the JS-driven state gets the transition-free treatment; without the
   class the image simply sits scaled and still, which is the correct
   no-JS and reduced-motion fallback. */
.photo-bg.is-parallax {
  transition: none;
}

/* Sliding reveal over a trip card's photograph. The reference site slides a
   solid block up from the bottom edge on hover; this keeps the idea but uses
   a gradient scrim and the site's own gold rule, so the photograph stays
   readable underneath instead of being covered by a flat panel. */
.pkg-card-veil {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 16px 20px 18px;
  background: linear-gradient(to top, rgba(20, 15, 9, .92), rgba(20, 15, 9, .55) 60%, transparent);
  border-top: 2px solid var(--gold);
  transform: translateY(101%);
  transition: transform .45s var(--ease-glass);
  pointer-events: none;
}
.pkg-card-veil span {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--cream);
}
.pkg-card:hover .pkg-card-veil,
.pkg-card:focus-within .pkg-card-veil {
  transform: translateY(0);
}

/* Touch devices have no hover, so the veil would never appear and the card
   would be missing a cue the pointer version has. Showing it at rest there
   keeps both versions equally informative. */
@media (hover: none) {
  .pkg-card-veil { transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  /* The global reduced-motion block already collapses durations. These two
     go further and remove the movement itself: a parallax that snaps to its
     scroll position is worse than none, and the veil should simply be
     present rather than arriving instantly. */
  .photo-bg { transform: scale(1); }
  .pkg-card-veil { transform: translateY(0); }
}

/* ============================================================
   Balloon safari add-on (templates/partials.js balloonSection)
   Rendered only on the parks in data/experiences.js `parks`, so these
   rules are inert on every other destination and package page.
   ============================================================ */

.balloon-grid {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  gap: 38px;
  align-items: start;
}
.balloon-media {
  position: relative;
  aspect-ratio: 3/4;
  overflow: hidden;
  background: var(--ink);
  border-radius: 6px;
}
.balloon-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform .6s var(--ease-glass);
}
.balloon-media:hover img { transform: scale(1.04); }
.balloon-copy .body { margin-top: 14px; }

.balloon-foot {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 38px;
  margin-top: 46px;
  padding-top: 34px;
  border-top: 1px solid var(--line);
}
.balloon-notes {
  list-style: none;
  margin: 14px 0 0;
  padding: 0;
}
.balloon-notes li {
  position: relative;
  padding-left: 22px;
  margin-bottom: 12px;
  font-size: 15.5px;
  line-height: 1.7;
  color: var(--ink);
}
/* Gold rule as the marker rather than a bullet glyph, matching the
   .incl-excl lists elsewhere on package pages. */
.balloon-notes li::before {
  content: "";
  position: absolute;
  left: 0;
  top: .72em;
  width: 12px;
  height: 2px;
  background: var(--gold-deep);
}
.balloon-foot .btn { margin-top: 20px; }

@media (max-width: 860px) {
  .balloon-grid,
  .balloon-foot { grid-template-columns: 1fr; gap: 28px; }
  .balloon-media { aspect-ratio: 4/3; }
}

/* ============================================================
   Back to top
   Sits above the floating WhatsApp button (.nk-wa: 58px tall at
   right/bottom 24px, z-index 75) rather than beside it, so the two never
   collide on a narrow screen. Hidden until app.js adds .is-visible.
   ============================================================ */
.nk-top {
  position: fixed;
  right: 24px;
  bottom: 94px;
  z-index: 74;
  width: 46px;
  height: 46px;
  display: grid;
  place-items: center;
  border: 1px solid rgba(246, 239, 222, .28);
  border-radius: 50%;
  background: rgba(34, 41, 30, .88);
  color: var(--cream-2);
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px);
  transition: opacity .3s ease, transform .3s ease, visibility .3s, background .2s ease;
}
.nk-top.is-visible {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
.nk-top:hover,
.nk-top:focus-visible {
  background: var(--gold-deep);
  border-color: var(--gold);
}

@media (max-width: 720px) {
  /* Tracks .nk-wa's own 16px inset at this breakpoint. */
  .nk-top { right: 16px; bottom: 82px; }
}

@media (prefers-reduced-motion: reduce) {
  .nk-top { transition: opacity .001ms, visibility .001ms; transform: none; }
}

/* ============================================================
   Gallery mosaic (/gallery/)
   Replaced a one-frame-at-a-time carousel. With 31 photographs the reel
   showed one per screen; a mosaic shows the body of work at once, which is
   the point of a gallery. The lightbox is unchanged.
   ============================================================ */

.gal-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 30px;
}
.gal-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 10px 18px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: transparent;
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .16em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background .25s ease, border-color .25s ease, color .25s ease;
}
.gal-chip:hover { border-color: var(--gold-deep); }
.gal-chip.is-on {
  background: var(--forest);
  border-color: var(--forest);
  color: var(--cream);
}
.gal-chip-n {
  font-size: 10px;
  opacity: .62;
  letter-spacing: .08em;
}

/* Dense packing lets the feature and tall tiles slot into gaps the smaller
   ones leave, so the grid has no holes without any JS measuring anything. */
.gal-mosaic {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: 210px;
  grid-auto-flow: dense;
  gap: 14px;
}
.gal-item {
  position: relative;
  margin: 0;
  overflow: hidden;
  border-radius: 8px;
  background: var(--ink);
  grid-column: span 1;
  grid-row: span 1;
}
.gal-tile-feature { grid-column: span 2; grid-row: span 2; }
.gal-tile-tall { grid-row: span 2; }

.gal-item-media { position: absolute; inset: 0; }
.gal-item-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform .7s var(--ease-glass), filter .5s ease;
}
.gal-item:hover .gal-item-media img,
.gal-item:focus-within .gal-item-media img {
  transform: scale(1.07);
  filter: saturate(1.06);
}

/* The whole tile is one button, so the caption is the accessible name and
   there is exactly one tab stop per photograph. */
.gal-open {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 4px;
  width: 100%;
  padding: 16px 16px 18px;
  border: 0;
  text-align: left;
  cursor: pointer;
  color: var(--cream);
  background: linear-gradient(to top, rgba(20,15,9,.90) 0%, rgba(20,15,9,.45) 42%, rgba(20,15,9,0) 72%);
  opacity: 0;
  transition: opacity .4s ease;
}
.gal-item:hover .gal-open,
.gal-open:focus-visible { opacity: 1; }
.gal-card-cat {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--gold);
}
.gal-card-title {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 20px;
  line-height: 1.2;
}
/* The story is the lightbox's copy, read out of this element by app.js. It
   stays in the DOM on every tile and is clamped rather than removed, so the
   data survives even where the tile is too small to show it. */
.gal-card-story {
  font-size: 13px;
  line-height: 1.55;
  color: var(--muted-4);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.gal-item:not(.gal-tile-feature) .gal-card-story { display: none; }
.gal-open-cue {
  align-self: flex-start;
  margin-top: 10px;
  padding: 7px 14px;
  border: 1px solid var(--gold);
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--gold);
  transform: translateY(8px);
  transition: transform .4s var(--ease-glass) .06s;
}
.gal-item:hover .gal-open-cue,
.gal-open:focus-visible .gal-open-cue { transform: translateY(0); }

/* Filtered-out tiles collapse out of the grid entirely rather than being
   left in place invisible, so the mosaic reflows and never leaves gaps. */
.gal-item[hidden] { display: none; }

.gal-count {
  margin-top: 26px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--muted-2);
}

/* Staggered entrance. The delay comes from nth-child rather than a per-tile
   inline custom property, because the CSP forbids style attributes; an
   eight-step cycle is enough to read as a wave without JS touching layout. */
.gal-item[data-reveal] {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity .6s ease, transform .6s var(--ease-glass);
}
.gal-item[data-reveal].nk-in { opacity: 1; transform: none; }
.gal-item[data-reveal]:nth-child(8n+2) { transition-delay: .05s; }
.gal-item[data-reveal]:nth-child(8n+3) { transition-delay: .10s; }
.gal-item[data-reveal]:nth-child(8n+4) { transition-delay: .15s; }
.gal-item[data-reveal]:nth-child(8n+5) { transition-delay: .20s; }
.gal-item[data-reveal]:nth-child(8n+6) { transition-delay: .25s; }
.gal-item[data-reveal]:nth-child(8n+7) { transition-delay: .30s; }
.gal-item[data-reveal]:nth-child(8n+8) { transition-delay: .35s; }

@media (max-width: 1024px) {
  .gal-mosaic { grid-template-columns: repeat(3, 1fr); grid-auto-rows: 190px; }
}
@media (max-width: 700px) {
  .gal-mosaic { grid-template-columns: repeat(2, 1fr); grid-auto-rows: 150px; gap: 10px; }
  .gal-tile-feature { grid-column: span 2; grid-row: span 2; }
  .gal-card-title { font-size: 16px; }
  /* No hover on touch, so the caption has to be visible at rest or the
     photographs carry no context at all on a phone. */
  .gal-open { opacity: 1; }
  .gal-open-cue { display: none; }
}
@media (hover: none) {
  .gal-open { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .gal-item[data-reveal] { opacity: 1; transform: none; transition-delay: 0s !important; }
  .gal-item:hover .gal-item-media img { transform: none; }
  .gal-open-cue { transform: none; }
}

/* ============================================================
   Community partner block (IWACO-KENYA, /about/)
   ============================================================ */

.partner {
  display: grid;
  grid-template-columns: 200px minmax(0, 1fr);
  gap: 38px;
  align-items: start;
}
.partner-mark {
  display: block;
  padding: 18px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--cream);
  transition: border-color .25s ease, transform .35s var(--ease-glass);
}
.partner-mark:hover { border-color: var(--gold-deep); transform: translateY(-3px); }
.partner-mark img { width: 100%; height: auto; }
.partner-body .body { margin-top: 14px; }
.partner-body .btn { margin-top: 24px; }

@media (max-width: 760px) {
  .partner { grid-template-columns: 1fr; gap: 24px; }
  .partner-mark { max-width: 180px; }
}

/* ============================================================
   Live Google reviews (/reviews/, filled by app.js)
   The section ships hidden and is unhidden only once real reviews arrive,
   so an unconfigured site shows nothing rather than an empty shell.
   ============================================================ */

.grev-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  margin-bottom: 28px;
  padding-bottom: 22px;
  border-bottom: 1px solid var(--line);
}
.grev-score {
  display: flex;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
}
.grev-rating {
  font-family: var(--font-display);
  font-size: 40px;
  line-height: 1;
  color: var(--ink);
}
.grev-stars {
  font-size: 18px;
  letter-spacing: .1em;
  color: var(--gold-deep);
}
.grev-total {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--muted-2);
}
/* Required attribution, so it must stay legible rather than being faded
   into the background: --muted-2 clears 4.5:1 on the section ground. */
.grev-attrib {
  margin-top: 24px;
  font-size: 13px;
  line-height: 1.6;
  color: var(--muted-2);
}
#nk-grev-list .review-by a {
  color: var(--muted-2);
  text-decoration: underline;
  text-underline-offset: .2em;
}
#nk-grev-list .review-by a:hover,
#nk-grev-list .review-by a:focus-visible { color: var(--gold-deep); }

@media (max-width: 620px) {
  .grev-head { flex-direction: column; align-items: flex-start; }
}

/* ==========================================================================
   Legal pages: /terms/, /privacy/, /cookies/, /copyright/
   ==========================================================================
   These are documents, not marketing pages, and they are set as documents:
   a narrower measure, a smaller heading scale than the rest of the site
   (the sitewide .h2 clamps up to 58px, which across eighteen sections of
   terms reads as shouting), and generous line height, because someone
   deciding whether to send a deposit is going to read this properly. */

.legal-head .lede { max-width: 62ch; }
.legal-updated {
  margin-top: 18px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--muted-2);
}

/* Contents list. On the terms page this is the difference between a
   document someone skims for the clause they need and one they close. */
.legal-toc {
  margin-top: 40px;
  border-top: 1px solid rgba(178,138,63,.28);
  border-bottom: 1px solid rgba(178,138,63,.28);
  padding: 26px 0;
}
.legal-toc-heading {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--muted-2);
  margin-bottom: 18px;
}
.legal-toc-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 10px 32px;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  counter-reset: legal-toc;
}
.legal-toc-list li { counter-increment: legal-toc; }
.legal-toc-list a {
  display: block;
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.5;
  color: var(--muted-2);
  /* 44px minimum touch target, the same floor the lightbox close button
     uses; a contents list of eighteen entries is unusable on a phone if the
     rows are set at their natural text height. */
  min-height: 44px;
  padding: 10px 0;
  border-bottom: 1px solid transparent;
  transition: color .25s ease, border-color .25s ease;
}
.legal-toc-list a::before {
  content: counter(legal-toc, decimal-leading-zero) "  ";
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .1em;
  color: var(--gold-deep);
}
.legal-toc-list a:hover {
  color: var(--ink);
  border-bottom-color: rgba(178,138,63,.45);
}

/* Sections. scroll-margin-top clears the fixed nav when a contents link
   jumps to one, otherwise the heading lands underneath it. */
.legal-section {
  scroll-margin-top: 96px;
}
.legal-num {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .2em;
  color: var(--gold-deep);
  margin-bottom: 12px;
}
.legal-section .h2 {
  font-size: clamp(24px, 2.7vw, 35px);
  line-height: 1.2;
  margin-bottom: 20px;
}
.legal-subhead { margin: 34px 0 14px; }
.legal-section .body + .body { margin-top: 18px; }
.legal-section .body a,
.legal-list a,
.legal-credits a,
.legal-crosslinks a {
  color: var(--gold-deep);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  text-decoration-color: rgba(122,90,34,.4);
  transition: text-decoration-color .25s ease;
}
.legal-section .body a:hover,
.legal-list a:hover,
.legal-credits a:hover { text-decoration-color: var(--gold-deep); }

.legal-list {
  margin: 20px 0 0;
  padding: 0;
  list-style: none;
}
.legal-list li {
  position: relative;
  padding-left: 26px;
  font-family: var(--font-body);
  font-size: 17px;
  line-height: 1.8;
  color: var(--muted-2);
}
.legal-list li + li { margin-top: 12px; }
.legal-list li::before {
  content: "";
  position: absolute;
  left: 4px;
  top: 13px;
  width: 7px;
  height: 1px;
  background: var(--gold-dark);
}

/* Pulled-out note. Used for the "where the numbers live" clause on the
   terms page, which is the one thing on it a client must not skim past. */
.legal-note {
  margin-top: 28px;
  border-left: 3px solid var(--gold);
  background: rgba(201,162,75,.10);
  border-radius: 0 12px 12px 0;
  padding: 22px 26px;
}
.legal-note-heading {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--gold-deep);
  margin-bottom: 10px;
}
.legal-note p:not(.legal-note-heading) {
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.75;
  color: var(--muted-2);
}

/* Tables. The wrapper scrolls, never the page: a four-column table of
   processors does not fit a 375px screen and must not push the body wide. */
.legal-table-wrap {
  margin-top: 26px;
  border: 1px solid rgba(178,138,63,.28);
  border-radius: 14px;
  background: var(--cream);
}
/* The scroll container is declared only at the widths that need one. Below
   701px the table stacks into blocks and nothing overflows there, so undoing
   this rule in the mobile block would mean reopening horizontal overflow,
   which test/styles.test.js forbids sitewide and rightly so. Scoping the
   declaration is the honest way round it. */
@media (min-width: 701px) {
  .legal-table-wrap {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
}
.legal-table {
  width: 100%;
  min-width: 560px;
  border-collapse: collapse;
  font-family: var(--font-body);
  font-size: 14.5px;
  line-height: 1.65;
  color: var(--muted-2);
  text-align: left;
}
.legal-table-caption {
  caption-side: top;
  padding: 18px 20px 0;
  font-family: var(--font-body);
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--muted-2);
  text-align: left;
}
.legal-table th,
.legal-table td {
  padding: 14px 20px;
  vertical-align: top;
}
.legal-table thead th {
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--gold-deep);
  border-bottom: 1px solid rgba(178,138,63,.3);
  white-space: nowrap;
}
.legal-table tbody tr + tr th,
.legal-table tbody tr + tr td { border-top: 1px solid rgba(178,138,63,.18); }
.legal-table tbody th {
  font-family: var(--font-body);
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
}
.legal-table tbody th a { color: var(--gold-deep); text-decoration: underline; text-underline-offset: 3px; }
.legal-table code {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--ink);
}
.legal-tag {
  display: block;
  margin-top: 6px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--gold-deep);
}

/* The privacy-choice control on /cookies/. app.js writes the status line;
   the server-rendered fallback text is true with JavaScript off, because
   with no JavaScript nothing was ever stored. */
.legal-prefs {
  margin-top: 26px;
  border: 1px solid rgba(178,138,63,.32);
  border-radius: 14px;
  background: var(--cream);
  padding: 24px 26px;
}
.legal-prefs-status {
  font-family: var(--font-body);
  font-size: 15.5px;
  line-height: 1.7;
  color: var(--muted-2);
}
.legal-prefs-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 18px;
}

/* Creative Commons credits, generated from data/journeys.js. */
.legal-credits {
  margin: 20px 0 0;
  padding: 0;
  list-style: none;
}
.legal-credits li {
  padding: 16px 0;
  border-top: 1px solid rgba(178,138,63,.2);
}
.legal-credits li:last-child { border-bottom: 1px solid rgba(178,138,63,.2); }
.legal-credit-page {
  display: block;
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 600;
  color: var(--ink);
  margin-bottom: 4px;
}
.legal-credit-page:hover { color: var(--gold-deep); }
.legal-credit-meta {
  display: block;
  font-family: var(--font-body);
  font-size: 14.5px;
  line-height: 1.7;
  color: var(--muted-2);
}
.legal-copyline {
  margin-top: 20px;
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: .1em;
  color: var(--muted-2);
}

.legal-foot .h2 { font-size: clamp(24px, 2.7vw, 35px); margin-bottom: 18px; }
.legal-crosslinks {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 26px;
  margin-top: 26px;
  padding-top: 22px;
  border-top: 1px solid rgba(178,138,63,.25);
}
.legal-crosslinks a {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--gold-deep);
  text-decoration: none;
  min-height: 44px;
  display: flex;
  align-items: center;
}
.legal-crosslinks a:hover { color: var(--ink); }

/* Legal row in the site footer, set apart from the navigation links above
   it so the four documents are findable rather than lost in a list. */
.footer-legal-links {
  max-width: var(--wrap-w);
  margin: 30px auto 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px 26px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: .16em;
  text-transform: uppercase;
}
.footer-legal-links a {
  color: var(--muted-3);
  text-decoration: none;
  min-height: 44px;
  display: flex;
  align-items: center;
}
.footer-legal-links a:hover { color: var(--gold); }
.footer-legal a { color: inherit; text-decoration: underline; text-underline-offset: 3px; }
.footer-legal a:hover { color: var(--gold); }

/* Second line of the consent banner. */
.consent-fine {
  font-size: 12.5px !important;
  color: var(--muted-3) !important;
  margin-top: -6px !important;
}

@media (max-width: 640px) {
  .legal-toc-list { grid-template-columns: 1fr; }
  .legal-note { padding: 18px 20px; }
  .legal-prefs { padding: 20px; }
  .legal-prefs-actions .btn { width: 100%; justify-content: center; }
}

/* .btn-ink is a translucent glass button built for the dark consent banner
   and the dark hero sections: 32% ink over a dark ground composites to a
   dark button, and its cream text reads fine. Reused on the cream
   .legal-prefs panel the same rule composites to a pale grey, leaving
   near-white text on it at roughly 1.7:1, far under WCAG AA's 4.5:1. On a
   light surface it needs to be a solid button. */
.legal-prefs .btn-ink {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--cream);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  box-shadow: none;
}
.legal-prefs .btn-ink:hover { background: #3B2D1E; }

/* Legal tables on a phone.
   A three- or four-column table cannot be made to fit 375px, and the
   horizontally scrolling wrapper above put the column that actually matters
   ("What they see") off the right-hand edge, where nobody would find it.
   Below 700px each row becomes a labelled block instead: the same markup, a
   real <table> for anyone using a screen reader, and every cell visible
   without scrolling. The column headings are carried by data-label on each
   cell, since the <thead> is hidden in this mode. */
@media (max-width: 700px) {
  .legal-table-wrap { padding: 4px 0; }
  .legal-table { min-width: 0; }
  /* A <caption> inside a table switched to display:block loses its table
     context and shrink-wraps to a single-word column; it needs to be told
     to be a block in its own right. */
  .legal-table-caption {
    display: block;
    width: auto;
    padding: 14px 18px 4px;
  }
  /* Hidden visually but left in the accessibility tree: the header cells are
     still what a screen reader announces each data cell against. */
  .legal-table thead {
    position: absolute;
    display: block;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0,0,0,0);
    white-space: nowrap;
  }
  /* The cells need collapsing too, not just the thead around them. Left as
     table cells they keep their natural widths and lay out a box running
     past the right edge of a 375px viewport. It paints nothing, because the
     thead clips it, and it creates no page overflow, because the thead is
     out of flow, but a stray 441px-wide box inside a 375px page is one
     `position` change away from becoming a real horizontal scrollbar. */
  .legal-table thead tr,
  .legal-table thead th {
    display: block;
    width: 1px;
    height: 1px;
    padding: 0;
    overflow: hidden;
  }
  .legal-table,
  .legal-table tbody,
  .legal-table tr,
  .legal-table tbody th,
  .legal-table td {
    display: block;
    width: auto;
  }
  .legal-table tr { padding: 16px 18px; }
  .legal-table tbody tr + tr {
    border-top: 1px solid rgba(178,138,63,.22);
  }
  /* Reset the desktop per-cell rules the block layout would otherwise
     redraw as a line above every single cell. */
  .legal-table tbody tr + tr th,
  .legal-table tbody tr + tr td { border-top: 0; }
  .legal-table tbody th {
    white-space: normal;
    padding: 0 0 10px;
    font-family: var(--font-display);
    font-size: 21px;
    font-weight: 600;
  }
  .legal-table td { padding: 0 0 14px; }
  .legal-table tr td:last-child { padding-bottom: 0; }
  .legal-table td::before {
    content: attr(data-label);
    display: block;
    margin-bottom: 3px;
    font-family: var(--font-mono);
    font-size: 9.5px;
    letter-spacing: .16em;
    text-transform: uppercase;
    color: var(--gold-deep);
  }
  .legal-tag { display: inline-block; margin: 0 0 0 8px; }
}

/* ==========================================================================
   Installable app: /app/ and /offline/
   ========================================================================== */

/* An author rule that sets `display` beats the user-agent sheet's
   `[hidden] { display: none }` regardless of specificity, so any element
   carrying a display rule silently ignores the attribute. `.btn` sets
   `display: inline-flex`, which meant the install button on /app/ (rendered
   `hidden`, revealed only when the browser says it can install) was visible
   on every device including the ones that cannot install anything. This is
   the standard reset for it: `hidden` means the element is not relevant, and
   a stylesheet overriding that is always a bug rather than an intention. */
[hidden] { display: none !important; }

.install-panel {
  margin-top: 34px;
  border: 1px solid rgba(178,138,63,.32);
  border-radius: 16px;
  background: var(--cream);
  padding: 26px 28px;
}
.install-status {
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.7;
  color: var(--muted-2);
}
.install-btn { margin-top: 18px; }

.install-points {
  margin: 24px 0 0;
  padding: 0;
  list-style: none;
}
.install-points li {
  position: relative;
  padding-left: 26px;
  font-family: var(--font-body);
  font-size: 17px;
  line-height: 1.8;
  color: var(--muted-2);
}
.install-points li + li { margin-top: 14px; }
.install-points li::before {
  content: "";
  position: absolute;
  left: 4px;
  top: 13px;
  width: 7px;
  height: 1px;
  background: var(--gold-dark);
}
.install-points strong { color: var(--ink); font-weight: 600; }
.install-points a,
.install-status a {
  color: var(--gold-deep);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.install-steps {
  margin-top: 34px;
  border-top: 1px solid rgba(178,138,63,.25);
  padding-top: 26px;
}
.install-steps .h3 { margin-bottom: 16px; }
.install-step-list {
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: install-step;
}
.install-step-list li {
  counter-increment: install-step;
  position: relative;
  padding-left: 42px;
  min-height: 30px;
  font-family: var(--font-body);
  font-size: 16.5px;
  line-height: 1.75;
  color: var(--muted-2);
}
.install-step-list li + li { margin-top: 14px; }
.install-step-list li::before {
  content: counter(install-step);
  position: absolute;
  left: 0;
  top: 1px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 1px solid rgba(178,138,63,.5);
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--gold-deep);
}
.install-step-list strong { color: var(--ink); font-weight: 600; }

/* The offline screen. Carries no photograph and nothing it would have to
   fetch: it renders precisely when the network is gone, so any asset it
   referenced beyond the precached stylesheet would paint as a broken box. */
.offline-section {
  min-height: 78vh;
  display: flex;
  align-items: center;
}
.offline-section .label { margin-bottom: 18px; }
.offline-section .lede { margin-top: 16px; max-width: 54ch; }
.offline-section .body { margin-top: 18px; max-width: 60ch; }
.offline-links {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 26px;
  margin-top: 26px;
  padding-top: 22px;
  border-top: 1px solid rgba(178,138,63,.25);
}
.offline-links a {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--gold-deep);
  min-height: 44px;
  display: flex;
  align-items: center;
}
.offline-links a:hover { color: var(--ink); }
.offline-retry { margin-top: 26px; }
.offline-note {
  margin-top: 26px;
  font-size: 15px;
}
.offline-note a {
  color: var(--gold-deep);
  text-decoration: underline;
  text-underline-offset: 3px;
}

@media (max-width: 640px) {
  .install-panel { padding: 20px; }
  .install-btn { width: 100%; }
  .offline-section { min-height: 70vh; }
  .offline-retry { width: 100%; }
}

/* /offline/ only. templates/layout.js omits the footer's full logo there
   (minimalFooter), so the footer needs its top padding back to sit right
   without the mark above it. */
.is-offline .footer { padding-top: clamp(40px, 5vw, 64px); }

/* ---------- credited photographs ----------
   A frame in the gallery that is not Nissa's own work. The credit is quiet
   but always present: the whole point of it is that someone looking at the
   tile is not left assuming the photograph is his. On the tile it is plain
   text, because the tile is a single <button> and an anchor cannot live
   inside one; the linked version is in the lightbox. */
.gal-card-credit {
  margin-top: 6px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: rgba(240,232,214,.72);
}
.lb-credit {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid rgba(201,162,75,.28);
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1.6;
  color: var(--muted-4);
}
.lb-credit a {
  color: var(--gold);
  text-decoration: underline;
  text-underline-offset: 3px;
}
/* Which page the credited image appears on, in the /copyright/ list. */
.legal-credit-where {
  display: inline-block;
  margin-bottom: 4px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--gold-deep);
}
