/* ============================================================
   DLT TRAVEL — SHARED DESIGN SYSTEM
   Logo navy #253D87 · sky #2F93D1 · gold CTAs #FCD46D
   Headings: Fraunces · Body: Inter
   Used by every page (dlt-homepage-redesign.html, dlt-tours-redesign.html, ...).
   Page-specific sections (hero, cards, etc.) stay in each page's own <style>.
   ============================================================ */
:root{
  --bg:#FAF9F6;
  --panel:#F1EFE9;
  --navy:#253D87;
  --navy-deep:#1D3070;
  --ink:#333844;
  --ink-soft:#6F6E6A;
  /* Two jobs, two values. --sky is a 3:1-class colour: correct for rules,
     bars, focus rings and icon strokes, which only need 3:1 against what is
     beside them. It cannot carry text — against pure white it tops out at
     3.38:1, so no background in this palette can rescue it. Anything a person
     reads uses --sky-text, which clears 4.5:1 on white, --bg and --panel.
     --sky-soft used to sit here holding #FCD46D. That is not a soft sky, it is
     --gold byte for byte, under a name that made every reader picture blue. */
  --sky:#2F93D1;
  --sky-text:#256E9C;
  --gold:#FCD46D;
  --gold-deep:#EFBE45;
  --line:#E5E2DA;
  --white:#FFFFFF;
  --heading:'Fraunces',Georgia,serif;
  --body:'Inter',Helvetica,Arial,sans-serif;
  --ease:cubic-bezier(.22,.9,.28,1);
  /* Overshoots past 1 before settling. Only for small travel — on anything
     large the rebound reads as a wobble rather than momentum. */
  --ease-back:cubic-bezier(.34,1.56,.64,1);
}
*{margin:0;padding:0;box-sizing:border-box}
html{scroll-behavior:smooth}
/* Every in-page anchor target has to clear the fixed header, or the jump parks
   the section's label underneath it. #header is 84px at rest and 56px once it
   goes .solid — which it always has by the time any anchor jump lands — so 104px
   leaves about 48px of air above the heading.

   `section[id]`, not `[id]` or `:target`. Every anchor target on the five public
   pages is a section (#top #why #services #journeys #contact #branches #team
   #enquire #regions #onboard), so this catches them all and any future one for
   free — while staying away from the things scroll-margin would quietly break:
   the intake form scrolls its first invalid FIELD into view with block:'center',
   and the visa page scrolls a destination CHIP with block:'nearest'. Neither is
   a section, and both would be shifted 104px off target by a blanket rule.

   #enquire and #regions used to declare this themselves. They no longer need to;
   contact.html and cruises.html still scroll to them by hand on load, and that
   scrollIntoView honours this margin exactly as their own declaration did.

   IT IS 64px, NOT 104 -- corrected 20 Aug 2026, after Lun reported the hero's
   "Start Exploring" and "Our Services" landing too low. 104 CLEARED THE HEADER
   AND THEN ADDED AIR THE SECTION ALREADY HAD. Measured on the homepage: the
   section landed with its top at 104px against a 56px solid header -- 48px of
   clear space -- and then `section{padding}` added 76 more on mobile, 84 on
   desktop, putting the first line of content 124px below the bar. The air was
   being counted twice.
   THIS VALUE'S ONE JOB IS TO CLEAR THE HEADER. The section's own padding is
   what provides the breathing room, and it does so on every one of these
   targets because they are all sections -- which is the same fact that lets
   the rule be scoped to `section[id]` at all.

   AND IT IS EXACTLY THE HEADER HEIGHT, 56, WITH NO TOLERANCE ADDED. 64 was
   tried first and Lun rejected it on sight: any margin beyond the header shows
   a strip of the PREVIOUS section above the one you jumped to, and where the
   two have different backgrounds -- ivory `--bg` above the grey `--panel` of
   #journeys -- that strip reads as a mistake rather than as spacing. At 56 the
   target's own background starts flush under the bar.
   THE 8px OF TOLERANCE IS NOT NEEDED, and that was checked rather than
   assumed: nothing lands in the top 56px of these sections, because every one
   of them opens with `section{padding}` -- 76px on mobile, 84 on desktop. So
   even on a landing that arrives while the header is still 84px at rest, what
   sits behind the bar is padding, never a word.
   After: content 76px below the header on mobile, 84 on desktop. */
section[id]{scroll-margin-top:56px}
body{
  background:var(--bg);color:var(--ink);
  font-family:var(--body);font-weight:300;line-height:1.7;
  overflow-x:hidden;-webkit-font-smoothing:antialiased;
}
img,video{display:block;max-width:100%}
a{color:inherit;text-decoration:none}
::selection{background:var(--gold);color:var(--ink)}
:focus-visible{outline:2px solid var(--sky);outline-offset:3px;border-radius:2px}

/* 92% alone leaves only ~16px each side on a 390px phone. The third term caps the
   gutter at a comfortable 24px on small screens; above ~600px the 92% is narrower
   and wins, so wider screens are unchanged. */
.container{width:min(1200px,92%,100% - 48px);margin-inline:auto}
h1,h2,h3{font-family:var(--heading);font-weight:600;line-height:1.12;color:var(--navy)}
.h-xl{font-size:clamp(2.5rem,6vw,4.6rem)}
.h-lg{font-size:clamp(1.9rem,3.8vw,2.9rem)}
.label{
  font-size:.72rem;letter-spacing:.32em;text-transform:uppercase;
  color:var(--sky-text);font-weight:600;display:inline-flex;align-items:center;gap:12px;
}
.label::before{content:"";width:30px;height:1px;background:var(--sky)}
.label.center{justify-content:center;display:flex}
.label.center::after{content:"";width:30px;height:1px;background:var(--sky)}
.lede{color:var(--ink-soft);font-size:1.02rem;max-width:56ch}
.accent{color:var(--sky);font-style:italic;font-weight:500}

/* ---------- Reveal animations ---------- */
.reveal{opacity:0;transform:translateY(34px);transition:opacity .9s var(--ease),transform .9s var(--ease)}
.reveal.in{opacity:1;transform:none}
.reveal-d1{transition-delay:.12s}.reveal-d2{transition-delay:.24s}.reveal-d3{transition-delay:.36s}
/* h1 runs a 1.12 line-height, so descenders — the italic "y" in Fraunces
   especially — fall outside the line box and get clipped by overflow:hidden.
   Padding extends the clip box; the matching negative margin keeps the visual
   line spacing exactly as it was. */
.mask-line{display:block;overflow:hidden;padding-bottom:.16em;margin-bottom:-.16em}
.mask-line span{display:block;transform:translateY(130%);transition:transform 1s var(--ease)}
.in .mask-line span{transform:none}
.mask-line:nth-child(2) span{transition-delay:.14s}

/* ---------- Buttons ----------
   The hover colour wipes across from the left rather than cross-fading, so it
   arrives under each word in the order the label is read — which is what the
   letter-spaced uppercase setting wants. overflow:hidden trims the wipe to the
   button's radius, since a parent's border-radius doesn't clip children.

   isolation:isolate is what lets a z-index:-1 layer sit above the button's own
   background while staying below its label.

   Note for anyone adding an icon to a button: site.js rewrites innerHTML on
   every [data-i18n] element, so it will eat any sibling markup inside the same
   element. Put data-i18n on a <span> around the label and keep the icon outside
   it — see .chooser-cta. */
.btn{
  /* gap:0, not 10px. The arrow below collapses to zero width at rest, and a gap
     would still reserve its 10px — leaving the label sitting visibly off-centre
     until you hover. The arrow carries its own margin instead. */
  display:inline-flex;align-items:center;justify-content:center;gap:0;
  background:var(--gold);color:var(--navy-deep);border:1px solid var(--gold);
  padding:14px 34px;font-size:.78rem;letter-spacing:.18em;text-transform:uppercase;
  /* Fully rounded, matching the pill on the photo cards and the intake form's
     own buttons, which were already 999px. overflow:hidden then clips the wipe
     below to this radius, so the ::before needs no radius of its own. */
  font-weight:600;border-radius:999px;cursor:pointer;font-family:var(--body);
  position:relative;overflow:hidden;isolation:isolate;
  /* Longhand, because the two outline variants below need to delay the colour
     swap on its own and transition-delay can't single out one property from
     the shorthand list. */
  transition-property:background,border-color,transform,box-shadow,color;
  transition-duration:.3s,.3s,.3s,.3s,.2s;
  transition-delay:0s,0s,0s,0s,0s;
}
/* The origin flips between the two states on purpose: the wipe grows from the
   left on the way in and collapses to the right on the way out, so it always
   travels the same direction instead of rewinding itself. */
.btn::before{
  content:"";position:absolute;inset:0;z-index:-1;background:var(--gold-deep);
  transform:scaleX(0);transform-origin:right;
  transition:transform .42s var(--ease);
}
.btn:hover::before,.btn:focus-visible::before{transform:scaleX(1);transform-origin:left}
/* The background stays put now — repainting it on hover would leave the circle
   nothing to reveal. The lift and shadow are unchanged. */
.btn:hover,.btn:focus-visible{border-color:var(--gold-deep);transform:translateY(-2px);box-shadow:0 10px 24px rgba(37,61,135,.18)}

/* The outline variants keep the end colours they already had; only the delivery
   changes. Their labels invert, so the swap is held back until the fill has
   covered enough of the button to sit on — otherwise white text lands on a
   still-transparent background and disappears for a fifth of a second. The
   delay lives on the hover rule alone, so leaving reverts immediately and the
   label never matches the colour shrinking away beneath it. */
.btn.ghost{background:transparent;border-color:var(--navy);color:var(--navy)}
.btn.ghost::before{background:var(--navy)}
.btn.ghost:hover,.btn.ghost:focus-visible{
  border-color:var(--navy);color:#fff;box-shadow:none;
  transition-delay:0s,0s,0s,0s,.17s;
}
.btn.light-ghost{background:transparent;border-color:#fff;color:#fff}
.btn.light-ghost::before{background:var(--gold)}
.btn.light-ghost:hover,.btn.light-ghost:focus-visible{
  border-color:var(--gold);color:var(--navy-deep);
  transition-delay:0s,0s,0s,0s,.17s;
}
.btn.small{padding:10px 22px;font-size:.68rem}

/* ---------- The arrow ----------
   Hidden at rest and revealed on hover, so the button widens as you approach it
   — the same stretch the pill on the photo cards makes.

   max-width rather than width or display:none: it animates, and it lets the
   glyph keep its natural size instead of being pinned to a guessed pixel value
   that would differ between the .68rem and .78rem button sizes.

   The label lives in its own <span> carrying the data-i18n; the arrow sits
   outside it. site.js rewrites innerHTML on every translated element, so an
   arrow inside would vanish the first time anyone switched language.

   The transform is what gives it life: the arrow starts pulled slightly left
   and eases out on a curve that carries it a little past its resting place
   before settling, the way something with momentum would. It rides on the same
   hover as the reveal, so it stays one movement rather than two beats — a
   second, delayed nudge leaves the button still animating after you have
   already read it, which reads as fidgety across a row of them. */
.btn i{
  font-style:normal;flex:none;display:inline-block;overflow:hidden;
  max-width:0;opacity:0;margin-left:0;transform:translateX(-6px);
  transition:max-width .4s var(--ease),opacity .28s var(--ease),
             margin-left .4s var(--ease),transform .5s var(--ease-back);
}
.btn:hover i,.btn:focus-visible i{max-width:1.4em;opacity:1;margin-left:10px;transform:translateX(0)}
/* Touch devices never hover, so the arrow would never appear. Show it. */
@media(hover:none){
  .btn i{max-width:1.4em;opacity:1;margin-left:10px}
}

/* Snapping straight to the filled state keeps the hover legible without the
   travel — the colour change is the information, the sweep is decoration. */
@media(prefers-reduced-motion:reduce){
  .btn::before{transition:none}
  .btn i{transition:none}
  .btn.ghost:hover,.btn.ghost:focus-visible,
  .btn.light-ghost:hover,.btn.light-ghost:focus-visible{transition-delay:0s}
}

/* ---------- Navigation ---------- */
header{
  position:fixed;inset:0 0 auto;z-index:100;padding:20px 0;
  transition:background .4s,box-shadow .4s,padding .4s;
}
/* No backdrop-filter here on purpose: it would make <header> the containing block
   for the position:fixed mobile menu, so translateY(-100%) would only shift the
   panel by the header's height and the last links would peek out below it.
   The background is 95% opaque anyway, so the blur was doing almost nothing. */
header.solid{background:rgba(250,249,246,.95);box-shadow:0 1px 0 var(--line);padding:10px 0}
nav{display:flex;align-items:center;justify-content:space-between;gap:20px}
.logo{display:flex;align-items:center}
.logo img{height:44px;width:auto;transition:height .4s}
header.solid .logo img{height:36px}
.nav-links{display:flex;gap:32px;list-style:none;align-items:center}
.nav-links a{font-size:.78rem;letter-spacing:.14em;text-transform:uppercase;font-weight:500;position:relative;padding:4px 0;color:var(--ink)}
.nav-links a::after{content:"";position:absolute;left:0;bottom:0;height:2px;width:0;background:var(--sky);transition:width .35s var(--ease)}
.nav-links a:hover::after{width:100%}
.nav-links a.active{color:var(--navy);font-weight:600}
.nav-links a.active::after{width:100%;background:var(--navy)}
.nav-right{display:flex;align-items:center;gap:18px}
.lang{
  position:relative;display:flex;width:90px;height:34px;padding:3px;
  border:1px solid var(--line);border-radius:999px;background:var(--white);
}
.lang::before{
  content:"";position:absolute;top:3px;left:3px;
  width:calc(50% - 3px);height:calc(100% - 6px);
  background:var(--navy);border-radius:999px;
  transition:transform .4s var(--ease);
  box-shadow:0 2px 6px rgba(37,61,135,.25);
}
.lang.zh::before{transform:translateX(100%)}
/* Three-slot variant — EN / ខ្មែរ / 中文, used on the intake form and ready for
   the rest of the site. translateX steps by the marker's OWN width, so the slot
   maths has to divide the padded inner width rather than the outer: at 132px
   wide with 3px padding each side, each slot is (132-6)/3 = 42px, and 100%/200%
   land exactly on the second and third. */
.lang.three{width:132px}
.lang.three::before{width:calc((100% - 6px)/3)}
.lang.three.km::before{transform:translateX(100%)}
.lang.three.zh::before{transform:translateX(200%)}
.lang button{
  position:relative;z-index:1;flex:1;background:none;border:0;cursor:pointer;font-family:var(--body);
  font-size:.68rem;font-weight:600;letter-spacing:.06em;color:var(--ink-soft);
  display:flex;align-items:center;justify-content:center;transition:color .35s .05s;
}
.lang button.on{color:#fff}
.burger{display:none;background:none;border:0;cursor:pointer;flex-direction:column;gap:6px;padding:6px}
.burger span{width:26px;height:2px;background:var(--navy);transition:.3s var(--ease)}
/* Contact block + CTA inside the mobile menu — hidden on desktop, where
   .nav-links is just the horizontal link row. */
.nav-extra{display:none}

/* ---------- Mobile menu open state ---------- */
/* Placed after header.solid so it wins when both are present. */
header.menu-open{background:transparent;box-shadow:none}
header.menu-open .burger span{background:#fff}
header.menu-open .burger span:nth-child(1){transform:translateY(8px) rotate(45deg)}
header.menu-open .burger span:nth-child(2){opacity:0}
header.menu-open .burger span:nth-child(3){transform:translateY(-8px) rotate(-45deg)}
body.menu-lock{overflow:hidden}

/* ---------- Sections (base) ---------- */
/* 84, not 100. Measured on the homepage at 100: the gaps between two sections'
   content ran 177-243px, median 207 — comfortably loose — and the space after
   the hero was already the TIGHTEST of them at 177, so trimming that one spot
   would have made an already-tight boundary the outlier. Lun's call,
   7 Aug 2026: bring the whole rhythm in instead, which keeps every boundary in
   proportion. One value, five pages, every section.
   The 760px block below sets its own 76px and is deliberately NOT scaled with
   this — mobile was already tighter than desktop and the complaint was about
   desktop. Trimming mobile is a separate decision. */
section{padding:84px 0}
/* Grid, not flex-with-wrap. As a wrapping flex row the lede sat beside the
   heading only when the heading happened to be short enough — "Flexi Tours"
   kept it alongside, "What your fare actually covers." pushed it underneath.
   Same rule, two different layouts, decided by copy length rather than design.
   Fixed columns mean a long heading wraps inside its own column instead of
   displacing the paragraph. */
/* TOP-ALIGNED, NOT BOTTOM. This was `align-items:end` for months, which
   bottom-aligns the two columns — and since the heading is a fixed height while
   the lede is not, where the lede's FIRST line landed was decided entirely by
   how many lines the copy happened to run. Measured across the eleven headers
   that have both a heading and a lede, the lede's first line sat anywhere from
   55px BELOW the heading's first line (homepage, 2-line lede) to 80px ABOVE it
   (visa, 5-line lede) — a 135px spread, sorting almost perfectly by lede
   length: 2 lines ~ +3, 3 lines -25, 4 lines -38, 5 lines -80.
   The cruises page looked consistent only by accident, because all three of its
   ledes happen to run two lines; a few more words in any of them moved it.
   `flex-start` makes the first lines level every time, which is the edge a
   reader actually starts from. Bottom-alignment bought a tidy lower edge on the
   short-copy headers and cost a predictable top edge on all of them.
   Lun's call, 7 Aug 2026, after seeing the five pages side by side. This
   REVERSES the 7 Aug decision recorded earlier the same day to leave the mixed
   alignment alone — that one was made from the numbers on a single page.
   contact.html used to override two of its headers to flex-start locally for
   exactly this reason; those overrides are gone, this rule covers them. */
.sec-head{
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,42ch);
  gap:40px;align-items:flex-start;margin-bottom:56px;
}
.sec-head > :last-child{justify-self:end}
/* A heading with nothing beside it should use the full width, not two-thirds. */
.sec-head > :only-child{grid-column:1/-1;justify-self:start}
/* ...and a header with nothing BELOW it needs no trailing margin. The 56px above
   exists to separate a section header from the section's own content; where the
   header IS the whole section it just stacks on top of `section{padding:84px 0}`
   and makes the block lopsided — it was 100px of air above the heading and 156px
   below it when that padding was 100. Two sections site-wide are shaped that way, `tours.html` and `visa.html`'s
   `.intro`, and both read as a hole between the heading and whatever follows.
   Scoped to a direct child of .container so it only fires on that shape. */
.container > .sec-head:last-child{margin-bottom:0}
@media(max-width:900px){
  .sec-head{grid-template-columns:1fr}
  .sec-head > :last-child{justify-self:start}
}
.sec-head h2{margin-top:16px}

/* ---------- Enquiry form ---------- */
/* Deliberately roomier than a standard section, and scaled with it: 110 was
   +10 against a 100px baseline, so 92 is +8 against 84. Left at 110 it becomes
   +26 and turns the one slightly-airier section into the loosest on the site,
   which is the opposite of what tightening the baseline was for. */
.enquiry{padding:92px 0}
.enquiry-grid{display:grid;grid-template-columns:1fr 1.2fr;gap:70px;align-items:start}
.form{display:grid;grid-template-columns:1fr 1fr;gap:18px}
.form .full{grid-column:1/-1}
.field label{display:block;font-size:.68rem;letter-spacing:.2em;text-transform:uppercase;font-weight:600;color:var(--ink-soft);margin-bottom:8px}
.field input,.field textarea{
  width:100%;border:1px solid var(--line);border-radius:8px;background:var(--white);
  padding:13px 16px;font-family:var(--body);font-size:.95rem;color:var(--ink);
  transition:border-color .3s,box-shadow .3s;
}
.field input:focus,.field textarea:focus{outline:none;border-color:var(--sky);box-shadow:0 0 0 3px rgba(47,147,209,.15)}
.field textarea{min-height:120px;resize:vertical}
.form .btn{grid-column:1/-1;justify-self:start}
.form-note{font-size:.8rem;color:var(--ink-soft);grid-column:1/-1}

/* ---------- Footer ---------- */
footer{background:var(--navy-deep);color:#DCE4F2;padding:76px 0 34px}
.foot-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:46px;margin-bottom:40px}
.foot-grid h5{font-size:.68rem;letter-spacing:.3em;text-transform:uppercase;color:var(--gold);margin-bottom:20px;font-weight:600}
.foot-grid ul{list-style:none}
.foot-grid li{margin-bottom:11px}
/* The tallest column is the link list, so its trailing margin sits directly above
   the divider and reads as dead space. */
.foot-grid li:last-child{margin-bottom:0}
.foot-grid a,.foot-grid p{font-size:.9rem;color:rgba(220,228,242,.75);transition:color .3s}
.foot-grid a:hover{color:var(--gold)}
.foot-grid a.btn,.foot-grid a.btn:hover{color:var(--navy-deep)}
.foot-base{display:flex;justify-content:space-between;align-items:center;border-top:1px solid rgba(220,228,242,.15);padding-top:30px;font-size:.74rem;color:rgba(220,228,242,.6);flex-wrap:wrap;gap:12px}
.foot-socials{display:flex;gap:10px;margin-top:6px}
.foot-socials a{
  width:40px;height:40px;border-radius:50%;border:1px solid rgba(220,228,242,.3);
  display:grid;place-items:center;transition:background .3s,border-color .3s;
}
.foot-socials a:hover{background:var(--gold);border-color:var(--gold)}
.foot-socials svg{width:18px;height:18px;fill:rgba(220,228,242,.9);transition:fill .3s}
.foot-socials a:hover svg{fill:var(--navy-deep)}

/* ---------- Floating chat buttons ---------- */
.chat-float{position:fixed;right:18px;bottom:18px;z-index:90;display:flex;flex-direction:column;gap:12px}
.chat-float a{
  position:relative;width:52px;height:52px;border-radius:50%;display:grid;place-items:center;
  background:var(--navy);box-shadow:0 6px 18px rgba(29,48,112,.35);transition:transform .3s,background .3s;
}
/* Hover matches .foot-socials — gold fill, navy glyph. The buttons keep their
   solid navy base because they float over light page content and need to stay
   findable, unlike the footer's outlined marks on dark ground.
   The Telegram/WhatsApp brand-coloured status dots were the only appearance of
   #2AABEE and #25D366 anywhere on the site, so they've gone. */
.chat-float a:hover{transform:translateY(-3px) scale(1.05);background:var(--gold)}
.chat-float svg{width:24px;height:24px;fill:#fff;transition:fill .3s}
.chat-float a:hover svg{fill:var(--navy-deep)}

/* ---------- Chinese mode (fonts lazy-loaded on first switch) ----------
   The system faces after Noto are the ones that matter in mainland China, where
   fonts.googleapis.com is blocked and Noto never arrives. Without them the chain
   fell to Fraunces — which has no Chinese glyphs — and then to a generic serif,
   leaving the browser to pick something. Naming Songti, Source Han and SimSun
   makes the blocked case a decision rather than a lottery.

   Anyone who can reach Google Fonts still sees Noto, exactly as before. */
body.lang-zh{
  --heading:'Noto Serif SC','Songti SC','Source Han Serif SC','SimSun','Fraunces',serif;
  --body:'Noto Sans SC','PingFang SC','Microsoft YaHei','Hiragino Sans GB','Inter',sans-serif;
}
/* Display text keeps a little tracking in Chinese — section eyebrows, buttons,
   nav. Spaced-out short runs (疏排) read as deliberate there, the way tracked
   uppercase does in Latin. */
body.lang-zh .label,body.lang-zh .nav-links a,body.lang-zh .btn,
body.lang-zh .j-card .tag,body.lang-zh .stat span,body.lang-zh .cruise-regions li,
body.lang-zh .foot-grid h5{letter-spacing:.12em}

/* Form labels are read, not displayed, and there are twenty-seven of them in a
   row on the intake form — at .12em that stops looking like a device and starts
   looking like broken text. Chinese glyphs already carry their own spacing.
   This has to live here rather than in the form: it out-specifies any plain
   .field label rule a page might set, which is what defeated the form's own
   letter-spacing:normal. */
body.lang-zh .field label{letter-spacing:normal}

/* ---------- Khmer mode ----------
   Tracking goes to zero, not merely down as it does for Chinese. Khmer builds
   each syllable as a cluster — a base consonant carrying subscripts and vowel
   signs — and letter-spacing prises those parts away from each other, so a word
   renders as scattered fragments rather than one shape. Chinese only looks a
   little loose; Khmer stops reading as words at all.

   The few Latin runs that survive a translated page (DL Travel, phone numbers)
   lose their tracking too. That is the right trade. */
/* Same reasoning as Chinese, though the stakes are lower — Khmer readers are
   overwhelmingly in Cambodia, where Google Fonts is reachable. These cover a
   failed load rather than a blocked one. */
body.lang-km{
  --heading:'Kantumruy Pro','Khmer Sangam MN','Khmer UI','Noto Sans Khmer','Fraunces',serif;
  --body:'Kantumruy Pro','Khmer Sangam MN','Khmer UI','Noto Sans Khmer','Inter',sans-serif;
}
body.lang-km .label,body.lang-km .nav-links a,body.lang-km .btn,
body.lang-km .j-card .tag,body.lang-km .stat span,body.lang-km .cruise-regions li,
body.lang-km .field label,body.lang-km .foot-grid h5,
body.lang-km .lang button,body.lang-km .step-indicator,
body.lang-km .review-group h3{letter-spacing:normal}

/* ---------- Responsive (shared) ---------- */
@media(max-width:1020px){
  .services-grid{grid-template-columns:1fr 1fr}
  .enquiry-grid{grid-template-columns:1fr;gap:44px}
  .foot-grid{grid-template-columns:1fr 1fr}
}
/* The nav has to collapse earlier than the layout does: logo + five links + the
   language toggle stop fitting around 900px, well before the 760px grid breakpoint.
   Keeping them on one breakpoint left the links overflowing the header. */
@media(max-width:900px){
  .nav-extra{display:block}

  /* ---------- Bottom sheet ----------
     Rises from the bottom instead of taking the whole screen. Two reasons: the
     links land in thumb reach on a phone, and the sheet is only as tall as its
     contents — the full-screen version left five links stranded in the middle
     of a screen-height navy field, which is what looked wrong.
     max-height is a guard for short landscape screens, not the normal case. */
  .nav-links{
    position:fixed;left:0;right:0;bottom:0;top:auto;
    background:var(--navy-deep);
    flex-direction:column;align-items:stretch;justify-content:flex-start;
    gap:0;padding:22px 26px calc(24px + env(safe-area-inset-bottom, 0px));
    border-radius:20px 20px 0 0;
    max-height:88vh;overflow-y:auto;
    /* 101%, not 100%: the extra percent carries the shadow off-screen too, so
       no dark seam sits along the bottom edge while closed. */
    transform:translateY(101%);
    transition:transform .5s var(--ease),visibility .5s;z-index:-1;
    visibility:hidden;                 /* can't peek even if the transform is defeated */
    box-shadow:0 -18px 50px rgba(15,27,51,.28);
  }
  .nav-links.open{transform:none;visibility:visible}

  /* The handle is the affordance that says this dismisses downward. */
  .nav-links::before{
    content:"";flex:none;width:36px;height:4px;border-radius:2px;
    background:rgba(255,255,255,.26);margin:0 auto 18px;
  }

  /* Scrim. Hung off the header so none of the six pages needs new markup, and
     it inherits the header's stacking context: -2 sits below the sheet, both
     above the page. */
  #header::after{
    content:"";position:fixed;inset:0;z-index:-2;background:rgba(15,27,51,.5);
    opacity:0;visibility:hidden;
    transition:opacity .45s var(--ease),visibility .45s;
  }
  #header.menu-open::after{opacity:1;visibility:visible}

  .nav-links li{
    width:100%;opacity:0;transform:translateY(10px);
    transition:opacity .38s var(--ease),transform .38s var(--ease);
  }
  .nav-links.open li{opacity:1;transform:none}
  .nav-links.open li:nth-child(1){transition-delay:.08s}
  .nav-links.open li:nth-child(2){transition-delay:.13s}
  .nav-links.open li:nth-child(3){transition-delay:.18s}
  .nav-links.open li:nth-child(4){transition-delay:.23s}
  .nav-links.open li:nth-child(5){transition-delay:.28s}
  .nav-links.open li:nth-child(6){transition-delay:.34s}
  /* Same treatment as the desktop bar, only larger and reversed out. The sheet
     used to set these in Fraunces, which read as deliberate at 2.4rem — at this
     size it just disagreed with the desktop nav and with the contact details
     and button directly beneath it. On this site Fraunces means heading and
     tracked uppercase Inter means navigation; these are navigation. */
  .nav-links a{
    font-family:var(--body);font-size:clamp(.82rem,3.4vw,.92rem);font-weight:500;
    letter-spacing:.14em;text-transform:uppercase;color:#fff;padding:13px 0;display:block;
    position:relative;transition:color .15s var(--ease);
  }
  .nav-links a::after{display:none}   /* drop the desktop underline */
  /* Tap feedback: a gold bar flush with the sheet's edge, flashing in with the
     link text on touchdown. Needs the touchstart unlock in site.js — iOS Safari
     won't fire :active on a tap otherwise. Scoped to li:not(.nav-extra) so it
     never touches the Enquire button or the phone/email links in that li —
     .nav-cta is also an <a> in .nav-links, and .btn already owns its own
     hover/active treatment (the gold wipe); a second color:var(--gold) here
     landed text and fill on the exact same gold and erased the label. */
  .nav-links li:not(.nav-extra) a::before{
    content:"";position:absolute;left:-26px;top:0;bottom:0;width:3px;
    background:var(--gold);opacity:0;transition:opacity .15s var(--ease);
  }
  .nav-links li:not(.nav-extra) a:active{color:var(--gold)}
  .nav-links li:not(.nav-extra) a:active::before{opacity:1}
  .nav-links a.active{color:var(--gold)}
  /* Cursor feedback for anyone opening this sheet with a real pointer (a
     resized window, an iPad with a mouse) — same gold bar as the tap state.
     Gated to hover:hover so a phone tap can't leave it stuck "on" the way
     :hover does on touchscreens. */
  @media(hover:hover){
    .nav-links li:not(.nav-extra) a:hover{color:var(--gold)}
    .nav-links li:not(.nav-extra) a:hover::before{opacity:1}
  }

  /* Contact + CTA below the links */
  .nav-extra{
    margin-top:18px;padding-top:16px;
    border-top:1px solid rgba(255,255,255,.14);
  }
  .nav-contact{display:flex;flex-direction:column;gap:6px;margin-bottom:14px}
  .nav-links .nav-contact a{
    font-family:var(--body);font-size:.85rem;font-weight:400;letter-spacing:.02em;
    text-transform:none;color:rgba(255,255,255,.72);padding:0;
  }
  .nav-links .nav-contact a:hover{color:var(--gold)}
  .nav-links .nav-cta{
    font-family:var(--body);font-size:.68rem;font-weight:600;letter-spacing:.18em;
    text-transform:uppercase;color:var(--navy-deep);
    display:inline-flex;padding:12px 26px;width:auto;
  }
  .burger{display:flex}

  /* The sheet still opens and closes, it just stops travelling to get there. */
  @media(prefers-reduced-motion:reduce){
    .nav-links,.nav-links li,#header::after,
    .nav-links a,.nav-links a::before{transition-duration:.01ms}
  }
}
@media(max-width:760px){
  .services-grid,.form,.flexi-grid{grid-template-columns:1fr}
  section{padding:76px 0}
}
@media(prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms!important;transition-duration:.01ms!important}
  html{scroll-behavior:auto}
}
