/*
 * Public customer site — mobile-first (§1: ~70% of traffic is mobile).
 *
 * Every layout rule below is the phone layout; the media queries only widen
 * it. Written in that order deliberately — v1 was built desktop-first and
 * retrofitted, which is the mistake §1 asks not to repeat.
 *
 * Visual direction follows §8.2: clean, uncluttered, photo-forward,
 * destination-led.
 */

.skip {
  position: absolute;
  left: -9999px;
  background: var(--brand);
  color: #fff;
  padding: .7rem 1rem;
  z-index: 100;
}
.skip:focus { left: .5rem; top: .5rem; }

/* ------------------------------------------------------------------ head
 *
 * A DELIBERATE COPY OF padum-diving.com's HEADER, not a variation on it.
 *
 * The main site is WordPress and this is not, so no code can be shared - but
 * the two are one brand to a customer, and the WordPress "Trips" link opens
 * this site in a NEW TAB. The header is therefore the only thing telling a
 * visitor they have not been handed off to somebody else's booking system.
 *
 * Every value below is read off the live theme (wp-content/themes/padum),
 * not approximated: the strip is #16324F with white at .72, the bar is
 * rgba(14,39,64,.88) behind saturate(130%) blur(14px) with a
 * rgba(255,255,255,.14) hairline, both columns cap at 1320px on a
 * clamp(20px,4.5vw,56px) gutter, the wordmark is 13px/700 tracked .42em,
 * and the nav is 13px/600 uppercase tracked .06em at white .74, going coral
 * on hover.
 *
 * Oswald is the main site's display face. This site's own headings stay on
 * Barlow Condensed (--font-head) - only the header changes, because only the
 * header is the thing being matched.
 */
.site-util,
.site-head { --font-nav: 'Oswald', 'Barlow Condensed', 'Arial Narrow', sans-serif;
  --site-gutter: clamp(20px, 4.5vw, 56px); }

/* ---------- utility strip ---------- */
.site-util { background: #16324F;
  color: rgba(255, 255, 255, .72);
  font-family: var(--font-body);
  font-size: 14px; }
.site-util__in { display: flex;
  align-items: center;
  gap: 1.5rem;
  flex-wrap: wrap;
  padding: 8px var(--site-gutter);
  max-width: 1320px;
  margin-inline: auto; }
.site-util__sep { opacity: .45; }
/* The strapline and its separator are the first things to go on a phone: the
   language control has to survive, the marketing line does not. The main site
   hides the same two at the same point — `@media (max-width:700px)` on its
   `.hide-sm`, so this carries the same number rather than a nearby one.

   It was an unconditional `display: none`, which hid the line at EVERY width:
   the markup shipped on all three pages and never once rendered, so the strip
   read as "SSI Dive Centre · Bangkok" alone against the main site's fuller
   line. Only the phone rule was ever intended. */
@media (max-width: 700px) {
  .site-util__sep,
  .site-util__wide { display: none; }
}

/* The language control is the shared .lang-switch component - same markup and
   the same data-lang wiring site.js listens on - restyled to the main site's
   plain EN / TH pair. Scoped to .site-util so the admin's pill is untouched. */
.site-util .lang-switch { margin-left: auto;
  border: 0;
  background: none;
  border-radius: 0;
  overflow: visible;
  gap: 8px;
  align-items: center; }
.site-util .lang-switch button { min-height: 0;
  padding: 0;
  background: none;
  font-family: var(--font-body);
  font-size: 14px;
  font-weight: 600;
  color: rgba(255, 255, 255, .55);
  transition: color 120ms cubic-bezier(.22, .61, .36, 1); }
.site-util .lang-switch button.is-active { background: none; color: #fff; }
.site-util .lang-switch button:hover { color: #fff; }
.site-util .lang-switch button:focus-visible { outline: 2px solid var(--brand-pale);
  outline-offset: 3px; }
.site-lang__sep { opacity: .4; }

/* ---------- header bar ---------- */
/* `position: sticky` with `top: 0` was authored here and has never once been
   in effect: the layering rule further down re-declared `.site-head` at the
   same specificity and later in source, so it won and the header has always
   been `position: relative`. It stays relative — five live pages carry this
   header and making it stick is a visible change nobody asked for.
   The stacking level is a different matter, and it IS load-bearing: the phone
   menu is a `position: fixed` overlay INSIDE this element, so the level the
   header sits at is the level the overlay paints at. Pinned to 1 by that same
   override it lost to `#main`, which is later in the document at the same
   level — so the menu opened UNDERNEATH the page, unreadable and unclickable.
   Anything that changes this number changes whether the menu works. */
.site-head { position: relative;
  z-index: 60;
  background: rgba(14, 39, 64, .88);
  backdrop-filter: saturate(130%) blur(14px);
  border-bottom: 1px solid rgba(255, 255, 255, .14);
  color: #fff; }
.site-head__in { display: flex;
  align-items: center;
  gap: 1rem;
  max-width: 1320px;
  margin-inline: auto;
  padding: 14px var(--site-gutter); }

.site-head__brand { display: flex;
  align-items: center;
  gap: .75rem;
  color: #fff;
  text-decoration: none; }
.site-head__brand img { width: 46px; height: 46px; flex: none; }
/* 13px at .42em is the main site's wordmark exactly. It reads as a small
   tracked-out logotype beside the mark, NOT as a heading - resist making it
   bigger, that restraint is what makes the two headers look like one. */
.site-head__brand span { font-family: var(--font-nav);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .42em;
  text-transform: uppercase;
  line-height: 1.1; }

/* ---------- nav ----------
 *
 * Pushed to the right-hand end of the bar, which is what the main site does:
 * `.padum-head .wp-block-navigation { margin-left: auto }` under its
 * "logo left, nav and CTA together on the right" heading. Without it the menu
 * sits immediately after the wordmark and stops mid-bar — at 1440px ours ended
 * at x 768 against WordPress's 1324, so the two headers did not read as one.
 *
 * The nav's right edge then lands on the same gutter as the language control
 * in the strip above and the content column below, all three sharing the
 * 1320px cap. That shared right edge is the whole effect.
 *
 * Harmless on a phone: below 900px .site-nav is `position: fixed; inset: 0`,
 * and an auto margin on a fixed box whose left and right are both 0 resolves
 * to 0.
 */
.site-nav { margin-left: auto; }
.site-nav ul { display: flex;
  align-items: center;
  gap: 1.75rem;
  margin: 0;
  padding: 0;
  list-style: none; }
/* Eight items need about 920px at the main site's 1.75rem spacing, and this
   menu starts at 900px: in between, the wordmark broke onto two lines. Tighter
   spacing up to 1023px leaves room. */
@media (min-width: 900px) and (max-width: 1023px) {
  .site-nav ul { gap: 1.25rem; }
}
.site-nav a { font-family: var(--font-nav);
  font-size: 13px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: rgba(255, 255, 255, .74);
  text-decoration: none;
  transition: color 120ms cubic-bezier(.22, .61, .36, 1); }
.site-nav a:hover { color: #D2564E; }
.site-nav a.is-current { color: #fff; }
.site-nav a:focus-visible { outline: 2px solid var(--brand-pale); outline-offset: 4px; }

/* ---------- the phone menu ----------
 *
 * Below 900px the eight items do not fit, so they become a full-screen overlay:
 * the same shape the main site uses - deep ground, 19px uppercase rows with
 * hairline rules, close button top right.
 *
 * `backdrop-filter` on .site-head makes it a CONTAINING BLOCK for fixed
 * descendants, so an overlay inside it resolves against the bar instead of the
 * viewport and renders as a stripe with the links spilling out. The main site
 * hit exactly this and fixed it the same way: drop the blur while the menu is
 * open. The overlay covers the bar anyway, so nothing is lost.
 */
.site-nav__toggle,
.site-nav__close { display: none;
  border: 0;
  background: none;
  padding: 6px;
  color: #fff;
  cursor: pointer; }
.site-nav__toggle svg,
.site-nav__close svg { display: block; fill: currentColor; }

@media (max-width: 899px) {
  .site-nav__toggle { display: block; margin-left: auto; }

  .site-nav { position: fixed;
    inset: 0;
    z-index: 70;
    background: #16324F;
    padding: 96px 24px 24px;
    overflow-y: auto;
    /* Kept in the tree so it can transition, and taken out of it for everyone
       else. display:none would make the animation impossible; hidden alone
       still leaves the links tabbable in some engines, so site-nav.js also
       sets inert. */
    visibility: hidden;
    opacity: 0;
    transition: opacity 200ms cubic-bezier(.22, .61, .36, 1), visibility 200ms; }
  .site-nav.is-open { visibility: visible; opacity: 1; }
  body.nav-open { overflow: hidden; }
  .site-head:has(.site-nav.is-open) { backdrop-filter: none; }

  .site-nav__close { display: block; position: absolute; top: 26px; right: 20px; }

  .site-nav ul { display: block; }
  .site-nav li { border-bottom: 1px solid rgba(255, 255, 255, .14); }
  .site-nav a { display: block;
    padding: 18px 0;
    font-size: 19px;
    color: #fff; }
  .site-nav a:hover { color: #D2564E; }
}

/* ------------------------------------------------------------------ hero */
.hero { padding-block: 1.75rem 1rem; }
.hero__eyebrow {
  margin: 0 0 .3rem;
  font-size: .8125rem;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--brand-soft);
}
.hero__lead { margin: 0; max-width: 46ch; color: var(--text-muted); }

/* ----------------------------------------------------------------- cards */
/*
 * One container for everything that must share a left edge.
 *
 * Before this, the hero, the filter bar and the card grid each set their own
 * horizontal padding and only two of them shared the max-width — so at 1280px
 * the hero text began at 56px, the cards at 72px and the filter chips at 16px.
 * The cap and the gutter now live in exactly one rule, which is the only way
 * three separate blocks stay aligned as the page changes.
 */
.page-wrap {
  max-width: 1200px;
  margin-inline: auto;
  /* 1rem read as cramped on a phone. Because hero, filters, year headings and
     cards all share this container, changing it here moves them together and
     they stay aligned. */
  padding-inline: 1.25rem;
}

.trip-grid {
  list-style: none;
  margin: 0;
  padding-block: 1rem 3rem;
  display: grid;
  gap: 1rem;
}

.trip {
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
}

/* EVERY CARD'S COVER IS ONE SHAPE, --cover-ratio (tokens.css, 4:3): a trip's on
 * the list and a boat's on /boats (a boat page's hero too, further down), at
 * every width, whatever was uploaded. object-fit crops the picture into the
 * box, centred; the uploaded file is never touched.
 *
 * The photo is out of flow at EVERY width. In flow, the photo's own ratio sized
 * the box and the aspect-ratio lost: on a desktop a 3:2 cover made its card
 * 3:2, and a 4:3 one (Okinawa's whale) stood taller than the rest of its row.
 * A trip with no photo keeps the shape too, so the grid stays even. */
.trip__media {
  aspect-ratio: var(--cover-ratio);
  background:
    linear-gradient(135deg, var(--brand) 0%, var(--brand-mid) 55%, var(--brand-soft) 100%);
  display: grid;
  place-items: center;
  position: relative;
}
.trip__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* Most trips have no cover photo yet — the emoji flag on a brand gradient is
   a deliberate, on-brand placeholder rather than a broken image frame. */
.trip__flag { font-size: 3.25rem; line-height: 1; filter: drop-shadow(0 2px 6px rgba(0,0,0,.3)); }

.trip__dates {
  position: absolute;
  left: .75rem;
  bottom: .75rem;
  background: rgba(255,255,255,.94);
  color: var(--brand-dark);
  font-weight: 600;
  font-size: .8125rem;
  padding: .25rem .65rem;
  border-radius: var(--radius-pill);
}

.trip__body { padding: 1rem 1.1rem 1.25rem; }
.trip__route {
  margin: 0 0 .15rem;
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--brand-soft);
}
/* Same treatment as the admin's trip headers: condensed *bold* reads as a
   solid block at this size, so 600 with an open line-height and a little
   tracking keeps the CI voice without the density. */
.trip__title {
  font-family: var(--font-head);
  font-size: 1.4rem;
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: .01em;
  color: var(--brand);
  margin: 0 0 .35rem;
}
/* The price on a card, and on the trip hero.
 *
 * MUST STAY ABOVE the max-width:767px block further down. These rules and the
 * mobile overrides have equal specificity, so whichever is written LAST wins.
 * Placed after that block, the mobile card silently rendered the price in dark
 * brand blue on a dark scrim — it did, until a CSSOM sweep caught it. */
.trip__price {
  margin: 0 0 .35rem;
  font-size: .9375rem;
  font-weight: 700;
  color: var(--brand);
}
.trip-hero__price {
  margin: .2rem 0 0;
  font-size: 1rem;
  font-weight: 700;
  color: var(--accent);
  text-shadow: 0 1px 8px rgba(4, 16, 29, .6);
}

.trip__tagline { margin: 0 0 .85rem; color: var(--text-muted); font-size: .95rem; }
.trip__meta { display: flex; flex-wrap: wrap; gap: .4rem; }

/* THE DESTINATION PILL GIVES WAY, at every width — the overlay card already
   had this and the ordinary card did not, so a long dive-site list ran past
   the card edge. Racha's is 51 characters: "Koh Racha, Koh Phee Phee, Shark
   Point, King Cruiser".
   It is the destination that shrinks and not the dive count, because it is the
   one that varies: a dive count is "15 Dives", a dive-site list is however
   many places the boat visits. */
.trip__meta .pill { min-width: 0; overflow: hidden; flex: 0 0 auto; }
/* Named, not counted: it is the destination that shortens because it is the
   destination that is long, and that stays true if the pills are ever
   reordered or one of them is absent. */
.trip__meta .pill--destination { flex: 0 1 auto; }
.trip__meta .pill > span:not(.pill__icon) {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.trip__meta .pill__icon { flex: none; }
/* The boat pill on the trip page links to the boat's page (metaPills), and the
   chevron is what says so; a card never gets one. */
.pill--link { text-decoration: none; }
.pill--link:hover { background: var(--brand-wash); color: var(--brand); }
.pill--link:focus-visible { outline: 3px solid var(--brand-soft); outline-offset: 2px; }
.trip__meta .pill__go { flex: none; font-size: .9375rem; line-height: 1; }

/* The route's flag leads the hero the way it leads every card. */
.trip-hero__flag { margin-right: .35rem; }

/* ---------------------------------------------------------------- chips */
.chip {
  font: inherit;
  font-size: .875rem;
  font-weight: 600;
  white-space: nowrap;
  padding: .4rem .9rem;
  min-height: 40px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text-muted);
  cursor: pointer;
}
.chip.is-active {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
}

/* --------------------------------------------------------------- status */
.status { padding: 0 1rem; color: var(--text-muted); }
.status--error { color: var(--danger); font-weight: 600; }

.empty {
  margin: 1rem;
  padding: 2rem 1.25rem;
  text-align: center;
  color: var(--text-muted);
  background: var(--surface);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-lg);
}

/* ------------------------------------------------------------------ foot */
.site-foot {
  padding: 1.5rem 1rem 2.5rem;
  border-top: 1px solid var(--border);
  color: var(--text-faint);
  font-size: .875rem;
}

/* ------------------------------------------------- widen for tablet/desktop */
@media (min-width: 768px) {
  .page-wrap { padding-inline: 2rem; }

  .trip-grid {
    padding: 1.5rem 2rem 4rem;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: 1.5rem;
  }
  .status, .empty { margin-inline: 2rem; }
}

@media (min-width: 1280px) {
  .hero, .trip-grid, .site-foot { max-width: 1200px; margin-inline: auto; }
}

/* ======================================================================
   Underwater treatment
   ======================================================================
   Pure CSS — no canvas, no library, matching the technique on the brand's
   own trips.padum-diving.com. Only transform and opacity animate, so the
   whole thing stays on the compositor and costs nothing on a phone.
   ====================================================================== */

:root {
  --sea-deep:    #0A1E33;   /* the dark below                     */
  --sea-mid:     #0E2740;   /* the reference page's own base      */
  --sea-shallow: #17466E;   /* light coming through the surface   */
}

body {
  /* Fixed so the water stays put while the page scrolls over it. */
  background:
    linear-gradient(180deg,
      var(--sea-shallow) 0%,
      var(--sea-mid)     42%,
      var(--sea-deep)    100%) fixed;
  color: #EAF3FA;
  min-height: 100dvh;
}

.sea {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;   /* never intercepts a tap */
}

/* --- caustics: light refracted through a moving surface ---------------- */
.sea__caustic {
  position: absolute;
  display: block;
  /* `screen` makes the stripes add light rather than paint over the page. */
  mix-blend-mode: screen;
}
.sea__caustic--a {
  inset: -25% -25% 25%;
  background: repeating-linear-gradient(104deg,
    rgba(149, 209, 235, .26) 0 3px,
    rgba(149, 209, 235, 0)   3px 92px);
  filter: blur(15px);
  animation: seaCausticA 17s ease-in-out infinite;
}
.sea__caustic--b {
  inset: -25% -25% 40%;
  background: repeating-linear-gradient(76deg,
    rgba(255, 255, 255, .18) 0 2px,
    rgba(255, 255, 255, 0)   2px 132px);
  filter: blur(22px);
  animation: seaCausticB 24s ease-in-out infinite;
}

@keyframes seaCausticA {
  0%, 100% { transform: translate3d(-6%, -2%, 0) rotate(-2deg) scaleY(1);    opacity: .5; }
  50%      { transform: translate3d( 6%,  2%, 0) rotate( 2deg) scaleY(1.12); opacity: .8; }
}
@keyframes seaCausticB {
  0%, 100% { transform: translate3d( 5%,  1%, 0) scaleX(1);    opacity: .35; }
  50%      { transform: translate3d(-5%, -3%, 0) scaleX(1.15); opacity: .65; }
}

/* --- bubbles ----------------------------------------------------------- */
.bubble {
  position: absolute;
  bottom: -24px;
  display: block;
  border-radius: 50%;
  /* Staggered durations and delays that share no common factor, so the
     pattern does not visibly repeat. */
  animation: bubbleRise linear infinite;
}
.bubble--1 { left:  8%; width:  5px; height:  5px; background: rgba(255,255,255,.5);   box-shadow: 0 0 8px rgba(149,209,235,.8); animation-duration: 15s; animation-delay:  0s; }
.bubble--2 { left: 24%; width:  9px; height:  9px; background: rgba(149,209,235,.32);  box-shadow: inset 0 0 6px rgba(255,255,255,.6); animation-duration: 21s; animation-delay:  4s; }
.bubble--3 { left: 41%; width:  4px; height:  4px; background: rgba(255,255,255,.45);  animation-duration: 12s; animation-delay:  7s; }
.bubble--4 { left: 58%; width: 12px; height: 12px; background: rgba(149,209,235,.20);  box-shadow: inset 0 0 8px rgba(255,255,255,.5); animation-duration: 26s; animation-delay:  2s; }
.bubble--5 { left: 72%; width:  6px; height:  6px; background: rgba(149,209,235,.4);   animation-duration: 18s; animation-delay:  9s; }
.bubble--6 { left: 86%; width:  7px; height:  7px; background: rgba(255,255,255,.35);  box-shadow: 0 0 6px rgba(149,209,235,.6); animation-duration: 23s; animation-delay: 12s; }
.bubble--7 { left: 95%; width:  4px; height:  4px; background: rgba(149,209,235,.45);  animation-duration: 14s; animation-delay:  5s; }

@keyframes bubbleRise {
  0%   { transform: translate3d(0, 30px, 0);      opacity: 0; }
  10%  {                                          opacity: .65; }
  90%  {                                          opacity: .3; }
  100% { transform: translate3d(26px, -105vh, 0); opacity: 0; }
}

/*
 * Motion off, water still there.
 *
 * This is a lot of continuous movement, and vestibular sensitivity is real.
 * The gradient and the caustic texture stay — only the animation stops, so
 * the page keeps its character without moving.
 */
@media (prefers-reduced-motion: reduce) {
  .sea__caustic, .bubble { animation: none !important; }
  .bubble { opacity: .4; }
}

/* ------------------------------------------------ content over the water */
/* Everything real sits above .sea, which is z-index 0.
   `.site-head` is deliberately NOT in this list: it declares its own position
   and z-index up in the header section, and naming it here re-declared both at
   the same specificity, later in source — the accident the note below records
   for `background`, except this one also broke the phone menu. Add a selector
   here only if it has no position or z-index of its own. */
.site-util, #main, .site-foot, .skip { position: relative; z-index: 1; }

/* .site-head's own glass used to be re-declared here, in a lighter blue with a
   pale hairline. It sat AFTER the header block and so won on source order,
   which would have quietly undone the match with the main site. The surface is
   declared once now, up in the head section. */

/* --- hero ---------------------------------------------------------------
 *
 * Deliberately short. This page is a list of departures, and the hero is the
 * thing standing between somebody and the trips — it used to take most of a
 * phone screen before the first card, which is a lot of screen spent saying
 * something the visitor already knows. It introduces and gets out of the way.
 */
.hero { padding-block: clamp(1rem, 3vw, 2rem) clamp(.75rem, 2vw, 1.25rem); }

.hero__eyebrow {
  display: flex;
  align-items: center;
  gap: .85rem;
  margin: 0 0 .35rem;
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .28em;
  text-transform: uppercase;
  color: var(--accent);
}
.hero__rule { width: 34px; height: 2px; background: var(--accent); flex: none; }

.hero__title {
  margin: 0 0 .4rem;
  font-family: var(--font-head);
  font-weight: 700;
  text-transform: uppercase;
  line-height: .95;
  letter-spacing: .01em;
  /* Sized for the WHOLE phrase on one line, not for one word stacked over
     another. Twelve characters of condensed caps run about 5.4em, so the cap
     leaves roughly 430px of the 1136px container — and the 10vw floor keeps
     it on one line at 320px. */
  font-size: clamp(2.1rem, 10vw, 5rem);
}
/*
 * "DIVE WITH US" reads as one line. It stacked as two, which cost a phone
 * screen's worth of height above the trips — and the outline second line only
 * ever worked as a stacked block, so it is the phrase that carries the effect
 * now. The whitespace between the two spans in the HTML is the word space.
 */
.hero__line { display: inline; }
.hero__line--outline {
  color: transparent;
  -webkit-text-stroke: clamp(1.5px, .28vw, 2.5px) rgba(149, 209, 235, .85);
}
/* Thai has no uppercase and its outline stroke renders poorly at this size. */
:lang(th) .hero__title { text-transform: none; line-height: 1.05; }
:lang(th) .hero__line--outline {
  -webkit-text-stroke: 0;
  color: var(--brand-pale);
}

.hero__lead {
  max-width: 34rem;
  margin: 0;
  color: rgba(234, 243, 250, .82);
  font-size: 1rem;
  line-height: 1.45;
}

/* --- cards lift off the water ------------------------------------------ */
.trip {
  background: var(--surface);
  color: var(--text);
  border: 1px solid rgba(255, 255, 255, .12);
  box-shadow: 0 10px 34px rgba(4, 16, 29, .38);
  cursor: pointer;
  transition: transform .18s ease, box-shadow .18s ease;
}
.trip:hover, .trip:focus-visible {
  transform: translateY(-3px);
  box-shadow: 0 16px 44px rgba(4, 16, 29, .5);
}
.trip:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }

.status { color: rgba(234, 243, 250, .75); }
.site-foot { color: rgba(234, 243, 250, .55); }

/* --- availability, the reason a customer is scanning this page ---------- */
/* `.trip__spaces` used to render the count as text beside the card title. The
   availability badge replaced it and nothing has emitted these classes since —
   including .trip__spaces--full, which carried the same green-means-full
   inversion fixed below. Deleted rather than corrected: a rule no markup can
   reach is not worth keeping right. */

.trip__more {
  margin-top: .75rem;
  font-size: .8125rem;
  font-weight: 600;
  color: var(--brand-soft);
}

/* ================================================== trip detail overlay */
body.sheet-open { overflow: hidden; }

.sheet-overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: rgba(6, 20, 34, .72);
  backdrop-filter: blur(6px);
  display: flex;
  justify-content: center;
  align-items: flex-end;        /* phones: rises from the bottom */
  animation: sheetFade .18s ease both;
}
@keyframes sheetFade { from { opacity: 0; } to { opacity: 1; } }

.sheet {
  width: 100%;
  max-height: 94dvh;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  color: var(--text);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  box-shadow: 0 -12px 50px rgba(4, 16, 29, .5);
  animation: sheetRise .22s cubic-bezier(.2, .8, .3, 1) both;
}
@keyframes sheetRise { from { transform: translateY(3%); } to { transform: none; } }
.sheet:focus { outline: none; }

.sheet__bar {
  position: sticky;
  top: 0;
  z-index: 1;
  padding: .5rem .75rem;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
.sheet__back {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  font: inherit;
  font-weight: 600;
  min-height: 44px;
  padding: .4rem .7rem;
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  color: var(--brand);
  cursor: pointer;
}
.sheet__back:hover { background: var(--brand-wash); }

.sheet__body {
  flex: 1;
  overflow-y: auto;
  padding: 1.1rem 1.15rem calc(1.5rem + env(safe-area-inset-bottom));
  -webkit-overflow-scrolling: touch;
}
.sheet__loading { color: var(--text-muted); font-style: italic; }

.sheet__head { margin-bottom: 1.25rem; }
.sheet__flag { font-size: 2rem; line-height: 1; }
.sheet__route {
  margin: .3rem 0 .1rem;
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--brand-soft);
}
.sheet__title {
  margin: 0 0 .5rem;
  font-family: var(--font-head);
  font-size: clamp(1.6rem, 6vw, 2.2rem);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: .01em;
  color: var(--brand);
}
.sheet__facts {
  display: flex;
  flex-wrap: wrap;
  gap: .2rem .9rem;
  margin: 0 0 .6rem;
  font-size: .9375rem;
  color: var(--text-muted);
}
.sheet__when { font-weight: 600; color: var(--text); }
.sheet__spaces { margin: 0 0 .5rem; font-size: 1.05rem; }
.sheet__tagline { margin: 0; color: var(--text-muted); }

.sheet__section { margin-bottom: 1.4rem; }
.sheet__section h3 {
  margin: 0 0 .45rem;
  font-size: 1rem;
  font-weight: 600;
  color: var(--brand);
  padding-bottom: .3rem;
  border-bottom: 1px solid var(--border);
}
/* Brochure copy is plain text with meaningful line breaks — preserved, never
   parsed as markup. */
.sheet__prose {
  margin: 0;
  font: inherit;
  font-size: .9375rem;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--text);
}

.sheet__rooms { list-style: none; margin: 0; padding: 0; display: grid; gap: .35rem; }
.sheet__rooms li {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: .55rem .75rem;
  border-radius: var(--radius);
  background: var(--brand-wash);
  font-size: .9375rem;
}
.sheet__room-type { font-weight: 600; }
/* Price and availability travel together on the right of a cabin row — the
   two halves of "can I have this one". */
.sheet__room-right { display: flex; align-items: center; gap: .6rem; }
.sheet__room-price { font-weight: 700; white-space: nowrap; }
.sheet__room-note {
  margin: .6rem 0 0;
  font-size: .8125rem;
  color: var(--text-muted);
}

.sheet__cta {
  margin-top: 1.5rem;
  padding: 1rem 1.1rem;
  border-radius: var(--radius-lg);
  background: linear-gradient(135deg, var(--brand) 0%, var(--brand-mid) 100%);
  color: #fff;
}
/* Words left, action right. Column below 560px, where a row leaves the heading
   two words wide and the button squeezed against it. */
.sheet__cta {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: .9rem;
}
.sheet__cta h3 { margin: 0 0 .25rem; color: #fff; font-size: 1.05rem; }
.sheet__cta p  { margin: 0; color: rgba(255,255,255,.85); font-size: .9375rem; }
.sheet__cta-text { min-width: 0; }

@media (min-width: 560px) {
  .sheet__cta { flex-direction: row; align-items: center; justify-content: space-between; }
  .btn-line { flex: none; }
}

/* --------------------------------------------------- payment terms table */
/* # · when · how much. An ordered list rather than a <table>: it is three
   fields, and a table on a 375px phone needs horizontal scrolling to say them. */
.pay-terms { list-style: none; margin: 0; padding: 0; display: grid; }
.pay-term {
  display: grid;
  /* Three fields, one row, at every width: number, when, amount. It used to
     carry a fourth (Deposit / Payment 2 / Last Payment) which forced a
     two-row stack on a phone; the number says the same thing in 1.4rem. */
  grid-template-columns: 1.4rem minmax(0, 1fr) auto;
  align-items: baseline;
  gap: .6rem;
  padding: .55rem .1rem;
  border-bottom: 1px solid var(--border);
}
.pay-term:last-child { border-bottom: 0; }
/* A muted figure rather than a filled disc: a column of blue circles down a
   five-installment schedule is decoration the number does not need. */
.pay-term__no {
  font-size: .8125rem;
  font-weight: 700;
  color: var(--text-muted);
}
.pay-term__when { font-size: .9375rem; color: var(--text); }
.pay-term__amount { font-weight: 700; white-space: nowrap; text-align: right; }
/* The balance is the room price, which we do not publish — it reads as an
   invitation to ask, so it must not look like a missing number. */
.pay-term__amount.is-balance {
  font-weight: 600;
  font-size: .8125rem;
  color: var(--brand);
  white-space: normal;
}
.pay-terms__note {
  margin: .6rem 0 0;
  font-size: .8125rem;
  color: var(--text-muted);
}
/* The deposit condition — a caution, not a hint.
 *
 * Louder than .pay-terms__note above it because the two say different kinds of
 * thing: that one is a convenience ("we will send the account"), this one is a
 * condition the customer is agreeing to. Given the same muted grey it read as
 * more small print and was missed.
 *
 * line-height is 1.65 rather than the .8125rem default: the Thai wraps to three
 * lines at 375px, and Thai ascenders and vowel marks stack above the line box —
 * at the Latin default the tone marks of one line touch the descenders of the
 * next. Measured on the string, not guessed. */
.pay-terms__remark {
  margin: .5rem 0 0;
  padding: .55rem .7rem;
  font-size: .8125rem;
  line-height: 1.65;
  color: var(--danger-ink);
  background: var(--danger-wash);
  border-radius: var(--radius);
}

/* The LINE button, in LINE's own green.
 *
 * This was #067A35 — the brand green darkened until WHITE text passed. That
 * fixed the wrong half: #06C755 with white measures 2.26:1 and fails at every
 * size (16px bold is not "large text"; that starts at 18.66px bold), but the
 * same green with dark ink measures 6.36:1 and passes comfortably. So the
 * brand colour stays and the text changes, which is also what LINE's own
 * guidelines allow. */
.btn-line {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  min-height: 46px;
  padding: .6rem 1.4rem;
  border-radius: var(--radius-pill);
  background: #06C755;
  color: #00320F;
  font-size: 1rem;
  font-weight: 700;
  text-decoration: none;
}
.btn-line:hover { background: #05B34C; }
/* White, not the brand green: the ring sits on the panel's blue gradient, and
   a green ring on green would be invisible exactly when it matters. */
.btn-line:focus-visible { outline: 3px solid #fff; outline-offset: 3px; }

/* The same button in a page's intro — the trips list, the calendar and the
   boats list — sized to sit under the 34px view toggle rather than the
   46px of the trip page's. Its label shortens on a phone (below). */
.btn-line--sm {
  min-height: 36px;
  gap: .4rem;
  padding: .35rem 1rem;
  font-size: .875rem;
}
.btn-line__short { display: none; }

/* The trip's route page on padum-diving.com (trip-page.js, db/046), under the
   pills. The LINE button's shape and size, outlined in the brand blue rather
   than filled: it opens more reading, and must not outshout the LINE button,
   which is the one that books. */
.btn-route {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .45rem;
  min-height: 46px;
  margin-top: .9rem;
  padding: .5rem 1.3rem;
  border: 2px solid var(--brand);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--brand);
  font-size: 1rem;
  font-weight: 700;
  text-decoration: none;
}
.btn-route:hover { background: var(--brand-wash); }
.btn-route svg { flex: none; }
.btn-route:focus-visible { outline: 3px solid var(--brand-soft); outline-offset: 3px; }
/* The route and boat buttons share one row, which wraps on a narrow phone; the
   row carries the space above them that the route button carried alone. */
.sheet__links { display: flex; flex-wrap: wrap; gap: .6rem; margin-top: .9rem; }
.sheet__links .btn-route { margin-top: 0; }

@media (min-width: 768px) {
  .sheet-overlay { align-items: center; padding: 2rem 1rem; }
  .sheet {
    width: min(720px, 100%);
    border-radius: var(--radius-lg);
    max-height: 88dvh;
  }
  .sheet__body { padding: 1.5rem 1.75rem 2rem; }
}

@media (prefers-reduced-motion: reduce) {
  .sheet-overlay, .sheet { animation: none; }
}

/* ===================================================== filters + sections */

/*
 * The bar's background spans the viewport so the blur reaches both edges, but
 * its contents sit in .page-wrap — that is why .filters carries no horizontal
 * padding of its own and .filter-row does the aligning.
 */
.filters {
  position: sticky;
  top: 0;
  z-index: 5;
  padding-block: 10px;
  margin-bottom: .25rem;
  background: rgba(10, 30, 51, .82);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid rgba(149, 209, 235, .16);
}

/* One row, always. Groups are separated by rules rather than by wrapping. */
.filter-row {
  display: flex;
  align-items: center;
  gap: 6px;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.filter-row::-webkit-scrollbar { display: none; }

.filter-sep {
  flex: none;
  width: 1px;
  height: 20px;
  margin-inline: .35rem;
  background: rgba(149, 209, 235, .32);
}

/* Pushes the route picker to the right edge, level with the last card. */
.filter-row .route-select { margin-left: auto; }

.chip {
  font: inherit;
  font-size: .8125rem;
  font-weight: 600;
  white-space: nowrap;
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  min-height: 38px;
  padding: .3rem .8rem;
  border: 1px solid rgba(149, 209, 235, .35);
  border-radius: var(--radius-pill);
  background: transparent;
  color: rgba(234, 243, 250, .88);
  cursor: pointer;
}
.chip:hover { border-color: var(--brand-pale); }
.chip.is-active {
  background: var(--brand-pale);
  border-color: var(--brand-pale);
  color: var(--brand-dark);
}
.chip__count {
  font-size: .6875rem;
  font-weight: 600;
  padding: .05rem .4rem;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, .16);
}
.chip.is-active .chip__count { background: rgba(26, 58, 92, .18); }
.chip:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }

.route-select {
  font: inherit;
  font-size: .8125rem;
  font-weight: 600;
  flex: none;
  min-height: 38px;
  max-width: 15rem;
  padding: .3rem .7rem;
  border: 1px solid rgba(149, 209, 235, .35);
  border-radius: var(--radius-pill);
  background: transparent;
  color: rgba(234, 243, 250, .88);
}
.route-select option { color: var(--text); background: var(--surface); }

.year-head {
  grid-column: 1 / -1;
  display: flex;
  align-items: baseline;
  gap: .6rem;
  margin: .75rem 0 -.25rem;
  padding-bottom: .35rem;
  border-bottom: 1px solid rgba(149, 209, 235, .22);
}
.year-head h2 {
  margin: 0;
  font-size: 1.5rem;
  color: #fff;
}
.year-head span { font-size: .8125rem; color: rgba(234, 243, 250, .6); }

.btn-quiet {
  font: inherit;
  font-size: .875rem;
  font-weight: 600;
  min-height: 40px;
  margin-top: .5rem;
  padding: .4rem 1rem;
  border: 1px solid rgba(149, 209, 235, .4);
  border-radius: var(--radius-pill);
  background: transparent;
  color: rgba(234, 243, 250, .9);
  cursor: pointer;
}

/* ================================================== availability badge */
/* On the photo, so it costs the card no height at all. */
.avail {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  padding: .22rem .6rem;
  border-radius: var(--radius-pill);
  font-size: .75rem;
  font-weight: 600;
  line-height: 1.5;
  white-space: nowrap;
  /* Solid fills, not washes: this sits over a photo whose colours are
     unknown, so a tint would be unreadable against a pale cover. */
  box-shadow: 0 1px 6px rgba(4, 16, 29, .35);
}
/* Dark TEXT is what lets these badges be light.
   They sit over a cover photo whose colours are unknown, so the fill has to be
   solid — and a solid fill carrying white text only clears AA once it is quite
   dark (white measures 4.05:1 on --danger, 4.32:1 on --success, both failing).
   Inverting it — a bright tint with its own ink on top — is both lighter to
   look at and higher contrast: 5.30, 4.57 and 5.26. The yellow badge worked
   this way all along; the other two now match it.
   The hairline is what separates a pale badge from a pale photo, so the drop
   shadow is not doing that job alone. A border carries no contrast
   requirement, so it costs nothing. */
.avail          { border: 1px solid rgba(27, 39, 51, .18); }
.avail--full    { background: var(--danger-tint);  color: var(--danger-ink); }
.avail--limited { background: var(--accent);       color: var(--accent-ink); }
.avail--open    { background: var(--success-tint); color: var(--success-ink); }
.avail__icon { font-size: .8125rem; line-height: 1; }

.pill__icon { font-size: .8125rem; line-height: 1; }

/* The card is a link now; strip the anchor styling and keep the layout. */
.trip__link {
  display: block;
  color: inherit;
  text-decoration: none;
}
.trip__link:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }
.trip__media { position: relative; }

/* ======================================================= the trip page */
/*
 * Horizontal padding comes from .page-wrap, which this element also carries.
 * Restating it here would win on source order and silently pin the page to a
 * different gutter than the rest of the site — which is exactly what it did.
 *
 * The narrower max-width IS deliberate: a single column of brochure text
 * reads better at 52rem than at the list page's 1200px.
 */
.trip-page {
  max-width: 52rem;
  margin-inline: auto;
  padding-block: 1rem 3rem;
}
.trip-page__back {
  display: inline-block;
  margin-bottom: .85rem;
  font-size: .875rem;
  font-weight: 600;
  color: rgba(234, 243, 250, .85);
  text-decoration: none;
}
.trip-page__back:hover { color: #fff; }

/*
 * No horizontal padding, on purpose.
 *
 * The hero photo is a direct child and must reach both card edges; padding
 * here left 32px of white down each side of it. Body content gets its inset
 * from its own margin-inline instead, which also means that inset is applied
 * once rather than added to this — sections were sitting at 64px.
 *
 * overflow: hidden is what clips the photo to the rounded top corners.
 */
.trip-page__body {
  background: var(--surface);
  color: var(--text);
  border-radius: var(--radius-lg);
  padding-block: 0 1.75rem;
  overflow: hidden;
  box-shadow: 0 10px 34px rgba(4, 16, 29, .38);
}
/* Nothing to show — a trip or boat that is not public — means no sheet, not
   an empty white bar under the message saying so. */
.trip-page__body:empty { display: none; }
.trip-page .sheet__avail { margin: 0 0 .7rem; }
/* In the page body the badge is inline, not floating over a photo. */
.trip-page .avail { position: static; box-shadow: none; }

@media (min-width: 768px) {
  .trip-page__body { padding-block: 0 2.25rem; }
}

/* ========================================================== boat cards */
/*
 * /boats (boats-page.js) is the trip list's grid with a boat in each card. The
 * media block, title, pills and the .trip__link anchor are the trip card's own,
 * so a boat card takes the phone treatment below without opting into it. The
 * one new line says how many trips the boat has and where.
 *
 * ABOVE the max-width:767px block on purpose: that block recolours this line
 * for the photo overlay, and at equal specificity the rule written LAST wins
 * (the trap .trip__price above records).
 */
.boats-page { padding-top: 1.5rem; }
.boat-card__trips {
  margin: 0 0 .6rem;
  font-size: .875rem;
  font-weight: 600;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The boat's one line (db/051), under its name. Two lines at most on the
   desktop card, so a long one cannot make its card taller than its row. */
.boat-card__tagline {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  margin: 0 0 .5rem;
  font-size: .875rem;
  color: var(--text-muted);
}
/* On the phone's photo card it is one line over the scrim, in the white the
   title uses: the card's height is the photo's, and a second line would push
   the title up into the bright part of the picture. Written after the rule
   above for the reason .boat-card__trips gives. */
@media (max-width: 767px) {
  .boat-card__tagline {
    -webkit-line-clamp: 1;
    margin: 0 0 .25rem;
    font-size: .8125rem;
    color: rgba(255, 255, 255, .9);
    text-shadow: 0 1px 8px rgba(4, 16, 29, .65);
  }
}

/* A BOAT'S HERO IS THE CARDS' SHAPE (--cover-ratio, as .trip__media above), so
 * a boat's cover looks alike on /boats and on its page whatever was uploaded: a
 * 3:2 phone shot, a square collage, a 2:1 deck plan.
 *
 * A boat with no photo keeps the shape on its card, but not in the hero, where
 * 4:3 of gradient would be a screen of nothing: there it keeps the trip page's
 * band. */
.trip-hero.boat-hero { aspect-ratio: var(--cover-ratio); }
.trip-hero.boat-hero.trip-hero--placeholder { aspect-ratio: auto; }

/* ================================================== compact mobile card */
/*
 * Phone only — the desktop grid is deliberately untouched.
 *
 * At full height only 1.5 cards fitted on a 375px screen, so a customer had
 * to scroll past almost nothing to see almost nothing. The photo and the
 * tagline are the two tallest blocks; the tagline now lives on the trip page,
 * which every card links to.
 */
@media (max-width: 767px) {
  /* PHOTO-FIRST CARD. The picture was a 72px strip, which read as a thumbnail
     rather than a photograph. It is now the card, with the text over it on the
     same scrim the trip hero already uses — so the list and the trip page
     finally look like one site.

     One grid cell, two children stacked in it: no markup change, and the
     desktop card above is untouched. */
  .trip__link {
    display: grid;
    /* minmax(0, 1fr), NOT the default auto column: an auto track sizes to
       max-content, so the unwrapped title stretched the column to 395px on a
       375px screen and the photo overflowed the card, clipping the price. */
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "card";
  }
  .trip__media,
  .trip__body { grid-area: card; }

  .trip__media { width: 100%; }
  /* SHORTER ON A PHONE: 16:9, trip and boat cards alike. At the cover's 4:3
     (--cover-ratio, tokens.css) a card is 262px tall at 390px and a phone
     showed two; at 16:9 it is 197px and three and a half fit. The desktop
     grid keeps --cover-ratio. The admin's crop preview shows 4:3, so on a
     phone a cover loses a little more off its top and bottom than it shows.
     `.boat-card` named too: it outranks a rule that sets the boat card's
     ratio on its own selector. */
  .trip__media,
  .boat-card .trip__media { aspect-ratio: 16 / 9; }
  .trip__flag { font-size: 2.5rem; }

  .trip__body {
    align-self: end;
    z-index: 1;
    padding: 2.5rem .8rem .7rem;
    /* Bottom-up, so the title sits on the opaque end and the top of the photo
       stays clear. Same ramp as .trip-hero__scrim. */
    background: linear-gradient(
      to top,
      rgba(4, 16, 29, .92) 0%,
      rgba(4, 16, 29, .74) 42%,
      rgba(4, 16, 29, .26) 78%,
      rgba(4, 16, 29, 0) 100%);
  }
  .trip__tagline { display: none; }

  /* The route line goes: the title and the destination pill already say where
     the trip is, and two lines over a photo read better than three. */
  .trip__route { display: none; }

  .trip__title {
    font-size: 1.05rem;
    line-height: 1.2;
    margin-bottom: .2rem;
    color: #fff;
    text-shadow: 0 2px 12px rgba(4, 16, 29, .6);
  }
  /* Smaller than the title on purpose — the trip is the headline, the price is
     the answer to the next question. */
  .trip__price {
    margin: 0;
    font-size: .8125rem;
    color: var(--accent);
    text-shadow: 0 1px 8px rgba(4, 16, 29, .65);
  }
  /* A boat card's trips line sits where a trip card's price does. */
  .boat-card__trips {
    margin: 0;
    font-size: .75rem;
    color: rgba(255, 255, 255, .92);
    text-shadow: 0 1px 8px rgba(4, 16, 29, .65);
  }

  /* Dives and boat, on their own row under the price.
     Sharing the price's line overflowed 3 of the 22 trips at 375px — Palau's
     dive_count alone is "18 Dives (incl. 1 night dive)" — so they get a row.
     It costs no card height: the body and the photo share one grid cell, and
     at 115px the body is still far shorter than the 187px photo.
     SCOPED TO THE CARD (.trip). The trip page heads its white sheet with the
     same pills, and unscoped these rules painted them white on white there and
     hid the destination — invisible, and the boat pill on that page is a link. */
  .trip .trip__meta {
    margin-top: .3rem;
    flex-wrap: nowrap;
    gap: .3rem;
    min-width: 0;
  }
  /* dives, boat, destination — destination is the longest of the three and is
     on the trip page anyway. */
  .trip .trip__meta .pill:nth-child(n+3) { display: none; }

  /* Over a photo, not on the card's own surface: --brand-wash and the other
     pill fills are light blocks with dark text and read as stickers here.
     White at 17% behaves like glass on any cover. */
  .trip .trip__meta .pill {
    font-size: .6875rem;
    padding: .12em .55em;
    background: rgba(255, 255, 255, .17);
    color: #fff;
    text-shadow: 0 1px 6px rgba(4, 16, 29, .55);
    min-width: 0;
  }
  /* The truncation rules now live at the top of this file and apply at every
     width, so only the overlay's own colours stay here.
     WHICH PILL GIVES WAY IS REVERSED, deliberately. It used to be the dive
     count, on the reasoning that its wording is the part that varies. In the
     real data it is the shortest of the three — "15 Dives", "~26 Dives",
     "3-4 dives/day" — while a dive-site list runs to 51 characters ("Koh
     Racha, Koh Phee Phee, Shark Point, King Cruiser"). The destination is what
     overflows, so the destination is what shortens. */

  /* "View trip" would push the scrim halfway up the photo, and the whole card
     is the link anyway. */
  .trip__more { display: none; }

  /* Top-left, opposite the availability badge: at the bottom it collided with
     the title now that the body is an overlay. */
  .trip__dates {
    top: 6px;
    left: 6px;
    bottom: auto;
    font-size: .6875rem;
    padding: .18rem .5rem;
  }

  .avail { top: 6px; right: 6px; font-size: .6875rem; padding: .18rem .5rem; }

  .filters { padding-block: 8px; }
  .year-head { margin: .5rem 0 -.35rem; }
  .year-head h2 { font-size: 1.25rem; }
}

/* ============================================ bullets in the trip detail */
/* Brochure prose (.prose-list / .prose-note) now lives in css/tokens.css,
   because the admin renders the same fields through the same /shared/prose.js.
 */

/* =================================================== trip page hero */
.trip-hero {
  position: relative;
  isolation: isolate;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  overflow: hidden;
  /* A band on a phone, a hero on a desktop — never the whole first screen. */
  min-height: clamp(150px, 34vw, 280px);
  display: flex;
  align-items: flex-end;
  /* Same gradient the card uses behind its emoji, so a trip with no photo
     gets the identical layout rather than a different one. */
  background: linear-gradient(135deg, var(--brand) 0%, var(--brand-mid) 55%, var(--brand-soft) 100%);
}
.trip-hero__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: -2;
}
/*
 * The scrim is what makes overlaid text safe. The photo could be a bright
 * surface shot or a dark wreck shot, so the text cannot rely on the image
 * being dark — this guarantees a floor to contrast against.
 */
.trip-hero__scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(
    to top,
    rgba(4, 16, 29, .88) 0%,
    rgba(4, 16, 29, .70) 34%,
    rgba(4, 16, 29, .28) 68%,
    rgba(4, 16, 29, .12) 100%);
}
.trip-hero__overlay { padding: 1rem 1.25rem 1.1rem; width: 100%; }
.trip-hero__route {
  display: flex;
  align-items: center;
  gap: .4rem;
  margin: 0 0 .25rem;
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--brand-pale);
}
.trip-hero__flag { font-size: 1.1rem; letter-spacing: normal; }
.trip-hero__title {
  margin: 0 0 .3rem;
  font-family: var(--font-head);
  font-size: clamp(1.5rem, 5.5vw, 2.4rem);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: .01em;
  color: #fff;
  text-shadow: 0 2px 14px rgba(4, 16, 29, .55);
}
.trip-hero__when {
  margin: 0;
  font-size: .9375rem;
  font-weight: 600;
  color: #fff;
  text-shadow: 0 1px 8px rgba(4, 16, 29, .6);
}
/* The badge floats over the hero, top-right, as it does on the card. */
.trip-hero > .avail { position: absolute; top: .75rem; right: .75rem; box-shadow: 0 1px 6px rgba(4,16,29,.4); }

/* The hero is flush to the card edges, so the body padding starts below it. */
.trip-page__body > .sheet__head { padding: 1.1rem 1.25rem 0; margin-bottom: 1.1rem; }
.trip-page__body > .sheet__section,
.trip-page__body > .sheet__cta { margin-inline: 1.25rem; }

/* Room rows carry a badge now rather than a bare count, and the row's wash
   has to agree with the badge sitting inside it.
 *
 * These were inverted: full was green and nearly-full was red, the opposite of
 * .avail--full / .avail--limited above and of the convention trip-bits.js
 * documents — so a sold-out cabin rendered as a green stripe with a red badge
 * on it. Red means full, amber means hurry, green means room: the same reading
 * a customer already brings from every hotel and airline. */
.sheet__rooms li.is-full    { background: var(--danger-wash);  color: var(--danger-ink); }
.sheet__rooms li.is-limited { background: var(--accent-wash);  color: var(--accent-ink); }
.sheet__rooms li.is-open    { background: var(--success-wash); color: var(--success-ink); }
.sheet__rooms .avail { position: static; box-shadow: none; }

/* Emoji ahead of the title, when the photo has taken the media block. */
.trip__title-flag { margin-right: .35rem; }

@media (min-width: 768px) {
  /* The row is already one line at every width now that the label is gone —
     only the breathing room changes. */
  .pay-term { padding: .45rem .1rem; }

  .trip-hero__overlay { padding: 1.5rem 2rem 1.6rem; }
  .trip-page__body > .sheet__head { padding: 1.6rem 2rem 0; }
  .trip-page__body > .sheet__section,
  .trip-page__body > .sheet__cta { margin-inline: 2rem; }
}

/* ======================================================= the boat page */
/*
 * /boat/<id> (boat-page.js) is the trip page's frame: .trip-page, .trip-hero,
 * .sheet__head and .sheet__section. These are the differences. (The hero's
 * shape, --cover-ratio, is set with the cards', above the phone block.)
 *
 * A FIRMER SCRIM. The trip scrim assumes a dark photograph and fades to .12 at
 * the top. A boat's cover can be a collage, or a deck plan that is nearly all
 * white. Two classes, so these win over .trip-hero wherever either sits.
 */
.boat-hero:not(.trip-hero--placeholder) .trip-hero__scrim {
  background: linear-gradient(
    to top,
    rgba(4, 16, 29, .94) 0%,
    rgba(4, 16, 29, .82) 32%,
    rgba(4, 16, 29, .42) 64%,
    rgba(4, 16, 29, .18) 100%);
}
/* No photo — most boats: the trip page's band and scrim, and the boat glyph on
   the gradient, so the band reads as meant rather than as a missing image. */
.boat-hero__glyph {
  position: absolute;
  top: .9rem;
  right: 1.1rem;
  z-index: -1;
  font-size: clamp(3rem, 12vw, 5.5rem);
  line-height: 1;
  filter: drop-shadow(0 4px 14px rgba(4, 16, 29, .35));
}
/* The style eyebrow is Thai on a Thai page ("เรือนอน"); the tracking meant for
   Latin capitals only spreads it apart. */
:lang(th) .boat-hero .trip-hero__route,
:lang(th) .boat-card .trip__route { letter-spacing: 0; }

/* The facts box: the size staff stated, or the manifest's count. The number
   is drawn above its label; the label comes first in the markup, which is the
   order a screen reader should hear them in. */
.boat-facts {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin: 0;
}
.boat-fact {
  position: relative;
  flex: 1 1 6rem;
  display: flex;
  flex-direction: column-reverse;
  justify-content: flex-end;
  /* Room on the left for the icon (agreed mockup, option A). */
  padding: .55rem .8rem .6rem 2.85rem;
  border-radius: var(--radius);
  background: var(--brand-wash);
}
/* The bed, people and calendar beside each number. Drawn inside the <dt> so
   the list stays valid markup, and lifted out to the box's left edge, centred
   on the number and label together rather than on the label alone. */
.boat-fact dt svg {
  position: absolute;
  left: .75rem;
  top: 50%;
  width: 1.5rem;
  height: 1.5rem;
  transform: translateY(-50%);
  color: var(--brand-soft);
}
@media (min-width: 768px) {
  .boat-fact { padding-left: 3.1rem; }
  .boat-fact dt svg { left: .85rem; width: 1.625rem; height: 1.625rem; }
}
.boat-fact dd {
  margin: 0;
  font-family: var(--font-head);
  font-size: 1.75rem;
  font-weight: 700;
  line-height: 1.1;
  color: var(--brand);
}
.boat-fact dt {
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-muted);
}
:lang(th) .boat-fact dt { letter-spacing: 0; }

/* The layout reuses the trip page's room rows, minus the price and badge. */
.boat-rooms__count { color: var(--text-muted); white-space: nowrap; }

/* A type with photos: its name and counts on the first line, as on every row,
   and its photos under them across the whole row. A grid rather than the flex
   row, so a long name still wraps beside the counts instead of pushing them
   down. A type without photos keeps the plain row. */
.boat-rooms li.boat-room--photos {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: 1rem;
  row-gap: .55rem;
}
/* One line of thumbnails that scrolls sideways on a phone, so a type with many
   photos does not push the next type off the screen. Each is a 4:3 crop at a
   fixed size: the whole picture is a tap away, in the viewer. */
.boat-room__photos {
  grid-column: 1 / -1;
  display: flex;
  gap: .4rem;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
}
.boat-thumb {
  flex: none;
  display: block;
  width: 8rem;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border-radius: calc(var(--radius) - 2px);
  background: var(--surface-alt);
  scroll-snap-align: start;
  cursor: zoom-in;
}
.boat-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* Inside the edge: the scrolling row would clip a ring drawn outside it. */
.boat-thumb:focus-visible { outline: 3px solid var(--brand); outline-offset: -3px; }

/* The boat's tagline (db/051), between its name and its next trip, on the
   same scrim as both — a little softer than the name, which is the headline. */
.boat-hero__tagline {
  max-width: 40rem;
  margin: 0 0 .4rem;
  font-size: .9375rem;
  line-height: 1.4;
  color: rgba(255, 255, 255, .92);
  text-shadow: 0 1px 8px rgba(4, 16, 29, .6);
}

/* WHAT IS ON BOARD (db/051), as small pills that wrap: an icon and the whole
   fact in words, as many to a line as fit — three or four on a desktop, two on
   a phone. Words rather than icons alone, because "2 tenders" and "Nitrox per
   tank or package" are the answers; the icon only finds them. */
.boat-amenities {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: .375rem;
}
.boat-amenity {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .3rem .7rem .3rem .55rem;
  border-radius: var(--radius-pill);
  background: var(--brand-wash);
  color: var(--brand-dark);
  font-size: .8125rem;
  line-height: 1.25;
}
.boat-amenity svg { flex: none; color: var(--brand); }

/* THE CABINS AS CARDS — the agreed layout. A row per type left most of the
   width empty beside one 9.5rem photo; a card per type puts the photo first
   and fills the line: two across on a phone, three on a desktop. */
.boat-cabins {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: .5rem;
}
.boat-cabin {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--brand-wash);
}
/* The type's first photo, a 4:3 crop at the card's width: the whole picture,
   and the type's others, are a tap away in the viewer. */
.boat-cabin__photo {
  position: relative;
  display: block;
  aspect-ratio: 4 / 3;
  background: var(--surface-alt);
  cursor: zoom-in;
}
/* Out of flow, as every card's cover is (.trip__media img): in flow,
   the photo's own ratio sized the box and the 4:3 lost — measured 185, 187 and
   181px tall on three 228px cards, so the cards in a row did not line up. */
.boat-cabin__photo img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Inside the edge: the card's overflow:hidden would clip a ring drawn outside. */
.boat-cabin__photo:focus-visible { outline: 3px solid var(--brand); outline-offset: -3px; }
/* No photo for this type while others have one: a pale bed keeps the cards
   level without pretending to be a picture. */
.boat-cabin__photo--none {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--brand-pale);
  cursor: default;
}
/* "+2": more photos of this type than the one on the card. */
.boat-cabin__more {
  position: absolute;
  right: .4rem;
  bottom: .4rem;
  padding: .1rem .5rem;
  border-radius: var(--radius-pill);
  background: rgba(4, 16, 29, .72);
  color: #fff;
  font-size: .75rem;
  font-weight: 600;
}
.boat-cabin__text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: .15rem;
  padding: .55rem .7rem .7rem;
}
.boat-cabin__type { font-weight: 600; line-height: 1.25; }
.boat-cabin__count { font-size: .8125rem; color: var(--text-muted); }
/* Its bathroom: green for its own, grey for shared — the one difference
   between cabin types a customer asks about first. */
.boat-cabin__bath {
  margin-top: .3rem;
  padding: .1rem .5rem;
  border-radius: var(--radius-pill);
  background: var(--surface-alt);
  color: var(--text-muted);
  font-size: .75rem;
  font-weight: 600;
}
.boat-cabin__bath.is-ensuite { background: var(--success-wash); color: var(--success-ink); }
@media (min-width: 768px) {
  .boat-cabins { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .6rem; }
}

/* The deck plan, whole: as wide as the page, on white like the drawing, and
   never taller than most of a screen. Tapped, it opens in the viewer to zoom;
   the strip under it says so without covering any of the drawing. */
.boat-plans { display: grid; gap: .6rem; }
.boat-plan {
  display: block;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: #fff;
  color: var(--brand);
  text-decoration: none;
  cursor: zoom-in;
}
.boat-plan img {
  display: block;
  width: 100%;
  height: auto;
  max-height: 36rem;
  object-fit: contain;
}
.boat-plan:focus-visible { outline: 3px solid var(--brand-soft); outline-offset: 2px; }
.boat-plan__hint {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .35rem;
  padding: .45rem .75rem;
  border-top: 1px solid var(--border);
  background: var(--brand-wash);
  font-size: .8125rem;
  font-weight: 600;
  line-height: 1.2;
}
.boat-plan:hover .boat-plan__hint { background: #DCEBF4; }
.boat-plan__hint svg { fill: currentColor; flex: none; }
/* "Tap" on a touch screen, "Click" where there is a mouse. */
.boat-plan__click { display: none; }
@media (hover: hover) and (pointer: fine) {
  .boat-plan__tap { display: none; }
  .boat-plan__click { display: inline; }
}

/* The gallery, UNCROPPED: columns rather than a grid of squares, because some
   uploads are collages, which a square crop reduces to a corner of itself.
   Each picture is a link that opens it in the viewer. */
.boat-gallery { column-count: 2; column-gap: .5rem; }
.boat-gallery__photo {
  display: block;
  margin: 0 0 .5rem;
  border-radius: var(--radius);
  break-inside: avoid;
  cursor: zoom-in;
}
.boat-gallery__photo:focus-visible { outline: 3px solid var(--brand-soft); outline-offset: 2px; }
.boat-gallery img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius);
  background: var(--surface-alt);
}

/* Each upcoming trip, as a row that links to its page — the dates, the trip
   as its card names it, then the route and dives. */
.boat-trips { list-style: none; margin: 0; padding: 0; display: grid; gap: .35rem; }
.boat-trip {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "when go" "title go" "meta go";
  column-gap: .75rem;
  padding: .6rem .75rem;
  border-radius: var(--radius);
  background: var(--brand-wash);
  color: var(--text);
  text-decoration: none;
}
.boat-trip:hover { background: #DCEBF4; }
.boat-trip:focus-visible { outline: 3px solid var(--brand-soft); outline-offset: 2px; }
.boat-trip__when { grid-area: when; font-size: .8125rem; font-weight: 600; color: var(--text-muted); }
.boat-trip__title { grid-area: title; font-weight: 700; color: var(--brand); }
.boat-trip__flag { margin-right: .35rem; }
.boat-trip__meta { grid-area: meta; font-size: .8125rem; color: var(--text-muted); }
.boat-trip__go { grid-area: go; align-self: center; font-size: 1.5rem; line-height: 1; color: var(--brand); }

@media (min-width: 768px) {
  .boat-gallery { column-count: 3; }
  /* Room for every thumbnail: they wrap rather than scroll. */
  .boat-room__photos { flex-wrap: wrap; overflow-x: visible; }
  .boat-thumb { width: 9.5rem; }
}

/* ======================================================= photo viewer */
/*
 * photo-viewer.js: one <dialog> over the whole screen, opened with showModal()
 * so it is above every z-index on the page. html.viewer-open stops the page
 * behind from scrolling, and touch-action:none hands every finger to the
 * script — swipe, pinch and double-tap — instead of the browser zooming the
 * page.
 */
html.viewer-open { overflow: hidden; }
.viewer {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  overflow: hidden;
  background: #04101D;
  color: #fff;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}
.viewer::backdrop { background: rgba(4, 16, 29, .6); }
/* Clear of the close button above and the caption below. */
.viewer__stage { position: absolute; inset: 3.5rem 0 calc(3.5rem + env(safe-area-inset-bottom)); }
.viewer__img {
  position: absolute;
  left: 50%;
  top: 50%;
  max-width: none;
  /* A plan saved as a PNG with no background reads as lines on dark otherwise. */
  background: #fff;
  transform-origin: center;
  transition: transform .2s ease;
  cursor: zoom-in;
  -webkit-user-drag: none;
}
.viewer.is-zoomed .viewer__img { cursor: grab; }
.viewer.is-dragging .viewer__img { transition: none; }
.viewer.is-zoomed.is-dragging .viewer__img { cursor: grabbing; }
/* Until it has been sized to the screen, which is when it has loaded. */
.viewer.is-loading .viewer__img { visibility: hidden; }
.viewer.is-loading .viewer__stage::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 2.25rem;
  height: 2.25rem;
  margin: -1.125rem 0 0 -1.125rem;
  border: 3px solid rgba(255, 255, 255, .25);
  border-top-color: #fff;
  border-radius: 50%;
  animation: viewer-spin .8s linear infinite;
}
@keyframes viewer-spin { to { transform: rotate(1turn); } }

.viewer__btn {
  position: absolute;
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  /* Dark enough to show on a white deck plan, which the arrows sit over. */
  background: rgba(4, 16, 29, .55);
  color: #fff;
  cursor: pointer;
}
.viewer__btn svg { fill: currentColor; }
.viewer__btn:hover:not(:disabled) { background: rgba(83, 157, 184, .85); }
.viewer__btn:focus-visible { outline: 3px solid var(--brand-soft); outline-offset: 2px; }
.viewer__btn:disabled { opacity: .3; cursor: default; }
.viewer__btn[hidden] { display: none; }
.viewer__close { top: .4rem; right: .5rem; }
.viewer__prev,
.viewer__next { top: 50%; margin-top: -1.375rem; }
.viewer__prev { left: .5rem; }
.viewer__next { right: .5rem; }
/* A band of its own: a zoomed picture runs underneath it, and white text on a
   white deck plan would vanish. */
.viewer__caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  margin: 0;
  padding: 1rem 1rem calc(1rem + env(safe-area-inset-bottom));
  background: rgba(4, 16, 29, .82);
  font-size: .9375rem;
  font-weight: 600;
  text-align: center;
}
.viewer__count { margin-left: .6rem; font-weight: 400; opacity: .75; }

@media (min-width: 768px) {
  .viewer__prev { left: 1rem; }
  .viewer__next { right: 1rem; }
  .viewer__close { top: .75rem; right: 1rem; }
}
@media (prefers-reduced-motion: reduce) {
  .viewer__img { transition: none; }
  .viewer.is-loading .viewer__stage::after { animation: none; }
}

/* ---------------------------------------------------------- poster page
 *
 * /poster is one canvas and very little else. The canvas is the poster
 * itself, not a preview of it — the same pixels Save image writes out — so it
 * is sized in percent and never re-laid-out. Everything here is the frame
 * around it.
 */
.poster-page { padding-block: 1.5rem 3rem; }
.poster-page__bar { display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: .5rem; }
.poster-page__bar h1 { margin: 0; }
.poster-page__bar .btn { margin-left: auto; }
.poster-page__note { margin: 0 0 1rem; color: rgba(255, 255, 255, .78); }

.poster-page__sheet { border-radius: var(--radius);
  overflow: hidden;
  /* The poster's own ground is a deep slate; this lifts its edges off the
     page's darker water rather than letting the two merge. */
  box-shadow: 0 18px 46px rgba(4, 14, 26, .45); }
.poster-page__sheet canvas { display: block; width: 100%; height: auto; }

/* The same trips as real text, under the picture.
 *
 * A canvas is one flat bitmap: a screen reader gets its aria-label and nothing
 * more, and a search engine gets nothing at all. On a page whose whole point
 * IS the picture, this list is what makes it a page rather than an excuse. */
.poster-page__text { margin-top: 2rem; color: rgba(255, 255, 255, .86); }
.poster-page__text h2 { font-family: var(--font-head);
  text-transform: uppercase;
  letter-spacing: .04em;
  font-size: 1.1rem;
  color: #fff; }
.poster-page__list { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }
.poster-page__list li { display: flex;
  flex-wrap: wrap;
  gap: .25rem .75rem;
  padding: .5rem 0;
  border-bottom: 1px solid rgba(255, 255, 255, .12);
  font-size: .9rem; }
.poster-page__list strong { color: #fff; }
.poster-page__list em { font-style: normal;
  color: var(--accent);
  text-transform: uppercase;
  font-size: .8rem;
  letter-spacing: .04em; }

/* --------------------------------------------------------- trip calendar
 *
 * /calendar is the same trips as the list page, arranged by WHEN. The grid is
 * a picture of the month and the list beneath it is the same month in words:
 * on a phone a column is about 45px and no label fits inside a bar, so the
 * bars there carry colour and position only. That is not a degraded view —
 * it is the one a screen reader gets at every width, which is why the list is
 * always rendered rather than being a mobile fallback.
 *
 * Colours are the CI's, and they mean here what they mean everywhere else in
 * both apps: coral = sold out (a person must act), yellow = nearly full, blue
 * = Thailand, the supporting teal = international. Green is deliberately
 * absent — it means "done and needs nothing", which no upcoming trip is.
 */
.cal-page { padding-block: 1.5rem 3rem; }

/* A page's own heading and lead, under the site header. Named for the page
   rather than the calendar since /boats opens the same way. */
.page-intro { padding-top: .5rem; }
.page-eyebrow {
  margin: 0;
  font-family: var(--font-head);
  font-size: clamp(1.6rem, 4vw, 2.2rem);
  text-transform: uppercase;
  letter-spacing: .02em;
  color: #fff;
}
.page-lead {
  margin: .35rem 0 1rem;
  max-width: 34rem;
  color: rgba(234, 243, 250, .82);
  font-size: 1.0625rem;
}

/* --- the two views of the same trips ----------------------------------- */
.view-switch {
  display: inline-flex;
  gap: .25rem;
  padding: .25rem;
  margin-bottom: .85rem;
  background: rgba(255, 255, 255, .08);
  border: 1px solid rgba(255, 255, 255, .16);
  border-radius: var(--radius-pill);
}
.view-switch__btn {
  padding: .4rem 1.1rem;
  border-radius: var(--radius-pill);
  font-size: .8125rem;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  text-decoration: none;
  color: rgba(234, 243, 250, .82);
}
.view-switch__btn:hover { color: #fff; }
.view-switch__btn.is-current { background: #fff; color: var(--brand-dark); }
.view-switch__btn:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }
.view-switch__btn { display: inline-flex; align-items: center; justify-content: center; }
/* Words on a desktop, as before; the icons are for the phone below. */
.view-switch__icon { display: none; flex: none; }

/* The toggle on the heading's own line — the trips list's "Upcoming
   departures", the calendar's "Trip calendar" — at the right, instead of a
   row of its own under the intro. The filters and the first card start that
   row higher, and the toggle sits in the same place on both pages. */
.hero__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  margin: 0 0 .35rem;
}
.hero__top .hero__eyebrow { margin: 0; min-width: 0; }
.hero__top .page-eyebrow { min-width: 0; }
.hero__top .view-switch { flex: none; margin: 0; }
.hero__top > .contact-line { flex: none; }

/* The LINE button UNDER the toggle (trips list, calendar). Two columns: the
   heading row spans both, the title and lead take the left, and the button
   the right — beside the title, so nothing below it moves down. It comes
   last in the HTML, after the lead it acts on; the grid places it here. */
.hero--contact {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: 1.25rem;
  align-items: start;
}
.hero--contact > .hero__top { grid-column: 1 / -1; }
.hero--contact > :not(.hero__top, .contact-line) { grid-column: 1; }
.hero--contact > .contact-line { grid-column: 2; grid-row: 2; justify-self: end; margin-top: .4rem; }

@media (max-width: 767px) {
  /* Icons only: the eyebrow leaves no room for the words beside it. The
     words stay in the page, visually hidden (tokens.css .visually-hidden's
     rules), so each link is still named aloud in either language. */
  .view-switch__icon { display: block; }
  .view-switch__btn { padding: .35rem .7rem; }
  /* With the toggle beside it, "UPCOMING DEPARTURES" at .28em wrapped onto two
     lines at 390px. A little tighter keeps it on one down to 375px; narrower
     than that it may wrap, which is still better than a row of its own. */
  .hero__top .hero__eyebrow { gap: .6rem; letter-spacing: .2em; }
  .hero__top .hero__rule { width: 22px; }
  .view-switch__label {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }
}

@media (max-width: 767px) {
  /* A row of its own under the toggle, at the right: beside a title set to
     fill the width there would be no room. The short label, with the full
     one kept for screen readers (tokens.css .visually-hidden's rules). */
  .hero--contact > .contact-line { grid-column: 1 / -1; margin: .15rem 0 .5rem; }
  .btn-line--sm .btn-line__short { display: inline; }
  .btn-line--sm .btn-line__long {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }
}

/* --- the sheet the month sits on --------------------------------------- */
.cal-sheet {
  /* The height of one bar lane. Every week reserves the same number of them
     (see .cal-week__bars), so this is what makes a week with a trip in it the
     same height as a quiet one. It has to be at least the bar's own content
     height at this breakpoint or the rows go uneven again by a pixel or two —
     the minmax() below tolerates that, but only by growing the busy week. */
  --cal-lane: 1.65rem;
  padding-block: 1rem 1.25rem;
  background: rgba(255, 255, 255, .06);
  border: 1px solid rgba(255, 255, 255, .14);
  border-radius: var(--radius-lg);
  box-shadow: 0 18px 46px rgba(4, 14, 26, .38);
}

.cal-head {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin-bottom: .75rem;
}
.cal-title {
  margin: 0;
  flex: 1 1 auto;
  text-align: center;
  font-family: var(--font-head);
  font-size: clamp(1.15rem, 3.2vw, 1.5rem);
  text-transform: uppercase;
  letter-spacing: .06em;
  color: #fff;
}
.cal-step {
  flex: 0 0 auto;
  width: 2.25rem;
  height: 2.25rem;
  display: grid;
  place-items: center;
  font-size: 1.5rem;
  line-height: 1;
  color: #fff;
  background: rgba(255, 255, 255, .1);
  border: 1px solid rgba(255, 255, 255, .2);
  border-radius: 50%;
  cursor: pointer;
}
.cal-step:hover:not(:disabled) { background: rgba(255, 255, 255, .2); }
/* Disabled rather than removed at the ends of the season: a control that
   disappears shifts the month name sideways every time somebody pages. */
.cal-step:disabled { opacity: .3; cursor: default; }
.cal-step:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }

.cal-today {
  flex: 0 0 auto;
  padding: .35rem .8rem;
  font-size: .75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--brand-dark);
  background: var(--accent);
  border: 0;
  border-radius: var(--radius-pill);
  cursor: pointer;
}
.cal-today:focus-visible { outline: 3px solid #fff; outline-offset: 2px; }

/* --- the grid ----------------------------------------------------------
 *
 * Each week is TWO stacked 7-column grids: the day numbers, then the bars.
 * They share one column template, so a bar spanning Friday to Sunday lines up
 * with those three numbers exactly — which a single grid with bars flowing
 * between numbered cells could not guarantee once a week needed three lanes.
 */
.cal-weekdays,
.cal-week__days,
.cal-week__bars {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 2px;
}
.cal-weekdays {
  padding: 0 .25rem .35rem;
  font-size: .7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .06em;
  text-align: center;
  color: rgba(234, 243, 250, .6);
}

.cal-week { padding: 0 .25rem; }
.cal-week + .cal-week { margin-top: 2px; }

.cal-day {
  min-height: 1.9rem;
  padding: .2rem .35rem 0;
  background: rgba(255, 255, 255, .05);
  border-radius: var(--radius) var(--radius) 0 0;
  font-size: .8125rem;
  color: rgba(234, 243, 250, .9);
}
/* Days belonging to the neighbouring month are dimmed, not hidden: a trip
   sailing on the 31st and returning on the 2nd has to be visible from both
   months or it looks like it stops at the edge of the page. */
.cal-day.is-outside { color: rgba(234, 243, 250, .3); background: rgba(255, 255, 255, .02); }
.cal-day.is-today { background: rgba(250, 198, 105, .22); box-shadow: inset 0 0 0 1px var(--accent); }
.cal-day__n { font-variant-numeric: tabular-nums; }

.cal-week__bars {
  /* One row per lane, and EVERY week in the month gets the same lane count —
     the busiest week's — set once on the grid by calendar-page.js.
     This used to be per-week: a quiet week stayed short and a week with a trip
     in it grew, which made the boxes visibly different heights down the month
     and stopped the thing reading as a grid at all. Uniform costs some empty
     space in a month whose busiest week has three overlapping trips; that is
     the trade that was chosen.
     `minmax(lane, auto)` rather than a fixed row: an empty lane still reserves
     its height, and a bar whose text is taller than --cal-lane grows the row
     instead of being clipped. */
  grid-template-rows: repeat(var(--lanes, 1), minmax(var(--cal-lane), auto));
  align-content: start;
  gap: 2px;
  padding-bottom: .3rem;
  background: rgba(255, 255, 255, .05);
  border-radius: 0 0 var(--radius) var(--radius);
}

/* --- a bar -------------------------------------------------------------- */
.cal-bar {
  display: flex;
  align-items: center;
  gap: .3rem;
  min-width: 0;
  padding: .22rem .45rem;
  border-radius: var(--radius-pill);
  font-size: .75rem;
  font-weight: 600;
  line-height: 1.25;
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
}
.cal-bar:hover { filter: brightness(1.08); }
.cal-bar:focus-visible { outline: 3px solid var(--accent); outline-offset: 1px; }
.cal-bar__name { overflow: hidden; text-overflow: ellipsis; }
.cal-bar__boat { font-weight: 400; opacity: .8; overflow: hidden; text-overflow: ellipsis; }
.cal-bar__flag { flex: 0 0 auto; }

/* A clipped end is squared off, so a bar continuing into the next week never
   reads as a trip that ends on Sunday. The dates a customer books flights
   around are the one thing on this page that must not be guessable-wrong. */
.cal-bar.is-cut-left { border-top-left-radius: 0; border-bottom-left-radius: 0; }
.cal-bar.is-cut-right { border-top-right-radius: 0; border-bottom-right-radius: 0; }

.cal-bar--dom     { background: var(--brand-pale);  color: var(--brand-dark); }
.cal-bar--int     { background: var(--brand-soft);  color: #06243A; }
.cal-bar--limited { background: var(--accent);      color: var(--accent-ink); }
.cal-bar--full    { background: var(--danger);      color: #fff; }

/* --- legend ------------------------------------------------------------- */
.cal-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .3rem .5rem;
  margin: .9rem .25rem 0;
  font-size: .75rem;
  color: rgba(234, 243, 250, .7);
}
.cal-legend__item { display: inline-flex; align-items: center; gap: .35rem; }
.cal-key {
  width: .85rem;
  height: .85rem;
  border-radius: 3px;
  flex: 0 0 auto;
}
.cal-key--dom     { background: var(--brand-pale); }
.cal-key--int     { background: var(--brand-soft); }
.cal-key--limited { background: var(--accent); }
.cal-key--full    { background: var(--danger); }

/* --- the month in words -------------------------------------------------
 *
 * Always rendered, at every width. It is the phone layout, the screen-reader
 * layout and what a search engine indexes — the grid above is a picture.
 */
.cal-list { margin-top: 2rem; }
.cal-list__head {
  display: flex;
  align-items: baseline;
  gap: .6rem;
  margin: 0 0 .75rem;
  font-family: var(--font-head);
  font-size: 1.1rem;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: #fff;
}
.cal-list__count {
  font-family: var(--font-body);
  font-size: .8rem;
  font-weight: 400;
  color: rgba(234, 243, 250, .65);
}
.cal-list__items {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  /* minmax(0, 1fr), not the implicit `auto`. An auto track is at least as wide
     as its content's min-content width, so one long trip title pushed the
     whole list — and with it the page — wider than a phone screen. */
  grid-template-columns: minmax(0, 1fr);
  gap: .5rem;
}

.cal-row {
  background: var(--surface);
  border-radius: var(--radius);
  /* The bar colour repeated as a stripe, so a row and its bar are recognisably
     the same trip without the row having to be tinted and lose its contrast. */
  border-left: 4px solid var(--brand-pale);
  box-shadow: 0 6px 20px rgba(4, 16, 29, .28);
}
.cal-row--int     { border-left-color: var(--brand-soft); }
.cal-row--limited { border-left-color: var(--accent); }
.cal-row--full    { border-left-color: var(--danger); }

.cal-row__link {
  display: flex;
  align-items: center;
  gap: .75rem;
  padding: .7rem .9rem;
  color: var(--text);
  text-decoration: none;
}
.cal-row__link:focus-visible { outline: 3px solid var(--accent); outline-offset: -2px; }
.cal-row__dates {
  flex: 0 0 auto;
  min-width: 6.5rem;
  font-family: var(--font-head);
  font-size: .95rem;
  font-weight: 700;
  letter-spacing: .02em;
  color: var(--brand);
  font-variant-numeric: tabular-nums;
}
.cal-row__body { flex: 1 1 auto; min-width: 0; }
/*
 * The flag and the title sit side by side, so this is a flex container — and
 * `text-overflow` does nothing on one. The truncation has to happen on the
 * text span INSIDE it, which also needs min-width: 0, because a flex item
 * defaults to min-width: auto and refuses to shrink below its own content.
 * Without that the longest trip name set the width of the whole page.
 */
.cal-row__title {
  display: flex;
  align-items: center;
  gap: .35rem;
  min-width: 0;
  font-weight: 600;
}
.cal-row__title > span:not(.cal-row__flag) {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cal-row__meta {
  display: flex;
  flex-wrap: wrap;
  gap: .2rem .7rem;
  margin-top: .15rem;
  font-size: .8125rem;
  color: var(--text-muted);
}
.cal-row__price { font-weight: 600; color: var(--brand); }
/* Inside a row the badge is in normal flow — the .avail rules position it over
   a card photo, and there is no photo here. */
.cal-row .avail { position: static; box-shadow: none; flex: 0 0 auto; }

.cal-none {
  margin: 0;
  padding: 1.75rem 1.25rem;
  text-align: center;
  color: rgba(234, 243, 250, .7);
  border: 1px dashed rgba(255, 255, 255, .22);
  border-radius: var(--radius-lg);
}

/* --- phone -------------------------------------------------------------
 *
 * Under 640px a column is about 45px. The bar keeps its NAME — a strip of
 * colour with only an emoji on it says a trip exists and nothing about which
 * trip, which is no use to somebody scanning for a destination. What it gives
 * up instead is the boat name, the bracketed style (see shortRouteName) and,
 * on a one- or two-day bar, the flag: three things the list below still prints
 * in full. Type is small here on purpose — it is a label on a bar, not body
 * copy, and the row underneath is where it is read properly.
 */
@media (max-width: 640px) {
  .cal-bar {
    padding: .28rem .3rem;
    gap: .18rem;
    min-height: 1.15rem;
    font-size: .625rem;
    letter-spacing: -.01em;
  }
  .cal-bar__boat { display: none; }
  .cal-bar__flag { font-size: .65rem; }
  .cal-bar.is-narrow { padding-inline: .18rem; }
  .cal-bar.is-narrow .cal-bar__flag { display: none; }
  .cal-day { min-height: 1.6rem; padding-inline: .2rem; font-size: .7rem; text-align: center; }
  /* Smaller type and tighter padding on the bar, so the reserved lane shrinks
     with it — otherwise every week carries phone-sized bars in desktop-sized
     rows. */
  .cal-sheet { padding-inline: .35rem; --cal-lane: 1.45rem; }
  .cal-weekdays { font-size: .6rem; letter-spacing: .02em; }
  .cal-row__link { flex-wrap: wrap; gap: .35rem .6rem; }
  .cal-row__dates { min-width: 0; }
  .cal-row__body { flex-basis: 100%; order: 3; min-width: 0; }
  /* On a phone the title WRAPS instead of truncating: vertical space is the
     cheap one here, and a customer choosing between two Koh Chang trips needs
     the part of the name that says which boat. */
  .cal-row__title { align-items: flex-start; }
  .cal-row__title > span:not(.cal-row__flag) { white-space: normal; }
}

@media (min-width: 768px) {
  .cal-day { min-height: 2.6rem; font-size: .875rem; }
  .cal-bar { font-size: .8125rem; }
  .cal-sheet { --cal-lane: 1.8rem; }
}

/* ================================================== pre-trip form (db/049) */
/* /form — one card on the water, narrow because it holds a sentence or two
   and a button. form-page.js draws everything inside it. */
.form-page {
  max-width: 30rem;
  margin-inline: auto;
  padding-block: 1.5rem 3rem;
}
.form-card {
  display: grid;
  justify-items: center;
  gap: .85rem;
  padding: 1.75rem 1.25rem;
  border-radius: var(--radius-lg);
  background: var(--surface);
  color: var(--text);
  text-align: center;
  box-shadow: 0 10px 34px rgba(4, 16, 29, .38);
}
.form-card__title {
  margin: 0;
  font-family: var(--font-head);
  font-size: 1.75rem;
  line-height: 1.15;
  color: var(--brand-dark);
}
.form-card__lead { margin: 0; color: var(--text); }
.form-card__privacy, .form-card__soon { margin: 0; font-size: .8125rem; color: var(--text-muted); }
.form-card__error {
  justify-self: stretch;
  margin: 0;
  padding: .6rem .75rem;
  border-radius: var(--radius);
  background: var(--danger-wash);
  color: var(--danger-ink);
  font-weight: 600;
  text-align: left;
}
.form-card__signin { justify-self: stretch; }
.form-card__trip {
  justify-self: stretch;
  display: grid;
  gap: .15rem;
  padding: .75rem .9rem;
  border-radius: var(--radius);
  background: var(--brand-wash);
  color: var(--brand-dark);
  text-align: left;
  text-decoration: none;
}
.form-card__trip:hover { background: #DCEBF4; }
.form-card__trip-label { font-size: .8125rem; color: var(--text-muted); }
.form-card__trip-name { font-size: 1.05rem; }
.form-card__trip-when { font-size: .875rem; }
.form-card__signout {
  padding: .25rem .5rem;
  border: 0;
  background: none;
  font: inherit;
  font-size: .8125rem;
  color: var(--text-muted);
  text-decoration: underline;
  cursor: pointer;
}
/* The LINE buttons sit on the white card, not the blue panel .btn-line was
   drawn for, so its white focus ring would vanish here. */
.form-card .btn-line:focus-visible { outline-color: var(--brand); }

/* ------------------------------------------------------------ consent dialog
   The statistics consent dialog (site/src/track.js).

   It was a bar along the bottom edge until 2026-10-17. Over its first day on
   production, 16 real visitors saw it and 6 answered: the other ten read right
   past it, which is what a strip docked to the bottom of a page gets, because
   that is where cookie notices and chat bubbles live and readers have learned
   to skip that band entirely. Centred over a dimmed page, it has to be dealt
   with.

   Decline is styled identically to Accept, and comes first in the source so it
   is also first for a keyboard. Making refusal quieter would raise the number
   and make the consent worthless. */
.consent-backdrop {
  position: fixed;
  inset: 0;
  z-index: 200;        /* above the phone nav overlay, which is 100 */
  display: grid;
  place-items: center;
  padding: 1.25rem;
  background: rgb(4 18 31 / .72);
  animation: consent-fade .18s ease-out;
}

/* Dark card, light text — the palette the bottom bar used, which is the one
   that reads correctly here. --bg is #F6F6F6, nearly white, while this site's
   body text is nearly white too: a card painted with --bg and inheriting the
   page's colour renders white on white and the first version of this dialog
   shipped with an invisible heading and an invisible paragraph. */
.consent {
  width: min(32rem, 100%);
  max-height: calc(100dvh - 2.5rem);
  overflow-y: auto;
  padding: clamp(1.25rem, 4vw, 1.75rem);
  border-radius: var(--radius-lg);
  background: var(--brand-dark);
  color: #fff;
  box-shadow: 0 18px 48px rgb(0 0 0 / .45);
  animation: consent-rise .22s cubic-bezier(.2, .8, .3, 1);
}

/* The dialog takes focus when it opens so a screen reader lands inside it;
   that focus ring would otherwise draw a box round the whole card. */
.consent:focus { outline: none; }
.consent:focus-visible { outline: 3px solid #fff; outline-offset: 2px; }

.consent__title {
  margin: 0 0 .5rem;
  font-family: var(--font-head);
  font-size: clamp(1.15rem, 3.5vw, 1.4rem);
  line-height: 1.25;
}

.consent__text {
  margin: 0;
  font-size: var(--fs-meta);
  line-height: 1.6;
}

.consent__more {
  display: inline-block;
  margin-top: .6rem;
  font-size: var(--fs-meta);
  color: #fff;
  text-decoration: underline;
}

.consent__actions {
  display: flex;
  gap: .625rem;
  margin-top: 1.25rem;
}

.consent__btn {
  flex: 1 1 0;
  min-height: 46px;
  padding: .5rem 1rem;
  border: 2px solid #fff;
  border-radius: var(--radius-pill);
  background: #fff;
  color: var(--brand-dark);
  font-family: inherit;
  font-size: 1rem;
  font-weight: 700;
  cursor: pointer;
}

.consent__btn:hover { background: var(--brand-pale); border-color: var(--brand-pale); }
/* White ring on the dark card, for the same reason .btn-line uses one. */
.consent__btn:focus-visible { outline: 3px solid #fff; outline-offset: 3px; }

/* Outlined rather than filled — the only difference allowed between the two,
   and the same size, weight and hit area either way. */
.consent__btn--quiet {
  background: transparent;
  color: #fff;
  border-color: rgb(255 255 255 / .65);
}

.consent__btn--quiet:hover {
  background: rgb(255 255 255 / .14);
  border-color: #fff;
}

@keyframes consent-fade {
  from { opacity: 0; }
}

@keyframes consent-rise {
  from { opacity: 0; transform: translateY(12px); }
}

/* Somebody who asked for less movement gets the dialog without the entrance. */
@media (prefers-reduced-motion: reduce) {
  .consent-backdrop, .consent { animation: none; }
}

/* ------------------------------------------------------------ legal document
   The privacy notice (site/privacy.html). Both languages are in the markup and
   privacy-page.js hides the one that does not apply, so these rules describe a
   plain prose document and nothing language-specific.

   A reading measure, not the 1200px the card grids use: a line of body text
   that runs the full width of a desktop window is hard to track back to the
   start of the next one, and this is the one page on the site somebody
   actually reads top to bottom. */
.legal {
  max-width: 70ch;
  padding-block: 1rem 3rem;
}

.legal h1 {
  margin: 0 0 .25rem;
  font-family: var(--font-head);
  font-size: clamp(1.6rem, 4vw, 2.2rem);
  line-height: 1.2;
}

.legal h2 {
  margin: 2rem 0 .5rem;
  font-size: var(--fs-sub);
}

.legal p, .legal li { line-height: 1.7; }
.legal ul { padding-inline-start: 1.25rem; }
.legal li { margin-block: .35rem; }

.legal__meta {
  margin: 0 0 1.5rem;
  font-size: var(--fs-meta);
  color: var(--brand-soft);
}

/* The current-setting line under the consent buttons. Reads as a status, not
   as another paragraph of the policy. */
.legal__state {
  margin-top: .75rem;
  font-size: var(--fs-meta);
  font-weight: 600;
}

/* The two consent buttons borrow .btn-route's outline shape; they need the
   gap and the wrap that a <p> of two buttons does not give them on a phone. */
.legal .btn-route { margin-inline-end: .5rem; }
.legal p:has(.btn-route) {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
}
