/* ==========================================================================
   cabin.css, the three cabin pages
   (/stay/hunter-one/, /stay/hunter-two/, /stay/hunter-three/)

   ONE stylesheet serves all three. The cabins are architecturally identical,
   so the pages are identical in structure and differ only in photography and
   short "feel" copy. Anything that differs per cabin lives in the page HTML,
   never here.

   Layered on top of tokens.css + base.css + home.css (shared chrome), the same
   way contact.css / reviews.css / relax.css are. Every value references
   tokens.css. No new tokens, no hex literals.

   The page is OFF-WHITE dominant (--c-base), not Forest-dominant like the
   homepage, with Forest ink on top. Sandstone is the occasional alternate
   band. Copper stays an accent only: hovers, underlines, hairlines. Never
   body text, never a fill behind small type.
   ========================================================================== */


/* --- 0 · Active nav state -------------------------------------------------
   Identical to the rule subpage.css / contact.css / reviews.css carry, kept
   here so a cabin page renders correctly even if subpage.css is ever dropped
   from its <head>. Copper hairline underline marks the current page. */
.nav__link[aria-current="page"] {
  color: var(--color-text-on-dark);
  border-bottom-color: var(--color-accent);
}
.nav__dropdown a[aria-current="page"] { color: var(--color-accent); }
.mobile-menu__list a[aria-current="page"] { color: var(--color-accent); }
.footer-col a[aria-current="page"] { color: var(--color-text-on-dark); }


/* --- 0b · Solid header ----------------------------------------------------
   Everywhere else on the site the header starts transparent, because it opens
   over a Forest hero or a dark photograph and its off-white nav is legible on
   both. These pages have no hero: the gallery is the first thing under the
   header, per the brief, and off-white nav links over a pale, sunlit
   photograph would be unreadable at scroll 0.

   So on cabin pages the header is Forest from the first pixel and the
   top-down scrim it uses over photography is switched off, it has nothing to
   protect and would only muddy a solid bar. .is-scrolled (set by main.js)
   already paints the same Forest, so scrolling changes nothing here, which is
   the intent: the bar is simply always solid.

   The gallery then needs to clear it, since a fixed header is out of flow. */
.cabin-page .site-header { background-color: var(--c-forest); }
.cabin-page .site-header::before { opacity: 0; }

.cabin-page .cabin-gallery {
  padding-block-start: calc(var(--header-h) + var(--space-lg));
}
@media (min-width: 900px) {
  .cabin-page .cabin-gallery {
    padding-block-start: calc(var(--header-h) + var(--space-xl));
  }
}


/* --- 1 · Off-white band ---------------------------------------------------
   base.css ships .band--light (linen) and .band--light-alt (sandstone) but no
   off-white band, because until now no page wanted one: the homepage is
   Forest-dominant. These pages are the opposite, so this is the workhorse
   band and Sandstone becomes the relief.

   Not a new token, it paints --c-base, the off-white already defined in
   tokens.css, and takes its ink from --color-text-on-light (Forest), exactly
   as the other light bands do. */
.band--offwhite {
  background-color: var(--c-base);
  color: var(--color-text-on-light);
}
.band--offwhite h1,
.band--offwhite h2,
.band--offwhite h3 { color: var(--color-text-on-light); }
.band--offwhite .eyebrow { color: var(--color-text-on-light-2); }
.band--offwhite .link-arrow,
.band--offwhite .text-link { color: var(--color-text-on-light); }
.band--offwhite .link-arrow:hover,
.band--offwhite .link-arrow:focus-visible,
.band--offwhite .text-link:hover,
.band--offwhite .text-link:focus-visible { color: var(--color-accent); }

/* Primary button on off-white: same treatment the other light bands get, so a
   CTA here matches one on /relax/ or the homepage's linen sections. */
.band--offwhite .btn--primary {
  background-color: var(--c-forest);
  color: var(--c-base);
}
.band--offwhite .btn--primary:hover,
.band--offwhite .btn--primary:focus-visible {
  background-color: var(--c-earth);
  color: var(--c-base);
}


/* --- 2 · Gallery ----------------------------------------------------------
   One large hero photo, two stacked beside it in a narrower right-hand
   column, and a "Show all photos" button over the bottom-right corner. Every
   photo in the set is still one click away: the three visible tiles open the
   spotlight on themselves, the button opens it at the first photo.

   This replaces a justified (Flickr-style) full-set grid. That layout showed
   all six frames at their native ratio and cropped nothing, which was its
   whole argument, but six tiles of photography ran the length of a screen
   before a guest reached a single word about the cabin. Three frames and a
   button is the trade: the page leads with one photograph at a size worth
   looking at, and the rest of the set moves into the spotlight, where it was
   always going to be viewed properly anyway.

   Cropping is the cost, and it is deliberate here. All three tiles are cut
   to one block and squared off on the same bottom edge, so they read as a
   single unit, which means each image is covered into its box rather than
   shown at its own ratio. The two right-hand tiles crop hardest, because a
   near-square box takes only the middle band out of a portrait source. The spotlight is where the uncropped frame lives:
   .lightbox__img is object-fit: contain and always shows the whole photo. If
   a real frame ever crops badly here, fix it with object-position on that
   tile rather than by letting it size itself.

   The remaining tiles are still in the DOM inside .cabin-gallery__rest. They
   are display:none here and never fetched (they are loading="lazy" inside a
   hidden box), and the <noscript> block in the page markup restores them as
   the no-JS fallback. Do not delete them: cabin-gallery.js builds the
   spotlight's photo set from these tiles, so the set is complete only because
   they are present. */
.cabin-gallery { padding-block: var(--space-xl); }

/* Positioning context for the "Show all photos" button. */
.cabin-gallery__frame { position: relative; }

/* 12fr / 5fr is not an arbitrary split. With the hero at 3:2 and the two
   right-hand tiles stacked and squared off on its bottom edge, the column
   widths are what decide the tiles' shape: a right column c wide gives tiles
   c by (heroHeight - gap) / 2. Solving that for a near-square 5:4 tile under
   a 3:2 hero puts c at 5/17 of the width and the hero at 12/17, which is
   12fr / 5fr. The gap makes the rendered tiles a touch wider than 5:4 (about
   1.27:1 at the 1104px content width), which is not visible.

   True 1:1 squares are possible but cost 13% more hero: they need roughly
   3fr / 1fr, and the whole block grows with it. This was the client's call
   on 29 Aug 2026, taken against that alternative.

   Change the hero's ratio or the tiles' and this split has to be re-solved,
   or the three stop landing on one bottom edge. */
.cabin-gallery__grid {
  display: grid;
  grid-template-columns: 12fr 5fr;
  gap: var(--space-2xs);
}

.cabin-gallery__item {
  min-width: 0;
  margin: 0;
}

/* The hero's ratio sets the height of the whole block, and the stack beside it
   is stretched to match. Sizing from a ratio rather than a fixed height or a
   vh is what keeps the three in proportion at every width: a viewport-derived
   height turns the narrow tiles into slivers on a tablet, where the block is
   much less wide but the screen is not much shorter. */
.cabin-gallery__item--hero { aspect-ratio: 3 / 2; }

/* Every tile takes its photo out of flow, and that is what puts them all on
   one bottom edge. A grid row is as tall as its tallest item, so while the
   images were in flow a portrait companion sized the row from its own file
   and the hero, held to 3/2 by its aspect-ratio, stopped short of it. It is
   not enough to give the hero the ratio: the companions have to stop having
   an opinion about their own height. Absolutely positioned, they contribute
   nothing, the hero's ratio alone sets the row, and the stack stretches into
   exactly that box. */
.cabin-gallery__item--hero,
.cabin-gallery__item--side { position: relative; }
.cabin-gallery__item--hero > .cabin-gallery__btn,
.cabin-gallery__item--side > .cabin-gallery__btn { position: absolute; inset: 0; }

/* The two companions share the right-hand column and split its height. This
   is a wrapper rather than two more grid children because the pair has to
   divide one column between them: as direct children they would each claim a
   row of the outer grid and the hero could no longer set the block height on
   its own. The stack has no intrinsic height itself, so the row is still
   sized by the hero and the stack stretches into it.

   Both frames are near-square, so a portrait source is cropped hard to its
   middle band and a landscape one loses its edges. If a real frame loses its
   subject that way, set object-position on that tile's img rather than
   changing the ratio of the set. */
.cabin-gallery__stack {
  grid-column: 2;
  display: grid;
  grid-template-rows: 1fr 1fr;
  gap: var(--space-2xs);
  min-width: 0;
}

/* Hidden set: present for the spotlight, restored by <noscript>. When it is
   shown it is the old justified row layout, where each tile's width is
   proportional to its own --ar so a row lands on one height with every ratio
   intact and nothing is cropped. */
.cabin-gallery__rest {
  display: none;
  grid-column: 1 / -1;
  flex-wrap: wrap;
  gap: var(--space-2xs);
}
.cabin-gallery__rest .cabin-gallery__item {
  aspect-ratio: var(--ar);
  flex-grow: var(--ar);
  flex-shrink: 1;
  flex-basis: calc(var(--ar) * 19rem);
}
/* Absorbs the leftover space on the final row. Without it the last row's
   tiles grow to fill the width and render noticeably larger than the row
   above. */
.cabin-gallery__rest::after {
  content: "";
  flex-grow: 1000000;
  min-width: 30%;
}

.cabin-gallery__btn {
  display: block;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
  /* Radius stays at the 4px ceiling the site never crosses. No shadow. */
  border-radius: var(--radius);
  overflow: hidden;
}
/* The photos are <picture> elements now (AVIF / WebP / JPEG). A <picture> is
   an inline box by default and it, not the button, is the img's containing
   block, so without this the img's height: 100% has nothing to resolve
   against and every tile collapses to its intrinsic height. */
.cabin-gallery__btn picture {
  display: block;
  width: 100%;
  height: 100%;
}
.cabin-gallery__btn img {
  display: block;
  width: 100%;
  height: 100%;
  /* Crops the two hero tiles to the shared panel height. In .cabin-gallery__
     rest the tile already carries the file's own ratio, so it crops nothing
     there. */
  object-fit: cover;
  transition: transform var(--dur-mid) var(--ease-out),
              opacity var(--dur-fast) var(--ease);
}
.cabin-gallery__btn:hover img,
.cabin-gallery__btn:focus-visible img { opacity: 0.88; }

.cabin-gallery__btn:focus-visible {
  outline: var(--divider-w) solid var(--color-focus);
  outline-offset: 3px;
}

/* "Show all photos". Sits over the corner of the photography, so it is an
   off-white fill with Forest ink and a hairline border: the same pairing as
   .btn--primary, which is the site's control for exactly this situation, a
   button over a photograph. Squared to the 4px ceiling like every other. */
.cabin-gallery__all {
  position: absolute;
  right: var(--space-sm);
  bottom: var(--space-sm);
  background-color: var(--c-base);
  color: var(--c-forest);
  border: var(--divider-w) solid var(--border-on-light);
  padding: var(--space-2xs) var(--space-sm);
}
.cabin-gallery__all:hover,
.cabin-gallery__all:focus-visible {
  background-color: var(--c-sandstone);
  color: var(--c-forest);
}
.cabin-gallery__all svg { fill: currentColor; }

@media (max-width: 639px) {
  /* One frame on phones. Both companion tiles are dropped rather than
     squeezed: at this width tiles side by side are all too small to read, and
     every photo is still one tap from full screen either way. */
  .cabin-gallery__grid { grid-template-columns: 1fr; }
  /* Squarer on a phone: at 3/2 across a narrow column the hero is too short
     to carry the top of the page on its own. */
  .cabin-gallery__item--hero { aspect-ratio: 4 / 3; }
  .cabin-gallery__stack { display: none; }
  .cabin-gallery__all {
    right: var(--space-2xs);
    bottom: var(--space-2xs);
  }
  /* No-JS fallback stacks: one tile per row, native ratio, nothing cropped. */
  .cabin-gallery__rest .cabin-gallery__item { flex-basis: 100%; }
  .cabin-gallery__rest::after { display: none; }
}


/* --- 3 · Two-column body --------------------------------------------------
   Left column carries the whole story, right column carries the booking box
   and nothing else. Single column below 900px, where the booking box is
   replaced entirely by the site's persistent bottom bar (see §5). */
.cabin-layout { padding-block-start: 0; }

.cabin-layout__grid {
  display: grid;
  gap: var(--space-xl);
  align-items: start;
}

@media (min-width: 900px) {
  .cabin-layout__grid {
    /* The right track has a FLOOR, not just a ratio. The Lodgify booking box
       inside it will not compress below about 252px: its date-picker trigger
       is "Check-in -> Check-out" on one line and overflows rather than
       wrapping. At 900px a plain minmax(0, 1fr) gave the track 196px and the
       widget spilled ~56px out through the side of the booking card.

       320px is that minimum plus the booking card's own 1.5rem padding either
       side and its hairline border, with a little slack. The number is derived
       from the vendor's box, not chosen, so re-measure it if the embed is
       regenerated or if the box is ever given back its own 14px padding: with
       Lodgify's card left on, the same measurement came out at 344px. The left
       column absorbs the difference and is 394px at 900px, its narrowest,
       across the 900-1100 band only; above that the 1.9fr ratio takes over and
       1440 is unchanged at 660/348. */
    grid-template-columns: minmax(0, 1.9fr) minmax(320px, 1fr);
    gap: var(--space-3xl);
  }
}

.cabin-body > * + * { margin-block-start: var(--space-lg); }
.cabin-body h2 { margin-block-start: var(--space-2xl); }
.cabin-body > :first-child { margin-block-start: 0; }

/* --- Mobile running order -------------------------------------------------
   Below 900px the grid is already single-column and the booking rail has
   flowed inline to the very end of the story, after Getting here. The client
   asked for it directly under the spec line instead, with the description
   after it and the review card after that:

     H1 / spec line / booking box / description / review card / the rest

   This is not a reorder within one container. The box is `.booking-col`, a
   SIBLING of `.cabin-body`, and it has to land between two of `.cabin-body`'s
   own children. `order` cannot cross that boundary.

   So `.cabin-body` gives up its box below 900px and its children become grid
   items of `.cabin-layout__grid` directly, alongside `.booking-col`. One
   `order` scale then covers all of them. This is safe because the div carries
   no padding, background, border or width at any width: its only rules are
   child margins and descendant type sizes, and both are DOM-based, so they
   survive `display: contents` untouched.

   Anything not named below keeps order 6 and therefore its DOM order, so The
   essentials / What this cabin has / Getting here still run in sequence after
   the four that move.

   Desktop is untouched. Above 900px `.cabin-body` is a block again and the
   two columns are exactly as they were. */
@media (max-width: 899px) {
  .cabin-body { display: contents; }

  /* The row gap separated exactly two items before this, the body and the
     rail. With the body's children promoted it would fire between every one
     of them, on top of the margins they already carry. Those margins are the
     spacing system on this page, so the row gap goes rather than being fought
     with negative margins. */
  .cabin-layout__grid { row-gap: 0; }

  .cabin-body > *             { order: 6; }
  .cabin-body > h1            { order: 1; }
  .cabin-body > .cabin-specs  { order: 2; }
  .cabin-body > .cabin-about  { order: 4; }
  .cabin-body > .cabin-rating { order: 5; }

  /* The rail is the one item with no margin of its own, because it was spaced
     by the grid gap that just came off. */
  .booking-col {
    order: 3;
    margin-block-start: var(--space-lg);
  }
}

/* --- Spec line + rating box, under the H1 --------------------------------
   The Airbnb-listing lockup, in this site's language: a plain spec line, then
   a small bordered card carrying the review proof.

   The card is NOT a copy of Airbnb's. Theirs is a white pill with a shadow and
   two laurel emoji. This one is built from what the site already uses: the
   .booking-card's border, radius and Linen fill, the shared gold .stars, and
   the Copper hairline rule the homepage review banner puts between the score
   and the caption. It reads as the same family as the booking card opposite
   it, which is the point: they are the two boxes on this page. */
.cabin-specs {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2xs) 0;
  margin-block-start: var(--space-2xs);
  list-style: none;
  padding: 0;
  font-size: var(--fs-small);
  color: var(--color-text-on-light-2);
}
/* Separators are generated, so the markup stays three list items and a screen
   reader is never asked to read a middle dot as a word. */
.cabin-specs li + li::before {
  content: "·";
  margin-inline: var(--space-2xs);
  /* Not aria-hidden because generated content is not in the a11y tree here,
     but muted so it reads as punctuation rather than a fourth item. */
  color: var(--color-text-on-light-2);
}

.cabin-rating {
  margin-block-start: var(--space-md);
  padding: var(--space-sm) var(--space-md);
  border: var(--divider-w) solid var(--border-on-light);
  border-radius: var(--radius);
  background-color: var(--c-linen);
  /* Queried by its own width below, not by the viewport's. See the note on
     .cabin-rating__stats for why the viewport cannot answer the question. */
  container-type: inline-size;
  container-name: rating;
}

/* Three figures, evenly spaced.

   It was two groups until 30 Aug 2026: the score held the left, 300+ and 97%
   grouped against the right behind a Copper rule, and the space between them
   was the design. The client asked for the three spaced evenly instead, so the
   .cabin-rating__evidence wrapper is gone and all three are siblings here.

   The columns are max-content and the free space is distributed by
   space-between, so every gap is identical no matter what the figures say. The
   score is still the widest of the three because the stars are inside it; that
   now falls out of its content rather than being granted by the layout, which
   is the difference the client was asking for.

   --- Why this is a CONTAINER query and nothing else on the site is ---

   The three fit on one line or they do not, and that depends on the CARD's
   width, which is not a function of the viewport's here. The card is 660px at
   1440, drops to 394px between 900 and 1100 where the booking rail claims the
   other column, and widens again to about 600px below 900 where the rail moves
   out and the layout goes single-column. So "fits on one line" is true at
   >=1100 AND at 640-900, and false in the 900-1100 band between them. A
   viewport media query cannot express that without hard-coding the same
   discontinuity in two places and re-deriving it whenever the grid changes.

   The container query asks the card directly. 34rem is derived, not picked.
   Measured at 1440: the three boxes are 181 + 89 + 143 = 413px including the
   24px inset each rule sits on. The card spends 48px of its own width on
   padding. Requiring at least --space-lg of clear gap either side of the two
   rules gives 413 + 64 + 48 = 525px, rounded up to 34rem (544px), where the
   gaps land at 41px each. The obvious 30rem is wrong: it is the width at which
   the three merely FIT, and a row that fits with 9px between its figures reads
   as a jam, not as a row. Re-measure if a figure's wording changes. */
.cabin-rating__stats {
  display: grid;
  align-items: center;
  gap: var(--space-sm) 0;
}

/* Stars beside the score, not above it. Above, they read as a heading for the
   whole card and leave the figures looking unanchored; beside, they belong to
   the number they describe. */
.cabin-rating__score {
  display: flex;
  align-items: center;
  gap: var(--space-2xs) var(--space-sm);
  flex-wrap: wrap;
}

/* BOTH layouts are in a container query, and neither is the base.

   The first attempt made the narrow layout the base and overrode it inside the
   wide query. That silently lost, and it is worth recording why: a container
   query adds NO specificity of its own, so `.cabin-rating__stat +
   .cabin-rating__stat` (two classes) outside the query beat `.cabin-rating__stats
   > * + *` (one class) inside it. The third figure kept a 32px margin the wide
   layout had told it to drop, and the two gaps measured 78px and 110px on a
   card that was supposed to have made them identical.

   Writing the two as an exclusive pair means there is nothing to override.
   Whichever matches, applies. */

/* Narrow card: ONE two-column grid, both rows sharing the same two edges.

   Rebuilt 30 Aug 2026. It used to be two stacked layouts in one box: the score
   spanned a full row and the two figures sat below it on max-content columns
   packed left. Measured on a 380px card that produced four different left
   edges - stars at 25, 4.99 at 143, 300+ at 25, 97% at 118 - and the two that
   looked like they should line up, 4.99 and 97%, missed each other by 25px.
   A near-miss reads as a mistake in a way a large offset does not. The copper
   rule was a short stub floating at 118 with nothing above or below it, and
   the score row stopped at 206 of an available 355, so the card was visibly
   left-heavy on top and full-width underneath.

   Now the figures land on a real grid: stars over 300+ in column one, 4.99
   over 97% in column two. The two near-miss edges become one shared edge, and
   the copper rule sits on the column boundary as two aligned segments rather
   than one floating stub.

   `.cabin-rating__score` has to dissolve for that: it wraps the stars and the
   4.99 together, and they need to sit in different columns. Same
   `display: contents` reasoning as the mobile running order in section 3, and
   the same caveat - it is a bare flex wrapper with no box of its own, so
   nothing is lost with it. Auto-placement then fills the four items across two
   columns in DOM order without a single explicit placement.

   Note this is a CONTAINER query, so it is not strictly "mobile": the card is
   also under 34rem in the 900-1100px band where the booking rail claims the
   other column. Checked there too. */
@container rating (width < 34rem) {
  .cabin-rating__score { display: contents; }

  .cabin-rating__stats {
    grid-template-columns: max-content minmax(0, 1fr);
    justify-content: start;
  }

  /* Column one carries its breathing room as PADDING rather than a column gap.
     A gap would open a hole in the middle of the horizontal rule below, which
     is drawn from the two lower items' top borders meeting at the column
     boundary. This is the reason there is no column-gap here. */
  .cabin-rating__score .stars,
  .cabin-rating__stats > .cabin-rating__stat:not(:last-child) {
    padding-inline-end: var(--space-md);
  }

  /* Column two: both items take the same inset, so the 4.99 and the 97% share
     one text edge whether or not either carries a rule.

     The transparent border is not decoration, it is the alignment. Only the
     lower item draws a copper rule, and a 1px border widens the box it sits
     on; without a matching transparent one here the two figures sat a pixel
     apart. A pixel is invisible on its own, but exact alignment between these
     two is the whole point of the grid they are on, so it is held in the
     geometry rather than left to luck. */
  .cabin-rating__score .cabin-rating__stat,
  .cabin-rating__stats > .cabin-rating__stat + .cabin-rating__stat {
    padding-inline-start: var(--space-md);
    border-inline-start: var(--divider-w) solid transparent;
  }

  /* The copper rule is on the lower pair ONLY. The matching rule between the
     stars and the 4.99 was removed on request: those two are one statement,
     the score, and fencing them apart said they were two. What separates the
     score from the figures is the horizontal rule below, not a vertical one. */
  .cabin-rating__stats > .cabin-rating__stat + .cabin-rating__stat {
    border-inline-start-color: var(--c-copper);
  }

  /* The horizontal rule between the score and the two figures, in the card's
     own hairline rather than copper: it divides the card, it is not one of its
     accents. It is the two lower items' top borders, continuous only because
     there is no column gap.

     `align-self: stretch` is load-bearing, not tidying. The base grid centres
     its items, and centred items with different heights do not share a top
     edge, so the moment "perfect scores" wraps to two lines below a 320px card
     the rule would break into two offset segments. Stretched, their tops stay
     on one line at every width. */
  .cabin-rating__stats > .cabin-rating__stat {
    align-self: stretch;
    padding-block-start: var(--space-sm);
    border-block-start: var(--divider-w) solid var(--border-on-light);
  }
}

/* Wide card: one row of three. The tracks are max-content and space-between
   shares the slack equally between them, so both gaps are the same width
   whatever the figures happen to say. Every figure after the first carries the
   rule at an identical inset, and no margin: the gap is the grid's to
   distribute, and a margin on one item would put its rule out of step with the
   other's. */
@container rating (width >= 34rem) {
  .cabin-rating__stats {
    grid-template-columns: max-content max-content max-content;
    justify-content: space-between;
  }
  .cabin-rating__stats > * + * {
    padding-inline-start: var(--space-md);
    border-inline-start: var(--divider-w) solid var(--c-copper);
  }
}

.cabin-rating__stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.cabin-rating__num {
  font-size: var(--fs-h3);
  font-weight: var(--fw-light);
  line-height: 1;
  color: var(--color-text-on-light);
  font-variant-numeric: tabular-nums;
}
.cabin-rating__label {
  font-size: var(--fs-eyebrow);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--color-text-on-light-2);
}

/* Source line and the link. One row where there is space, stacked when there
   is not, with the link pushed to the end so it lands under the third figure
   and reads as the card's exit. */
.cabin-rating__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2xs) var(--space-sm);
  margin-block-start: var(--space-sm);
  padding-block-start: var(--space-sm);
  border-block-start: var(--divider-w) solid var(--border-on-light);
  font-size: var(--fs-small);
}
.cabin-rating__sources { color: var(--color-text-on-light-2); }

/* --- The story's "See more" disclosure ------------------------------------
   The paragraphs used to be direct children of .cabin-body and took their
   rhythm from the rule above. They are wrapped now, so the wrapper and the
   collapsed region reproduce it rather than inheriting it. */
.cabin-about > * + * { margin-block-start: var(--space-lg); }
.cabin-about__rest > * + * { margin-block-start: var(--space-lg); }

/* The button sits tight under the text it opens, not a full paragraph gap
   away, so it reads as attached to the story rather than as the next thing. */
.cabin-about__more { margin-block-start: var(--space-md); }

/* Smaller than a standard .btn. This is a secondary, repeatable control inside
   a block of prose, not a call to action, and at the full button size it
   competed with the booking rail beside it.

   Scoped here rather than added to base.css as a .btn--sm modifier: one use,
   one place. Promote it if a second use appears.

   --fs-eyebrow is the site's existing 11px uppercase label size, already used
   for small caps labels elsewhere, so this introduces no new type size.

   The 44px minimum tap target is deliberate and is what stops it shrinking
   further. At this type and padding the box is well under that, so
   min-block-size does the rest: below 900px, where this is thumb-operated, it
   stays a 44px target while still LOOKING small. Padding alone could not do
   both. */
.cabin-about__more .btn {
  padding: var(--space-3xs) var(--space-2xs);
  font-size: var(--fs-eyebrow);
  letter-spacing: var(--ls-label);
  min-block-size: 44px;
}

@media (min-width: 900px) {
  /* Pointer input, so the tap-target floor comes off and the control can sit
     at its true size. */
  .cabin-about__more .btn { min-block-size: 0; }
}

/* The reveal's fade lives in cabin-about.js, as a Web Animations call rather
   than an opacity-0 class flipped in requestAnimationFrame. The class idiom
   leaves the prose at opacity 0 if the rAF callback never fires, which is
   exactly what a backgrounded tab does. Nothing here sets an opacity on this
   block, so its resting state is visible no matter what the script does. */


/* Headings run smaller here than the site-wide tokens set them. Those tokens
   are sized for full-bleed hero bands, where the H1 is the whole composition;
   on these pages the H1 opens a text column beside the booking rail, and
   --fs-h1 at its 5rem ceiling overwhelmed both the column and the rail. H1 caps
   at 3rem and H2 at 2rem, which keeps H1 > H2 > lede reading as three distinct
   steps. Both floors sit at or below the existing small-screen sizes, so
   nothing grows on mobile. Scoped to .cabin-body on purpose: the homepage and
   the other pages still want the large tokens. */
.cabin-body h1 { font-size: clamp(2rem, 3.9vw, 3rem); }
.cabin-body h2 { font-size: clamp(1.5rem, 2.5vw, 2rem); }

/* Lede, the one-line feel plus the opening paragraph, set slightly larger so
   the column opens with a change of pace. Weight stays 400: Montserrat at 300
   goes thin against Forest ink. */
.cabin-lede {
  font-size: clamp(1.1875rem, 1vw + 1rem, 1.375rem);
  line-height: 1.55;
  color: var(--color-text-on-light);
  max-width: var(--measure);
}

/* Client-input marker. Deliberately conspicuous so an unfilled slot can never
   ship unnoticed. Sandstone fill with Forest ink, never Sandstone text on a
   light background, which the site rules out. Delete the class with the
   bracketed text when the real copy lands. */
.needs-copy {
  background-color: var(--c-sandstone);
  color: var(--c-forest);
  padding: var(--space-3xs) var(--space-2xs);
  border-radius: var(--radius-sm);
  font-weight: var(--fw-medium);
}


/* --- 4 · Quick facts ------------------------------------------------------
   A short ruled list, not a spec table. Same hairline language as the FAQ
   accordion on the homepage. */
.quick-facts {
  display: grid;
  gap: 0;
  margin-block-start: var(--space-md);
  border-block-start: var(--divider-w) solid var(--border-on-light);
}
/* Grid at every width, single-column below 640px and a two-column pair above
   it. It was a wrapping flex row with space-between, which fails in one
   specific way: a value too long to sit beside its key drops onto a
   full-width line of its own, and the pair stops reading as a pair. That is
   width-dependent, so it hit some rows and not others in the same table.
   Kitchen alone at 1200px, Kitchen and Bathroom at 1024px, a different mix
   again between 375 and 639. One row looking broken while its six neighbours
   did not was the whole problem. A grid cannot do that: a cell wraps inside
   its own column and stays where it belongs. */
.quick-facts__row {
  display: grid;
  gap: var(--space-2xs) var(--space-md);
  padding-block: var(--space-xs);
  border-block-end: var(--divider-w) solid var(--border-on-light);
}

/* From 640px up, a real two-column pair. `auto` on the key column keeps each
   key at its own width, which is the existing look: this is a set of pairs,
   not an aligned two-column table. `minmax(0, 1fr)` rather than `1fr` so a
   long unbroken word cannot push the column past the row. Baseline alignment
   holds the key to the value's first line, not to the middle of a wrapped
   block. */
@media (min-width: 640px) {
  .quick-facts__row {
    grid-template-columns: auto minmax(0, 1fr);
    align-items: baseline;
  }
}
.quick-facts__key {
  font-size: var(--fs-eyebrow);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--color-text-on-light-2);
}
/* Values are right-aligned only where they sit beside their key. Below 640px
   the row is stacked, and a right-aligned block under a left-aligned key is
   just two things not lining up with anything. Left there, right from 640. */
.quick-facts__val {
  color: var(--color-text-on-light);
  text-align: left;
  max-width: none;
  margin: 0;
  /* Same idiom base.css uses on headings. A value that needs two lines would
     otherwise leave one word alone on the second, which against a ragged-left
     right-aligned column reads as a mistake. Balancing splits it evenly.
     Single-line values are unaffected. */
  text-wrap: balance;
}
@media (min-width: 640px) {
  .quick-facts__val { text-align: right; }
}


/* --- 5 · Amenities --------------------------------------------------------
   Grouped exactly as the client's amenities source groups them. Two columns
   of groups on desktop so ten groups don't run as one very long ribbon; the
   groups themselves are never split across columns. */
.amenity-groups {
  margin-block-start: var(--space-lg);
  column-gap: var(--space-xl);
}
@media (min-width: 640px) {
  .amenity-groups { column-count: 2; }
}
.amenity-group {
  break-inside: avoid;
  margin-block-end: var(--space-lg);
}
.amenity-group__title {
  font-size: var(--fs-eyebrow);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--color-text-on-light-2);
  padding-block-end: var(--space-2xs);
  border-block-end: var(--divider-w) solid var(--border-on-light);
}
.amenity-group__list {
  margin-block-start: var(--space-xs);
  display: grid;
  gap: var(--space-3xs);
  font-size: var(--fs-small);
  line-height: var(--lh-small);
  color: var(--color-text-on-light);
}

/* "Not included" is a feature here, not an apology, no wifi and no TV are the
   promise. Struck-through text would frame it as a lack, so it is styled
   identically to every other group and only the heading names it. */
.amenity-group--absent .amenity-group__list { color: var(--color-text-on-light-2); }

/* Optional per-group footnote. One group uses it: the breakfast provisions,
   where "first morning only" and the 48-hour dietary notice have to travel
   with the list or the list overstates what is provided. Set a step quieter
   than the items so it reads as a qualifier on them, not as another item.
   max-width is unset because these sit in a two-column layout already
   narrower than --measure. */
.amenity-group__note {
  margin-block-start: var(--space-xs);
  font-size: var(--fs-small);
  line-height: var(--lh-small);
  color: var(--color-text-on-light-2);
  max-width: none;
}


/* --- 7 · Getting here ----------------------------------------------------- */
.getting-here__list {
  display: grid;
  gap: var(--space-md);
  margin-block-start: var(--space-md);
}
@media (min-width: 640px) {
  .getting-here__list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.getting-here__item {
  border-block-start: var(--divider-w) solid var(--border-on-light);
  padding-block-start: var(--space-xs);
}
.getting-here__time {
  font-size: var(--fs-h3);
  line-height: var(--lh-h3);
  color: var(--color-text-on-light);
}
.getting-here__from {
  font-size: var(--fs-small);
  color: var(--color-text-on-light-2);
  margin-block-start: var(--space-3xs);
}


/* --- 8 · Cross-links to the other two cabins ------------------------------
   Rebuilt 29 Aug 2026. What was here: two empty bordered boxes, each holding a
   cabin name and nothing else, under a highlighted [CLIENT INPUT] line. The
   name alone gives a guest no reason to click, and the marker read as an
   unfinished page. It is now the homepage's cabin-card language, reused: a
   photo, the name, one line about the outlook, an arrow cue.

   Reused deliberately rather than invented. These are the same three cabins
   the homepage cards introduce, so meeting them a second time in a different
   visual language would say they are different things. Everything below
   mirrors .cabin-card in home.css (4:5 media, radius, cover, 1.03 hover
   scale); it is not shared because the whole card is one anchor here, which
   changes the elements and the hover targets.

   The block is a <section> with its own hairline rule, not four flat children
   of .cabin-body. It is the last thing in the story column and it changes
   register: the page stops describing this cabin and starts pointing at the
   other two. The rule and the space are what say so. */
.other-cabins {
  margin-block-start: var(--space-2xl);
  padding-block-start: var(--space-xl);
  border-block-start: var(--divider-w) solid var(--border-on-light);
}
/* .cabin-body h2 carries a --space-2xl top margin for headings in the flow of
   the story. This one is spaced by its section instead, or the two stack. */
.other-cabins .other-cabins__title { margin-block-start: 0; }

.other-cabins__intro {
  color: var(--color-text-on-light-2);
  max-width: 46ch;
  margin-block-start: var(--space-2xs);
}

/* The single-column track is DECLARED, not left implicit. With no
   grid-template-columns an implicit column is sized auto, i.e. max-content,
   and the widest thing in these cards is a 1600px-wide <img>: below 640px the
   column blew out to 1600 and the copy ran off the side of the phone. The old
   text-only boxes never showed it because they had no intrinsic width. The
   minmax(0, …) floor is the same guard the two-column rule already carried. */
.other-cabins__list {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-md);
  margin-block-start: var(--space-lg);
}
@media (min-width: 640px) {
  .other-cabins__list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* The whole card is the anchor: a guest aiming at a photo of a cabin expects
   the photo to be the target. Its children are spans, because flow content is
   invalid inside <a>, so the name is sized by class rather than by tag. */
.other-cabin {
  display: block;
  color: inherit;
}
.other-cabin__media {
  display: block;
  overflow: hidden;
  border-radius: var(--radius);
  aspect-ratio: 4 / 5;
  background-color: var(--color-band-light-alt);
}
.other-cabin__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.other-cabin__body {
  display: block;
  padding-block-start: var(--space-sm);
}
.other-cabin__name {
  display: block;
  /* h1-h3 take Medium 500 in base.css. This is a span, so the weight has to
     be restated or the name renders Regular beside a homepage card title
     that does not. */
  font-weight: var(--fw-medium);
  font-size: var(--fs-h3);
  line-height: var(--lh-h3);
  color: var(--color-text-on-light);
  transition: color var(--dur-fast) var(--ease);
}
.other-cabin__line {
  display: block;
  font-size: var(--fs-small);
  line-height: var(--lh-small);
  color: var(--color-text-on-light-2);
  margin-block-start: var(--space-3xs);
}
/* The arrow cue is styled like .link-arrow but is not one: .link-arrow is an
   <a>, and there is no second anchor inside this card to hang it on. Its
   underline is drawn on the span and lit by hover on the CARD, so the cue
   responds wherever in the card the pointer actually is. */
.other-cabin__cue {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  margin-block-start: var(--space-xs);
  font-size: var(--fs-small);
  font-weight: var(--fw-medium);
  letter-spacing: 0.02em;
  color: var(--color-text-on-light);
  border-block-end: var(--divider-w) solid transparent;
  padding-block-end: 2px;
  transition: color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.other-cabin:hover .other-cabin__name,
.other-cabin:focus-visible .other-cabin__name,
.other-cabin:hover .other-cabin__cue,
.other-cabin:focus-visible .other-cabin__cue { color: var(--color-accent); }
.other-cabin:hover .other-cabin__cue,
.other-cabin:focus-visible .other-cabin__cue { border-block-end-color: var(--color-accent); }

/* The photo lift. Motion only, so it is the one thing behind the guard: the
   colour and underline changes above still fire under reduced motion, and the
   card is never left without a hover state. */
@media (prefers-reduced-motion: no-preference) {
  .other-cabin__media img { transition: transform var(--dur-mid) var(--ease); }
  .other-cabin:hover .other-cabin__media img,
  .other-cabin:focus-visible .other-cabin__media img { transform: scale(1.03); }
}


/* --- 9 · Booking rail ---------------------------------------------------
   At 900px and up the rail is sticky within the two-column grid, so it tracks
   the guest down the left column the way Airbnb's price box does. The offset
   clears the fixed header (--header-h) plus a little breathing room.

   Below 900px the rail is NOT removed. It used to be: the site ships a
   persistent bottom booking bar (.booking-bar in home.css, itself hidden at
   >=900px), and two Book Now CTAs on one phone screen is one too many. That
   held while the rail's only content was a link. It stopped holding when the
   real Lodgify booking box went in, because the box renders into an element
   addressed BY ID (#lodgify-book-now-box) and an id is unique per document.
   There cannot be a desktop instance and a separate mobile one.

   So there is one instance, in the rail, at every width. Below 900px the grid
   is single-column and the rail simply flows inline at the end of the story,
   un-stuck. The bottom bar keeps its place as the always-reachable CTA but
   stops being a second booking entry point: on these pages it is an anchor to
   #book, i.e. to this rail. scroll-margin holds the landing position clear of
   the fixed header. */
/* The anchor target. Static on purpose: #book must not point at the sticky
   element itself. Chrome resolves an anchor jump against the element's CURRENT
   box, and a sticky box slides down as the page scrolls toward it, so the jump
   chases it to the bottom of its sticky range and dumps the guest in the
   footer. Measured on Hunter One at 1440: the jump landed at scrollY 3974 of a
   5053px document, roughly 3.4 screens past the booking box. Anchoring the
   static wrapper lands it correctly. */
.booking-col {
  scroll-margin-block-start: calc(var(--header-h) + var(--space-md));
}

.booking-rail { position: relative; }

@media (min-width: 900px) {
  /* Stretch the wrapper to the full grid row so the sticky rail inside it has
     the whole left column to travel down. Without this the wrapper is only as
     tall as the card and the rail has nowhere to stick to. */
  .booking-col { align-self: stretch; }

  .booking-rail {
    position: sticky;
    inset-block-start: calc(var(--header-h) + var(--space-md));
  }
}

.booking-card {
  border: var(--divider-w) solid var(--border-on-light);
  border-radius: var(--radius);
  padding: var(--space-md);
  background-color: var(--c-base);
}
/* The card's lead line, "Add dates for prices". It replaced a hard-coded
   "$569 a night" and the direct-booking saving line beneath it: with the
   Lodgify box rendering the live rate, the card stated the price twice and the
   hard-coded half was the one that could go stale.

   It keeps the price line's size because it occupies the same slot at the top
   of the card, but not its weight of meaning: it is a prompt, so it takes the
   muted secondary colour rather than full-strength Forest.

   NOTE: removing .booking-card__direct also removed the Copper hairline under
   "$30 less", which was the only place Copper appeared on these pages as
   anything but a hover or an underline. Copper is now hover/focus only here. */
.booking-card__lead {
  font-size: var(--fs-h3);
  line-height: var(--lh-h3);
  color: var(--color-text-on-light-2);
  text-wrap: balance;
}

/* Lodgify book-now-box, themed.

   Lodgify's generated embed ships a <style> block that defines all of these on
   :root as literal hex. Neither half of that is used here. The hex duplicates
   tokens.css by hand (its #2e3628 and #f7f4ee ARE --c-forest and --c-base), and
   :root would leak a dozen vendor variables into every element on the page for
   the benefit of one widget. They are defined once instead, here, scoped to the
   element that needs them, in tokens. Restyling the widget is a token change
   like everything else on this site.

   The block WAS pasted verbatim for a while on 29 Aug 2026, and the client
   compared the two directly and chose this one. See DECISIONS.md. If the
   Lodgify panel is regenerated, check whether it has grown any new --ldg-*
   variables and add them here rather than pasting the block back.

   The box is also stripped of its own card. It sits INSIDE .booking-card, which
   already supplies the border, radius, padding and background, so Lodgify's
   white fill, 54px drop shadow and 3.58em pill button rendered a second card
   floating inside the first, in a button shape that appears nowhere else on the
   site. Background and radius match the card; shadow and padding go. */
.booking-card__widget {
  margin-block-start: var(--space-md);

  --ldg-bnb-background: transparent;
  --ldg-bnb-border-radius: var(--radius);
  --ldg-bnb-box-shadow: none;
  --ldg-bnb-padding: 0px;
  --ldg-bnb-input-background: var(--c-base);
  --ldg-bnb-button-border-radius: var(--radius);

  --ldg-bnb-color-primary: var(--c-forest);
  --ldg-bnb-color-primary-lighter: var(--c-sage);
  --ldg-bnb-color-primary-darker: var(--c-forest-deep);
  --ldg-bnb-color-primary-contrast: var(--c-base);

  /* Calendar. The selection endpoints take the full Forest/off-white pair. The
     in-range cells take Sage, but with FOREST text, not the off-white Lodgify's
     generated block used: off-white on Sage is about 2:1 and fails outright,
     while Forest on Sage is the 5:1 pair tokens.css already documents. */
  --ldg-component-calendar-cell-selection-bg-color: var(--c-forest);
  --ldg-component-calendar-cell-selection-color: var(--c-base);
  --ldg-component-calendar-cell-selected-bg-color: var(--c-sage);
  --ldg-component-calendar-cell-selected-color: var(--c-forest);

  /* Inherit, so the box picks up Montserrat from the page rather than shipping
     a font of its own. This one is verbatim from Lodgify's block. */
  --ldg-bnb-font-family: inherit;
}

/* Lodgify addresses this element by id and renders into it. The id is fixed by
   the vendor script; the class beside it is ours and carries the theming. */
#lodgify-book-now-box { width: 100%; }
.booking-card__note {
  margin-block-start: var(--space-sm);
  font-size: var(--fs-small);
  line-height: var(--lh-small);
  color: var(--color-text-on-light-2);
}

/* Mobile only: keep the last section clear of the fixed bottom bar so the
   footer's final line is never sitting underneath it. */
@media (max-width: 899px) {
  .site-footer {
    padding-block-end: calc(var(--space-3xl) + env(safe-area-inset-bottom, 0px));
  }
}


/* --- 10 · Lightbox --------------------------------------------------------
   Full-screen spotlight. Plain HTML/CSS/JS, no carousel library.

   Each photo is scaled to FIT the viewport at its own native ratio and
   centred, so a landscape frame goes wide and a portrait frame goes tall with
   scrim either side. Nothing is letterboxed to a common ratio, the set is
   deliberately mixed portrait and landscape.

   One exception, at the bottom of this section: below 640px landscape frames
   are cropped 3:2 -> 5:4. On desktop nothing is cropped at all.

   Hidden via the `hidden` attribute when closed, so it is out of the
   accessibility tree and out of the tab order without a display-none
   override. */
.lightbox[hidden] { display: none; }

.lightbox {
  position: fixed;
  inset: 0;
  z-index: calc(var(--z-mobile-menu) + 10);
  display: flex;
  flex-direction: column;
  /* Near-black Forest scrim. color-mix keeps it derived from the Forest token
     rather than a second hard-coded colour; the rgba above it is the fallback
     for engines without color-mix and is the same Forest at the same alpha. */
  background-color: rgba(46, 54, 40, 0.97);
  background-color: color-mix(in srgb, var(--c-forest) 97%, transparent);
  color: var(--color-text-on-dark);
  padding: var(--space-sm);
}

.lightbox__stage {
  position: relative;
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.lightbox__img {
  display: block;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  /* contain by default: the whole frame is visible. The one override is the
     mobile landscape crop at the bottom of this section. */
  object-fit: contain;
  border-radius: var(--radius-sm);
}

.lightbox__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  padding-block-end: var(--space-2xs);
}
.lightbox__count {
  font-size: var(--fs-eyebrow);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--color-text-on-dark-alt);
}
.lightbox__caption {
  margin-block-start: var(--space-2xs);
  text-align: center;
  font-size: var(--fs-small);
  line-height: var(--lh-small);
  color: var(--color-text-on-dark-alt);
  max-width: var(--measure);
  margin-inline: auto;
  min-height: 1.6em;
}

/* Controls, Forest tint on the scrim, off-white glyphs. Hit areas are 44px
   so they clear the touch-target minimum on a phone. */
.lightbox__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border: var(--divider-w) solid var(--border-on-dark);
  border-radius: var(--radius);
  background-color: var(--overlay-on-dark);
  color: var(--color-text-on-dark);
  transition: background-color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease);
}
.lightbox__btn:hover,
.lightbox__btn:focus-visible {
  border-color: var(--color-accent);
  color: var(--color-accent);
}
.lightbox__btn:focus-visible {
  outline: var(--divider-w) solid var(--color-focus);
  outline-offset: 2px;
}
.lightbox__btn svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Prev/next sit vertically centred over the scrim at the stage edges. They are
   inside .lightbox__stage, which is itself the click-to-close scrim target, so
   they stop propagation in JS. */
.lightbox__nav {
  position: absolute;
  inset-block-start: 50%;
  transform: translateY(-50%);
  z-index: 2;
}
.lightbox__nav--prev { inset-inline-start: 0; }
.lightbox__nav--next { inset-inline-end: 0; }

/* Fade in. Under prefers-reduced-motion the opacity fade is all that is kept
   (see the block below) and there is no movement of any kind. */
@media (prefers-reduced-motion: no-preference) {
  .lightbox.is-open { animation: lightbox-in var(--dur-fast) var(--ease-out); }
  .lightbox__img { transition: opacity var(--dur-fast) var(--ease); }
}
@keyframes lightbox-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* The image is swapped by JS with a brief opacity dip. .is-swapping is the
   only state that touches it, and it is a pure fade, no slide, so it is
   safe to keep under reduced motion at a shorter duration. */
.lightbox__img.is-swapping { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  .lightbox.is-open { animation: none; }
  .lightbox__img { transition: opacity var(--dur-fast) var(--ease); }
}

/* Mobile sizing. The spotlight opened far too small on a phone: at 375px a
   3:2 landscape came out 265x176, about 13% of the screen, while the stage
   had 667px of height sitting empty. Two causes, both fixed here.

   First, the stage used to be inset 52px each side so the floating nav
   buttons landed in scrim rather than over the photo. That inset was taking
   28% of the width off every frame, and landscape frames are width-bound, so
   it came straight off their size. The inset is gone and the buttons carry
   their own Forest tint instead, which also covers 480-639px where they were
   already overlaying the photo on an 8% wash.

   Second, a 3:2 frame simply cannot fill a portrait screen. Landscape frames
   only are cropped to 5:4, centred: a 17% trim off the width that buys 68%
   more height. 4:3 was tried first and read as too timid on a real phone.
   Portraits are left alone and still contain, a 2:3 at full width is ~553px
   tall and still fits the stage. `.is-landscape` is set per render by
   cabin-gallery.js. */
@media (max-width: 639px) {
  .lightbox__img.is-landscape {
    width: 100%;
    height: auto;
    aspect-ratio: 5 / 4;
    object-fit: cover;
  }

  /* Same Forest as the scrim at a lower alpha, so the arrows stay legible
     over a bright frame. The rgba above is the fallback for engines without
     color-mix, the same pattern as .lightbox itself. */
  .lightbox__nav {
    background-color: rgba(46, 54, 40, 0.72);
    background-color: color-mix(in srgb, var(--c-forest) 72%, transparent);
  }
}

@media (max-width: 479px) {
  .lightbox__stage { padding-inline: 0; }
  .lightbox__btn { width: 40px; height: 40px; }
}
