/* ==========================================================================
   BOOKALLO — bkx_routes, THE SHELL EVERYWHERE ELSE
   The homepage's visual system, reaching /search, /hotels, hotel detail,
   /plan, the destination pages and every editorial page.

   WHY THIS IS AN ADAPTER AND THE HOMEPAGE WAS A REWRITE
   -----------------------------------------------------
   The homepage's markup is written in HTML files, so it could be replaced.
   These pages are not. A flight result card, a hotel card, a hotel detail
   panel, a planner day and a comparison row are all emitted at RUNTIME by
   janet_live_search.js (178 KB), janet_hotel_render.js (96 KB),
   janet_hotel_card.css's markup contract and janet_plan.js (68 KB). Those four
   files own the money path: the price semantics, the seller attribution, the
   affiliate deep links, the availability truth labels and the booking handoff.

   Rewriting their output to change how it LOOKS would mean re-deriving all of
   that, and the first field missed is a revenue path that fails silently. So
   the renderers keep emitting exactly what they emit, and this sheet dresses
   it. The visual system is the same one; only the mechanism differs, and it
   differs because the risk profile does.

   IT LOADS LAST, ON PURPOSE. These pages carry janet_live_search.css,
   janet_hotels.css, janet_plan.css, janet_hero_v4.css and janet_hotel_card.css
   — the previous system, still holding the layout its renderers assume. This
   sheet is injected after all of them by scripts/build_dist.js and re-points
   what a reader SEES without moving what a renderer READS.

   01 the shell everywhere   04 hotel detail
   02 search + results       05 the planner
   03 hotels                 06 destination + editorial
   ========================================================================== */


/* == 01 · THE SHELL, ON EVERY PAGE ======================================== */

/*
 * The ground first. These pages set their own body background in three
 * different sheets, and a page that is midnight at the top and #0E1116 in the
 * middle is two products with one header.
 */
html, body { background: var(--x-void, #04070C); }
body { color: var(--x-ink, #F5F1E8); }

/* --- the header ---------------------------------------------------------
 * `.hd` is the shared site header on every non-homepage route. It becomes the
 * shell's nav in place: the brand, the links and the actions keep their
 * classes, their hrefs and their order.
 */
.hd {
  position: sticky;
  inset-block-start: 0;
  z-index: 90;
  display: flex;
  align-items: center;
  gap: clamp(14px, 2.4vw, 36px);
  padding: clamp(10px, 1.2vw, 16px) clamp(20px, 4vw, 64px);
  background: rgba(4, 7, 12, .88);
  -webkit-backdrop-filter: blur(22px) saturate(150%);
  backdrop-filter: blur(22px) saturate(150%);
  border-block-end: 1px solid var(--x-line, rgba(240, 236, 226, .10));
  box-shadow: 0 18px 40px -34px rgba(0, 0, 0, .9);
}
.hd .brand, .jp-bar__logo, .jp-bar__home {
  display: flex;
  align-items: center;
  gap: .6rem;
  min-block-size: 44px;
  text-decoration: none;
  color: var(--x-ink, #F5F1E8);
}
.hd .brand-mark {
  display: grid;
  place-items: center;
  inline-size: 34px; block-size: 34px;
  border-radius: 10px;
  background: linear-gradient(150deg, var(--x-gold, #E3BE79), var(--x-gold-deep, #C9A24A));
  color: var(--on-sky, #10131A);
  font-weight: 700;
  box-shadow: 0 18px 50px -20px rgba(227, 190, 121, .4);
}
.hd .brand-name { letter-spacing: .18em; font-weight: 600; }

.hd-nav { display: flex; align-items: center; gap: clamp(12px, 1.8vw, 28px); margin-inline-end: auto; }
.hd-nav a {
  position: relative;
  min-block-size: 44px;
  display: inline-flex;
  align-items: center;
  font-size: .93rem;
  color: var(--x-ink-2, #C3C6CC);
  text-decoration: none;
  transition: color .25s var(--x-ease, cubic-bezier(.22,1,.36,1));
}
.hd-nav a::after {
  content: "";
  position: absolute;
  inset-block-end: 8px;
  inset-inline-start: 0;
  inline-size: 0;
  block-size: 1px;
  background: var(--x-gold, #E3BE79);
  transition: inline-size .3s var(--x-ease, cubic-bezier(.22,1,.36,1));
}
.hd-nav a:hover { color: var(--x-ink, #F5F1E8); }
.hd-nav a:hover::after { inline-size: 100%; }

.hd-actions { display: flex; align-items: center; gap: 10px; }
.hd-el, .hd-lang {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  min-block-size: 44px;
  padding-inline: .8rem;
  border-radius: 999px;
  border: 1px solid var(--x-line-2, rgba(240, 236, 226, .18));
  background: rgba(255, 255, 255, .03);
  color: var(--x-ink-2, #C3C6CC);
  text-decoration: none;
  font-size: .86rem;
  transition: border-color .25s var(--x-ease), color .25s var(--x-ease);
}
.hd-el:hover, .hd-lang:hover { border-color: var(--x-gold-edge, rgba(227,190,121,.42)); color: var(--x-gold, #E3BE79); }
.hd-el__av { inline-size: 26px; block-size: 26px; border-radius: 50%; }

/* --- section rhythm and headings ---------------------------------------- */
.jn-sec { padding-block: clamp(48px, 6.5vh, 96px); position: relative; }
.jn-sec + .jn-sec { border-block-start: 1px solid var(--x-line, rgba(240,236,226,.10)); }
.jn-sec h2, .ls-head h1, .jh-hero h1, .jp-intro h1, .dhero h1,
.jn-head h2, .jhd__name {
  font-family: "Frank Ruhl Libre", Georgia, serif;
  font-weight: 500;
  letter-spacing: -.018em;
  line-height: 1.06;
}

/* --- the footer ---------------------------------------------------------- */
.ft {
  border-block-start: 1px solid var(--x-line, rgba(240,236,226,.10));
  background: var(--x-deep, #070B13);
  padding-block: clamp(32px, 5vh, 60px);
}
.ft a { color: var(--x-ink-2, #C3C6CC); }
.ft a:hover { color: var(--x-gold, #E3BE79); }

/* --- one material for every raised surface on the site -------------------
 * Every card, panel and popover on these routes, in one declaration. The
 * previous system gave each renderer its own ground and its own hairline, so
 * the product read as five components that happened to share a header.
 */
.ls-card, .jhc, .jp-card, .jdh-trip, .ls-prefs, .jh-facets, .ls-cal,
.jhd__book, .jp-cmp, .jp-day, .ls-dh, .jn-guide, .jn-why__i {
  border-radius: var(--x-r, 16px);
  border: 1px solid var(--x-line, rgba(240, 236, 226, .10));
  background: linear-gradient(150deg, rgba(16, 22, 32, .72), rgba(7, 11, 18, .84));
  box-shadow: 0 18px 44px -20px rgba(0, 0, 0, .86);
  color: var(--x-ink, #F5F1E8);
}

/* Every primary action, in one champagne. */
/* `.dhero__cta` is the WRAPPER around a destination hero’s two buttons, not a
   button — painting it champagne produced a full-width gold bar with the real
   button sitting inside it. The buttons are `.btn` and `.ghost`. */
.ls-btn, .ls-card__cta, .jhc__cta, .jh-go, .jp-btn, .jn-cta,
.dhero__cta > .btn, .ghero__cta > .btn,
.jhd__book .ls-btn, #jh-go, #ls-go {
  border-radius: 999px;
  background: linear-gradient(160deg, var(--x-gold, #E3BE79), var(--x-gold-deep, #C9A24A));
  color: var(--on-sky, #10131A);
  border: 0;
  font-weight: 700;
  min-block-size: 46px;
  box-shadow: 0 18px 50px -20px rgba(227, 190, 121, .4);
  transition: transform .2s var(--x-ease), box-shadow .2s var(--x-ease);
}
.ls-btn:hover, .ls-card__cta:hover, .jh-go:hover, .jp-btn:hover,
.jn-cta:hover, .dhero__cta > .btn:hover { transform: translateY(-2px); }
.ls-btn:active, .jh-go:active, .jp-btn:active { transform: translateY(1px) scale(.99); }

/* Every price, in one champagne, in tabular figures so a column of them
   lines up rather than shimmering. */
.ls-card__price, .jhc__amount, .jhd__book-ppn, .jp-cmp__price,
.ls-dh__price, .jp-ledger__v, .jp-meter__head b {
  color: var(--x-gold, #E3BE79);
  font-variant-numeric: tabular-nums;
}

/* Focus, once, for the whole site, and never removed. */
:focus-visible { outline: 2px solid var(--x-gold, #E3BE79); outline-offset: 3px; }


/*
 * THE GLASS LIST, RE-AIMED.
 *
 * janet_premium.css publishes a cream glass for the surfaces that float over
 * content — `.jp-glass, .hd.scrolled, .sticky-search, .ls-bar, .bottom-nav,
 * .mnav` — and, separately, `body:has(.dhero__img) .hd`, added deliberately
 * because a transparent header measured failing over a destination hero.
 *
 * Both were right for a cream page and both outrank a bare `.hd`: (0,2,0) and
 * (0,2,1) against (0,1,0). Measured on /tisot-zolot-bud, the header rendered
 * rgba(251,250,248,.82) — a cream bar above a midnight page, on all eleven
 * destination pages, while every other route had the dark one.
 *
 * Matched here at the same specificity rather than with !important, so the
 * reason it wins is the cascade and not a hammer.
 */
body:has(.dhero__img) .hd,
.hd.scrolled,
.jp-glass, .sticky-search, .bottom-nav, .mnav {
  background: rgba(4, 7, 12, .88);
  -webkit-backdrop-filter: blur(22px) saturate(150%);
  backdrop-filter: blur(22px) saturate(150%);
  border-block-end-color: var(--x-line, rgba(240, 236, 226, .10));
  border-color: var(--x-line, rgba(240, 236, 226, .10));
}
@supports not (backdrop-filter: blur(1px)) {
  body:has(.dhero__img) .hd, .hd.scrolled { background: rgba(4, 7, 12, .97); }
}

/* == 01b · THE PALETTES THE :root RE-POINT CANNOT REACH ===================
 *
 * THE MECHANISM janet-visual-system.md DESCRIBES HAS ONE BLIND SPOT, and it
 * cost fifty-two cards on /hotels.
 *
 * The rule is: a component declares its palette at `:where()` — zero
 * specificity — and the theme re-points it at `:root`. janet_hotel_card.css
 * obeys that rule exactly. It still lost, because ZERO SPECIFICITY IS NOT THE
 * SAME AS NOT BEING THERE: the declaration sits on `.jhc` itself, and a custom
 * property declared on an element always beats the same property INHERITED
 * from :root, whatever the specificity of either selector. Specificity only
 * decides between declarations on the SAME element.
 *
 * Measured on the live page: `--jhc-sand` read #111316 at :root and #F7F0E7
 * inside every card. So the theme was correct, the component was correct, and
 * the page was cream anyway.
 *
 * The fix is to re-point on the component's own element, which is what this
 * block does. It is deliberately a MAPPING onto the shell's `--x-*` tokens
 * rather than a set of literals, so there is still exactly one place where the
 * midnight and the champagne are defined.
 *
 * Only palettes that declare COLOUR this way are listed. `.ls`, `.jp` and
 * `.jb` declare geometry and semantic status colours locally and read their
 * surfaces from the base tokens, which the :root re-point does reach.
 */
.jhc, .jhf, .jhd {
  --jhc-ink:        var(--x-ink, #F5F1E8);
  --jhc-ink-2:      var(--x-ink-2, #C3C6CC);
  --jhc-mute:       var(--x-ink-3, #8E939C);
  --jhc-line:       var(--x-line, rgba(240,236,226,.10));
  --jhc-line-2:     var(--x-line, rgba(240,236,226,.10));
  --jhc-card:       var(--x-raised, #0C1119);
  --jhc-coral:      var(--x-gold, #E3BE79);
  --jhc-coral-ink:  var(--x-gold, #E3BE79);
  --jhc-coral-soft: var(--x-gold-wash, rgba(227,190,121,.12));
  /*
   * --jhc-navy and --jhc-teal each do TWO jobs in janet_hotel_card.css: they
   * paint a chip BEHIND white text (.jhc__score-n, .jhd__score b, .jhc__flag)
   * and they colour TEXT (.jhc__type, .jhc__zone, .jhd__fac li::before).
   * One value cannot serve both once the theme inverts — mapped to a light
   * ink they made #fff invisible on the review score, which is the number
   * every reader looks at first. They stay DARK SURFACES here, and the three
   * text usages are re-coloured below.
   */
  --jhc-navy:       #22303F;
  --jhc-gold:       var(--x-gold, #E3BE79);
  --jhc-sand:       var(--x-deep, #070B13);
  /* The two verdict colours stay GREEN and stay distinguishable. A palette
     sweep that painted "free cancellation" champagne would have removed the
     one signal on the card that is not decoration. */
  --jhc-teal:       #1F6B4A;
  --jhc-good:       #57C08A;
  --jhc-good-bg:    rgba(87, 192, 138, .12);
}
.ed {
  --ed-cream:   var(--x-deep, #070B13);
  --ed-sand:    var(--x-raised, #0C1119);
  --ed-ink:     var(--x-ink, #F5F1E8);
  --ed-ink-2:   var(--x-ink-2, #C3C6CC);
  --ed-mute:    var(--x-ink-3, #8E939C);
  --ed-coral:   var(--x-gold, #E3BE79);
  --ed-coral-t: var(--x-gold-deep, #C9A24A);
  --ed-line:    var(--x-line, rgba(240,236,226,.10));
}
/*
 * Two more component-scoped palettes with BARE LITERALS, found by the new
 * assertion in test_one_visual_system.js rather than by eye. Both are status
 * colours picked for a cream page: an error at #a11b1b and a budget bar at
 * #2E6F4E are legible on paper and nearly invisible on midnight. They are text
 * and small fills, so the conformance sweep — which looks for light SURFACES
 * and white-on-white — could not have caught them.
 *
 * The ROLE survives the re-point: green still means within budget, amber still
 * means over, red still means wrong. Only the lightness changes, because the
 * ground did.
 */
.jb {
  --jb-warn: #E0A44A;
  --jb-bad:  #F0776A;
  --jb-good: #57C08A;
}
.jp {
  --jp-ok:   #57C08A;
  --jp-warn: #E08A4A;
}
.ls {
  /* Status washes, not surfaces: the light backgrounds behind "cheapest" and
     "price is older" are pale panels designed for a cream page. */
  --ls-cheap:    #57C08A;
  --ls-cheap-bg: rgba(87, 192, 138, .12);
  --ls-warn:     #E0A44A;
  --ls-warn-bg:  rgba(224, 164, 74, .12);
  --ls-stale:    #C08F5A;
}


/* == 02 · SEARCH — SPATIAL FLIGHT RESULTS ================================= */

/*
 * The reference's flight page is a lit aviation environment with the results
 * layered in front of it. The environment is a fixed, very dark gradient scene
 * rather than a photograph: this page can hold sixty result cards, and a
 * full-bleed image behind a long scrolling list is a repaint on every frame of
 * that scroll for a picture nobody can see through the results anyway.
 */
.ls-main, body.ls-page { position: relative; }
.bkx-sky {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(120% 80% at 50% -10%, rgba(58, 96, 150, .30), transparent 62%),
    radial-gradient(90% 60% at 80% 8%, rgba(227, 190, 121, .12), transparent 60%),
    linear-gradient(180deg, #071019 0%, #04070C 58%);
}
/* One slow band of light across the top of the frame, the horizon. */
.bkx-sky::after {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  block-size: 62vh;
  background: linear-gradient(102deg, transparent 42%, rgba(255, 246, 226, .07) 50%, transparent 58%);
  background-size: 280% 100%;
}
html.js .bkx-sky::after { animation: bkx-sheen 24s linear infinite; }

/* --- the search bar becomes the console --------------------------------- */
.ls-bar {
  position: sticky;
  inset-block-start: var(--x-nav-h, 62px);
  z-index: 40;
  border-radius: 0 0 var(--x-r-lg, 22px) var(--x-r-lg, 22px);
  border: 1px solid var(--x-line-2, rgba(240,236,226,.18));
  border-block-start: 0;
  background: linear-gradient(180deg, rgba(31, 34, 40, .90), rgba(13, 15, 18, .95));
  -webkit-backdrop-filter: blur(20px) saturate(150%);
  backdrop-filter: blur(20px) saturate(150%);
  box-shadow: 0 26px 60px -30px rgba(0, 0, 0, .95), 0 0 0 1px rgba(227, 190, 121, .08);
}
.ls-bar::before {
  content: "";
  position: absolute;
  inset-block-start: -1px;
  inset-inline: 14%;
  block-size: 1px;
  background: linear-gradient(90deg, transparent, rgba(227, 190, 121, .7), transparent);
  pointer-events: none;
}
.ls-field { transition: background-color .28s var(--x-ease), box-shadow .28s var(--x-ease); }
.ls-field:focus-within { background: rgba(227, 190, 121, .05); box-shadow: inset 0 -2px 0 var(--x-gold, #E3BE79); }
.ls-field input, .ls-field select { min-block-size: 46px; box-sizing: border-box; }

/* --- the results, in depth ----------------------------------------------
 * `--i` is the card's index, written once by bkx_routes.js. Each card enters
 * from a little further back, so the list ARRIVES rather than appearing, and
 * the stagger is capped so the twentieth card is not still dealing itself out
 * while the reader is reading the third.
 */
.ls-list { perspective: 1400px; }
.ls-card {
  position: relative;
  transform-origin: 50% 60%;
  transition:
    transform .42s var(--x-ease, cubic-bezier(.22,1,.36,1)),
    box-shadow .42s var(--x-ease),
    border-color .3s var(--x-ease);
}
html.js .ls-list.bkx-in .ls-card {
  animation: bkx-card-in .72s var(--x-ease-2, cubic-bezier(.16,1,.3,1)) both;
  animation-delay: calc(min(var(--i, 0) * 55ms, 520ms));
}
@keyframes bkx-card-in {
  from { opacity: 0; transform: translate3d(0, 26px, -120px) rotateX(6deg); }
  to   { opacity: 1; transform: none; }
}
.ls-card:hover {
  transform: translate3d(0, -3px, 26px);
  border-color: var(--x-gold-edge, rgba(227,190,121,.42));
  box-shadow: 0 30px 66px -26px rgba(0, 0, 0, .95);
}
/* The chosen offer comes FORWARD, and it is the only card that does. */
.ls-card[data-bkx-picked] {
  z-index: 3;
  transform: translate3d(0, -6px, 44px);
  border-color: var(--x-gold-edge, rgba(227,190,121,.42));
  box-shadow:
    0 34px 76px -26px rgba(0, 0, 0, .98),
    0 0 0 1px rgba(227, 190, 121, .34),
    0 18px 50px -20px rgba(227, 190, 121, .35);
}

/* --- the route line ------------------------------------------------------
 * Drawn between the two airport codes the card already prints. It is a
 * DECORATION over data the card owns: no time, no distance, no stop count is
 * invented here, and a card with one stop still says so in its own words.
 */
.ls-card__route { position: relative; }
.bkx-route {
  position: relative;
  block-size: 1px;
  margin-block: .5rem;
  background: linear-gradient(90deg,
    rgba(227, 190, 121, 0), var(--x-gold-edge, rgba(227,190,121,.42)) 18%,
    var(--x-gold-edge, rgba(227,190,121,.42)) 82%, rgba(227, 190, 121, 0));
  transform-origin: center;
  scale: 0 1;
  transition: scale .9s var(--x-ease-2, cubic-bezier(.16,1,.3,1));
}
html.js .ls-list.bkx-in .bkx-route { scale: 1 1; }
.bkx-route::after {
  content: "";
  position: absolute;
  inset-block-start: -3px;
  inset-inline-start: 0;
  inline-size: 7px; block-size: 7px;
  border-radius: 50%;
  background: var(--x-gold, #E3BE79);
  box-shadow: 0 0 10px rgba(227, 190, 121, .8);
  opacity: 0;
}
@keyframes bkx-route-fly {
  from { transform: translateX(0); opacity: 0; }
  14%  { opacity: 1; }
  86%  { opacity: 1; }
  to   { transform: translateX(var(--bkx-route-w, 200px)); opacity: 0; }
}
html.js .ls-list.bkx-in .ls-card:hover .bkx-route::after {
  animation: bkx-route-fly 1.4s var(--x-ease, cubic-bezier(.22,1,.36,1)) 1;
}

/* --- filters, in the same material -------------------------------------- */
.ls-prefs, .jdh-filters, .jh-facets {
  border-radius: var(--x-r, 16px);
  overflow: hidden;
}
.ls-prefs__body, .jh-facets__body {
  transition: max-block-size .45s var(--x-ease-2, cubic-bezier(.16,1,.3,1)), opacity .3s linear;
}
.ls-chip, .jh-chip, .jp-chip, .ls-prefs__chips span {
  border-radius: 999px;
  border: 1px solid var(--x-line-2, rgba(240,236,226,.18));
  background: rgba(255, 255, 255, .03);
  transition: border-color .25s var(--x-ease), background-color .25s var(--x-ease), color .25s var(--x-ease);
}
.ls-chip:hover, .jh-chip:hover, .jp-chip:hover {
  border-color: var(--x-gold-edge, rgba(227,190,121,.42));
  color: var(--x-gold, #E3BE79);
}
.ls-chip[aria-pressed="true"], .jp-chip[aria-pressed="true"], .jp-chip.is-on {
  background: var(--x-gold-wash, rgba(227,190,121,.12));
  border-color: var(--x-gold-edge, rgba(227,190,121,.42));
  color: var(--x-gold, #E3BE79);
}

/* Sorting and filtering must not FLASH. The list fades and settles rather
   than blanking, which is what makes a re-sort read as the same list moving. */
.ls-list.bkx-settling { opacity: .35; transition: opacity .16s linear; }
.ls-list { transition: opacity .32s var(--x-ease, cubic-bezier(.22,1,.36,1)); }



/* == 02b · SEARCH — THE SURFACE THE READER ACTUALLY LOOKS AT ==============
 *
 * MEASURED, not assumed: on /search the full seventeen-card `.ls-list` sits
 * inside `#jdh-all-flights`, which ships `hidden` behind a toggle. What the
 * page actually shows is `janet_deal_hunter.js`'s three chosen flights, three
 * chosen hotels and three packages — the `.jdh-*` markup. The first version of
 * this sheet dressed only `.ls-card`, so every rule in it applied to elements
 * with a zero-height box and the page a reader sees was left untouched.
 *
 * So this is the /search scene. The `.ls-*` rules above stay what dress the
 * full list once the reader opens it.
 */

/* --- each block of three is a SCENE, not a row of boxes ------------------ */
.jdh { position: relative; margin-block: clamp(3rem, 7vw, 6rem); }
.jdh__head { margin-block-end: clamp(1.2rem, 2.4vw, 2rem); }
.jdh__h {
  font-family: var(--x-display, "Frank Ruhl Libre", Georgia, serif);
  font-size: clamp(1.55rem, 3vw, 2.5rem);
  font-weight: 500;
  line-height: 1.12;
  letter-spacing: -.01em;
  color: var(--x-ink, #F5F1E8);
}
.jdh__sub { color: var(--x-ink-3, #8E939C); max-inline-size: 62ch; }

/*
 * THE THREE CARDS STAND IN SPACE. `--i` is the card's position, written by
 * bkx_routes.js, and the depth each one enters from is its index — so they
 * land front to back rather than all at once. The stagger is capped because
 * three is the normal case and a fourth must not feel late.
 */
.jdh__grid { perspective: 1500px; perspective-origin: 50% 34%; }
.jdh-card {
  position: relative;
  border-radius: var(--x-r-lg, 22px);
  border: 1px solid var(--x-line, rgba(240,236,226,.10));
  background: linear-gradient(158deg, rgba(16, 22, 32, .78), rgba(7, 11, 18, .90));
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  backdrop-filter: blur(14px) saturate(140%);
  box-shadow: 0 30px 68px -32px rgba(0, 0, 0, .95);
  transform-origin: 50% 62%;
  transition:
    transform .44s var(--x-ease, cubic-bezier(.22,1,.36,1)),
    box-shadow .44s var(--x-ease),
    border-color .3s var(--x-ease);
}
html.js .jdh__grid .jdh-card { opacity: 0; }
html.js .jdh__grid.bkx-in .jdh-card {
  animation: bkx-card-in .8s var(--x-ease-2, cubic-bezier(.16,1,.3,1)) both;
  animation-delay: calc(min(var(--i, 0) * 120ms, 600ms));
}
.jdh-card:hover {
  transform: translate3d(0, -4px, 30px);
  border-color: var(--x-gold-edge, rgba(227,190,121,.42));
  box-shadow: 0 38px 82px -28px rgba(0, 0, 0, .98);
}

/*
 * TWO DIFFERENT KINDS OF FORWARD, and they must not be confused.
 *
 * `--pick` is BOOKALLO's choice — a standing editorial claim, so it carries a
 * champagne edge and a small permanent lift.
 * `[data-jdh-on]` is the READER's choice, set by janet_deal_hunter.js when they
 * press "בחר את הטיסה הזו". That is the one that comes furthest forward. It is
 * the product's own selection state; nothing here invents a second one.
 */
.jdh__grid > .jdh-card { block-size: 100%; }
.jdh-card--pick {
  border-color: var(--x-gold, #E3BE79);
  transform: translateZ(18px) scale(1.015);
  box-shadow:
    0 36px 80px -28px rgba(0, 0, 0, .97),
    0 0 0 1px rgba(227, 190, 121, .26),
    0 16px 46px -22px rgba(227, 190, 121, .28);
}
.jdh-card--pick::before {
  content: "";
  position: absolute;
  inset-block-start: -1px;
  inset-inline: 18%;
  block-size: 1px;
  background: linear-gradient(90deg, transparent, rgba(227, 190, 121, .85), transparent);
  pointer-events: none;
}
.jdh-card[data-jdh-on] {
  z-index: 3;
  transform: translate3d(0, -8px, 52px);
  border-color: var(--x-gold, #E3BE79);
  box-shadow:
    0 40px 90px -26px rgba(0, 0, 0, .98),
    0 0 0 1px rgba(227, 190, 121, .40),
    0 20px 56px -22px rgba(227, 190, 121, .34);
}

/* --- the badges, the facts, the price ----------------------------------- */
.jdh-role, .jdh-chip, .jdh-fresh, .jdh-rate, .jdh-stars {
  border-radius: 999px;
  border: 1px solid var(--x-line-2, rgba(240,236,226,.18));
  background: rgba(255, 255, 255, .03);
  color: var(--x-ink-2, #C3C6CC);
}
.jdh-role--pick {
  background: linear-gradient(160deg, var(--x-gold, #E3BE79), var(--x-gold-deep, #C9A24A));
  border-color: transparent;
  color: var(--x-on-gold, #10131A);
  font-weight: 700;
}
.jdh-fresh--live { color: var(--x-gold, #E3BE79); border-color: var(--x-gold-edge, rgba(227,190,121,.42)); }
.jdh-card__air { color: var(--x-ink, #F5F1E8); font-weight: 600; }
.jdh-leg__t { color: var(--x-ink, #F5F1E8); font-variant-numeric: tabular-nums; }
.jdh-leg__tag, .jdh-leg__d, .jdh-facts, .jdh-price__note { color: var(--x-ink-3, #8E939C); }
.jdh-price__total, .jdh-pkg__total {
  color: var(--x-gold, #E3BE79);
  font-family: var(--x-display, "Frank Ruhl Libre", Georgia, serif);
  font-size: clamp(1.6rem, 2.6vw, 2.15rem);
  font-variant-numeric: tabular-nums;
  line-height: 1.06;
}
/* An unknown fact stays visibly unknown rather than borrowing the confident
   colour of the ones beside it. */
.jdh-unk { color: var(--x-ink-3, #8E939C); font-style: italic; }
.jdh-why { color: var(--x-ink-2, #C3C6CC); }
.jdh-why strong { color: var(--x-ink, #F5F1E8); }

/* --- the two actions ----------------------------------------------------- */
.jdh-cta {
  border-radius: 999px;
  background: linear-gradient(160deg, var(--x-gold, #E3BE79), var(--x-gold-deep, #C9A24A));
  color: var(--x-on-gold, #10131A);
  font-weight: 700;
  border: 0;
  transition: transform .22s var(--x-ease), box-shadow .22s var(--x-ease);
}
.jdh-cta:hover { transform: translateY(-2px); box-shadow: 0 16px 34px -14px rgba(227,190,121,.6); }
.jdh-select {
  border-radius: 999px;
  border: 1px solid var(--x-line-2, rgba(240,236,226,.18));
  background: transparent;
  color: var(--x-ink-2, #C3C6CC);
  transition: border-color .25s var(--x-ease), color .25s var(--x-ease), background-color .25s var(--x-ease);
}
.jdh-select:hover { border-color: var(--x-gold-edge, rgba(227,190,121,.42)); color: var(--x-gold, #E3BE79); }
.jdh-select[data-jdh-on] {
  background: var(--x-gold-wash, rgba(227,190,121,.12));
  border-color: var(--x-gold, #E3BE79);
  color: var(--x-gold, #E3BE79);
}
.jdh-cta--off { opacity: .55; }

/*
 * THE PHOTO WELL — and the case where there is no photograph.
 *
 * Hotel pictures come from the provider's CDN and some of them do not load.
 * An empty dark rectangle where a photograph should be reads as a broken page;
 * "we have no picture of this property" and "this page is broken" must not look
 * the same. So the well carries a designed plate UNDER the image: when the
 * image loads it covers the plate completely, and when it fails bkx_routes.js
 * removes the <img> and the plate is what the reader sees.
 */
.jdh-card__ph {
  position: relative;
  overflow: hidden;
  border-radius: var(--x-r, 16px) var(--x-r, 16px) 0 0;
  background:
    radial-gradient(120% 90% at 30% 0%, rgba(58, 96, 150, .30), transparent 62%),
    linear-gradient(160deg, #0D1420, #070B13);
}
.jdh-card__ph::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, transparent 42%, rgba(4, 7, 12, .72));
  pointer-events: none;
}
.jdh-card__ph[data-bkx-nophoto]::before {
  content: "";
  position: absolute;
  inset-block-end: 14px;
  inset-inline-start: 14px;
  inline-size: 26px;
  block-size: 1px;
  background: var(--x-gold-edge, rgba(227,190,121,.42));
}
.jdh-card__ph img {
  position: relative;
  z-index: 1;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  transition: transform 1.2s var(--x-ease, cubic-bezier(.22,1,.36,1));
}
.jdh-card:hover .jdh-card__ph img { transform: scale(1.04); }
.ls-hotel-card__gal { z-index: 2; }

/* --- the trip the reader is assembling, as a floating control surface ---- */
.jdh-trip {
  position: sticky;
  inset-block-end: 12px;
  z-index: 45;
  border-radius: var(--x-r-lg, 22px);
  border: 1px solid var(--x-line-2, rgba(240,236,226,.18));
  background: linear-gradient(180deg, rgba(31, 34, 40, .92), rgba(13, 15, 18, .96));
  -webkit-backdrop-filter: blur(20px) saturate(150%);
  backdrop-filter: blur(20px) saturate(150%);
  box-shadow: 0 34px 74px -30px rgba(0, 0, 0, .96), 0 0 0 1px rgba(227, 190, 121, .10);
}
.jdh-trip__total { color: var(--x-gold, #E3BE79); font-variant-numeric: tabular-nums; }

/* --- the top deal panel -------------------------------------------------- */
.ls-deal-hero, .ls-dh {
  border-radius: var(--x-r-lg, 22px);
  border: 1px solid var(--x-gold-edge, rgba(227,190,121,.42));
  background: linear-gradient(158deg, rgba(20, 27, 39, .82), rgba(7, 11, 18, .92));
  box-shadow: 0 36px 82px -30px rgba(0, 0, 0, .96);
}
.ls-dh__total-price {
  font-family: var(--x-display, "Frank Ruhl Libre", Georgia, serif);
  font-size: clamp(2rem, 4.4vw, 3.4rem);
  line-height: 1.02;
}

/* --- the panels below, in one material ---------------------------------- */
.ls-sum, .ls-save, .ls-trust, .ls-sellers, .ls-cal, .ls-flex, .ls-janet, .jdh-break {
  border-radius: var(--x-r, 16px);
  border: 1px solid var(--x-line, rgba(240,236,226,.10));
  background: linear-gradient(158deg, rgba(16, 22, 32, .62), rgba(7, 11, 18, .80));
}
.ls-sum__v, .ls-trust__v { color: var(--x-ink, #F5F1E8); }
.ls-sum__k, .ls-trust__k, .ls-trust__note { color: var(--x-ink-3, #8E939C); }

@media (max-width: 860px) {
  /* Three cards side by side become three you swipe between. The perspective
     goes with them: a rotated card at 380px costs legibility for an effect
     nobody can see at that size. */
  .jdh__grid { perspective: none; }
  .jdh-card--pick, .jdh-card[data-jdh-on] { transform: none; }
  .jdh-card[data-jdh-on] { border-color: var(--x-gold, #E3BE79); }
}


/* == 03 · HOTELS — DEPTH, NOT A CAROUSEL ================================= */

/*
 * THIS WAS A RAIL, AND THE RAIL WAS WRONG. Kept as a note rather than quietly
 * replaced, because the reasoning is the part worth not repeating.
 *
 * The homepage's destination strip is a curated handful you browse, so a
 * horizontal rail with the cards turned in space is right there. `/hotels`
 * returned FIFTY properties for one Budapest search — a comparison surface
 * somebody filters, sorts and scans. Forcing that into a one-card-at-a-time
 * carousel hides forty-nine results behind a swipe, and the screenshot showed
 * the second cost too: `.jhc` lays its photograph and its facts out side by
 * side, so at a fixed 340px the text column collapsed to about one character
 * per line and every card became an unreadable vertical ribbon.
 *
 * So the grid stays a grid, and the depth comes from the axis a grid has going
 * spare: cards arrive from behind the screen, the one under the pointer comes
 * forward, and the photograph moves inside its own frame. Visibly spatial;
 * still fifty results you can compare.
 */
#ls-hotel-grid, .jh-grid { perspective: 1600px; perspective-origin: 50% 30%; }

.jhc {
  position: relative;
  border-radius: var(--x-r-lg, 22px);
  border: 1px solid var(--x-line, rgba(240,236,226,.10));
  background: linear-gradient(158deg, rgba(16, 22, 32, .74), rgba(7, 11, 18, .88));
  box-shadow: 0 26px 60px -32px rgba(0, 0, 0, .94);
  transform-origin: 50% 64%;
  transition:
    transform .42s var(--x-ease, cubic-bezier(.22,1,.36,1)),
    box-shadow .42s var(--x-ease),
    border-color .3s var(--x-ease);
}
.jhc:hover, .jhc:focus-within {
  transform: translate3d(0, -5px, 40px);
  z-index: 3;
  border-color: var(--x-gold-edge, rgba(227,190,121,.42));
  box-shadow:
    0 38px 84px -28px rgba(0, 0, 0, .98),
    0 0 0 1px rgba(227, 190, 121, .22);
}

/*
 * The arrival. `--i` is written by bkx_routes.js and capped hard: fifty cards
 * at even 40ms each is two seconds of dealing, and the reader is looking at
 * the first row the whole time. Past the twelfth card they simply appear —
 * they are below the fold, and nobody watches an animation they cannot see.
 */
html.js #ls-hotel-grid.bkx-in .jhc,
html.js .jh-grid.bkx-in .jhc {
  animation: bkx-card-in .7s var(--x-ease-2, cubic-bezier(.16,1,.3,1)) both;
  animation-delay: calc(min(var(--i, 0) * 45ms, 540ms));
}

/* The photograph lives in its own frame and moves inside it. */
.jhc__media {
  position: relative;
  overflow: hidden;
  border-radius: var(--x-r, 16px) var(--x-r, 16px) 0 0;
  background: linear-gradient(160deg, #0D1420, #070B13);
}
.jhc__media img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  transition: transform .8s var(--x-ease, cubic-bezier(.22,1,.36,1));
}
.jhc:hover .jhc__media img { transform: scale(1.05); }
/* Same rule as the picks: a supplier photograph that never arrives leaves a
   designed plate, not a hole. */
.jhc__media[data-bkx-nophoto]::before {
  content: "";
  position: absolute;
  inset-block-end: 14px;
  inset-inline-start: 14px;
  inline-size: 26px;
  block-size: 1px;
  background: var(--x-gold-edge, rgba(227,190,121,.42));
}

/* The hotels hero keeps its photograph and takes the shell's grade. */
.jh-hero { position: relative; isolation: isolate; }
.jh-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    linear-gradient(180deg, rgba(4,7,12,.72) 0%, rgba(4,7,12,.18) 30%, rgba(4,7,12,.92) 100%),
    radial-gradient(110% 70% at 50% 40%, transparent 24%, rgba(4,7,12,.72) 100%);
}
.jh-hero > * { position: relative; z-index: 2; }

/* The bands and the facet rail in the shell's material. */
.jh-band--sand, .jh-band--soft { background: transparent; }
.jh-aside, .jh-sortbar, .jh-summary, .jh-promise, .jh-guides, .jh-notices {
  border-radius: var(--x-r, 16px);
  border: 1px solid var(--x-line, rgba(240,236,226,.10));
  background: linear-gradient(158deg, rgba(16, 22, 32, .60), rgba(7, 11, 18, .78));
}
.jh-rhead__h { font-family: var(--x-display, "Frank Ruhl Libre", Georgia, serif); }

@media (max-width: 860px) {
  #ls-hotel-grid, .jh-grid { perspective: none; }
  .jhc:hover, .jhc:focus-within { transform: none; }
}



/* --- FOUR LIGHT-THEME SURVIVORS, MEASURED ON THE LIVE PAGE ---------------
 *
 * The `--jhc-*` palette IS re-pointed by janet_premium.css and resolves
 * correctly here (`--jhc-sand` reads #111316). These four are the two ways a
 * component still resists a theme, both of which janet-visual-system.md names,
 * and all four were live on 52 cards at once:
 *
 *  1. A LITERAL no stylesheet can reach.
 *     `.jhc__media--none` paints #FFF6EC → #EFE3D4 → the "no photograph from
 *     the supplier" plate was a cream card on a midnight page. It is the most
 *     common state in this result set, so the page's dominant surface was the
 *     one surface the theme could not touch.
 *     `.jhc__details` and `.jhf__g` are the same defect: `background: #fff`.
 *
 *  2. A TOKEN THAT INVERTED.
 *     `.jhc .ls-card__cta--check` sets `background: var(--jhc-ink)` with
 *     `color: #FFF6EA`. Under the light theme --jhc-ink is #241C16 and that is
 *     near-white on near-black. Re-pointed, --jhc-ink IS the light text colour
 *     (#F5F3EF) — so the button became #FFF6EA on #F5F3EF, which is text you
 *     cannot see, on the control that checks whether a room is available.
 *
 * Repaired here rather than in janet_hotel_card.css because that sheet is
 * still the light-theme source of truth for anything that renders it without
 * the shell, and because the shell is what has to be true everywhere.
 */
.jhc__media--none, .ls-hotel-card__ph--none {
  background:
    radial-gradient(120% 90% at 30% 0%, rgba(58, 96, 150, .28), transparent 62%),
    linear-gradient(160deg, #0D1420, #070B13);
  color: var(--x-ink-3, #8E939C);
}
.jhc__media-fb {
  background: linear-gradient(160deg, var(--x-gold, #E3BE79), var(--x-gold-deep, #C9A24A));
  color: var(--x-on-gold, #10131A);
}
.jhc__details, .jhf__g {
  background: linear-gradient(158deg, rgba(16, 22, 32, .70), rgba(7, 11, 18, .86));
}
.jhc__details {
  border-color: var(--x-line-2, rgba(240,236,226,.18));
  color: var(--x-ink-2, #C3C6CC);
  transition: border-color .22s var(--x-ease), color .22s var(--x-ease), background-color .22s var(--x-ease);
}
.jhc__details:hover {
  background: var(--x-gold-wash, rgba(227,190,121,.12));
  border-color: var(--x-gold-edge, rgba(227,190,121,.42));
  color: var(--x-gold, #E3BE79);
}
/*
 * "Check availability" answers in place; "book" hands the reader off. The two
 * must not look identical — janet_hotel_card.css says so and it is right — so
 * this stays the SECONDARY of the pair: an outline against the champagne fill
 * the booking action carries.
 */
.jhc .ls-card__cta--check {
  background: transparent;
  border: 1px solid var(--x-line-2, rgba(240,236,226,.18));
  color: var(--x-ink-2, #C3C6CC);
}
.jhc .ls-card__cta--check:hover {
  background: var(--x-gold-wash, rgba(227,190,121,.12));
  border-color: var(--x-gold-edge, rgba(227,190,121,.42));
  color: var(--x-gold, #E3BE79);
}
/*
 * The price rail — the fifth literal, and the one the background-COLOUR sweep
 * missed because it is painted with a gradient. `linear-gradient(#FFFDFB,
 * #FDF6EF)` is a cream column beside a midnight card, and it holds the price,
 * which makes it the most looked-at surface on the page.
 *
 * Specificity here is `.jhc .jhc__rail` because the sheet declares it that
 * way; a single-class rule loses to it even loading later.
 */
.jhc .jhc__rail {
  background: linear-gradient(180deg, rgba(20, 27, 39, .70), rgba(7, 11, 18, .86));
  border-color: var(--x-line, rgba(240,236,226,.10));
}
.jhc__ppn, .jhc__conf, .ls-hotel-card__conf { color: var(--x-ink-3, #8E939C); }
/* The three places those two tokens are TEXT rather than a surface. */
.jhc__type { color: var(--x-ink-2, #C3C6CC); background: rgba(240, 236, 226, .06); }
.jhc__zone { color: #57C08A; background: rgba(87, 192, 138, .12); }
.jhd__fac li::before { color: #57C08A; }

/* The detail panel and its cards. Same two failure modes as the result cards,
   on the surface a reader reaches just before booking. */
.jhd { background: var(--x-deep, #070B13); color: var(--x-ink, #F5F1E8); }
.jhd__s, .jhr {
  background: linear-gradient(158deg, rgba(16, 22, 32, .70), rgba(7, 11, 18, .86));
  border-color: var(--x-line, rgba(240,236,226,.10));
}
.jhd__x, .jhd__nav {
  background: rgba(13, 15, 18, .82);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  border: 1px solid var(--x-line-2, rgba(240,236,226,.18));
  color: var(--x-ink, #F5F1E8);
}
.jhd__x:hover, .jhd__nav:hover {
  background: rgba(20, 27, 39, .92);
  border-color: var(--x-gold-edge, rgba(227,190,121,.42));
  color: var(--x-gold, #E3BE79);
}
.jhd__cap {
  border-color: var(--x-line-2, rgba(240,236,226,.18));
  background: radial-gradient(120% 100% at 50% 0%, rgba(58,96,150,.22) 0%, var(--x-deep, #070B13) 70%);
}
/*
 * On the detail panel this button is the PRIMARY action, not the secondary one
 * it is on a result card, and janet_hotel_card.css says so with its own
 * higher-specificity rule: `background: var(--jhc-ink); color: #FFF6EA`.
 * Re-pointed, --jhc-ink is the light text colour, so it painted #FFF6EA on
 * #F5F1E8 — the availability check, unreadable. Champagne, like every other
 * primary action in the shell.
 */
.jhd__book .ls-card__cta--check {
  background: linear-gradient(160deg, var(--x-gold, #E3BE79), var(--x-gold-deep, #C9A24A));
  color: var(--x-on-gold, #10131A);
  border: 0;
  font-weight: 700;
}
.jhd__book .ls-card__cta--check:hover {
  background: linear-gradient(160deg, #EFCE8E, var(--x-gold, #E3BE79));
  color: var(--x-on-gold, #10131A);
}
.jhd__nofoto-m {
  background: linear-gradient(160deg, var(--x-gold, #E3BE79), var(--x-gold-deep, #C9A24A));
  color: var(--x-on-gold, #10131A);
}

/*
 * THE FOUR PROVENANCE BADGES — live, cached, indicative, sandbox.
 *
 * janet_live_search.css paints them as pale pills with dark text: #e7f6ec on
 * #1c6b38 and three more like it. Correct on a cream page; on midnight they
 * were 48 pastel stickers scattered over dark cards, which the repetition rule
 * in shell_conformance.js reported the moment it started visiting /hotels WITH
 * a search rather than on its empty form.
 *
 * They are INVERTED rather than recoloured, and the four hues are kept apart,
 * because these are the labels that say where a price came from. Making them
 * one champagne would be tidier and would erase the distinction between a
 * price measured a moment ago and one read out of a cache.
 */
.ls-badge--live       { background: rgba(87, 192, 138, .14); color: #6FD3A0; }
.ls-badge--cached     { background: rgba(224, 164, 74, .14); color: #E0A44A; }
.ls-badge--indicative { background: rgba(150, 140, 220, .14); color: #A79CE8; }
.ls-badge--sandbox    { background: rgba(240, 119, 106, .14); color: #F0776A; }
.ls-tag { background: rgba(240, 236, 226, .06); color: var(--x-ink-2, #C3C6CC); }

/*
 * The favourite button. `--jt-cream-2` is a cream literal from the base
 * search sheet, so this rendered as a white disc in the corner of every card —
 * fifty-one of them on one page. It slipped past the conformance sweep because
 * a 38px circle is 1,444 square pixels and the sweep ignores anything under
 * 12,000, a floor that exists so a badge or a chip is not reported as a
 * surface. One white pill is a badge; fifty-one are a design, so the sweep now
 * counts repetition as well as area.
 */
.ls-card .jfav, .ls-hotel-card .jfav, .jhc .jfav {
  background: rgba(13, 15, 18, .78);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border-color: var(--x-line-2, rgba(240,236,226,.18));
  color: var(--x-ink-2, #C3C6CC);
}
.ls-card .jfav:hover, .ls-hotel-card .jfav:hover, .jhc .jfav:hover {
  border-color: var(--x-gold-edge, rgba(227,190,121,.42));
  color: var(--x-gold, #E3BE79);
}
.jfav.jfav--on { color: var(--x-gold, #E3BE79); }

.jhf__l { color: var(--x-ink, #F5F1E8); }
.jhf__opt { color: var(--x-ink-2, #C3C6CC); }
.jhf__opt input { accent-color: var(--x-gold, #E3BE79); }


/* == 04 · HOTEL DETAIL — THE GALLERY IN DEPTH ============================= */

/*
 * The reference's hotel detail is a depth stack: one image forward, the others
 * behind it and angled. `.jhd__stage` is the large frame the renderer already
 * builds and `.jhd__thumbs` its strip; the strip becomes the stack.
 */
.jhd { perspective: 1600px; }
.jhd__stage {
  position: relative;
  border-radius: var(--x-r-lg, 22px);
  overflow: hidden;
  box-shadow: 0 40px 90px -34px rgba(0, 0, 0, .96);
  transform: translate3d(0, 0, 0);
}
.jhd__stage img { transition: transform .8s var(--x-ease-2, cubic-bezier(.16,1,.3,1)); }

.jhd__thumbs {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  scrollbar-width: none;
  perspective: 1200px;
  padding-block: 14px;
}
.jhd__thumbs::-webkit-scrollbar { display: none; }
.jhd__thumb {
  --d: 0; --ad: 0;
  flex: 0 0 auto;
  border-radius: 12px;
  overflow: hidden;
  border: 1px solid var(--x-line, rgba(240,236,226,.10));
  transform-origin: 50% 60%;
  transform:
    translate3d(0, calc(var(--ad) * 6px), calc(var(--ad) * -90px))
    rotateY(calc(var(--d) * -22deg));
  transition: transform .18s linear, border-color .3s var(--x-ease), filter .3s var(--x-ease);
  filter: brightness(calc(1 - var(--ad) * .32));
  cursor: pointer;
}
.jhd__thumb[aria-current="true"], .jhd__thumb.is-on {
  border-color: var(--x-gold-edge, rgba(227,190,121,.42));
  filter: none;
  box-shadow: 0 0 0 1px rgba(227, 190, 121, .3), 0 18px 44px -20px rgba(0, 0, 0, .9);
}

/* The booking panel floats in front of the scene and stays with the reader. */
.jhd__book {
  position: sticky;
  inset-block-start: calc(var(--x-nav-h, 62px) + 16px);
  border-radius: var(--x-r-lg, 22px);
  border: 1px solid var(--x-line-2, rgba(240,236,226,.18));
  background: linear-gradient(157deg, rgba(18, 24, 34, .90), rgba(8, 11, 18, .95));
  -webkit-backdrop-filter: blur(20px) saturate(150%);
  backdrop-filter: blur(20px) saturate(150%);
  box-shadow: 0 40px 90px -34px rgba(0, 0, 0, .96), 0 0 0 1px rgba(227, 190, 121, .10);
}
.jhd__book::before {
  content: "";
  position: absolute;
  inset-block-start: -1px;
  inset-inline: 16%;
  block-size: 1px;
  background: linear-gradient(90deg, transparent, rgba(227, 190, 121, .7), transparent);
}
/* Room rows arrive from depth, once. */
html.js .jhd__rows.bkx-in .jhd__row {
  animation: bkx-card-in .6s var(--x-ease-2, cubic-bezier(.16,1,.3,1)) both;
  animation-delay: calc(min(var(--i, 0) * 60ms, 420ms));
}


/* == 05 · THE PLANNER — THE TRIP ASSEMBLES =============================== */

/*
 * The result is not a page of boxes: the destination lands, then the flight,
 * then the hotel, then the days, then the budget. Each stage is a class the
 * runtime adds when the previous one has settled, so the reader watches the
 * trip being built rather than meeting it already finished.
 *
 * EVERY FIGURE IS THE PLANNER'S OWN. This sheet moves what janet_plan.js
 * wrote and never writes a number; the budget meter is filled from a width the
 * runtime reads off the planner's own rendered total.
 */
.jp-intro, .jp-result { position: relative; }
.jp-bar { position: sticky; inset-block-start: 0; z-index: 90; }

.jp-card, .jp-day, .jp-cmp {
  transform-origin: 50% 60%;
}
html.js .jp-result .jp-card,
html.js .jp-result .jp-day,
html.js .jp-result .jp-cmp { opacity: 0; }
html.js .jp-result.bkx-built .jp-card,
html.js .jp-result.bkx-built .jp-day,
html.js .jp-result.bkx-built .jp-cmp {
  animation: bkx-assemble .78s var(--x-ease-2, cubic-bezier(.16,1,.3,1)) both;
  animation-delay: calc(160ms + min(var(--i, 0) * 210ms, 2400ms));
}
@keyframes bkx-assemble {
  from { opacity: 0; transform: translate3d(0, 30px, -140px) rotateX(7deg); }
  to   { opacity: 1; transform: none; }
}

/* The route that connects the days. It draws itself down the itinerary as the
   days land, which is the one line in this scene that is a picture of the plan
   rather than a piece of it. */
.jp-days { position: relative; }
.jp-days::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 18px;
  inline-size: 2px;
  block-size: 100%;
  border-radius: 2px;
  background: linear-gradient(180deg, var(--x-gold-edge, rgba(227,190,121,.42)), rgba(227,190,121,.06));
  transform-origin: top;
  scale: 1 0;
  transition: scale 1.6s var(--x-ease-2, cubic-bezier(.16,1,.3,1)) .5s;
}
html.js .jp-result.bkx-built .jp-days::before { scale: 1 1; }
.jp-day { position: relative; }
.jp-day__n {
  position: relative;
  z-index: 2;
  display: grid;
  place-items: center;
  inline-size: 38px; block-size: 38px;
  border-radius: 50%;
  background: linear-gradient(160deg, var(--x-gold, #E3BE79), var(--x-gold-deep, #C9A24A));
  color: var(--on-sky, #10131A);
  font-weight: 700;
}

/*
 * THE BUDGET METER IS janet_plan.js's OWN, dressed — not a second one.
 *
 * budget_meter.js decides WITHIN / OVER / UNKNOWN and janet_plan.js paints the
 * fill with an inline scaleX from the figure it actually holds. Nothing here
 * computes a width; `data-status` is read only to choose a colour, and the
 * UNKNOWN state is deliberately NOT gold — a withheld total must not look like
 * a measured one wearing a confident bar.
 */
.jp-meter { position: relative; }
.jp-meter__track {
  block-size: 5px;
  border-radius: 3px;
  background: var(--x-line, rgba(240,236,226,.10));
  overflow: hidden;
}
.jp-meter__fill {
  display: block;
  block-size: 100%;
  transform-origin: inset-inline-start center;
  background: linear-gradient(90deg, var(--x-gold-deep, #C9A24A), var(--x-gold, #E3BE79));
  transition: transform 1.3s var(--x-ease-2, cubic-bezier(.16,1,.3,1)) .4s;
}
.jp-meter[data-status="OVER"] .jp-meter__fill {
  background: linear-gradient(90deg, #B4552F, #E08A4A);
}
.jp-meter[data-status="UNKNOWN"] .jp-meter__fill {
  background: repeating-linear-gradient(135deg,
    rgba(240,236,226,.20) 0 6px, rgba(240,236,226,.06) 6px 12px);
}
.jp-meter__line { color: var(--x-ink-2, #C3C6CC); }

/* The five questions get the console's material. */
.jp-fieldset, .jp-grid > .jp-field {
  border-radius: var(--x-r, 16px);
  border: 1px solid var(--x-line, rgba(240,236,226,.10));
  background: linear-gradient(150deg, rgba(16, 22, 32, .68), rgba(7, 11, 18, .80));
}
.jp-nav__rail { display: flex; gap: 8px; }

/*
 * A URL IS AN LTR RUN, and this one is inside an RTL document.
 *
 * The share link renders as a scrambled string under `direction: rtl` —
 * the bidi algorithm reorders the neutral characters in the query string and
 * the reader is shown a link that is not the link. Same class of defect as the
 * reversed date range on the homepage, and the same shape of fix: state the
 * direction of the run rather than of the page.
 */
.jp-share input, .jp-share code, .jp-share textarea {
  direction: ltr;
  text-align: start;
  unicode-bidi: isolate;
  font-variant-numeric: tabular-nums;
}
.jp-share input {
  border-radius: 999px;
  border: 1px solid var(--x-line, rgba(240,236,226,.10));
  background: rgba(255, 255, 255, .03);
  color: var(--x-ink-2, #C3C6CC);
}


/* == 06 · DESTINATION + EDITORIAL ======================================== */

/*
 * A destination page opens on its own environment. The hero photograph is
 * already full-width in the generated markup; this gives it the shell's grade,
 * a slow breathing scale, and depth between the picture and the type.
 */
.dhero, .ghero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  min-block-size: min(72vh, 680px);
  display: grid;
  align-content: center;
}
.dhero__img, .ghero__img, .dhero img, .ghero img {
  position: absolute;
  inset: -4%;
  inline-size: 108%;
  block-size: 108%;
  object-fit: cover;
  z-index: -2;
}
@keyframes bkx-breathe { 0%,100% { scale: 1; } 50% { scale: 1.045; } }
html.js .dhero__img, html.js .ghero__img { animation: bkx-breathe 34s ease-in-out infinite; }
.dhero::after, .ghero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(180deg, rgba(4,7,12,.80) 0%, rgba(4,7,12,.22) 26%, rgba(4,7,12,.94) 100%),
    radial-gradient(112% 74% at 50% 44%, transparent 22%, rgba(4,7,12,.78) 100%);
}
.dhero h1, .ghero h1 {
  font-size: clamp(2.1rem, 3.6vw + .5rem, 4rem);
  text-shadow: 0 4px 40px rgba(0, 0, 0, .7);
}
.dhero__sub, .ghero__sub { color: var(--x-ink-2, #C3C6CC); max-inline-size: 54ch; }

/* Editorial imagery gets depth rather than sitting flat in the column. */
.jn-guide, .ed-fig, .jd-card {
  border-radius: var(--x-r-lg, 22px);
  overflow: hidden;
  transition: transform .4s var(--x-ease), box-shadow .4s var(--x-ease), border-color .3s var(--x-ease);
}
.jn-guide:hover, .jd-card:hover {
  transform: translateY(-6px);
  border-color: var(--x-gold-edge, rgba(227,190,121,.42));
  box-shadow: 0 30px 66px -26px rgba(0, 0, 0, .95);
}


/* == SHARED · reveals on every route ====================================== */

html.js .bkx-rise-r {
  opacity: 0;
  transform: translate3d(0, 24px, 0);
  transition: opacity .8s var(--x-ease-2, cubic-bezier(.16,1,.3,1)),
              transform .8s var(--x-ease-2, cubic-bezier(.16,1,.3,1));
}
html.js .bkx-rise-r.bkx-in { opacity: 1; transform: none; }


/* == RESPONSIVE =========================================================== */

@media (max-width: 900px) {
  .hd { padding-inline: clamp(14px, 4vw, 22px); gap: 12px; }
  .hd-nav { display: none; }
  .jhd__book { position: static; }
}
@media (max-width: 720px) {
  .jhd__thumb { transform: none; filter: none; }
}

@media (min-width: 2200px) {
  .hd { padding-inline: clamp(48px, 4vw, 96px); }
}


/* == REDUCED MOTION ======================================================= */

@media (prefers-reduced-motion: reduce) {
  html.js .ls-list.bkx-in .ls-card,
  html.js .jp-result.bkx-built .jp-card,
  html.js .jp-result.bkx-built .jp-day,
  html.js .jp-result.bkx-built .jp-cmp,
  html.js .jhd__rows.bkx-in .jhd__row { animation: none !important; opacity: 1 !important; transform: none !important; }
  html.js .jp-result .jp-card,
  html.js .jp-result .jp-day,
  html.js .jp-result .jp-cmp { opacity: 1; }
  html.js .bkx-rise-r { opacity: 1 !important; transform: none !important; transition: none !important; }
  html.js .dhero__img, html.js .ghero__img, html.js .bkx-sky::after { animation: none !important; }
  html.js #ls-hotel-grid.bkx-in .jhc,
  html.js .jh-grid.bkx-in .jhc,
  html.js .jdh__grid.bkx-in .jdh-card { animation: none !important; opacity: 1 !important; transform: none !important; }
  html.js .jdh__grid .jdh-card { opacity: 1; }
  .jhc:hover, .jhc:focus-within, .jdh-card:hover, .jdh-card--pick,
  .jdh-card[data-jdh-on], .jhd__thumb { transform: none !important; filter: none !important; transition: none !important; }
  .ls-card:hover, .ls-card[data-bkx-picked] { transform: none; }
  .jp-days::before, .bkx-route { scale: 1 1 !important; transition: none !important; }
  .jp-meter__fill { transition: none !important; }
  .jhd__thumbs { scroll-behavior: auto; }
}

/* == THE COVERFLOW ========================================================
 *
 * The engine writes transform, opacity, z-index and `--cf-a` (the card's own
 * distance from the front, 0 at centre). Everything here is the LOOK, and
 * nothing here animates transform — bkx_coverflow.js is the only animation
 * authority for position, and a CSS transition on top of a spring is two
 * animations arguing about one property.
 *
 * WHAT THIS SHEET MUST NOT DO, learned by doing it:
 * a first version set `position: relative; z-index: 4` on every direct child
 * of a card so the text would sit above the picture. The deals renderer paints
 * its photograph with `<span class="jn-deal__img" style="background-image:…">`
 * positioned absolutely — so the blanket rule pulled the picture back into
 * flow and every card went black. The deck rendered, moved and centred
 * perfectly, with no images in it.
 *
 * So the picture is named explicitly, and nothing here touches "every child".
 */
.bkx-cf {
  position: relative;
  perspective: 1500px;
  perspective-origin: 50% 46%;
  touch-action: pan-y;               /* vertical page scroll still belongs to the page */
  cursor: grab;
  user-select: none;
  -webkit-user-select: none;
  block-size: var(--cf-h, clamp(430px, 56vh, 560px));
  overflow: hidden;
}
.bkx-cf.is-dragging { cursor: grabbing; }
.bkx-cf:focus-visible { outline: 2px solid var(--x-gold, #E3BE79); outline-offset: 6px; border-radius: var(--x-r-lg, 22px); }

/* The track is only a positioning context: the cards are placed absolutely
   from the centre, because a flex row would fight the transforms for the left
   edge of every card. */
/*
 * SPECIFICITY, and why these selectors are three classes deep.
 *
 * The renderers style their own cards at TWO classes — `.bkx .jn-deal`
 * gives a fare card `position: relative`, `flex: 0 0 auto` and a fixed
 * 288px, and `.bkx .jn-deals__track` makes its container a flex scroller.
 * A single-class `.bkx-cf__item` loses to both, so the engine was writing
 * transforms onto cards that were still being LAID OUT by a flex row: the
 * deck moved, turned and lit correctly and was centred 431px off, because
 * `inset-inline-start: 50%` was resolving against a 288px card rather than
 * the 1440px frame.
 *
 * Three classes beats two whatever order the sheets load in, which matters
 * because this block is appended and a later edit could move it.
 */
.bkx-cf .bkx-cf__track {
  /*
   * THE TRACK MUST NOT CATCH THE POINTER, and this is not a nicety.
   *
   * Measured: elementFromPoint returned the TRACK at all fifteen points
   * across a side card's own box. Not one pixel of any card except the
   * centre one was hittable, so the headline interaction — hover a side
   * card, the deck turns — was dead for every reader, and so were the links
   * on those cards.
   *
   * The cause is `preserve-3d`: inside it, z-index does not decide stacking,
   * 3D POSITION does. The side cards are pushed back by translateZ, which
   * puts them behind their own parent's plane at z = 0 — and the parent
   * fills the frame, so it wins the hit test everywhere. Raising z-index on
   * the cards cannot fix it; the engine was already writing one.
   */
  pointer-events: none;
  position: absolute;
  display: block !important;
  overflow: visible;
  padding: 0;
  gap: 0;
  inset: 0;
  display: block;
  transform-style: preserve-3d;
}
.bkx-cf .bkx-cf__track .bkx-cf__item {
  /* …and the cards take it back. */
  pointer-events: auto;
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  margin-block-start: calc(var(--cf-card-h, 400px) / -2);
  margin-inline-start: calc(var(--cf-card-w, 300px) / -2);
  inline-size: var(--cf-card-w, 300px);
  block-size: var(--cf-card-h, 400px);
  transform-origin: 50% 58%;
  will-change: transform, opacity;
  border-radius: var(--x-r-lg, 22px);
  overflow: hidden;
  border: 1px solid var(--x-line, rgba(240,236,226,.10));
  background: var(--x-raised, #0C1119);
  box-shadow: 0 34px 76px -30px rgba(0, 0, 0, .96);
  text-decoration: none;
  /* NOT transform — see above. These two are the only properties the
     stylesheet is allowed to ease. */
  transition: box-shadow .34s var(--x-ease, cubic-bezier(.22,1,.36,1)),
              border-color .34s var(--x-ease, cubic-bezier(.22,1,.36,1));
}

/*
 * THE PICTURE, in whichever of the three shapes a renderer emits it: a real
 * <img>, the deals strip's background-image <span>, or the hotel row's.
 * Named rather than matched by position, so a renderer that adds a wrapper
 * does not silently lose its photograph.
 */
.bkx-cf__item > img,
.bkx-cf__item > .jn-deal__img,
.bkx-cf__item > .bkx-card__pic,
.bkx-cf__item > .jn-hot__img {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  background-size: cover;
  background-position: center;
  z-index: 0;
  filter: saturate(calc(1 - var(--cf-a, 0) * .20)) brightness(calc(1 - var(--cf-a, 0) * .08));
}

/* The dimming is driven by the SAME number the geometry uses, so a card half
   way to the front is half way lit. */
.bkx-cf__item::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    linear-gradient(180deg, rgba(4,7,12,.04) 0%, rgba(4,7,12,.06) 38%, rgba(4,7,12,.94) 100%),
    linear-gradient(rgba(4, 7, 12, calc(var(--cf-a, 0) * 0.24)), rgba(4, 7, 12, calc(var(--cf-a, 0) * 0.24)));
  transition: background-color .2s linear;
}
/* The card's own text sits above the veil. Named classes again, never `> *`. */
.bkx-cf__item > .jn-deal__body,
.bkx-cf__item > .bkx-card__body,
.bkx-cf__item > .jn-deal__where,
.bkx-cf__item > .jn-deal__meta,
.bkx-cf__item > .jn-deal__price,
.bkx-cf__item > .jn-deal__note,
.bkx-cf__item > .jn-deal__tag,
.bkx-cf__item > figcaption,
.bkx-cf__item > h3,
.bkx-cf__item > p,
.bkx-cf__item > span:not(.jn-deal__img):not(.bkx-card__pic):not(.jn-hot__img) { position: relative; z-index: 2; }

/* The front card is the primary object: champagne edge, its own light, no
   extra veil at all. */
.bkx-cf__item[data-cf-centre] {
  border-color: var(--x-gold, #E3BE79);
  box-shadow:
    0 44px 96px -28px rgba(0, 0, 0, .98),
    0 0 0 1px rgba(227, 190, 121, .34),
    0 22px 60px -22px rgba(227, 190, 121, .34);
}

/* The floor the deck stands on. */
.bkx-cf__floor {
  position: absolute;
  inset-block-end: 3%;
  inset-inline-start: 50%;
  translate: -50% 0;
  inline-size: min(1180px, 92%);
  block-size: 30%;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(56% 54% at 50% 100%,
    rgba(227, 190, 121, .20), rgba(227, 190, 121, .04) 46%, transparent 74%);
}

@media (max-width: 860px) {
  .bkx-cf { --cf-h: clamp(340px, 56vh, 460px); }
}
@media (prefers-reduced-motion: reduce) {
  .bkx-cf__item { transition: none !important; }
  .bkx-cf__item > img, .bkx-cf__item > .jn-deal__img { filter: none !important; }
  /*
   * THE DECK IS NOT BLANKED, IT IS FLATTENED.
   *
   * The old rail sat in flow, so `transform: none !important` was the right
   * reduced-motion answer for it and the suite pins that. A coverflow card is
   * positioned absolutely at the centre of its frame and placed by transform,
   * so the same rule would stack all eight cards on one spot — a page that
   * respects the preference by destroying the content.
   *
   * The flattening happens where the transform is WRITTEN instead:
   * bkx_coverflow.js sets rotateY and translateZ to zero under the same media
   * query, so a reader who asked for less motion gets a flat, still, readable
   * row and keeps every card. The spring is skipped there too — the deck moves
   * to its target in one frame rather than easing.
   *
   * The old guarantee still holds for every .bkx-card that is NOT in a deck.
   */
  .bkx-card:not(.bkx-cf__item) { transform: none !important; }
}

/* The picks deck sits in a taller frame than the homepage's destination
   cards: a hotel card carries a photograph, a name, a rating, amenities, a
   price and two actions, and squeezing that into a 400px portrait would make
   the deck unreadable at exactly the moment it is meant to be admired. */
.bkx-cf--picks {
  --cf-h: clamp(500px, 58vh, 620px);
  --cf-card-w: min(400px, 76vw);
  --cf-card-h: min(600px, 62vh);
  margin-block: clamp(10px, 2vh, 26px);
}
/*
 * The card keeps its OWN layout. A first version forced
 * `display:flex; flex-direction:column` on it, which collapsed the hotel
 * photograph into a 30px filmstrip: the renderer lays that card out as a grid
 * with a sized media row, and re-declaring the display threw the row sizing
 * away. The deck supplies a frame; what goes inside it is the renderer's.
 */
.bkx-cf--picks .jdh-card { overflow: auto; overscroll-behavior: contain; }
/* The front card is the only one a pointer may reach into: a card turned away
   is decoration, and letting a click land on it is how a reader books the
   wrong hotel. */
.bkx-cf--picks .jdh-card:not([data-cf-centre]) { pointer-events: none; }
.bkx-cf--picks .jdh-card[data-cf-centre] { pointer-events: auto; }
@media (max-width: 860px) {
  .bkx-cf--picks { --cf-h: clamp(500px, 74vh, 640px); --cf-card-w: min(320px, 84vw); }
}

/* == COVERFLOW · PASS 3 — A DECK, NOT A ROW =============================
 *
 * The note was "it reads as a row of portrait cards", and it did. The engine's
 * geometry is fixed in bkx_coverflow.js; these are the three things the
 * stylesheet owns that were also missing.
 */

/* 1. THE PERSPECTIVE WAS TOO FAR AWAY. At 1400px a 58-degree turn foreshortens
 *    by a few percent and reads as a slightly narrower card. At 900 the same
 *    turn is unmistakable, and the receding cards genuinely converge. */
.bkx-cf {
  perspective: 900px;
  perspective-origin: 50% 42%;
}

/* 2. SHADING THAT LEANS. A turned card in the world is lit unevenly: the edge
 *    swinging away from you goes dark, the near edge catches the light. `--cf-s`
 *    is the sign of the card's offset, so the gradient can point at whichever
 *    side is actually the far one. Without this, a rotated card is just a
 *    narrower rectangle — this is what makes the turn legible. */
.bkx-cf .bkx-cf__track .bkx-cf__item::after {
  background:
    linear-gradient(180deg, rgba(4,7,12,.04) 0%, rgba(4,7,12,.06) 38%, rgba(4,7,12,.94) 100%),
    linear-gradient(
      calc(90deg + var(--cf-s, 1) * 180deg),
      /* --cf-a runs to 2.6, so a 0.62 coefficient clamped the far edge of the
         outer cards to solid black and the deck lost its ends. These are priced
         against the RANGE, not against 1. */
      rgba(2, 4, 8, calc(var(--cf-a, 0) * 0.27)) 0%,
      rgba(2, 4, 8, calc(var(--cf-a, 0) * 0.07)) 48%,
      rgba(255, 246, 232, calc(var(--cf-a, 0) * 0.035)) 100%
    );
}

/* 3. THE FLOOR. The reference deck stands on something — there is a soft
 *    reflection and a pool of light under it. A deck floating on flat black is
 *    the other half of why it read as a row of tiles. */
.bkx-cf::after {
  content: "";
  position: absolute;
  inset-block-end: 0;
  inset-inline: 8%;
  block-size: 24%;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(46% 100% at 50% 0%, rgba(227, 190, 121, .16), transparent 70%),
    radial-gradient(70% 100% at 50% 0%, rgba(58, 96, 150, .10), transparent 72%);
  filter: blur(6px);
}

/* The front card carries a real cast shadow now, not just a glow — a lit object
   above a floor throws one, and its absence is subtle but reads as a sticker. */
.bkx-cf .bkx-cf__track .bkx-cf__item[data-cf-centre] {
  box-shadow:
    0 54px 90px -30px rgba(0, 0, 0, .98),
    0 0 0 1px rgba(227, 190, 121, .50),
    0 0 52px -8px rgba(227, 190, 121, .34),
    0 30px 76px -22px rgba(227, 190, 121, .30);
}
