/* ==========================================================================
   Design tokens
   --------------------------------------------------------------------------
   The palette is warm stone rather than screen-grey, and near-monochrome. Two
   references sit behind it: Apple, for precision, generous space and type that
   is allowed to be large and quiet; and Aman, for the material world - sand,
   limestone, deep shade - and for the discipline of using almost no colour at
   all.

   What that rules out is as important as what it allows. No candy tints, no
   drop shadows standing in for hierarchy, no accent bar on a rounded card. If
   an element needs separating from the one beside it, it gets a hairline and
   some air, not a shadow and a gradient.

   Everything below is a token. No component invents a colour, a radius or a
   step, which is what makes a change like this one possible in a single file.
   ========================================================================== */
:root {
  /* ---- Surfaces -----------------------------------------------------------
     Warm off-white, not blue-white. The old #f6f7f9 was a screen colour; this
     is closer to paper, and it makes photographs sit in the page rather than
     float above it. */
  --bg: #faf8f5;
  --surface: #ffffff;
  --surface-2: #f3f0ea;
  --border: #e5e0d7;
  --border-strong: #cfc8ba;

  /* ---- Ink ----------------------------------------------------------------
     Warm near-black rather than blue-black. At body sizes the difference is
     barely nameable and entirely felt: #10182b reads as a screen, #1c1b18 as
     print. Contrast on --bg is 15.8:1, well past AAA. */
  --ink: #1c1b18;
  --ink-2: #46443e;
  --muted: #78746b;
  --muted-2: #a8a398;

  /* ---- Accent -------------------------------------------------------------
     Deep eucalypt. It replaces an indigo that was the same indigo every
     generated site uses, and it earns its place three ways: it is quiet enough
     to sit under a photograph, dark enough to pass contrast as text (8.9:1 on
     white), and it belongs to an Australian site about leaving Australia.
     It appears rarely - a focus ring, a current state, a link. Restraint is
     the point. */
  --brand: #374a3f;
  --brand-ink: #2a3931;
  --brand-soft: #ebefea;
  --cta: #1c1b18;
  --cta-hover: #33312c;

  /* ---- Semantic -----------------------------------------------------------
     Desaturated into the same warm family so a warning does not look like it
     was pasted in from another site. Still unambiguous, and never the only
     signal - every status carries an icon and a word as well. */
  --ok: #3f6b4a;
  --ok-soft: #eaf0eb;
  --warn: #96662a;
  --warn-soft: #f7f0e4;
  --stop: #9c3b32;
  --stop-soft: #f7ebe9;
  --info: #3a5670;
  --info-soft: #ebeff3;

  /* ---- Category tints -----------------------------------------------------
     Same eight hues, roughly a third of the chroma. They still tell nine
     categories apart at a glance, which is their whole job, without turning a
     page of cards into a paint chart. */
  --tint-blue: #eaeef2;      --tint-blue-ink: #3a5670;
  --tint-green: #e9efe9;     --tint-green-ink: #3f6b4a;
  --tint-violet: #eeecf1;    --tint-violet-ink: #5a5270;
  --tint-amber: #f6f0e4;     --tint-amber-ink: #8a6228;
  --tint-peach: #f6ece6;     --tint-peach-ink: #9c5a3c;
  --tint-teal: #e7eeed;      --tint-teal-ink: #3a6460;
  --tint-sky: #eaeef3;       --tint-sky-ink: #3f5a78;
  --tint-pink: #f4eaee;      --tint-pink-ink: #8a4a60;

  /* ---- Type ---------------------------------------------------------------
     The system stack stays: on the Apple hardware this aesthetic is borrowed
     from, it resolves to San Francisco, which is the real thing rather than an
     imitation of it, and it costs no request.

     "Flag Emoji" leads but only ever draws regional-indicator pairs - its
     @font-face in 02-base.css restricts it by unicode-range, so every other
     character still falls through to the system font. Without it Windows
     draws flags as two letters. */
  --font: "Flag Emoji", -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, "Helvetica Neue", Arial, sans-serif;

  /* The display face. Aman sets its headlines in Lyon Display Light - a
     licensed face, so not an option here. Cormorant Garamond at 300 is the
     closest open equivalent: the same high-contrast, light, wide-open serif,
     and it was chosen by rendering it against EB Garamond and Newsreader at
     the site's own headline rather than from a description. EB Garamond read
     bookish and Newsreader cost 129KB for a similar result; this is 37KB.

     It is a DISPLAY face and used only as one - h1, h2 and the serif accent.
     At 16px a 300-weight Garamond is thin and tiring, so body text, tables,
     prices, forms and every small label stay on the system stack, which also
     keeps the parts of the site people actually operate as crisp as they were.

     "Cormorant Fallback" is the metric-matched stand-in that shows during the
     swap - see 02-base.css. Without it the headline reflows when the webfont
     lands, which is a layout shift on the largest text on the page. */
  --font-display: "Cormorant Garamond", "Cormorant Fallback", Georgia, "Times New Roman", serif;
  --font-serif: ui-serif, Georgia, "Iowan Old Style", "Times New Roman", serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* A slightly wider range than before. Small text is unchanged - it has to
     stay readable - and the display sizes are allowed to get properly large,
     because a big quiet headline with room around it is most of this look. */
  --step--1: clamp(0.8rem, 0.78rem + 0.1vw, 0.85rem);
  --step-0: clamp(0.95rem, 0.92rem + 0.15vw, 1rem);
  --step-1: clamp(1.1rem, 1.05rem + 0.3vw, 1.25rem);
  --step-2: clamp(1.35rem, 1.2rem + 0.6vw, 1.7rem);
  --step-3: clamp(1.7rem, 1.4rem + 1.3vw, 2.4rem);
  --step-4: clamp(2.2rem, 1.6rem + 2.6vw, 3.8rem);

  /* Tracking as tokens, because it is doing real work here. Large type wants
     to be tighter than a browser sets it; a small uppercase label wants to be
     much looser, and that spaced-out label is the single most recognisable
     thing about the reference. */
  /* A serif at display size wants almost none of the negative tracking a
     grotesque needs - the serifs already carry the eye from letter to letter,
     and tightening them past this starts closing up the counters. */
  --track-display: -0.008em;
  --track-tight: -0.018em;
  --track-label: 0.14em;

  /* ---- Space -------------------------------------------------------------
     The top of the scale is bigger than it was. Space is the material this
     design is made of - it is what makes a page feel considered rather than
     packed - so there is more of it to spend. */
  --sp-1: 0.25rem; --sp-2: 0.5rem; --sp-3: 0.75rem; --sp-4: 1rem;
  --sp-5: 1.5rem;  --sp-6: 2.25rem; --sp-7: 3.5rem;  --sp-8: 5.5rem;

  /* ---- Shape --------------------------------------------------------------
     Radii roughly halved. Sixteen-pixel corners on everything is the house
     style of the generated web; ten is soft enough to feel made and quiet
     enough to get out of the way.

     Shadows are all but gone. Depth here comes from a hairline and from space,
     not from a card appearing to hover a centimetre off the page. The one
     survivor is a whisper under the hero photograph, where a real edge helps a
     bright image meet a bright ground. */
  --r-sm: 4px; --r: 8px; --r-lg: 10px; --r-xl: 14px; --r-pill: 999px;
  --shadow-sm: none;
  --shadow: 0 1px 2px rgba(28, 27, 24, 0.03);
  --shadow-lg: 0 1px 3px rgba(28, 27, 24, 0.04), 0 24px 48px -32px rgba(28, 27, 24, 0.22);

  --container: 1180px;
  /* A comfortable measure for running text, so prose is never set the full
     width of a 1180px container. */
  --measure: 68ch;
  --header-h: 68px;
}

/* ============================================================ base ======= */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html {
  /* The design is light only - this stops browsers applying dark-mode chrome
     to form controls and scrollbars on a device set to dark. */
  color-scheme: light;
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + 1rem);
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--step-0);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  min-height: 100dvh;
  overflow-x: hidden;
}

img, svg, video { display: block; max-width: 100%; height: auto; }

/* Inline icons ship without width/height so they inherit colour and scale with
   text. Anything with explicit dimensions (generated artwork) is left alone. */
svg:not([width]) { width: 1.15em; height: 1.15em; flex: none; }
input, button, textarea, select { font: inherit; color: inherit; }
button { cursor: pointer; }

/* Large type is set tighter than a browser sets it, and gets lighter as it
   gets bigger: at 3.8rem, 700 weight reads as shouting, and 600 reads as
   confident. Small headings keep their weight because they have to hold their
   own against body text beside them. */
h1, h2, h3, h4 { line-height: 1.12; letter-spacing: var(--track-tight); font-weight: 650; text-wrap: balance; }

/* The two display sizes take the serif; h3 and h4 do not.

   h3 and h4 are mostly card titles and section labels sitting next to
   controls and prices - small, functional, and better served by the same
   face as the interface around them. A light Garamond at 20px next to a
   button reads as a different website. */
h1, h2 {
  font-family: var(--font-display);
  font-weight: 300;
  letter-spacing: var(--track-display);
  line-height: 1.06;
}
h1 { font-size: var(--step-4); }
h2 { font-size: var(--step-3); }
h3 { font-size: var(--step-2); }
h4 { font-size: var(--step-1); }
p { text-wrap: pretty; }

a { color: var(--brand-ink); text-decoration-color: color-mix(in srgb, var(--brand-ink) 35%, transparent); text-underline-offset: 2px; }
a:hover { text-decoration-color: currentColor; }

:focus-visible { outline: 3px solid var(--brand); outline-offset: 2px; border-radius: 4px; }

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 999;
  background: var(--cta); color: var(--surface); padding: 0.75rem 1rem; border-radius: 0 0 var(--r) 0;
}
.skip-link:focus { left: 0; }

.visually-hidden {
  position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---- Flag emoji ---------------------------------------------------------
   Windows ships no flag glyphs. The data holds the right characters - New
   Zealand is U+1F1F3 U+1F1FF - but with nothing to draw them with, Chrome
   falls back to rendering the two regional-indicator letters, so the
   destination picker read "NZ New Zealand" instead of showing a flag. macOS,
   iOS and Android are all fine; it is Windows and most Linux desktops that are
   not, which is most of the site's desktop traffic.

   The fix is a font that has those glyphs, scoped by unicode-range so it
   applies to nothing else. The browser only downloads it on a page that
   actually renders a flag, and every other character on the site keeps the
   font it already had.

   Twemoji by Twitter, CC-BY 4.0 - credited on /photo-credits/ under Typefaces.
   ------------------------------------------------------------------------ */
@font-face {
  font-family: "Flag Emoji";
  src: url("/fonts/twemoji-country-flags.woff2") format("woff2");
  unicode-range: U+1F1E6-1F1FF;
  font-display: swap;
}

/* ---- Display serif ------------------------------------------------------
   Cormorant Garamond, SIL Open Font License 1.1, self-hosted. Latin subset
   only - the site is written in English and place names stay inside Latin-1.
   Credited on /photo-credits/ with the rest of the licensed work.

   Loaded for headlines and nothing else, so the payload is 37KB on a page
   with a heading and 22KB more only where an italic accent appears.
   ------------------------------------------------------------------------ */
@font-face {
  font-family: "Cormorant Garamond";
  src: url("/fonts/cormorant-garamond-300.woff2") format("woff2");
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Cormorant Garamond";
  src: url("/fonts/cormorant-garamond-300-italic.woff2") format("woff2");
  font-weight: 300;
  font-style: italic;
  font-display: swap;
}

/* The stand-in shown during the swap.

   font-display: swap paints the headline in a fallback first, and the
   headline is the biggest text on the page - so if the fallback sets at a
   different width the whole page jumps when the real font lands. That jump is
   Cumulative Layout Shift, which is a ranking signal and, more to the point,
   visibly cheap.

   The numbers are measured rather than guessed: rendered at the same size,
   this face sets the site's own headline at 87.6% of Arial's width, with an
   ascent of 92 and a descent of 29 per 100px against Arial's 91 and 21. The
   overrides below reshape a local grotesque to those metrics, so the swap is
   close to invisible. */
@font-face {
  font-family: "Cormorant Fallback";
  src: local("Arial"), local("Helvetica Neue"), local("Liberation Sans"), local("Arimo");
  size-adjust: 87.6%;
  ascent-override: 105%;
  descent-override: 33.1%;
  line-gap-override: 0%;
}

.container { width: 100%; max-width: var(--container); margin-inline: auto; padding-inline: 1rem; }
@media (min-width: 720px) { .container { padding-inline: 1.5rem; } }

.serif-accent { font-family: var(--font-display); font-style: italic; font-weight: 300; color: var(--brand); letter-spacing: 0; }

.muted { color: var(--muted); }
.small { font-size: var(--step--1); }
.center { text-align: center; }
.nowrap { white-space: nowrap; }
.stack > * + * { margin-top: var(--sp-4); }
.mt-0 { margin-top: 0; }

.scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: thin; }

/* ======================================================= components ====== */

/* ---- Header ---- */
/* The rule is always there, at a weight that reads as an edge rather than a
   line, and firms up once the page has moved under it. A header that gains a
   border only on scroll makes the top of the page feel unfinished. */
.site-header {
  position: sticky; top: 0; z-index: 50;
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: saturate(140%) blur(16px);
  border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
  transition: border-color 0.2s, background 0.2s;
}
.site-header[data-scrolled="true"] { border-bottom-color: var(--border); }
.site-header .container { display: flex; align-items: center; gap: var(--sp-4); min-height: var(--header-h); }

.brand { display: inline-flex; align-items: center; gap: 0.5rem; font-weight: 700; font-size: 1.05rem; color: var(--ink); text-decoration: none; letter-spacing: -0.03em; flex: none; }
.brand svg { width: 18px; height: 18px; color: var(--brand); }
/* A fixed box with the artwork contained inside it. The mark is wider than it
   is tall, and a future one might not be - either way it sits in this box at
   its own proportions instead of being squashed to fit. `flex: none` stops the
   header's flex row from compressing it when space runs short. */
.brand__mark {
  width: 28px; height: 28px; flex: none; display: block;
  background: url('/img/logo-mark.png?v=5de7d313') center / contain no-repeat;
}
.brand__name { white-space: nowrap; }

.nav { display: none; margin-inline-start: auto; align-items: center; gap: 0.35rem; }
@media (min-width: 900px) { .nav { display: flex; } .nav--right { margin-inline-start: 0; } }
.nav a, .nav button {
  display: inline-flex; align-items: center; gap: 0.35rem;
  padding: 0.5rem 0.7rem; border-radius: var(--r-sm); border: 0; background: none;
  color: var(--ink-2); font-size: 0.925rem; font-weight: 500; text-decoration: none;
}
.nav a:hover, .nav button:hover { background: var(--surface); color: var(--ink); }
.nav a[aria-current="page"] { color: var(--ink); font-weight: 600; }

.nav-toggle {
  margin-inline-start: auto; display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: var(--r-sm); border: 1px solid var(--border); background: var(--surface); color: var(--ink);
}
@media (min-width: 900px) { .nav-toggle { display: none; } }

.mobile-nav { display: none; border-top: 1px solid var(--border); background: var(--surface); }
.mobile-nav[data-open="true"] { display: block; }
.mobile-nav ul { list-style: none; padding: 0.5rem 1rem 1rem; margin: 0; }
.mobile-nav a { display: block; padding: 0.75rem 0.25rem; color: var(--ink); text-decoration: none; font-weight: 500; border-bottom: 1px solid var(--border); }

/* Dropdown */
.has-menu { position: relative; }
.menu {
  position: absolute; top: calc(100% + 8px); left: 0; min-width: 260px; padding: 0.4rem;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg); display: none; z-index: 60;
}
.has-menu[data-open="true"] .menu { display: block; }
.menu a { display: flex; align-items: center; gap: 0.6rem; padding: 0.55rem 0.6rem; border-radius: var(--r-sm); font-size: 0.9rem; }

/* ---- Buttons ---- */
/* Pills. Of everything in this pass it is the most recognisably Apple, and it
   is also just correct for a control whose label is one or two words: the
   shape follows the text instead of boxing it. The 44px floor is the tap
   target and does not move. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem;
  padding: 0.7rem 1.35rem; border-radius: var(--r-pill); border: 1px solid transparent;
  font-weight: 590; font-size: 0.925rem; text-decoration: none; line-height: 1.2;
  letter-spacing: -0.005em;
  transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease, opacity 0.18s ease;
  min-height: 44px;
  white-space: nowrap;
}
.btn:active { opacity: .82; }
/* A disabled button that looks clickable is worse than no button: it is pressed,
   nothing happens, and the person assumes the page is broken. */
.btn:disabled, .btn[disabled] {
  opacity: .35; cursor: not-allowed; transform: none;
  pointer-events: auto;   /* keep the tooltip that says why */
}
.btn:disabled:hover, .btn[disabled]:hover { background: inherit; border-color: inherit; filter: none; }
.btn:disabled:active, .btn[disabled]:active { transform: none; }
.btn--primary { background: var(--cta); color: var(--bg); }
.btn--primary:hover { background: var(--cta-hover); }
.btn--brand { background: var(--brand); color: #fff; }
.btn--brand:hover { filter: brightness(1.05); }
.btn--ghost { background: var(--surface); color: var(--ink); border-color: var(--border); }
.btn--ghost:hover { border-color: var(--ink); }
.btn--quiet { background: transparent; color: var(--ink-2); }
.btn--quiet:hover { background: var(--surface); }
.btn--sm { padding: 0.45rem 0.95rem; min-height: 36px; font-size: 0.85rem; }
.btn--block { width: 100%; }
/* Was a filled pill in the category's tint, which put eight different
   saturated colours in one grid and read as a toy. The category colour is
   already carried by the chip-icon at the top of the same card; here the
   button just needs to look pressable. */
/* Doubled class for specificity: these buttons also carry a .t-* tint class,
   which is declared further down this file and would otherwise win on source
   order and paint the fill back in. */
.btn.btn--tint {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--ink);
  font-weight: 590;
}
.btn.btn--tint:hover { border-color: var(--ink); background: transparent; }

/* ---- Cards ---- */
/* A hairline and some room. The shadow is gone entirely: it was standing in
   for hierarchy that the spacing scale now provides, and a page of floating
   cards is the single most generated-looking thing a site can do. */
.card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); }
.card--pad { padding: var(--sp-5); }
@media (min-width: 720px) { .card--pad { padding: var(--sp-6); } }
.panel { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); padding: var(--sp-5); }
@media (min-width: 720px) { .panel { padding: var(--sp-6); } }

/* ---- Badges & pills ---- */
/* justify-self keeps it hugging its text inside a grid. inline-flex alone does
   not: a grid item stretches to its column by default, which was giving every
   guide card a full-width "VISAS" pill.

   Wide tracking on a small uppercase label is the one typographic mannerism
   borrowed wholesale from the reference, and it is what makes a two-word
   category read as a considered mark rather than a tag. */
.badge {
  display: inline-flex; align-items: center; gap: 0.3rem; padding: 0.25rem 0.6rem;
  justify-self: start;
  border-radius: var(--r-pill); font-size: 0.66rem; font-weight: 600;
  letter-spacing: var(--track-label);
  text-transform: uppercase; background: transparent; color: var(--muted); border: 1px solid var(--border);
}
.badge--ok { background: var(--ok-soft); color: var(--ok); border-color: transparent; }
.badge--warn { background: var(--warn-soft); color: var(--warn); border-color: transparent; }
.badge--stop { background: var(--stop-soft); color: var(--stop); border-color: transparent; }
.badge--brand { background: var(--brand-soft); color: var(--brand-ink); border-color: transparent; }

.pill {
  display: inline-flex; align-items: center; gap: 0.4rem; padding: 0.4rem 0.8rem;
  border-radius: var(--r-pill); background: var(--surface); border: 1px solid var(--border);
  font-size: 0.85rem; color: var(--ink-2); text-decoration: none; white-space: nowrap;
}
.pill:hover { border-color: var(--border-strong); color: var(--ink); }
.pill[aria-pressed="true"], .pill--active { background: var(--cta); color: var(--bg); border-color: var(--cta); }

/* ---- Category icon chip ---- */
/* Smaller, squarer and quieter than before. With the tints desaturated these
   read as a mark against a category rather than a row of coloured buttons. */
.chip-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: var(--r-sm); flex: none;
}
.chip-icon svg { width: 19px; height: 19px; }
.t-blue   { background: var(--tint-blue);   color: var(--tint-blue-ink); }
.t-green  { background: var(--tint-green);  color: var(--tint-green-ink); }
.t-violet { background: var(--tint-violet); color: var(--tint-violet-ink); }
.t-amber  { background: var(--tint-amber);  color: var(--tint-amber-ink); }
.t-peach  { background: var(--tint-peach);  color: var(--tint-peach-ink); }
.t-teal   { background: var(--tint-teal);   color: var(--tint-teal-ink); }
.t-sky    { background: var(--tint-sky);    color: var(--tint-sky-ink); }
.t-pink   { background: var(--tint-pink);   color: var(--tint-pink-ink); }

/* ---- Forms ---- */
.field { display: flex; flex-direction: column; gap: 0.35rem; }
.field > label { font-size: 0.78rem; font-weight: 600; color: var(--muted); }
.control {
  display: flex; align-items: center; gap: 0.55rem; width: 100%;
  padding: 0.6rem 0.75rem; min-height: 46px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r);
  color: var(--ink);
}
.control:focus-within { border-color: var(--brand); box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 18%, transparent); }
.control select, .control input { border: 0; background: none; width: 100%; outline: none; font-size: 0.95rem; }
/* min-width:0 lets the select shrink inside the flex row instead of pushing
   the caret out of the box, and the ellipsis means a label longer than its
   control degrades to "Something long..." rather than being sliced mid-word.
   Both are belt and braces: the labels themselves are short now. */
.control select {
  appearance: none;
  min-width: 0;
  text-overflow: ellipsis;
}
.control .caret { margin-inline-start: auto; color: var(--muted-2); flex: none; }
.control svg.lead { color: var(--muted-2); flex: none; width: 18px; height: 18px; }

.input {
  width: 100%; padding: 0.7rem 0.85rem; min-height: 46px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); color: var(--ink);
}
.input:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 18%, transparent); }

/* ---- Progress ring & bar ---- */
.ring { --pct: 0; width: 56px; height: 56px; flex: none; display: grid; place-items: center; position: relative; }
.ring svg { transform: rotate(-90deg); width: 56px; height: 56px; }
.ring circle { fill: none; stroke-width: 5; stroke-linecap: round; }
.ring .track { stroke: var(--border); }
.ring .value { stroke: var(--ok); stroke-dasharray: 151; stroke-dashoffset: calc(151 - (151 * var(--pct)) / 100); transition: stroke-dashoffset 0.5s ease; }
.ring output { position: absolute; font-size: 0.75rem; font-weight: 700; }

.bar { height: 8px; border-radius: var(--r-pill); background: var(--border); overflow: hidden; }
.bar > i { display: block; height: 100%; background: var(--ok); border-radius: inherit; transition: width 0.4s ease; }

/* ---- Comparison rows ---- */
/* The whole list is one grid so every row shares the same column tracks. Each
   .offer opts into those tracks with subgrid, which is what keeps prices, CTAs
   and save buttons in a straight line no matter how long any one row's text is.
   Without it each row sized its own `auto` columns and the list stair-stepped. */
.offer-list {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr) auto;
  /* The column gap lives here, not on the row: a subgrid inherits its parent's
     gutters, and re-declaring it on .offer would inset every cell inside its
     own track. Rows set only their row-gap. */
  column-gap: 1rem;
}
.offer {
  display: grid; row-gap: 0.6rem; padding: var(--sp-4) 0;
  border-bottom: 1px solid var(--border);
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
  /* Narrow layout: price and CTA share the bottom line, price left and button
     right. The price used to have no area here at all, so it was auto-placed
     into a row of its own below the button. */
  grid-template-areas: "logo name save" "logo meta meta" "price price cta";
  align-items: center;
}
.offer:last-child { border-bottom: 0; }
/* The wide layout is chosen by how much room the list actually has, not by how
   wide the window is. Those are different numbers: the sidebar arrives at
   1000px and takes 320px with it, so a 1024px window left the list 574px for
   six columns that need about 650 - and every comparison page on the site
   overflowed its card between roughly 940 and 1090px. A container query asks
   the question that actually matters. */
.offer-panel { container: offers / inline-size; }

/* 680px, not 640: the six tracks at their floors come to 652px once the gaps
   are counted, and the CTA column is sized by its longest label - "View
   details" is 16px wider than "View deal". Below this the row would be laid
   out at a width it does not fit in, which is what a 2px hairline of overflow
   on the travel-money pages turned out to be. */
@container offers (min-width: 680px) {
  .offer-list {
    /* The price track is a share of the row, not max-content. Some categories
       put a phrase there rather than a number - "0.43%-0.65% conversion fee"
       against an eSIM's "$7.50 AUD" - and a max-content track sized to the
       longest of those pushed the whole row 65px past its card on every
       travel-money page. As a fraction it takes what is going and wraps.
       The floor keeps a normal price on one line. */
    grid-template-columns:
      52px minmax(140px, 1.4fr) minmax(150px, 1.5fr) minmax(90px, 0.9fr) max-content max-content;
  }
  .offer { grid-template-areas: "logo name meta price cta save"; }
  .offer__price { justify-self: end; text-align: right; }
  .offer__cta { justify-self: stretch; }
}
/* Subgrid is everywhere we support, but a browser without it would collapse the
   row into a single column, so fall back to the old per-row tracks. */
@supports not (grid-template-columns: subgrid) {
  .offer-list { display: flex; flex-direction: column; }
  .offer { grid-template-columns: 44px 1fr auto; column-gap: 1rem; }
  @container offers (min-width: 680px) {
    .offer { grid-template-columns: 52px minmax(140px, 1.4fr) minmax(150px, 1.5fr) minmax(90px, 0.9fr) auto auto; }
  }
}
.offer__logo { grid-area: logo; }
.offer__name { grid-area: name; min-width: 0; }
.offer__meta { grid-area: meta; min-width: 0; display: flex; flex-wrap: wrap; gap: 0.35rem 1rem; color: var(--muted); font-size: 0.85rem; }
/* No nowrap: a price that is a sentence has to be allowed to wrap, or its
   track is sized to the unbroken string and the row overflows. A plain amount
   fits the track's floor on one line anyway. */
.offer__price { grid-area: price; font-weight: 700; min-width: 0; }
/* Right-aligned in the wide layout so the AUD suffix stacks in a straight line
   down the list instead of ragging with the number's width. Set in the
   container query above, alongside everything else the wide layout changes. */
.offer__price small { font-weight: 500; color: var(--muted); font-size: 0.72rem; }
.offer__cta { grid-area: cta; justify-self: end; }
.offer__save { grid-area: save; justify-self: end; }
.offer h3 { font-size: 1rem; letter-spacing: -0.01em; margin: 0; }
.offer h3 a { color: inherit; text-decoration: none; }

.logo-mark {
  width: 44px; height: 44px; border-radius: var(--r); display: grid; place-items: center;
  font-weight: 800; font-size: 0.85rem; color: #fff; letter-spacing: -0.03em; flex: none;
  overflow: hidden;
}
@media (min-width: 760px) { .logo-mark { width: 52px; height: 52px; } }

/* Brandmarks sit on white so logos with their own colour and logos with
   transparency both read correctly. */
.logo-mark--image { background: #fff; border: 1px solid var(--border); padding: 3px; }
.logo-mark--image img { width: 100%; height: 100%; object-fit: contain; }

.save-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; border-radius: var(--r-pill);
  border: 1px solid var(--border); background: var(--surface); color: var(--muted);
}
.save-btn:hover { color: var(--stop); border-color: var(--stop); }
.save-btn[aria-pressed="true"] { color: var(--stop); border-color: var(--stop); background: var(--stop-soft); }
.save-btn svg { width: 17px; height: 17px; }

/* ---- Stars ---- */
/* Gold, because a star rating that is not gold stops reading as a rating -
   this is one of the few conventions worth keeping literal. Pulled back from
   the old #f5a524 to sit in the warm palette rather than glow out of it. */
.stars { display: inline-flex; align-items: center; gap: 0.15rem; color: #b8860b; font-size: 0.8rem; }
.stars span { color: var(--muted); margin-inline-start: 0.3rem; }

/* ---- Tables ---- */
.table-wrap { overflow-x: auto; border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--surface); }
table.data { width: 100%; border-collapse: collapse; font-size: 0.9rem; min-width: 560px; }
table.data th, table.data td { padding: 0.75rem 0.9rem; text-align: left; border-bottom: 1px solid var(--border); vertical-align: top; }
table.data th { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); font-weight: 700; background: var(--surface-2); position: sticky; top: 0; }
table.data tr:last-child td { border-bottom: 0; }

/* ---- Signup ----
   The offer has to look like an offer. As a plain .panel it read as one more
   content block in a page full of them, which is part of why it was ignored.
   The brand tint and the accent rule are the whole treatment: enough to stop
   a scroll, not enough to feel like an interstitial. */
.signup { background: var(--brand-soft); border-color: var(--brand); display: grid; gap: var(--sp-4); }
.signup__head { font-size: var(--step-2); }
.signup__body { margin-top: 0.5rem; max-width: 52ch; }
.signup__form { display: grid; gap: 0.6rem; }
.signup__row { display: grid; gap: 0.5rem; }
.signup__fine { margin-top: 0.1rem; }
/* Side by side once there is room for the field and the button on one line,
   and for the copy to sit beside them rather than above. */
@media (min-width: 560px) {
  .signup__row { grid-template-columns: minmax(0, 1fr) auto; align-items: center; }
}
@media (min-width: 900px) {
  .signup { grid-template-columns: minmax(0, 1.05fr) minmax(320px, 0.95fr); align-items: center; gap: var(--sp-6); }
}

/* ---- Notices ---- */
.notice { display: flex; gap: 0.75rem; padding: var(--sp-4); border-radius: var(--r-lg); font-size: 0.9rem; }
.notice svg { width: 20px; height: 20px; flex: none; margin-top: 1px; }
.notice--info { background: var(--info-soft); color: var(--ink-2); }
.notice--warn { background: var(--warn-soft); color: var(--ink-2); }
.notice--ok { background: var(--ok-soft); color: var(--ink-2); }
.notice--stop { background: var(--stop-soft); color: var(--ink-2); }
.notice strong { color: var(--ink); }

/* ---- Breadcrumbs ---- */
.crumbs { display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; font-size: 0.8rem; color: var(--muted); padding: var(--sp-4) 0 0; }
.crumbs a { color: var(--muted); text-decoration: none; }
.crumbs a:hover { color: var(--ink); text-decoration: underline; }
.crumbs svg { width: 12px; height: 12px; opacity: 0.5; }

/* ---- Footer ---- */
.site-footer { border-top: 1px solid var(--border); margin-top: var(--sp-8); background: var(--surface); }
.site-footer .cols { display: grid; gap: var(--sp-5); padding: var(--sp-6) 0; grid-template-columns: 1fr; }
@media (min-width: 640px) { .site-footer .cols { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 960px) { .site-footer .cols { grid-template-columns: 1.5fr repeat(3, 1fr); } }
.site-footer h2 { font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); margin-bottom: 0.75rem; }
.site-footer ul { list-style: none; padding: 0; display: grid; gap: 0.45rem; }
.site-footer a { color: var(--ink-2); text-decoration: none; font-size: 0.9rem; }
.site-footer a:hover { color: var(--ink); text-decoration: underline; }
.site-footer .legal { border-top: 1px solid var(--border); padding: var(--sp-4) 0; display: flex; flex-wrap: wrap; gap: 0.75rem 1.5rem; align-items: center; font-size: 0.8rem; color: var(--muted); }

/* ---- Cookie / consent ---- */
.consent {
  position: fixed; inset-inline: 0.75rem; bottom: 0.75rem; z-index: 200;
  background: var(--surface); border: 1px solid var(--border-strong); border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg); padding: var(--sp-4); display: none;
  max-width: 620px; margin-inline: auto;
}
.consent[data-open="true"] { display: block; }
.consent .row { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.75rem; }

/* ---- Toast ---- */
.toast-host { position: fixed; inset-block-end: 1rem; inset-inline: 1rem; display: grid; gap: 0.5rem; z-index: 300; pointer-events: none; justify-items: center; }
.toast { background: var(--cta); color: var(--bg); padding: 0.65rem 1rem; border-radius: var(--r-pill); font-size: 0.875rem; box-shadow: var(--shadow-lg); }

/* ---- Modal / auth sheet ---- */
dialog.sheet {
  border: 0; border-radius: var(--r-xl); padding: 0; background: var(--surface); color: var(--ink);
  max-width: 420px; width: calc(100% - 2rem); box-shadow: var(--shadow-lg);
}
dialog.sheet::backdrop { background: rgba(16, 24, 43, 0.45); backdrop-filter: blur(2px); }
dialog.sheet .sheet__body { padding: var(--sp-6); }

/* ---- Data pages: charts, stat grids, ranked tables ---- */
/* Charts are inline SVG rather than a library, so their styling lives here and
   inherits the site's tokens. Text inside a chart wears the ink tokens; only
   the marks carry a series colour. */
.chart-fig { margin: var(--sp-4) 0 0; }
.chart-fig figcaption { display: flex; flex-wrap: wrap; gap: .2rem 1rem; align-items: baseline; margin-bottom: .6rem; }
.chart-wrap {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  padding: var(--sp-4); overflow-x: auto;
}
/* Deliberately more specific than the `svg:not([width])` icon default in the
   base sheet: a chart carries no width attribute either, and without this it
   inherits the 1.15em icon size and collapses to a smudge. */
.chart-wrap .chart { display: block; width: 100%; height: auto; min-width: 520px; flex: initial; }

/* The line chart stays SVG: it is a path, and a path is what SVG is for. The
   bar charts are HTML instead, because a bar is a box with a width - and an
   SVG bar chart has to be scaled as one rigid unit, which on a phone shrinks
   its labels to five pixels. HTML bars reflow and keep their type size. */
.ch-line { fill: none; stroke: var(--brand); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.ch-area { fill: var(--brand); opacity: .1; }
.ch-dot { fill: var(--brand); stroke: var(--surface); stroke-width: 2; }
.ch-grid { stroke: var(--border); stroke-width: 1; }
.ch-axis { fill: var(--muted); font-size: 11.5px; }
.ch-note { fill: var(--muted); font-size: 11.5px; font-weight: 600; }
.ch-hit { cursor: default; }

/* Ranked horizontal bars. */
.hbar { display: grid; gap: .5rem; }
.hbar__row { display: grid; grid-template-columns: minmax(0, 8rem) 1fr minmax(0, 5.4rem); gap: .8rem; align-items: center; }
.hbar__label { font-size: .85rem; font-weight: 600; color: var(--ink-2); text-align: right; overflow-wrap: anywhere; }
.hbar__track { display: block; height: 22px; }
/* Rounded at the data end, square at the baseline it grows from. */
.hbar__track > i { display: block; height: 100%; min-width: 3px; background: var(--brand); border-radius: 0 4px 4px 0; }
.hbar__value { font-size: .85rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.hbar__row:hover .hbar__track > i { opacity: .82; }

/* Diverging bars around a baseline placed by the data, not at the midpoint. */
.dbar { display: grid; gap: 0; }
.dbar__row { display: grid; grid-template-columns: minmax(0, 8rem) 1fr minmax(0, 3.6rem); gap: .8rem; align-items: center; padding: .25rem 0; }
.dbar__label { font-size: .85rem; font-weight: 600; color: var(--ink-2); text-align: right; overflow-wrap: anywhere; }
/* The zero line is painted as a background stripe on the plot rather than as
   an element per row, so it runs unbroken down the whole chart. */
.dbar__plot {
  position: relative; height: 30px; margin: -.25rem 0; display: block;
  background: linear-gradient(to right, transparent calc(var(--zero) - .5px), var(--muted-2) calc(var(--zero) - .5px),
    var(--muted-2) calc(var(--zero) + .5px), transparent calc(var(--zero) + .5px));
}
.dbar__fill { position: absolute; top: 4px; height: 22px; min-width: 3px; }
.dbar__fill--up { background: var(--brand); border-radius: 0 4px 4px 0; }
.dbar__fill--down { background: var(--warn); border-radius: 4px 0 0 4px; }
.dbar__value { font-size: .85rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.dbar__row:hover .dbar__fill { opacity: .82; }
/* The caption sits under the zero line rather than under the middle of the
   chart, because the zero line is rarely in the middle. It rides the same
   grid columns as a row so it lands in the plot column, then offsets by the
   same --zero the line uses. */
.dbar__axis {
  display: grid; grid-template-columns: minmax(0, 8rem) 1fr minmax(0, 3.6rem); gap: .8rem;
  margin: .45rem 0 0; font-size: .75rem; color: var(--muted);
}
.dbar__axis-tick { margin-left: var(--zero); transform: translateX(-50%); white-space: nowrap; }
@media (max-width: 560px) {
  /* Stacked layout: the caption goes back to plain centred text under the chart. */
  .dbar__axis { display: block; text-align: center; }
  .dbar__axis-tick { margin-left: 0; transform: none; }
}

/* Twelve months. The peak column is the point, so the rest recede. */
.mbar { display: flex; align-items: flex-end; gap: .25rem; }
.mbar__col { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; align-items: center; }
.mbar__slot { position: relative; width: 100%; height: 150px; display: flex; align-items: flex-end; justify-content: center; }
.mbar__slot > i { display: block; width: 100%; max-width: 22px; background: var(--brand); opacity: .38; border-radius: 4px 4px 0 0; }
.mbar__col--peak .mbar__slot > i { opacity: 1; }
.mbar__cap { position: absolute; left: 50%; transform: translateX(-50%); font-size: .74rem; font-weight: 700; white-space: nowrap; }
.mbar__lab { margin-top: .4rem; font-size: .72rem; color: var(--muted); }
.mbar__col:hover .mbar__slot > i { opacity: .82; }

@media (max-width: 560px) {
  /* Under this width there is no room for a label column beside the bar, so
     the label and its value move above a full-width track. */
  .hbar__row, .dbar__row {
    grid-template-columns: 1fr auto; grid-template-areas: "label value" "track track";
    row-gap: .15rem; column-gap: .6rem;
  }
  .hbar__label, .dbar__label { grid-area: label; text-align: left; }
  .hbar__value, .dbar__value { grid-area: value; }
  .hbar__track, .dbar__plot { grid-area: track; }
  .hbar__track { height: 16px; }
  .dbar__row { padding: .4rem 0; }
  .dbar__plot { height: 16px; margin: 0; }
  .dbar__fill { top: 0; height: 16px; }
  .mbar__slot { height: 110px; }
  .mbar__lab { font-size: .62rem; }
}

/* The hover readout. Values lead, the label follows. */
.ch-tip {
  position: fixed; z-index: 60; pointer-events: none; opacity: 0; transition: opacity .12s ease;
  background: var(--ink); color: #fff; border-radius: var(--r-sm); padding: .4rem .6rem;
  font-size: .78rem; line-height: 1.35; max-width: 15rem; box-shadow: var(--shadow-2);
}
.ch-tip[data-show] { opacity: 1; }
.ch-tip b { display: block; font-size: .92rem; }
.ch-tip span { color: var(--muted-2); }

.stat-grid { display: grid; gap: var(--sp-3); grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.stat-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  padding: var(--sp-4); display: flex; flex-direction: column; gap: .3rem;
}
.stat-card span { font-size: .8rem; color: var(--muted); }
.stat-card b { font-size: 1.5rem; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }

.data-table { width: 100%; border-collapse: collapse; font-size: .9rem; min-width: 640px; }
.data-table th, .data-table td { padding: .6rem .7rem; text-align: left; border-bottom: 1px solid var(--border); }
.data-table thead th { font-size: .78rem; color: var(--muted); font-weight: 700; white-space: nowrap; }
.data-table th[data-sortable] { cursor: pointer; user-select: none; }
.data-table th[data-sortable]:hover { color: var(--ink); }
.data-table th[aria-sort]::after { content: " \2193"; }
.data-table th[aria-sort="ascending"]::after { content: " \2191"; }
.data-table td.num, .data-table th[data-sortable="num"] { text-align: right; font-variant-numeric: tabular-nums; }
.data-table tbody tr:hover { background: var(--surface-2); }

.move { font-weight: 700; }
.move--up { color: var(--ok); }
.move--down { color: var(--warn); }

.tag-row { display: flex; flex-wrap: wrap; gap: .5rem; }

.data-details { margin-top: var(--sp-4); border-top: 1px solid var(--border); padding-top: var(--sp-3); }
.data-details summary { cursor: pointer; font-size: .88rem; font-weight: 600; color: var(--ink-2); }
.data-details > div { margin-top: var(--sp-3); }
/* Travel data calculator */
.data-calc{display:grid;grid-template-columns:minmax(0,1.25fr) minmax(18rem,.75fr);gap:var(--sp-5);align-items:start}
.data-calc__intro p{max-width:62ch;font-size:var(--step-1);margin-top:.6rem}
.data-calc__form fieldset{border:0;padding:0;margin:var(--sp-5) 0}.data-calc__form legend{font-weight:700;margin-bottom:var(--sp-3)}
.data-calc__row{padding:var(--sp-3) 0;border-top:1px solid var(--border)}.data-calc__row>div{display:flex;justify-content:space-between;gap:1rem}.data-calc__row label{font-weight:600}.data-calc__row input{width:100%;accent-color:var(--brand);margin-top:.55rem}
.data-calc__check{display:flex;gap:.65rem;align-items:flex-start;margin-bottom:var(--sp-4)}.data-calc__check input{margin-top:.25rem;accent-color:var(--brand)}.data-calc__check span,.data-calc__check small{display:block}
.data-calc__result{position:sticky;top:6rem}.data-calc__number{display:flex;align-items:baseline;gap:.65rem;margin:.35rem 0}.data-calc__number strong{font-size:clamp(2.6rem,7vw,4.5rem);line-height:1;color:var(--ink)}
.data-calc__meter{height:.65rem;background:var(--surface-2);border-radius:99px;overflow:hidden;margin:var(--sp-4) 0}.data-calc__meter span{display:block;height:100%;background:var(--brand);border-radius:inherit;transition:width .2s}
.data-calc__breakdown{display:grid;gap:.5rem;margin-bottom:var(--sp-4)}.data-calc__breakdown div{display:flex;justify-content:space-between;gap:1rem}.data-calc__breakdown dd{font-weight:700}.data-calc__tip{margin:var(--sp-4) 0}.data-calc__result>.btn{width:100%;justify-content:center}
@media(max-width:760px){.data-calc{grid-template-columns:1fr}.data-calc__result{position:static}}

/* ========================================================== layout ======= */

/* Sections breathe. This is the change that does most of the work: the same
   markup with half as much again of vertical room stops reading as a dense
   comparison site and starts reading as something considered. */
.section { padding-block: var(--sp-6); }
/* --sp-7 top and bottom means 7rem between two adjacent sections, which is
   generous. --sp-8 was tried and compounds to 11rem: correct for a resort's
   brochure, too much for a site someone is using to work out whether their
   passport is still valid. Air, not distance. */
@media (min-width: 720px) { .section { padding-block: var(--sp-7); } }
.section__head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 0.75rem; margin-bottom: var(--sp-4); }
.section__head h2 { font-size: var(--step-2); }
.section__head a { font-size: 0.9rem; font-weight: 600; text-decoration: none; display: inline-flex; align-items: center; gap: 0.3rem; }

/* Grid items default to min-width:auto, which lets long content push a track
   wider than the viewport. Every grid in the system opts out. */
.grid > *, .with-aside > *, .essentials-grid > *, .facts > *, .props > *,
.checklist > *, .rail > *, .offer > * { min-width: 0; }

.grid { display: grid; gap: var(--sp-4); }
.grid--2 { grid-template-columns: 1fr; }
.grid--3 { grid-template-columns: 1fr; }
.grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 640px) { .grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } .grid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 960px) { .grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } .grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

/* Two-column article layout with sticky sidebar */
.with-aside { display: grid; gap: var(--sp-5); align-items: start; }
@media (min-width: 1000px) { .with-aside { grid-template-columns: minmax(0, 1fr) 320px; gap: var(--sp-6); } }
/* Sticky ONLY where there is a sidebar to be sticky in. Below 1000px the
   aside is part of the single-column flow, and a sticky element there pins
   itself over the article and the text scrolls behind it. */
.aside-sticky { display: grid; gap: var(--sp-4); }
@media (min-width: 1000px) {
  .aside-sticky { position: sticky; top: calc(var(--header-h) + 1rem); }
}

/* Split variant: the main column is two blocks with the aside between them in
   the DOM, so on a phone the sidebar lands right after the answer instead of
   four screens below it. On desktop the two blocks rejoin column one and the
   aside spans both rows, so the sticky sidebar is unchanged. */
@media (min-width: 1000px) {
  .with-aside--split { grid-template-columns: minmax(0, 1fr) 320px; gap: var(--sp-6); }
  .with-aside--split > .col-main { grid-column: 1; }
  .with-aside--split > .col-main--a { grid-row: 1; }
  .with-aside--split > .col-main--b { grid-row: 2; margin-top: var(--sp-6); }
  .with-aside--split > .aside-sticky { grid-column: 2; grid-row: 1 / span 2; }
}

/* Horizontal scroller for cards on mobile */
.rail { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(240px, 78%); gap: var(--sp-4); overflow-x: auto; scroll-snap-type: x mandatory; padding-bottom: 0.5rem; margin-inline: -1rem; padding-inline: 1rem; }
.rail > * { scroll-snap-align: start; }
@media (min-width: 720px) { .rail { grid-auto-columns: minmax(230px, 1fr); grid-auto-flow: column; margin-inline: 0; padding-inline: 0; overflow-x: auto; } }
@media (min-width: 1000px) { .rail { grid-auto-flow: row; grid-template-columns: repeat(4, minmax(0, 1fr)); overflow: visible; } }

/* ---------------------------------------------------------- hero -------- */
.hero { padding-block: var(--sp-7) var(--sp-6); }
@media (min-width: 900px) { .hero { padding-block: var(--sp-7) var(--sp-6); } }
.hero__grid { display: grid; gap: var(--sp-5); align-items: center; }
@media (min-width: 900px) { .hero__grid { grid-template-columns: 1.05fr 1fr; gap: var(--sp-7); } }
.hero h1 { font-size: var(--step-4); letter-spacing: var(--track-display); }
.hero h1 .serif-accent { display: block; }
.hero__lede { font-size: var(--step-1); color: var(--muted); margin-top: var(--sp-5); max-width: 36ch; line-height: 1.5; }
.hero__media { position: relative; border-radius: var(--r-lg); overflow: hidden; box-shadow: var(--shadow-lg); aspect-ratio: 4 / 3.4; }
.hero__media > img, .hero__media > svg,
.hero__media > picture, .hero__media > picture > img { width: 100%; height: 100%; object-fit: cover; }

/* Trip search widget */
.trip-search { display: grid; gap: 0.75rem; }
@media (min-width: 560px) { .trip-search__row { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; } }
/* auto-width columns sized to their content, so a control is never narrower
   than the word inside it. The 1.3fr/1fr split squeezed "How busy?" until its
   own option ellipsised, which is a control telling the reader less than the
   label above it already did. */
@media (min-width: 560px) { .trip-search__row--3 { grid-template-columns: minmax(9rem, auto) minmax(8rem, auto) auto; justify-content: start; align-items: end; } }

.social-proof { display: flex; align-items: center; gap: 0.6rem; margin-top: var(--sp-4); font-size: 0.85rem; color: var(--muted); }
.avatars { display: flex; }
/* Three initials in the palette's own greys, staggered. They used to carry
   hard-coded indigo, cyan and amber straight from the markup, which made a
   decorative detail the brightest thing on the homepage. */
.avatars span { width: 26px; height: 26px; border-radius: 50%; border: 2px solid var(--bg); margin-inline-start: -8px; display: grid; place-items: center; font-size: 0.62rem; font-weight: 650; color: var(--surface); background: var(--muted-2); letter-spacing: 0; }
.avatars span:nth-child(2) { background: var(--muted); }
.avatars span:nth-child(3) { background: var(--ink-2); }
.avatars span:first-child { margin-inline-start: 0; }

/* ------------------------------------------------- essentials grid ------ */
.essentials-grid { display: grid; gap: var(--sp-4); grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 900px) { .essentials-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.essential-card { display: flex; flex-direction: column; gap: 0.7rem; padding: var(--sp-4); background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); text-decoration: none; color: inherit; position: relative; }
.essential-card:hover { border-color: var(--border-strong); box-shadow: var(--shadow); }
.essential-card__head { display: flex; align-items: center; gap: 0.6rem; }
.essential-card__head h3 { font-size: 0.95rem; letter-spacing: -0.01em; }
.essential-card__body { color: var(--muted); font-size: 0.85rem; flex: 1; display: grid; gap: 0.3rem; align-content: start; }
.essential-card__price { color: var(--ink); font-weight: 600; }
/* A price is information, not a status. Green said "good" about a number the
   site has no opinion on, and put a third colour into a card that already had
   two. */
.essential-card__price b { color: var(--ink); font-weight: 650; font-variant-numeric: tabular-nums; }
.essential-card .status { position: absolute; top: 0.85rem; right: 0.85rem; width: 20px; height: 20px; border-radius: 50%; display: grid; place-items: center; }
.essential-card .status svg { width: 12px; height: 12px; }
.status--done { background: var(--ok); color: #fff; }
.status--todo { background: var(--warn); color: #fff; }
.check-list { list-style: none; padding: 0; display: grid; gap: 0.35rem; font-size: 0.85rem; }
.check-list li { display: flex; align-items: center; gap: 0.45rem; color: var(--ink-2); }
.check-list svg { width: 14px; height: 14px; color: var(--ok); flex: none; }

/* ------------------------------------------------- destination cards ---- */
.dest-card { position: relative; border-radius: var(--r-lg); overflow: hidden; text-decoration: none; color: #fff; display: block; aspect-ratio: 4 / 3; background: var(--surface-2); }
.dest-card__art { position: absolute; inset: 0; }
.dest-card__art > svg, .dest-card__art > img,
.dest-card__art > picture, .dest-card__art > picture > img { width: 100%; height: 100%; object-fit: cover; }
.dest-card__body { position: absolute; inset-inline: 0; bottom: 0; padding: 0.9rem; background: linear-gradient(to top, rgba(9,12,22,0.85), rgba(9,12,22,0)); }
.dest-card h3 { font-size: 1.05rem; display: flex; align-items: center; gap: 0.4rem; }
.dest-card p { font-size: 0.8rem; opacity: 0.85; }

/* Destination hero banner */
.dest-hero { position: relative; border-radius: var(--r-xl); overflow: hidden; color: #fff; min-height: 210px; display: flex; align-items: flex-end; }
.dest-hero__art { position: absolute; inset: 0; }
.dest-hero__art > svg, .dest-hero__art > img,
.dest-hero__art > picture, .dest-hero__art > picture > img { width: 100%; height: 100%; object-fit: cover; }
.dest-hero__body { position: relative; padding: var(--sp-5); background: linear-gradient(to top, rgba(9,12,22,0.86), rgba(9,12,22,0.15)); width: 100%; }
.dest-hero h1 { font-size: var(--step-3); display: flex; align-items: center; gap: 0.5rem; }
.dest-hero p { opacity: 0.9; max-width: 60ch; margin-top: 0.35rem; }

/* Quick facts strip */
.facts { display: grid; gap: 0.75rem; grid-template-columns: repeat(2, minmax(0,1fr)); }
@media (min-width: 760px) { .facts { grid-template-columns: repeat(4, minmax(0,1fr)); } }
.fact { display: flex; gap: 0.6rem; align-items: flex-start; padding: 0.85rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); }
.fact svg { width: 18px; height: 18px; color: var(--muted-2); flex: none; margin-top: 2px; }
.fact b { display: block; font-size: 0.9rem; letter-spacing: -0.01em; }
.fact span { font-size: 0.78rem; color: var(--muted); }

/* Tabs */
.tabs { display: flex; gap: 0.25rem; overflow-x: auto; border-bottom: 1px solid var(--border); margin-bottom: var(--sp-5); }
.tabs a, .tabs button { padding: 0.7rem 0.85rem; font-size: 0.9rem; font-weight: 600; color: var(--muted); text-decoration: none; border: 0; background: none; border-bottom: 2px solid transparent; white-space: nowrap; }
.tabs a[aria-current="page"], .tabs button[aria-selected="true"] { color: var(--ink); border-bottom-color: var(--brand); }

/* ------------------------------------------------------- checklist ------ */
.checklist { display: grid; gap: var(--sp-5); }
@media (min-width: 760px) { .checklist { grid-template-columns: repeat(2, minmax(0,1fr)); } }
.checklist__group h3 { font-size: 0.95rem; margin-bottom: 0.65rem; }
.checklist__items { list-style: none; padding: 0; display: grid; gap: 0.15rem; }
.check-item { display: flex; gap: 0.65rem; align-items: flex-start; padding: 0.55rem 0.4rem; border-radius: var(--r-sm); }
.check-item:hover { background: var(--surface-2); }
.check-item input[type="checkbox"] { width: 20px; height: 20px; flex: none; margin-top: 1px; accent-color: var(--ok); cursor: pointer; }
.check-item label { cursor: pointer; font-size: 0.9rem; }
.check-item small { display: block; color: var(--muted); font-size: 0.78rem; }
.check-item[data-done="true"] label { color: var(--muted); text-decoration: line-through; text-decoration-color: var(--muted-2); }

/* ------------------------------------------------------------ prose ---- */
.prose { max-width: 68ch; }
.prose > * + * { margin-top: 1.1em; }

/* A picture in an article. It runs a little wider than the measure, because a
   photo constrained to 68ch reads as an afterthought, and never wider than the
   column it sits in. */
.article-figure { margin: 1.8em 0; }
.article-figure img {
  display: block; width: 100%; height: auto;
  border-radius: var(--r); background: var(--surface-2);
}
.article-figure figcaption {
  margin-top: .6rem; font-size: .875rem; color: var(--muted); line-height: 1.5;
}
.prose h2 { font-size: var(--step-2); margin-top: 2em; }
.prose h3 { font-size: var(--step-1); margin-top: 1.6em; }
.prose ul, .prose ol { padding-inline-start: 1.3em; display: grid; gap: 0.4em; }
.prose li { padding-inline-start: 0.15em; }
.prose blockquote { border-inline-start: 3px solid var(--brand); padding-inline-start: 1rem; color: var(--ink-2); font-style: italic; }
.prose code { background: var(--surface-2); border: 1px solid var(--border); border-radius: 5px; padding: 0.1em 0.35em; font-family: var(--font-mono); font-size: 0.88em; }
.prose hr { border: 0; border-top: 1px solid var(--border); margin-block: 2em; }
.prose img { border-radius: var(--r-lg); }
.prose em { color: var(--ink-2); }

.toc { font-size: 0.875rem; }
.toc ol { list-style: none; padding: 0; display: grid; gap: 0.45rem; counter-reset: toc; }
.toc a { color: var(--ink-2); text-decoration: none; display: block; }
.toc a:hover { color: var(--brand-ink); }

/* -------------------------------------------------------- FAQ ---------- */
.faq { display: grid; gap: 0.5rem; }
.faq details { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); overflow: hidden; }
.faq summary { padding: var(--sp-4); font-weight: 600; cursor: pointer; list-style: none; display: flex; justify-content: space-between; gap: 1rem; align-items: center; font-size: 0.95rem; }
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after { content: "+"; font-size: 1.25rem; color: var(--muted); font-weight: 400; line-height: 1; }
.faq details[open] summary::after { content: "−"; }
.faq details > div { padding: 0 var(--sp-4) var(--sp-4); color: var(--ink-2); font-size: 0.925rem; }
.faq details > div > * + * { margin-top: 0.8em; }

/* In an article the question stays a real <h3> for the outline, so its prose
   sizing has to be undone inside the summary - it is a row label there, not a
   heading in the flow. */
.faq--article summary h3 { font-size: inherit; font-weight: inherit; margin: 0; }
.prose .faq--article ul, .prose .faq--article ol { padding-inline-start: 1.3em; display: grid; gap: 0.4em; }

/* --------------------------------------------------- value props ------- */
.props { display: grid; gap: var(--sp-4); grid-template-columns: repeat(2, minmax(0,1fr)); }
@media (min-width: 900px) { .props { grid-template-columns: repeat(4, minmax(0,1fr)); } }
.prop { display: flex; gap: 0.7rem; align-items: flex-start; }
.prop svg { width: 20px; height: 20px; color: var(--muted-2); flex: none; margin-top: 2px; }
.prop b { display: block; font-size: 0.9rem; }
.prop span { font-size: 0.82rem; color: var(--muted); }

/* --------------------------------------------------------- ad slots ---- */
.ad-slot { display: grid; place-items: center; min-height: 90px; border: 1px dashed var(--border-strong); border-radius: var(--r-lg); background: var(--surface-2); color: var(--muted-2); font-size: 0.78rem; text-align: center; padding: var(--sp-4); }
.ad-slot[data-filled="true"] { border-style: solid; border-color: var(--border); background: var(--surface); }
.ad-label { font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted-2); display: block; margin-bottom: 0.35rem; }
/* An editorial badge - not a paid placement. See offerRow() in components.js. */
.offer--badged { outline: 1px solid var(--brand-soft); background: color-mix(in srgb, var(--brand-soft) 45%, var(--surface)); }

/* ------------------------------------------------------------ auth ----- */
.sso-buttons { display: grid; gap: 0.6rem; }
.sso {
  display: flex; align-items: center; justify-content: center; gap: 0.65rem;
  padding: 0.75rem 1rem; min-height: 48px; width: 100%;
  border: 1px solid var(--border-strong); border-radius: var(--r); background: var(--surface);
  font-weight: 600; font-size: 0.95rem; color: var(--ink); text-decoration: none;
}
.sso:hover { background: var(--surface-2); }
.sso svg { width: 19px; height: 19px; flex: none; }
.sso--apple { background: #000; color: #fff; border-color: #000; }
.sso--apple:hover { background: #1a1a1a; }
.divider { display: flex; align-items: center; gap: 0.75rem; color: var(--muted-2); font-size: 0.78rem; margin-block: var(--sp-4); }
.divider::before, .divider::after { content: ""; flex: 1; height: 1px; background: var(--border); }

/* -------------------------------------------------------- utilities --- */
[hidden] { display: none !important; }
.js-only { display: none; }
html.js .js-only { display: revert; }
html.js .no-js-only { display: none; }
.print-only { display: none; }

@media print {
  .site-header, .site-footer, .consent, .ad-slot, .btn, .save-btn, .print-hide { display: none !important; }
  body { background: #fff; }
  .card, .panel { break-inside: avoid; box-shadow: none; border-color: #ccc; }
  .print-only { display: revert; }
}

/* -------------------------------------------------- carry-on verdicts ----
   The yes / no / conditions chips on /can-i-take-it-on-a-plane/. Colour is
   never the only signal: each chip carries its icon and the word.          */
.verdict {
  display: inline-flex; align-items: center; gap: 0.3rem;
  font-size: 0.78rem; font-weight: 600; padding: 0.22rem 0.55rem;
  border-radius: 999px; white-space: nowrap;
}
.verdict svg { width: 13px; height: 13px; flex: none; }
.verdict--ok { background: var(--ok-soft); color: var(--ok); }
.verdict--warn { background: var(--warn-soft); color: var(--warn); }
.verdict--stop { background: var(--stop-soft); color: var(--stop); }

/* ------------------------------------------------------ widget slot ----
   A partner's own embed, set per page in the console. The embed brings its
   own styling inside an iframe, so this only supplies the frame around it:
   the site's heading treatment above, and a surface that keeps a white
   third-party card from floating on the page ground with no edge.        */
/* Collapsed until it has a widget, but still in the layout so the observer
   that lazy-loads it can see it at all. */
.widget-slot { padding-block: 0; }
.widget-slot:has(.widget-slot__inner:not([hidden])) { padding-block: var(--sp-6); }
.widget-slot__head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 0.75rem; margin-bottom: var(--sp-4); }
.widget-slot__head .ad-label { margin-bottom: 0; }
.widget-slot__body {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--shadow-sm);
  padding: var(--sp-4); overflow-x: auto;
}
/* Klook lays its cards out at a fixed width and relies on the page to scroll
   rather than wrapping, so the frame scrolls instead of the document. */
.widget-slot__body > * { max-width: none; }

/* Trim the partner frame to the size of what it actually draws.
   ---------------------------------------------------------------------------
   Klook's widget is not responsive in the way an embed usually is. It lays its
   three cards out at a fixed 646px and centres them however wide a frame it is
   given, and it stamps its iframe with a constant height:470px rather than the
   height of its own content. Measured at a range of viewports, that leaves:

     >= ~750px   cards 646 wide in a 1098 frame, content 382 tall in 470
                 -> 226px of dead white each side, 88px below
     <  ~750px   cards stack, content 488 tall in the same 470 frame
                 -> the bottom 18px is clipped, since the frame hides overflow

   So the slot is capped near the card width, and the frame is given the height
   the cards occupy in each of Klook's two layouts. The heading is capped with
   it so the box still lines up under its own title.

   Both numbers are Klook's rather than ours, and the breakpoint is the frame's
   width rather than the viewport's - hence 760px, comfortably clear of the
   ~670px frame width where their layout flips. scripts/smoke-widget.mjs
   measures the rendered cards against the frame at both layouts, so if Klook
   changes either one the check fails rather than the site quietly clipping.

   The slot carries a provider marker from our own template. Do not detect
   Klook from the wrapper id it happens to inject: that id is third-party
   implementation detail and changed, leaving the desktop iframe at zero
   height inside a full-width empty white box. */
.widget-slot[data-widget-provider="klook"] .widget-slot__inner {
  max-width: 760px;
  margin-inline: auto;
}
.widget-slot[data-widget-provider="klook"] .widget-slot__body iframe {
  display: block;
  width: 100% !important;
  min-width: 0;
  height: 505px !important;
  border: 0;
}
@media (min-width: 760px) {
  .widget-slot[data-widget-provider="klook"] .widget-slot__body iframe { height: 400px !important; }
}

/* The partner banner, at the foot of the content on every page but the home
   page.
   ---------------------------------------------------------------------------
   A fixed 300x250 display creative, which is the opposite problem to the Klook
   widget above: nothing to trim, but a full-bleed section around a 300px box
   reads as a mistake. So the banner is centred in its own column and keeps its
   own pixel size - stretching a fixed-size creative is how it ends up
   letterboxed or cropped.

   No card and no heading, on purpose. A cross-origin frame cannot be asked
   whether it painted, so the empty state is made harmless rather than
   detected: with Trip.com blocked or down, what is left is a label over page
   background, not an empty bordered box.

   max-width on the frame rather than a media query: at 320px the viewport is
   narrower than the creative, and the alternative to letting it shrink is a
   sideways scrollbar on the whole document. */
.trip-banner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
  /* More air above than below: above it separates the advert from the last
     thing the reader was actually reading, below the footer brings its own
     padding. It is also what the blocked case costs - whatever this slot
     reserves is the gap a reader sees on the day Trip.com does not paint, and
     scripts/smoke-widget.mjs measures the whole thing against the creative
     with the banner blocked, so loosening it fails there rather than on the
     page. */
  padding-block: var(--sp-6) var(--sp-5);
  padding-inline: var(--sp-4);
}
.trip-banner .ad-label { margin-bottom: 0; }
.trip-banner__frame {
  display: block;
  width: 300px;
  height: 250px;
  max-width: 100%;
  border: 0;
}


/* ============================================================== FX ====== */
/* One series, so the ink budget is: a 2px line, a 10% wash, hairline grid. */

.fx { position: relative; }
.fx[data-loading="true"] { opacity: 0.6; }

.fx__head { display: flex; flex-wrap: wrap; gap: 1rem; align-items: flex-start; justify-content: space-between; }
.fx__rate { font-size: var(--step-3); font-weight: 700; letter-spacing: -0.03em; line-height: 1.1; }
.fx__rate small { font-size: 0.5em; font-weight: 600; color: var(--muted); letter-spacing: 0; }
.fx__asof { font-size: 0.78rem; color: var(--muted); margin-top: 0.2rem; }

.fx__ranges { display: flex; gap: 0.35rem; }
.fx__ranges .pill { padding: 0.3rem 0.7rem; font-size: 0.8rem; }

.fx__verdict { display: flex; gap: 0.65rem; align-items: flex-start; padding: 0.8rem 0.9rem; border-radius: var(--r); margin-top: var(--sp-4); font-size: 0.875rem; }
.fx__verdict b { display: block; }
.fx__verdict span { color: var(--ink-2); }
.fx__verdict svg { width: 18px; height: 18px; flex: none; margin-top: 1px; }
.fx__verdict[data-level="good"] { background: var(--ok-soft); }
.fx__verdict[data-level="good"] svg { color: var(--ok); }
.fx__verdict[data-level="ok"], .fx__verdict[data-level="neutral"] { background: var(--info-soft); }
.fx__verdict[data-level="ok"] svg, .fx__verdict[data-level="neutral"] svg { color: var(--info); }
.fx__verdict[data-level="watch"] { background: var(--warn-soft); }
.fx__verdict[data-level="watch"] svg { color: var(--warn); }

.fx__stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.75rem; margin-top: var(--sp-4); }
.fx__stats span { display: block; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); }
.fx__stats b { font-size: 0.95rem; }

.fx__chart { position: relative; margin-top: var(--sp-4); }
.fx-chart { width: 100%; height: auto; display: block; overflow: visible; touch-action: pan-y; }

.fx-line { fill: none; stroke: var(--brand); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.fx-end { fill: var(--brand); stroke: var(--surface); stroke-width: 2; }
.fx-grid { stroke: var(--border); stroke-width: 1; }
.fx-avg { stroke: var(--muted-2); stroke-width: 1; stroke-dasharray: 3 3; opacity: 0.65; }
.fx-tick, .fx-axis { fill: var(--muted); font-size: 11px; font-family: var(--font); }
.fx-avg-label { fill: var(--muted-2); font-size: 10px; font-family: var(--font); letter-spacing: 0.02em; }
.fx-axis--end { text-anchor: end; }
.fx-cross { stroke: var(--border-strong); stroke-width: 1; }
.fx-dot { fill: var(--brand); stroke: var(--surface); stroke-width: 2; }

.fx-tip {
  position: absolute; top: 2px; transform: translateX(-50%);
  background: var(--cta); color: var(--bg); padding: 0.3rem 0.55rem; border-radius: var(--r-sm);
  font-size: 0.78rem; white-space: nowrap; pointer-events: none; box-shadow: var(--shadow);
}
.fx-tip b { font-weight: 700; }
.fx-tip span { opacity: 0.75; margin-inline-start: 0.4rem; }

.fx__disclaimer { font-size: 0.78rem; color: var(--muted); margin-top: var(--sp-4); }
.fx__table { margin-top: var(--sp-4); }
.fx__table table { font-size: 0.85rem; }
.fx__table summary { cursor: pointer; font-size: 0.85rem; color: var(--ink-2); }

/* ========================================================== account ===== */
/* Same tokens as the rest of the site: white cards, soft border, one accent. */

.account-gate { max-width: 460px; margin-inline: auto; }

.account { display: grid; gap: var(--sp-5); align-items: start; }
@media (min-width: 900px) { .account { grid-template-columns: 280px minmax(0, 1fr); gap: var(--sp-6); } }

/* ---- sidebar ---- */
.account__side { display: grid; gap: var(--sp-4); }
@media (min-width: 900px) { .account__side { position: sticky; top: calc(var(--header-h) + 1rem); } }

.account__identity { text-align: center; padding: var(--sp-5) var(--sp-4); background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-xl); }
.avatar {
  display: grid; place-items: center; width: 76px; height: 76px; margin-inline: auto;
  border-radius: 50%; background: var(--brand); color: #fff;
  font-size: 1.6rem; font-weight: 700; letter-spacing: -0.02em;
}
.avatar img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; }
.account__greeting { font-size: var(--step-2); font-weight: 700; line-height: 1.2; margin-top: var(--sp-4); }
.account__email { font-size: 0.82rem; color: var(--muted); margin-top: 0.2rem; word-break: break-all; }

.account__nav { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-xl); overflow: hidden; }
.account__nav-item {
  display: flex; align-items: center; gap: 0.7rem; padding: 0.85rem 1rem;
  color: var(--ink-2); text-decoration: none; font-size: 0.925rem; font-weight: 500;
  border-bottom: 1px solid var(--border);
}
.account__nav-item:last-child { border-bottom: 0; }
.account__nav-item:hover { background: var(--surface-2); color: var(--ink); }
.account__nav-item[aria-current="true"] { background: var(--brand-soft); color: var(--brand-ink); font-weight: 600; }
.account__nav-item span { flex: 1; }
.account__nav-item svg:last-child { color: var(--muted-2); width: 15px; height: 15px; }

/* ---- cards ---- */
.account__main { display: grid; gap: var(--sp-5); min-width: 0; }
.account-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-xl); overflow: hidden; }
.account-card__head { padding: var(--sp-4) var(--sp-5); background: var(--surface-2); border-bottom: 1px solid var(--border); }
.account-card__head h2 { font-size: 0.95rem; letter-spacing: -0.01em; }
.account-card__body { padding: var(--sp-5); }
.account-card__actions { display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: center; margin-top: var(--sp-5); }

.field-grid { display: grid; gap: var(--sp-4); grid-template-columns: 1fr; }
@media (min-width: 620px) { .field-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.field-grid .full { grid-column: 1 / -1; }
.field__optional { font-weight: 500; color: var(--muted-2); text-transform: none; letter-spacing: 0; }
.field__hint { font-size: 0.78rem; color: var(--muted); margin-top: 0.35rem; }
.input:disabled { background: var(--surface-2); color: var(--muted); cursor: not-allowed; }

/* ---- preference switches ---- */
.switch-row { display: flex; gap: 0.8rem; align-items: flex-start; padding: 0.85rem 0; border-bottom: 1px solid var(--border); cursor: pointer; }
.switch-row:last-of-type { border-bottom: 0; }
.switch-row input { width: 20px; height: 20px; flex: none; margin-top: 2px; accent-color: var(--brand); cursor: pointer; }
.switch-row b { display: block; font-size: 0.925rem; font-weight: 600; }
.switch-row small { display: block; font-size: 0.82rem; color: var(--muted); margin-top: 0.15rem; }

.sso--facebook { background: #1877F2; color: #fff; border-color: #1877F2; }
.sso--facebook:hover { background: #166fe0; }
.sso--facebook svg { fill: #fff; }

/* ---------------------------------------------------------- the gate ----
 * The sign-in page an unauthenticated request to a restricted area receives.
 * It lives in the public stylesheet rather than the console's own, because a
 * page that is shown to signed-out visitors cannot depend on a stylesheet
 * that is itself behind the gate. Nothing here describes what is inside.
 */
.gate-page { min-height: 100dvh; display: grid; place-items: center; padding: var(--sp-5); background: var(--surface-2); }
.gate-card {
  width: 100%; max-width: 380px; background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-xl); box-shadow: var(--shadow); padding: var(--sp-6);
}
.gate-brand { display: inline-flex; align-items: center; gap: .45rem; font-weight: 700; letter-spacing: -.03em; color: var(--ink); text-decoration: none; }
.gate-card h1 { font-size: var(--step-2); margin-top: var(--sp-5); }
.gate-card > p:first-of-type { margin-top: .5rem; }
.gate-card .sso-buttons { margin-top: var(--sp-5); }

/* ------------------------------------------------------- waiting for a link --
   Shown after a sign-in link is sent. The page has nothing to do until someone
   opens their email, so it says where the link went, where to look if it has
   not arrived, and offers to send it again. */
.signin-wait {
  margin-top: .9rem; padding: .9rem 1rem;
  border: 1px solid var(--border); border-radius: var(--r); background: var(--surface-2);
  display: grid; gap: .5rem; justify-items: start;
  text-align: left;
}
.signin-wait p { margin: 0; }
.signin-wait__sent { font-size: .9rem; color: var(--ink); overflow-wrap: anywhere; }
.signin-wait__hint { font-size: .825rem; color: var(--muted); line-height: 1.5; }
.signin-wait [data-resend-link] { margin-top: .2rem; }

/* --------------------------------------------------------- article covers --
 * One 16:9 box, whatever goes inside it. Fixing the ratio in CSS and the
 * width/height in the markup means the space is reserved before the photo
 * loads, so nothing on the page jumps.
 */
.card__media {
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--surface-2, #f3f0ea);
}
.card__media > img,
.card__media > picture > img,
.card__media > svg {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* A 16:9 cover at full column width is over 600px tall on a desktop, which
 * pushes the first paragraph off the screen. Cap the height and let the crop
 * take care of the rest. */
.article-cover {
  margin: var(--sp-5) 0 var(--sp-6);
  border-radius: var(--r-lg);
  overflow: hidden;
  aspect-ratio: 16 / 9;
  max-height: 380px;
  background: var(--surface-2, #f3f0ea);
}
.article-cover > img,
.article-cover > picture > img,
.article-cover > svg {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* A cover that runs the full width of the reading column looks oversized on a
 * phone, where the column already is the screen. Keep it a little shorter. */
@media (max-width: 640px) {
  .article-cover { aspect-ratio: 3 / 2; max-height: none; margin-bottom: var(--sp-5); }
}

/* ---------------------------------------------------------- site search --
 * Sits above the region pills on /destinations/. The two are alternatives, so
 * the pills hide while a query is active.
 */
.site-search {
  display: flex;
  gap: .6rem;
  margin-bottom: var(--sp-4);
  max-width: 560px;
}
.site-search .input { flex: 1 1 auto; min-width: 0; }
.site-search .btn { flex: none; }

@media (max-width: 480px) {
  .site-search .btn { padding-inline: var(--sp-4); }
}

/* ------------------------------------------------------------ house ads --
 * Fills an ad slot when no campaign is booked for it. Two shapes: the sidebar
 * rail stacks, the leaderboard runs across. Both are the same markup.
 */
.ad-slot--house {
  /* .ad-slot centres its placeholder text; a filled promo is left-aligned like
     everything else on the page. Switching to block is not enough on its own:
     `place-items: center` also sets justify-self on the children, which current
     browsers honour for block-level boxes too, so a width-capped child still
     gets centred. Reset the alignment as well. */
  display: block;
  place-items: start;
  justify-items: start;
  border-style: solid;
  border-color: var(--border);
  background: var(--surface);
  padding: var(--sp-4);
  text-align: left;
}
.ad-slot--house .ad-label { text-align: left; margin-bottom: var(--sp-3); }


.house-ad {
  display: grid;
  gap: var(--sp-3);
  width: 100%;
  text-decoration: none;
  color: inherit;
  text-align: left;
}
.house-ad:hover .house-ad__headline { text-decoration: underline; }
.house-ad:focus-visible { outline: 2px solid var(--brand); outline-offset: 4px; border-radius: var(--r-md); }

.house-ad__logo {
  width: 40px;
  height: 40px;
  border-radius: var(--r-sm);
  object-fit: contain;
  background: #fff;
  padding: 2px;
  border: 1px solid var(--border);
}
.house-ad__text { display: grid; gap: .3rem; min-width: 0; }
.house-ad__headline { font-size: .95rem; line-height: 1.25; color: var(--ink); }
.house-ad__body { font-size: .8rem; line-height: 1.45; color: var(--muted); }
.house-ad__cta { justify-self: start; white-space: nowrap; }

/* Sidebar rail: everything stacked, left aligned. */
.house-ad--tall { justify-items: start; gap: var(--sp-3); }
.house-ad--tall .house-ad__cta { margin-top: .2rem; }

/* Leaderboard: logo, then text, then the button on the right. Collapses to the
 * stacked layout when the column is too narrow for three across. */
.house-ad--wide {
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--sp-4);
}
/* The button sits at the far right of a wide slot; the words should not
   stretch to meet it, so cap the text and let the gap absorb the rest. */
.house-ad--wide .house-ad__text { max-width: 62ch; }
.house-ad--wide .house-ad__cta { justify-self: end; }

@media (max-width: 560px) {
  .house-ad--wide {
    grid-template-columns: auto 1fr;
    gap: var(--sp-3) var(--sp-4);
  }
  .house-ad--wide .house-ad__cta { grid-column: 1 / -1; justify-self: start; }
}

/* ==========================================================================
   Trip planner
   --------------------------------------------------------------------------
   Extends the existing card and chip system rather than introducing a second
   visual language. Nothing here invents a token: colour, radius, spacing and
   type all come from 01-tokens.css, so the planner reads as part of the site
   and follows it if the palette changes.

   The one new idea is the day rail - a thin left-hand spine down each day so a
   multi-day plan scans as a sequence at a glance, without numbering every item
   like a set of instructions. A plan is a shape, not a procedure.
   ========================================================================== */

/* ------------------------------------------------------------- controls -- */

.plan-controls {
  margin-top: var(--sp-6);
  display: grid;
  gap: var(--sp-5);
}

.plan-controls__set { border: 0; margin: 0; padding: 0; min-width: 0; }

.plan-controls__legend {
  padding: 0;
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--muted);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  margin-bottom: var(--sp-3);
}

.plan-controls__row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-5);
  align-items: flex-start;
}

/* The theme chooser. Four real options, each saying what it is - not four
   identical cards distinguished only by an icon. */
.plan-choice {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--sp-3);
}

.plan-choice__opt {
  display: grid;
  gap: .35rem;
  padding: var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface);
  text-decoration: none;
  color: inherit;
  transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}

.plan-choice__opt:hover { border-color: var(--brand-soft); transform: translateY(-1px); }
.plan-choice__opt:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

.plan-choice__opt.is-on {
  border-color: var(--brand);
  box-shadow: inset 0 0 0 1px var(--brand);
}

.plan-choice__name { font-weight: 650; font-size: .98rem; }
.plan-choice__sub { line-height: 1.35; }

/* Pace and length: a segmented control, because these are a small closed set
   and a dropdown would hide half the answer. */
.plan-seg { display: inline-flex; flex-wrap: wrap; gap: .35rem; }

.plan-seg__opt {
  position: relative;
  display: inline-flex;
  align-items: center;
  padding: .5rem .85rem;
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--surface);
  font-size: var(--step--1);
  cursor: pointer;
  user-select: none;
  transition: border-color .15s ease, background .15s ease;
}

.plan-seg__opt:hover { border-color: var(--brand-soft); }
.plan-seg__opt.is-on { border-color: var(--brand); background: var(--brand-soft); font-weight: 600; }

/* The radio stays in the DOM and keeps its hit area - it is what makes the
   control work without JavaScript and what a screen reader announces. */
.plan-seg__opt input {
  position: absolute;
  inset: 0;
  opacity: 0;
  cursor: pointer;
  margin: 0;
}

.plan-seg__opt:focus-within { outline: 2px solid var(--brand); outline-offset: 2px; }

/* ----------------------------------------------------------------- days -- */

.plan-days { list-style: none; margin: var(--sp-5) 0 0; padding: 0; display: grid; gap: var(--sp-5); }

.plan-day {
  position: relative;
  padding-left: var(--sp-5);
  border-left: 2px solid var(--border);
}

.plan-day__h {
  font-size: var(--step--1);
  font-weight: 650;
  letter-spacing: .03em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 var(--sp-3);
}

/* The marker on the rail. Small, and it does not carry a number - the heading
   already says which day it is, and numbering twice reads as a form. */
.plan-day::before {
  content: "";
  position: absolute;
  left: -5px;
  top: .35rem;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--brand);
}

.plan-day__slots { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-3); }
.plan-day__note { margin-top: var(--sp-3); font-style: italic; }

/* ---------------------------------------------------------------- slots -- */

/* Three columns: the photo, what it is, and the way to book it. The photo is
   the reason this reads as a plan rather than a list - a name alone does not
   tell anyone whether they want to spend an afternoon on it. */
.plan-slot {
  display: grid;
  grid-template-columns: 10rem 1fr auto;
  gap: var(--sp-4);
  align-items: center;
  padding: var(--sp-3);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface);
  transition: border-color .15s ease, box-shadow .15s ease;
}

.plan-slot:hover { border-color: var(--brand-soft); box-shadow: var(--shadow-sm); }

/* Fixed box with the ratio declared, so the row holds its height from first
   paint and a photo arriving late does not shove the rest of the day down. */
.plan-slot__media {
  position: relative;
  width: 10rem;
  aspect-ratio: 4 / 3;
  border-radius: var(--r);
  overflow: hidden;
  background: var(--surface-2);
}

.plan-slot__img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Not every item has a photo - the researched fills and the destination
   attractions do not. A themed tile is the honest answer: it looks chosen,
   where a stretched stock photo or an empty grey box looks broken. */
.plan-slot__art {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
}

.plan-slot__art svg { width: 32px; height: 32px; opacity: .85; }

.plan-slot__body { min-width: 0; display: grid; gap: .25rem; }

/* The time of day, as a glyph and a word. A sunrise, a sun and a moon down the
   left of a day let it be read as a shape at a glance, which three identical
   text labels never do. */
.plan-slot__when {
  display: flex;
  align-items: center;
  gap: .35rem;
  margin: 0;
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--muted);
}

.plan-slot__when svg { width: 15px; height: 15px; flex: none; }

.plan-slot__name { margin: 0; font-weight: 650; font-size: 1rem; line-height: 1.3; }
.plan-slot__meta { margin: 0; font-size: var(--step--1); color: var(--muted); }
.plan-slot__why { margin: 0; font-size: var(--step--1); line-height: 1.45; max-width: 54ch; }
.plan-slot__cta { display: flex; align-items: center; }
.plan-slot__self { white-space: nowrap; }

/* A supporting item is visibly a supporting item. Passing an off-theme
   suggestion off as part of the theme would be the dishonest option. */
.plan-slot--support { background: transparent; border-style: dashed; }
.plan-slot--support .plan-slot__media { opacity: .88; }

.plan-slot__tag {
  display: inline-block;
  margin-left: .1rem;
  padding: .1rem .4rem;
  border-radius: var(--r-sm);
  background: var(--surface-2);
  border: 1px solid var(--border);
  font-size: .66rem;
  font-weight: 500;
  letter-spacing: .02em;
  text-transform: none;
  color: var(--muted);
  white-space: nowrap;
}

/* ----------------------------------------------------------- budget ----- */

/* Anchors, not radio buttons, because two of these are separate pages. They
   borrow the theme chooser's shape so the two controls read as one row of
   decisions rather than two unrelated widgets. */
.plan-budget {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: var(--sp-3);
}

.plan-budget__opt {
  display: grid;
  gap: .3rem;
  padding: var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface);
  text-decoration: none;
  color: inherit;
  transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}

.plan-budget__opt:hover { border-color: var(--brand-soft); transform: translateY(-1px); }
.plan-budget__opt:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.plan-budget__opt.is-on { border-color: var(--brand); box-shadow: inset 0 0 0 1px var(--brand); }
.plan-budget__name { font-weight: 650; font-size: .98rem; }
.plan-budget__sub { line-height: 1.35; }

/* ----------------------------------------------------------- travel ----- */

/* The route, above the days: where you sleep and for how long. It is the first
   thing a reader checks - "how much of this is me on a train?" - and the plan
   could not have shown it before, because it did not know where anything was. */
.plan-route {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  margin: var(--sp-3) 0 0;
  font-size: var(--step--1);
}

.plan-route__stop {
  display: inline-flex;
  align-items: baseline;
  gap: .35rem;
  padding: .3rem .6rem;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: var(--surface);
  white-space: nowrap;
}

.plan-route > span[data-plan-route] {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
}

.plan-route__label {
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--muted);
}

.plan-route__arrow { display: inline-flex; color: var(--muted); }
.plan-route__arrow svg { width: 16px; height: 16px; }
.plan-route__km { color: var(--muted); margin-left: .15rem; }

/* A travel leg is a row in the day, not a footnote, because half a day on a
   train is half a day gone. It reads as part of the sequence but visibly
   different from something you chose to do: no photo, a tinted panel, and the
   mode's own glyph where the picture would be. */
.plan-slot--leg {
  background: var(--surface-2);
  border-style: dashed;
  border-color: var(--brand-soft);
}

.plan-slot__media--leg {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--brand-soft);
  color: var(--brand);
}

.plan-slot__media--leg svg { width: 34px; height: 34px; }

.plan-slot--leg .plan-slot__when { color: var(--brand); }

/* The day a reader changes base gets a marker they can find by scanning. */
.plan-day--move::before { background: var(--brand); box-shadow: 0 0 0 3px var(--brand-soft); }

.plan-day__place {
  font-weight: 600;
  color: var(--ink-2);
  text-transform: none;
  letter-spacing: 0;
}

.plan-day__place::before { content: "· "; color: var(--muted); }

/* ----------------------------------------------------------- rating ----- */

/* Shown only where enough people are behind it - see ratingBadge(). The star
   is filled and small; a row of five would be five times the ink for the same
   information. */
.plan-rating {
  display: inline-flex;
  align-items: center;
  gap: .25rem;
  margin-right: .5rem;
  color: var(--ink-2);
}

.plan-rating svg { width: 14px; height: 14px; color: var(--warn); }
.plan-rating b { font-weight: 650; font-variant-numeric: tabular-nums; }
.plan-rating__n { color: var(--muted); font-weight: 400; }

/* ------------------------------------------------------------- critical -- */

.plan-critical {
  list-style: none;
  margin: var(--sp-4) 0 0;
  padding: 0;
  display: grid;
  gap: .5rem;
}

.plan-critical li {
  display: flex;
  gap: .5rem;
  align-items: flex-start;
  font-size: var(--step--1);
}

.plan-critical svg { flex: none; margin-top: .15rem; color: var(--warn); }

/* ----------------------------------------------------------------- 560 --- */

@media (max-width: 560px) {
  /* The three-column row does not survive a phone: the photo and the button
     squeeze the name until it wraps to a word a line. The photo shrinks and
     keeps its place beside the name - dropping it entirely would take away the
     thing that makes the plan readable - and the button moves to its own row
     underneath, full width, where a thumb can reach it. */
  .plan-slot {
    grid-template-columns: 5.25rem 1fr;
    column-gap: var(--sp-3);
    row-gap: var(--sp-3);
    align-items: start;
  }

  .plan-slot__media { width: 5.25rem; }
  .plan-slot__art svg { width: 24px; height: 24px; }
  .plan-slot__media--leg svg { width: 24px; height: 24px; }
  /* The journey is the one row whose explanation is not optional, so unlike
     an activity's blurb it survives the phone. */
  .plan-slot--leg .plan-slot__why { display: block; }
  .plan-route { font-size: .82rem; }
  .plan-rating__n { display: none; }
  .plan-slot__why { display: none; }

  .plan-slot__cta { grid-column: 1 / -1; }
  .plan-slot__cta .btn { width: 100%; justify-content: center; }

  .plan-day { padding-left: var(--sp-4); }
  .plan-choice, .plan-budget { grid-template-columns: 1fr; }
  .plan-controls__row { gap: var(--sp-4); }
}

@media print {
  /* A plan is a thing people print and take with them. Photos are dropped -
     they are what makes it readable on a screen and what makes it unreadable
     out of an office printer. */
  .plan-controls, .plan-slot__cta, .plan-slot__media { display: none; }
  /* The leg keeps its panel in print - it is the part you check at the station. */
  .plan-slot--leg { background: none; border: 1px solid #999; }
  .plan-slot { grid-template-columns: 1fr; break-inside: avoid; border-color: #999; }
  .plan-day { break-inside: avoid; }
}

/* ---------------------------------------------------------------- meals -- */

/* Deliberately NOT another stack of photo cards. A packed day already carries
   four of those, and three more would bury the day it belongs to. Meals are a
   quieter band under the day: a time, a name, a line about it, and the reason
   we are willing to recommend it. */
.plan-day__eat {
  margin-top: var(--sp-4);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--border);
}

.plan-day__eat-h {
  margin: 0 0 var(--sp-2);
  font-family: inherit;
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--muted);
}

.plan-meals { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-2); }

.plan-meal {
  display: grid;
  grid-template-columns: 5.5rem 1fr;
  gap: var(--sp-3);
  align-items: baseline;
}

.plan-meal__when {
  margin: 0;
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
}

.plan-meal__body { min-width: 0; }
.plan-meal__name { margin: 0; font-weight: 600; }
.plan-meal__name a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.plan-meal__name a:hover { color: var(--brand); }

/* The reason to go. This is the line the block exists for, so it gets the
   readable size and the body ink - the old third column held a note about our
   own sourcing, which nobody deciding where to eat was ever going to read. */
.plan-meal__known { margin: .15rem 0 0; font-size: .85rem; color: var(--ink-2); }

/* Where it is, and how far. Quietest thing in the row. */
.plan-meal__meta { margin: .1rem 0 0; font-size: .75rem; color: var(--muted); }

@media (max-width: 640px) {
  .plan-meal { grid-template-columns: 4.5rem 1fr; }
}
