/* ==========================================================================
   Sabah Booking — brand content pages.
   Shared by the static/editorial pages: about, contact, faq, terms, esg,
   sitemap, testimonials, tips, tipsinfo, bookingProcedure, travelAlone,
   enquiry, weather, 404, 100.

   Loaded AFTER css/brand-home.css, which supplies the tokens and the shared
   brand shell (header, footer, buttons, cards, trust badge). This file adds
   ONLY what content pages need.

   The body carries "sb-home sb-page": `sb-home` is the brand shell scope that
   brand-home.css resets are written against, `sb-page` is this layer.

   Note brand-home.css zeroes margins on h1-h4/p and strips list styling from
   ul. Editorial copy needs those back, so `.sb-prose` re-establishes them for
   rich text (DB HTML and remote-fetched content) rather than fighting the
   reset page-wide.

   No !important anywhere: these pages do not load the legacy stylesheets.
   ========================================================================== */

/* A declared display value beats the `hidden` attribute's UA default, so any
   flex/grid component toggled via `hidden` would stay visible without this. */
.sb-page [hidden]{display:none}

/* ---- page hero ---------------------------------------------------------
   Replaces the legacy `hero_in bgN start_bg_zoom` banner. Same white
   protection gradient as the listing hero — never a dark scrim, so brand
   photography keeps its colour. Pages without a photo just omit the inline
   background-image and land on the sand wash. */
.sb-pagehero{
  position:relative;background-size:cover;background-position:center;
  background-color:var(--sand-100);
  padding:44px var(--gutter) 48px;
}
.sb-pagehero::before{
  content:"";position:absolute;inset:0;
  background:linear-gradient(100deg,
    rgba(255,255,255,.97) 0%,
    rgba(255,255,255,.92) 38%,
    rgba(255,255,255,.55) 68%,
    rgba(255,255,255,.15) 100%);
}
.sb-pagehero__inner{position:relative;z-index:2;max-width:720px}
.sb-pagehero h1{font:var(--text-h1);font-size:clamp(28px,3.4vw,40px)}
.sb-pagehero__sub{
  font:var(--text-subtitle);font-size:15px;color:var(--text-muted);margin-top:10px;
}
.sb-crumb{font:var(--text-caption);color:var(--text-muted)}
.sb-crumb a{color:var(--text-muted)}
.sb-crumb a:hover{color:var(--text-link)}
.sb-pagehero .sb-crumb{margin-bottom:8px}

/* ---- content scaffolding ----------------------------------------------- */
.sb-content{padding:48px var(--gutter)}
.sb-content--soft{background:var(--sand-100)}
.sb-content--tight{padding-top:32px;padding-bottom:32px}
.sb-content__inner{max-width:1180px;margin:0 auto}
/* editorial copy wants a shorter measure than the 1180 shell */
.sb-content__inner--narrow{max-width:820px}

.sb-pagehead{max-width:640px;margin:0 auto 32px;text-align:center}

/* Boki parked in the empty margin to the left of a centred .sb-pagehead. Absolute
   so the heading block keeps its own centring.
   Mobile-first on purpose: the 640px pagehead only leaves room for a 230px mascot
   once the viewport clears ~1260px, so below that Boki stays in normal flow above
   the heading (visible on phones) rather than being squeezed or hidden.
   Written as two-class selectors: a bare `.sb-content__boki` (0,1,0) would lose to
   brand-home.css's `.sb-home img{display:block}` (0,1,1). */
.sb-content--boki .sb-content__inner{position:relative}
.sb-content--boki .sb-content__boki{width:140px;height:auto;margin:0 auto 16px;pointer-events:none}
@media (min-width:1260px){
  .sb-content--boki .sb-content__boki{position:absolute;top:0;left:0;width:230px;margin:0}
}
.sb-pagehead h2{font:var(--text-h2)}
.sb-pagehead p{font:var(--text-subtitle);color:var(--text-muted);margin-top:8px}
.sb-pagehead--left{margin-left:0;text-align:left}

/* ---- trust panel ---------------------------------------------------------
   The transparency block. This is the brand's positioning — "the most
   transparent way to visit Sabah" — and the reason a traveller books here
   rather than direct, so it is deliberately NOT the generic .sb-feat card
   grid it started as: that read as three oversized, low-density tiles.

   It is built to look like a credential, not a marketing panel: a green
   header band, verified assurances with filled check discs, and the licence
   numbers as a compact footer strip. Everything is real, checkable fact —
   the MOTAC licence links out to the government register — which is what
   makes the claim worth making. */
.sb-trustpanel{
  border:2px solid var(--green-500);border-radius:var(--radius-lg);
  overflow:hidden;background:var(--surface-card);
  box-shadow:0 4px 16px rgba(108,194,67,.14),0 12px 32px rgba(34,34,34,.06);
}
/* Solid green header, not a tint: this is the one block on the page that
   should read as a stamp rather than another content card. */
.sb-trustpanel__head{
  display:flex;align-items:center;gap:13px;
  padding:17px 22px;
  background:var(--green-600);color:var(--text-inverse);
}
.sb-trustpanel__head .sb-icon{width:26px;height:26px;flex:none;color:var(--text-inverse)}
.sb-trustpanel__head h2{
  font:var(--text-h3);font-size:19px;color:var(--text-inverse);margin:0;
}
.sb-trustpanel__head p{
  font:var(--text-caption);color:rgba(255,255,255,.82);margin-top:2px;
}

.sb-trustpanel__body{padding:6px 20px 14px}
.sb-trustpanel__row{display:flex;gap:12px;align-items:flex-start;padding:12px 0}
.sb-trustpanel__row + .sb-trustpanel__row{border-top:1px solid var(--gray-100)}
.sb-trustpanel__check{
  width:22px;height:22px;flex:none;border-radius:50%;
  background:var(--green-500);color:var(--text-inverse);
  display:flex;align-items:center;justify-content:center;margin-top:1px;
}
.sb-trustpanel__check .sb-icon{width:13px;height:13px}
.sb-trustpanel__row b{
  display:block;font:var(--text-subtitle);font-weight:600;color:var(--text-heading);
}
.sb-trustpanel__row p{font:var(--text-caption);color:var(--text-muted);margin-top:3px}

/* credentials strip — the 1px grid gap is the divider */
.sb-trustpanel__creds{
  display:grid;grid-template-columns:repeat(3,1fr);gap:1px;
  background:var(--border-subtle);border-top:1px solid var(--border-subtle);
}
.sb-trustpanel__cred{
  background:var(--sand-100);padding:14px 16px;
  display:flex;gap:10px;align-items:flex-start;
}
.sb-trustpanel__cred .sb-icon{width:17px;height:17px;flex:none;color:var(--green-600);margin-top:1px}
.sb-trustpanel__cred b{
  display:block;font:var(--text-small);font-weight:600;color:var(--text-heading);
}
.sb-trustpanel__cred span{display:block;font:var(--text-caption);color:var(--text-muted);margin-top:2px}
.sb-trustpanel__cred a{font:var(--text-caption);color:var(--text-link);text-decoration:underline}

/* ---- trust panel: Boki beside the card ----------------------------------
   Moved here from brand-detail.css. It lived there because only the *info.jsp
   detail pages included brandTrustPanel.jsp, and brand-page.css carried a
   `display:none` guard so shuttleinfo.jsp — the one includer that does not
   load brand-detail.css — would not drop him in at his full 211x440. Now that
   shuttleinfo wants him too, the rules belong on the lower layer: every
   includer loads brand-page.css, so nothing is duplicated and the detail
   pages behave exactly as before.

   The PNG already carries its own transparent margin on the left and top, so
   he needs no cropping or negative offsets — flush against the card is enough
   for the two to read as one unit. align-items:flex-end puts his feet on the
   card's bottom edge whatever his rendered height turns out to be. He is
   decorative (alt=""), so hiding him narrow costs nothing and gives the card
   its full width back. */
.sb-trustpanel-wrap{display:flex;align-items:flex-end}
.sb-trustpanel-wrap .sb-trustpanel-wrap__mascot{display:block;flex:none}
/* squared off where Boki docks against the card; the hide breakpoints below
   restore the rounded corner since nothing sits there once he's gone */
.sb-trustpanel-wrap .sb-trustpanel{flex:1;min-width:0;border-bottom-left-radius:0}

/* Boki stands down through one awkward band: on a detail page the rail is
   still beside the main column here, but the creds strip does not collapse to
   one column until 900px, so a 200px mascot squeezes three credentials into
   ~112px each and doubles the strip's height. Below 900 the rail drops away
   and he fits again, so this is a band, not a floor.

   Both hide rules are scoped through the wrap on purpose: brand-home.css sets
   `.sb-home img{display:block}` (0,1,1), which outranks a bare class (0,1,0),
   so an unscoped `display:none` here is silently ignored. */
@media (max-width:1100px) and (min-width:901px){
  .sb-trustpanel-wrap .sb-trustpanel-wrap__mascot{display:none}
  .sb-trustpanel-wrap .sb-trustpanel{border-bottom-left-radius:var(--radius-lg)}
}
@media (max-width:900px){
  /* three credentials in a phone width give each ~112px; one per row instead */
  .sb-trustpanel__creds{grid-template-columns:1fr}
}
@media (max-width:767px){
  .sb-trustpanel-wrap .sb-trustpanel-wrap__mascot{display:none}
  .sb-trustpanel-wrap .sb-trustpanel{border-bottom-left-radius:var(--radius-lg)}
}

/* ---- review summary ------------------------------------------------------
   Also moved down from brand-detail.css, for the same reason: shuttleinfo.jsp
   grew a reviews section and does not load that sheet. The individual review
   cards reuse .sb-review* from brand-home.css; this is just the average +
   per-star distribution block above them. */
.sb-revsum{display:grid;grid-template-columns:180px minmax(0,1fr);gap:28px;align-items:center}
.sb-revsum__avg{text-align:center}
.sb-revsum__score{font:600 44px/1 var(--font-display);color:var(--text-heading)}
.sb-revsum__stars{color:var(--star);font-size:16px;margin-top:6px}
.sb-revsum__count{font:var(--text-caption);color:var(--text-muted);margin-top:6px}
.sb-revbars{display:flex;flex-direction:column;gap:7px}
.sb-revbar{display:flex;align-items:center;gap:10px}
.sb-revbar__label{
  font:var(--text-caption);color:var(--text-muted);
  white-space:nowrap;min-width:34px;text-align:right;
}
/* Both of these are <span>s. An inline box ignores height, so the track would
   collapse and the fill would never paint — they must be blockified. The track
   gets it from being a flex item; the fill needs it declared. */
.sb-revbar__track{
  flex:1;display:block;height:7px;border-radius:var(--radius-pill);
  background:var(--gray-100);overflow:hidden;
}
.sb-revbar__fill{display:block;height:100%;background:var(--star);border-radius:var(--radius-pill)}
.sb-revbar__n{font:var(--text-caption);color:var(--text-muted);min-width:26px}
@media (max-width:900px){
  .sb-revsum{grid-template-columns:1fr;gap:18px}
}
/* ---- prose / rich text -------------------------------------------------
   Scope for content this stylesheet does not author: DB article HTML and the
   remote-fetched terms/procedure text. brand-home.css strips margins and list
   markers globally, so they are restored here rather than page-wide. */
.sb-prose{font:var(--text-body);font-size:15px;color:var(--text-body-color)}
.sb-prose h1,.sb-prose h2,.sb-prose h3,.sb-prose h4{margin:28px 0 12px}
.sb-prose h2{font:var(--text-h2);font-size:22px}
.sb-prose h3{font:var(--text-h3)}
.sb-prose h4{font:var(--text-h4)}
.sb-prose > :first-child{margin-top:0}
.sb-prose p{margin:0 0 14px}
.sb-prose ul,.sb-prose ol{margin:0 0 14px;padding-left:22px}
.sb-prose ul{list-style:disc}
.sb-prose ol{list-style:decimal}
.sb-prose li{margin-bottom:6px}
.sb-prose a{color:var(--text-link);text-decoration:underline}
.sb-prose a:hover{color:var(--blue-600)}
.sb-prose img{max-width:100%;height:auto;border-radius:var(--radius-md);margin:12px 0}
/* CMS-authored prose HTML (route policies, terms, etc.) sometimes carries a
   legacy table with inline pixel widths on its <td>s (e.g. width:800px) from
   a desktop-era rich-text editor. `width:100%` alone doesn't stop that: an
   auto-layout table still grows to fit oversized cells, blowing the whole
   page out horizontally on mobile - which on a real phone can push a fixed-
   position sibling like #mobileBookBar's Book Now button off past the edge
   of the screen instead of just widening the table (found 2026-08-16 on a
   transferinfo "Private Car Charter" route's vehicle/pricing table).
   display:block+overflow-x:auto lets the table keep its authored column
   widths and become independently scrollable instead, containing the
   overflow - same technique as .sb-pricebar's horizontal scroll. */
.sb-prose table{display:block;overflow-x:auto;border-collapse:collapse;width:100%;margin:0 0 16px}
.sb-prose th,.sb-prose td{
  border:1px solid var(--border-subtle);padding:8px 10px;text-align:left;
}
.sb-prose strong,.sb-prose b{color:var(--text-heading)}
.sb-prose hr{border:0;border-top:1px solid var(--border-subtle);margin:24px 0}
/* The terms/procedure feed arrives as <br>-joined lines with no block
   elements, so give consecutive breaks a little air to fake paragraphing. */
.sb-prose br{line-height:2.1}

/* ---- feature cards -----------------------------------------------------
   Brand replacement for the legacy `box_feat` tile. Same shape as the
   homepage `.sb-why__card` but standalone (no sand band of its own), so a
   page can drop a grid of them into any section. */
.sb-feat__grid{display:grid;grid-template-columns:repeat(3,1fr);gap:20px}
.sb-feat__grid--2{grid-template-columns:repeat(2,1fr)}
.sb-feat__card{
  background:var(--surface-card);border-radius:var(--radius-lg);
  box-shadow:var(--shadow-card);padding:24px;text-align:center;
  display:block;color:inherit;
  transition:transform var(--dur-med) var(--ease-brand),
             box-shadow var(--dur-med) var(--ease-brand);
}
a.sb-feat__card:hover{transform:translateY(-2px);box-shadow:var(--shadow-float)}
.sb-feat__disc{
  width:48px;height:48px;border-radius:50%;background:var(--green-100);
  display:inline-flex;align-items:center;justify-content:center;
}
.sb-feat__disc .sb-icon{width:22px;height:22px;color:var(--green-600)}
.sb-feat__disc--blue{background:var(--blue-100)}
.sb-feat__disc--blue .sb-icon{color:var(--blue-600)}
.sb-feat__disc--coral{background:var(--coral-100)}
.sb-feat__disc--coral .sb-icon{color:var(--coral-600)}
.sb-feat__card h3{font:var(--text-h3);margin-top:12px}
.sb-feat__card p{font:var(--text-body);color:var(--text-body-color);margin-top:8px}

/* ---- duo: copy beside an image -----------------------------------------
   Named sb-duo, not sb-split: brand-home.css already owns `.sb-split` for the
   homepage journey band (display:flex plus a 40px/gutter padding). Reusing
   that name here would win on `display` but silently inherit the padding. */
.sb-duo{display:grid;grid-template-columns:1.15fr .85fr;gap:40px;align-items:center}
.sb-duo--flip > :first-child{order:2}
/* the default vertical-centers a shorter column against a taller one; a
   short plain list (contact.jsp's address/hours/phone/email) reads better
   pinned to the same top edge as the other column instead of floating. */
.sb-duo--top{align-items:start}
/* the default gives the first column the larger share (copy beside a
   smaller image); contact.jsp wants the reverse — the enquiry card carries
   the actual task (WhatsApp/email form), the address/hours list is just
   reference text, so the second column should get the extra room. */
.sb-duo--formwide{grid-template-columns:.85fr 1.15fr}
.sb-duo img{border-radius:var(--radius-lg);width:100%}
.sb-duo__stack{display:grid;grid-template-columns:1fr;gap:12px}
.sb-duo__stack img{aspect-ratio:4/3;height:auto;object-fit:cover}
.sb-duo__cap{font:var(--text-caption);color:var(--text-muted);margin-top:8px}

/* ---- B2B sign-in: login card beside the partner benefits -----------------
   Deliberately NOT built on .sb-duo/.sb-feat__grid, both of which are wrong
   here and are shared with about/esg/myAccount/review/weather:

     .sb-duo img{width:100%} is meant for editorial photography; it blew the
     50x50 benefit PNGs up to the column width, so they rendered soft and — as
     the three grid columns were not equal — at three different sizes.

     .sb-feat__grid uses repeat(3,1fr), and 1fr floors at auto, so a long word
     ("Competitive") widened its column and squeezed the others.

   Hence minmax(0,1fr) for genuinely equal columns, and a fixed image height
   so all six read as one set whatever their source dimensions. */
.sb-b2b{
  display:grid;grid-template-columns:minmax(0,400px) minmax(0,1fr);
  gap:40px;align-items:start;
}
.sb-b2b__why{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}
.sb-b2b__tile{
  background:var(--surface-card);border-radius:var(--radius-lg);
  box-shadow:var(--shadow-card);padding:22px 14px;
  display:flex;flex-direction:column;align-items:center;gap:12px;text-align:center;
}
/* (0,1,1), same as `.sb-home img` — brand-page.css loads later, so this wins */
.sb-b2b__tile img{height:52px;width:auto;max-width:100%;object-fit:contain}
.sb-b2b__tile span{font:600 14px/1.4 var(--font-display);color:var(--text-heading)}
.sb-b2b__join{margin-top:20px;text-align:center}

@media (max-width:900px){
  .sb-b2b{grid-template-columns:minmax(0,1fr);gap:28px}
}
@media (max-width:520px){
  .sb-b2b__why{grid-template-columns:repeat(2,minmax(0,1fr))}
}

/* ---- pull quote --------------------------------------------------------- */
.sb-pullquote{
  background:var(--sand-100);border-left:3px solid var(--green-500);
  border-radius:var(--radius-md);padding:20px 24px;margin:24px 0;
  font:var(--text-subtitle);font-size:16px;line-height:1.6;color:var(--text-heading);
}
.sb-pullquote cite{
  display:block;margin-top:10px;
  font:var(--text-caption);font-style:normal;color:var(--text-muted);
}

/* ---- two-column article layout -----------------------------------------
   faq / tips / tipsinfo: sticky category rail beside the content column. */
.sb-withaside{display:grid;grid-template-columns:260px 1fr;gap:36px;align-items:start}
.sb-aside{position:sticky;top:84px}      /* clears the sticky brand header */
.sb-aside__card{
  background:var(--surface-card);border-radius:var(--radius-lg);
  box-shadow:var(--shadow-card);padding:20px;
}
.sb-aside__card + .sb-aside__card{margin-top:20px}
.sb-aside__card h3{font:var(--text-h4);margin-bottom:12px}
.sb-aside__nav{display:flex;flex-direction:column;gap:2px}
.sb-aside__nav a{
  display:flex;align-items:center;gap:8px;
  padding:8px 10px;border-radius:var(--radius-sm);
  font:var(--text-subtitle);color:var(--text-heading);
  transition:background var(--dur-fast) var(--ease-brand);
}
.sb-aside__nav a:hover{background:var(--gray-100)}
.sb-aside__nav a.is-active{background:var(--green-100);color:var(--green-800)}
.sb-aside__nav .sb-icon{width:16px;height:16px;color:var(--text-muted)}
.sb-aside__nav a.is-active .sb-icon{color:var(--green-600)}

/* ---- accordion (faq) ---------------------------------------------------
   Native <details>/<summary>: no JS, keyboard-accessible for free, and the
   legacy Bootstrap collapse ids (which collided across the two categories)
   stop being load-bearing. */
.sb-acc{display:flex;flex-direction:column;gap:10px}
.sb-acc__item{
  background:var(--surface-card);border:1px solid var(--border-subtle);
  border-radius:var(--radius-md);overflow:hidden;
}
.sb-acc__item[open]{box-shadow:var(--shadow-card);border-color:transparent}
.sb-acc__q{
  display:flex;align-items:center;justify-content:space-between;gap:16px;
  padding:16px 18px;cursor:pointer;list-style:none;
  font:var(--text-h4);color:var(--text-heading);
}
.sb-acc__q::-webkit-details-marker{display:none}
.sb-acc__q:hover{color:var(--green-600)}
.sb-acc__q .sb-icon{
  width:18px;height:18px;flex:none;color:var(--text-muted);
  transition:transform var(--dur-med) var(--ease-brand);
}
.sb-acc__item[open] .sb-acc__q .sb-icon{transform:rotate(180deg)}
.sb-acc__a{padding:0 18px 18px}

/* ---- link lists (sitemap, travelAlone, 404 recovery) -------------------- */
.sb-linklist{display:flex;flex-direction:column;gap:2px}
.sb-linklist a{
  display:flex;align-items:center;gap:10px;
  padding:11px 14px;border-radius:var(--radius-sm);
  font:var(--text-subtitle);font-size:15px;color:var(--text-heading);
  transition:background var(--dur-fast) var(--ease-brand);
}
.sb-linklist a:hover{background:var(--sand-100);color:var(--green-600)}
.sb-linklist .sb-icon{width:16px;height:16px;color:var(--green-600)}
.sb-linklist--cols{
  display:grid;grid-template-columns:repeat(2,1fr);gap:2px 24px;
}
/* numbered editorial list (travelAlone's top-10) */
.sb-ranklist{counter-reset:sbrank;display:flex;flex-direction:column;gap:2px}
.sb-ranklist a{
  display:flex;align-items:center;gap:12px;
  padding:11px 14px;border-radius:var(--radius-sm);
  font:var(--text-subtitle);font-size:15px;color:var(--text-heading);
  transition:background var(--dur-fast) var(--ease-brand);
}
.sb-ranklist a::before{
  counter-increment:sbrank;content:counter(sbrank);
  width:26px;height:26px;flex:none;border-radius:50%;
  background:var(--green-100);color:var(--green-800);
  display:inline-flex;align-items:center;justify-content:center;
  font:var(--text-small);font-weight:600;
}
.sb-ranklist a:hover{background:var(--sand-100)}

/* ---- article (tipsinfo) ------------------------------------------------- */
.sb-article__meta{
  display:flex;align-items:center;gap:16px;flex-wrap:wrap;
  font:var(--text-caption);color:var(--text-muted);
  padding-bottom:16px;margin-bottom:24px;
  border-bottom:1px solid var(--border-subtle);
}
.sb-article__meta span{display:inline-flex;align-items:center;gap:6px}
.sb-article__meta .sb-icon{width:14px;height:14px}
.sb-article__share{
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  margin-top:32px;padding-top:20px;border-top:1px solid var(--border-subtle);
}
.sb-article__share > span{font:var(--text-subtitle);font-weight:600;color:var(--text-heading)}
.sb-photostrip{display:grid;grid-template-columns:repeat(2,1fr);gap:8px}
.sb-photostrip img{width:100%;height:88px;object-fit:cover;border-radius:var(--radius-sm)}

/* ---- testimonial tiles -------------------------------------------------
   Replaces the legacy `view view-tenth` hover mask, which hid the quote until
   hover — the text is the point, so it is always visible now. */
.sb-quotes{display:grid;grid-template-columns:repeat(4,1fr);gap:20px}
.sb-quotecard{
  background:var(--surface-card);border-radius:var(--radius-lg);
  box-shadow:var(--shadow-card);overflow:hidden;display:flex;flex-direction:column;
}
.sb-quotecard img{width:100%;height:180px;object-fit:cover}
.sb-quotecard__body{padding:16px;display:flex;flex-direction:column;gap:8px;flex:1}
.sb-quotecard__text{font:var(--text-body);color:var(--text-body-color)}
.sb-quotecard__by{
  font:var(--text-caption);color:var(--text-muted);margin-top:auto;padding-top:8px;
}

/* ---- info list (contact) ------------------------------------------------ */
.sb-infolist{display:flex;flex-direction:column;gap:20px}
.sb-infoitem{display:flex;gap:14px;align-items:flex-start}
.sb-infoitem__disc{
  width:40px;height:40px;flex:none;border-radius:50%;background:var(--blue-100);
  display:inline-flex;align-items:center;justify-content:center;
}
.sb-infoitem__disc .sb-icon{width:18px;height:18px;color:var(--blue-600)}
.sb-infoitem h4{font:var(--text-h4);margin-bottom:4px}
.sb-infoitem p,.sb-infoitem a{font:var(--text-body);color:var(--text-body-color)}
.sb-infoitem a:hover{color:var(--text-link)}
.sb-map{
  width:100%;height:380px;border:0;border-radius:var(--radius-lg);
  box-shadow:var(--shadow-card);
}

/* ---- form --------------------------------------------------------------- */
.sb-form{
  background:var(--surface-card);border-radius:var(--radius-lg);
  box-shadow:var(--shadow-card);padding:28px;
}
.sb-form__grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.sb-field{display:flex;flex-direction:column;gap:6px}
.sb-field--full{grid-column:1 / -1}
.sb-field label{font:var(--text-subtitle);font-weight:600;color:var(--text-heading)}
.sb-field input,.sb-field textarea,.sb-field select{
  font:var(--text-body);font-size:15px;color:var(--text-heading);
  padding:11px 13px;border:1px solid var(--gray-300);
  border-radius:var(--radius-md);background:var(--surface-page);
  width:100%;font-family:var(--font-body);
  transition:border-color var(--dur-fast) var(--ease-brand);
  /* `<select>` ignores the shorthand `font`'s line-height the way `<input>`
     doesn't (a native-rendering quirk, not a CSS override anywhere in this
     file) and comes out a few px shorter, so a date input and its Time
     select in the same .sb-bookbox__grid row sat at different heights and
     didn't line up. An explicit height pins both to the same box regardless
     of how each control wants to size itself; textarea's own min-height:130px
     rule below still wins over this since min-height beats a smaller height. */
  height:48px;
}
.sb-field input:focus,.sb-field textarea:focus,.sb-field select:focus{
  border-color:var(--blue-500);outline:none;box-shadow:0 0 0 3px var(--blue-100);
}
.sb-field textarea{min-height:130px;resize:vertical}
.sb-field__err{font:var(--text-caption);color:var(--coral-600);display:none}
.sb-field.is-invalid input,.sb-field.is-invalid textarea{border-color:var(--coral-500)}
.sb-field.is-invalid .sb-field__err{display:block}
.sb-form__foot{display:flex;align-items:center;gap:14px;margin-top:20px}
.sb-form__note{font:var(--text-caption);color:var(--text-muted)}
.sb-btn[disabled]{opacity:.6;cursor:default}

/* ---- account / credential forms -----------------------------------------
   login, register, express signup, profile, change password. Nothing here is
   auth-specific logic: it is the small set of shapes the legacy pages got
   from bootstrap (full-width submit, checkbox row, inline link button) plus
   the password reveal control the old pages built out of an icon font. */

/* the credential forms are single-column and much narrower than the 820px
   editorial measure, or the inputs stretch to an absurd line length */
.sb-authcard{max-width:460px;margin:0 auto}
/* Registration carries eleven fields against login's two. At 460px its
   half-width pairs (gender/age, country code/phone) never had room to sit
   side by side, so the form ran to a 2200px scroll. Wider, they pair. */
.sb-authcard--wide{max-width:620px}
/* Boki is absolutely positioned against the card's right edge and rises above
   it — 72px above at the narrow breakpoint, against a 32px gap to the heading,
   so he crosses it. "Login" is short enough to pass to his left; "Register" is
   not (measured: 11x39px). Widening the gap past his reach clears him for any
   heading, at any word length, in any locale — where shrinking him only buys
   room until the next longer word. Scoped to --wide: login and setupPassword
   sit clear already and keep their tighter spacing. */
@media (max-width:520px){
  .sb-authcard--wide .sb-pagehead{margin-bottom:78px}
}
/* .sb-form__grid drops to one column below 900px of VIEWPORT, but what
   actually constrains these fields is the 620px card. Between the width where
   the card stops growing (~660px) and that breakpoint it was stacking pairs
   with 620px of room to spare — this puts them back, rendering exactly as it
   does above 900px. */
@media (min-width:660px) and (max-width:900px){
  .sb-authcard--wide .sb-form__grid{grid-template-columns:1fr 1fr}
}

/* Boki peeking out from behind the card's top-right corner, not sitting
   flat on top of it. `.sb-authcard .sb-form` (scoped here, not the shared
   `.sb-form` rule above) gets its own stacking context via position+z-index,
   so the mascot — a child of it with z-index:-1 — paints behind the card's
   own background/shadow instead of the whole page: the half that overlaps
   the card hides behind it, the half poking out above stays visible.
   No transform or filter: either one forces the browser to resample the
   image, which is what was reading as blurry — not the source resolution. */
.sb-authcard .sb-form{position:relative;z-index:0;overflow:visible}
.sb-authcard__boki{
  position:absolute;z-index:-1;top:-104px;right:24px;
  display:block;height:190px;width:auto;
  pointer-events:none;
}

.sb-btn--block{width:100%;justify-content:center;padding:12px 20px}

/* password reveal. The wrapper (not .sb-field) is the positioning context so
   the button sits against the input whether or not an error line is present. */
.sb-pw{position:relative;display:flex}
.sb-pw input{padding-right:44px}
.sb-pw__eye{
  position:absolute;top:50%;right:8px;transform:translateY(-50%);
  display:flex;align-items:center;justify-content:center;
  width:28px;height:28px;padding:0;border:0;border-radius:var(--radius-sm);
  background:transparent;color:var(--gray-500);cursor:pointer;
}
.sb-pw__eye:hover{color:var(--text-heading);background:var(--gray-100)}
.sb-pw__eye .sb-icon{width:18px;height:18px}

/* inline text control (the "forgot your password?" trigger) */
.sb-linkish{
  background:none;border:0;padding:0;cursor:pointer;
  font:var(--text-caption);color:var(--text-link);text-decoration:underline;
}
.sb-linkish:hover{color:var(--blue-600)}

/* one-line status text written by script into a placeholder span */
.sb-note{font:var(--text-caption)}
.sb-note--ok{color:var(--green-600)}
.sb-note--bad{color:var(--coral-600)}

/* label + value rows (promotional code result) */
.sb-deflist{display:grid;grid-template-columns:auto 1fr;gap:10px 20px}
.sb-deflist dt{font:var(--text-subtitle);font-weight:600;color:var(--text-muted)}
.sb-deflist dd{font:var(--text-body);color:var(--text-heading)}

/* ---- titled detail group (myBooking) ------------------------------------
   A booking carries ~30 term/value pairs. Run as one list they read as an
   undifferentiated wall, and the old two-column split cut across meaning —
   payment ended up in the left column and the pickup arrangements in the
   right. These group the pairs by what a member is actually looking for:
   contact / arrangement / payment / documents.

   The heading is what does the work; the rule under it just stops two
   adjacent groups from reading as one list. */
.sb-dgroup + .sb-dgroup{margin-top:28px}
/* .sb-acc__a opens with no top padding, so the first group's underlined
   heading would sit flush against the "Details" summary and read as part
   of it rather than as the first section inside it. */
.sb-acc__a > .sb-dgroup:first-child{margin-top:8px}
/* 16px/700 against the 14px/600 labels below it. At the labels' own size the
   heading stopped reading as a heading at all — it just looked like another
   term with nothing beside it. */
.sb-dgroup > h4{
  font:var(--text-h4);font-weight:700;color:var(--text-heading);
  padding-bottom:8px;margin-bottom:14px;
  border-bottom:1px solid var(--border-subtle);
}

/* Two term/value pairs per row. The columns are declared on the <dl> itself
   so plain <dt>/<dd> children auto-flow into them — no per-row wrapper, which
   means the JSP keeps emitting the same flat list it always did.

   `max-content` on the label columns rather than `auto`: `auto` lets a long
   value in one row widen the label column for every other row, which is what
   made the labels drift away from their values. */
.sb-deflist--pairs{
  grid-template-columns:max-content minmax(0,1fr) max-content minmax(0,1fr);
  gap:12px 18px;
}
/* values wrap instead of forcing the grid wider than the card */
.sb-deflist--pairs dd{min-width:0;overflow-wrap:anywhere}
/* An unfilled optional field (no WhatsApp, no drop-off) reads as a rendering
   fault when it is simply blank. A dash says "nothing here" out loud.
   :empty deliberately does not match the AJAX placeholders (confirmation
   slip, invoice, bank slip) — those hold a <span>, so they keep their
   spinner and get filled in later. */
.sb-deflist--pairs dd:empty::after{content:"—";color:var(--text-muted)}
/* the two figures a member opens this panel to check */
.sb-deflist--pairs dd.sb-deflist__key{font-weight:700}

/* 1200px, not the 900px the rest of the page switches at: this list sits in
   the .sb-withaside content column, which is ~260px narrower than the
   viewport. Below this the second value column is too narrow to hold an
   address without wrapping every line. */
@media (max-width:1200px){
  .sb-deflist--pairs{grid-template-columns:max-content minmax(0,1fr)}
}
@media (max-width:520px){
  /* below this the label column starves the value column, so stack them */
  .sb-deflist--pairs{grid-template-columns:minmax(0,1fr);gap:2px 0}
  .sb-deflist--pairs dt{margin-top:10px}
  .sb-deflist--pairs dt:first-child{margin-top:0}
}

/* a long form split into titled blocks, separated by a rule (feedback) */
.sb-formsec + .sb-formsec{
  margin-top:28px;padding-top:24px;border-top:1px solid var(--border-subtle);
}
/* 4px put the heading right on top of the first label. A checkout form is
   read section by section, so each heading needs to own its block. */
.sb-formsec > h2{font:var(--text-h3);margin-bottom:18px}
.sb-formsec > h3{font:var(--text-h4);margin-bottom:4px}

/* ---- star rating -------------------------------------------------------
   Replaces the sky-forms `.rating` widget. The radios stay in the page's
   original descending order (5 -> 1) because their ids/values are the wire
   contract; `flex-direction:row-reverse` puts them back on screen as 1 -> 5,
   which is also what makes the sibling combinator work: the checked (or
   hovered) input is followed in the DOM by every LOWER star, i.e. every star
   to its left. Hover only ever adds fill, never removes it — same as the
   legacy widget. */
.sb-rate{display:flex;align-items:center;gap:16px;flex-wrap:wrap;padding:9px 0}
.sb-rate__label{font:var(--text-subtitle);color:var(--text-heading)}
.sb-rate__stars{
  position:relative;
  display:inline-flex;flex-direction:row-reverse;justify-content:flex-end;gap:4px;
}
.sb-rate__stars input{position:absolute;width:1px;height:1px;opacity:0}
.sb-rate__stars label{display:inline-flex;cursor:pointer;color:var(--gray-300)}
.sb-rate__stars label .sb-icon{width:26px;height:26px;fill:currentColor}
.sb-rate__stars input:checked ~ label{color:var(--star)}
.sb-rate__stars label:hover,
.sb-rate__stars label:hover ~ label{color:var(--star)}
.sb-rate__stars input:focus-visible + label{
  outline:2px solid var(--focus-ring);outline-offset:2px;border-radius:var(--radius-sm);
}

/* ---- checkbox / radio choices ------------------------------------------ */
.sb-check{
  display:flex;align-items:center;gap:9px;cursor:pointer;
  font:var(--text-body);font-size:15px;color:var(--text-body-color);
}
.sb-check input{
  width:17px;height:17px;flex:none;cursor:pointer;accent-color:var(--green-600);
}
.sb-checks{display:grid;grid-template-columns:repeat(2,1fr);gap:9px 20px}
/* a question with its yes/no pair on one line */
.sb-choicerow{
  display:flex;align-items:center;justify-content:space-between;
  gap:16px;flex-wrap:wrap;padding:9px 0;
}
.sb-choicerow__q{font:var(--text-subtitle);color:var(--text-heading)}
.sb-choices{display:flex;gap:22px}

/* ---- enquiry box: WhatsApp / email tabs ---------------------------------
   Mirrors helpBox2.jsp, which the detail pages used before the conversion.
   WhatsApp is the default and the primary channel — it is how most Malaysian
   guests actually reach us — so it leads and carries the green. */
.sb-helpbox{
  background:var(--green-100);border:1px solid var(--green-300);
  border-radius:var(--radius-lg);padding:24px;
}
.sb-helpbox__head{
  display:flex;align-items:center;justify-content:space-between;gap:14px;
  flex-wrap:wrap;margin-bottom:14px;
}
.sb-helpbox__title{display:flex;align-items:center;gap:12px}
.sb-helpbox__disc{
  width:42px;height:42px;flex:none;border-radius:50%;
  background:var(--green-600);color:var(--text-inverse);
  display:flex;align-items:center;justify-content:center;
}
.sb-helpbox__disc .sb-icon{width:22px;height:22px}
.sb-helpbox__title h2{font:var(--text-h3);color:var(--green-800)}
.sb-helptabs{
  display:inline-flex;border:2px solid var(--green-600);
  border-radius:var(--radius-sm);overflow:hidden;
}
.sb-helptabs button{
  display:flex;align-items:center;gap:6px;
  padding:8px 16px;border:0;cursor:pointer;
  font:var(--text-subtitle);font-weight:600;
  background:transparent;color:var(--green-600);
  transition:background var(--dur-fast) var(--ease-brand),
             color var(--dur-fast) var(--ease-brand);
}
.sb-helptabs button .sb-icon{width:15px;height:15px}
.sb-helptabs button[aria-selected="true"]{background:var(--green-600);color:var(--text-inverse)}
.sb-helpbox__intro{font:var(--text-body);color:var(--text-body-color)}
.sb-helpbox__note{
  display:flex;align-items:center;gap:7px;
  font:var(--text-caption);color:var(--text-muted);margin-top:6px;
}
.sb-helpbox__note .sb-icon{width:14px;height:14px;color:var(--green-600)}
.sb-helpbox__note--block{align-items:flex-start;line-height:1.55;margin-top:22px}
.sb-helpbox__note--block .sb-icon{flex-shrink:0;margin-top:3px}
.sb-helpbox__panel{margin-top:16px}
.sb-helpbox__panel[hidden]{display:none}
.sb-helpbox__foot{
  display:flex;align-items:center;gap:16px;flex-wrap:wrap;margin-top:16px;
}
/* WhatsApp keeps its own brand colour — a green button here is not our green */
.sb-btn--wa{background:#25D366;color:#fff;border-radius:var(--radius-md);padding:12px 24px}
.sb-btn--wa:hover{background:#1EBE5A}
.sb-helpbox .sb-form{box-shadow:none;border:1px solid var(--border-subtle);padding:20px}

/* ---- modal ---------------------------------------------------------------
   Replaces the Bootstrap dialog the member pages used for booking
   cancellation. `data-bs-toggle="modal"` needs Bootstrap's JS, which brand
   pages do not load — so that button silently did nothing and a member could
   not cancel a booking at all. Built on native <dialog>: it gets focus
   trapping, Escape-to-close and the top layer for free.

   Open with dlg.showModal(), close with dlg.close(). */
.sb-modal{
  border:0;padding:0;max-width:min(460px,92vw);width:100%;
  border-radius:var(--radius-lg);box-shadow:var(--shadow-float);
  background:var(--surface-card);color:var(--text-body-color);
}
.sb-modal::backdrop{background:rgba(17,17,17,.55)}
.sb-modal__head{
  display:flex;align-items:flex-start;gap:12px;
  padding:20px 22px 0;
}
.sb-modal__head h3{font:var(--text-h3);font-size:18px;flex:1}
.sb-modal__disc{
  width:38px;height:38px;flex:none;border-radius:50%;
  background:var(--coral-100);display:flex;align-items:center;justify-content:center;
}
.sb-modal__disc .sb-icon{width:19px;height:19px;color:var(--coral-600)}
.sb-modal__body{padding:14px 22px 0;font:var(--text-body);font-size:15px}
.sb-modal__foot{
  display:flex;justify-content:flex-end;gap:10px;flex-wrap:wrap;
  padding:20px 22px 22px;
}
.sb-btn--danger{background:var(--coral-500);color:var(--text-inverse);border-radius:var(--radius-md)}
.sb-btn--danger:hover{background:var(--coral-600)}
.sb-btn--quiet{background:var(--gray-100);color:var(--text-heading);border-radius:var(--radius-md)}
.sb-btn--quiet:hover{background:var(--gray-300)}
/* Destructive, but only the TRIGGER — the "Cancel Booking" that opens the
   confirm dialog. Solid coral (.sb-btn--danger) is kept for the confirm
   button inside that dialog, where cancelling really is the one thing the
   screen is asking for. On the booking panel it is not: a member who has
   already paid sees no Pay button, so a solid coral Cancel became the
   loudest thing on the page. Coral is capped at one per screen. */
.sb-btn--dangerquiet{
  background:var(--gray-100);color:var(--coral-600);border-radius:var(--radius-md);
}
.sb-btn--dangerquiet:hover{background:var(--coral-100);color:var(--coral-800)}

/* busy indicator for in-page uploads — the legacy pages used an icon font
   (icon-spin6 animate-spin) that brand pages no longer load. */
.sb-spin{
  display:inline-block;width:16px;height:16px;vertical-align:-3px;
  border:2px solid var(--gray-300);border-top-color:var(--green-600);
  border-radius:50%;animation:sb-spin .8s linear infinite;
}
@keyframes sb-spin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){
  .sb-spin{animation-duration:2.4s}
}

/* ---- alerts ------------------------------------------------------------- */
.sb-alert{
  display:flex;gap:12px;align-items:flex-start;
  padding:16px 18px;border-radius:var(--radius-md);
  font:var(--text-body);font-size:15px;
}
.sb-alert .sb-icon{width:20px;height:20px;flex:none}
.sb-alert--ok{background:var(--green-100);color:var(--green-800)}
.sb-alert--ok .sb-icon{color:var(--green-600)}
.sb-alert--bad{background:var(--coral-100);color:var(--coral-800)}
.sb-alert--bad .sb-icon{color:var(--coral-600)}
.sb-alert--info{background:var(--blue-100);color:var(--blue-800)}
.sb-alert--info .sb-icon{color:var(--blue-600)}

/* ---- data table (weather) ----------------------------------------------- */
.sb-tablewrap{overflow-x:auto;border-radius:var(--radius-lg);box-shadow:var(--shadow-card)}
.sb-table{border-collapse:collapse;width:100%;min-width:640px;background:var(--surface-card)}
.sb-table caption{
  caption-side:top;text-align:left;padding:16px 18px 12px;
  font:var(--text-h3);color:var(--text-heading);
}
.sb-table th,.sb-table td{
  padding:11px 14px;text-align:center;font:var(--text-body);
  border-bottom:1px solid var(--border-subtle);
}
.sb-table thead th{
  background:var(--sand-100);font:var(--text-subtitle);font-weight:600;
  color:var(--text-heading);white-space:nowrap;
}
/* row-header column: the day/metric label down the left edge */
.sb-table tbody th{
  background:var(--sand-100);text-align:left;white-space:nowrap;
  font:var(--text-subtitle);font-weight:600;color:var(--text-heading);
}
.sb-table tbody tr:last-child th,.sb-table tbody tr:last-child td{border-bottom:0}
.sb-table__today{color:var(--green-800)}

/* ---- booking card (myBooking) -------------------------------------------
   One booking used to render as two floating panels: a shadow-only
   .sb-tablewrap and, 14px below it, a bordered <details>. Nothing tied them
   together, so with several bookings listed it was not obvious which "Details"
   belonged to which code. They are now one outlined card, and the code and
   status move out of the table into the card's own head — which also drops the
   table from four columns to two and lets the tour name breathe.

   The outline is --sand-600 rather than --border-subtle: subtle is right for a
   rule *inside* a panel, but too faint to read as the edge of one against a
   white page. */
.sb-bk{
  background:var(--surface-card);
  border:1px solid var(--sand-600);border-radius:var(--radius-lg);
  overflow:hidden;box-shadow:var(--shadow-card);
  margin-bottom:20px;
}
.sb-bk__head{
  display:flex;align-items:center;justify-content:space-between;gap:16px;
  flex-wrap:wrap;padding:14px 18px;
  background:var(--sand-100);border-bottom:1px solid var(--border-subtle);
}
.sb-bk__code{font:var(--text-h4);color:var(--text-heading)}
.sb-bk__code small{
  display:block;margin-bottom:2px;
  font:var(--text-caption);color:var(--text-muted);
}
/* the panes inside give up their own frame — the card is the frame now */
.sb-bk .sb-tablewrap{border-radius:0;box-shadow:none}
.sb-bk .sb-acc__item{border:0;border-top:1px solid var(--border-subtle);border-radius:0}
.sb-bk .sb-acc__item[open]{box-shadow:none}
/* header row reads as a label strip, not a second banded block under the
   already-sand card head */
.sb-bk .sb-table thead th{background:transparent}
/* the name column's cell is left-aligned; with only two columns left, a
   centred heading above it reads as a mis-alignment rather than a style */
.sb-bk .sb-table thead th:first-child{text-align:left}
/* the shared 640px floor exists for the four-to-six column weather/listing
   tables. Two columns fit a phone, so it would only force a pointless
   sideways scroll here. */
.sb-bk .sb-table{min-width:0}

.sb-bk__status{
  display:inline-flex;align-items:center;gap:6px;white-space:nowrap;
  padding:4px 12px;border-radius:var(--radius-pill);
  font:600 13px/1.4 var(--font-display);
}
/* parseStatus() is shared with b2b/cp/e-mail and still wraps its label in
   <font color> / <b>. A presentational attribute loses to any author rule, so
   handing the colour back to the pill needs no !important and no change to
   that helper. */
.sb-bk__status font,.sb-bk__status b{color:inherit;font-weight:inherit}
.sb-bk__status--ok{background:var(--green-100);color:var(--green-800)}      /* confirmed, installment running */
.sb-bk__status--wait{background:var(--blue-100);color:var(--blue-800)}      /* processing, checking, awaiting approval */
.sb-bk__status--warn{background:var(--coral-100);color:var(--coral-800)}    /* needs the member to act */
.sb-bk__status--off{background:var(--gray-100);color:var(--gray-500)}       /* cancelled, closed, discarded */

/* ---- key / value card (bankDetail) --------------------------------------
   Replaces the legacy `tag-box tag-box-vN` panel. A <dl>, because these rows
   really are term/definition pairs — and the account number wraps rather than
   pushing the card wide on a phone. */
.sb-kv{
  background:var(--surface-card);border-radius:var(--radius-lg);
  box-shadow:var(--shadow-card);padding:8px 24px;
}
.sb-kv__row{
  display:flex;align-items:baseline;justify-content:space-between;gap:16px;
  padding:14px 0;border-bottom:1px solid var(--border-subtle);
}
.sb-kv__row:last-child{border-bottom:0}
.sb-kv dt{font:var(--text-subtitle);color:var(--text-muted)}
.sb-kv dd{
  font:var(--text-h4);font-size:17px;color:var(--text-heading);
  text-align:right;overflow-wrap:anywhere;
}
.sb-kv .sb-kv__amount{color:var(--blue-600)}
.sb-kv .sb-kv__due{color:var(--coral-600)}

/* ---- fare card (bookShuttle) --------------------------------------------
   One receipt instead of two blocks: the trip facts on top, the per-seat
   breakdown under a rule, the payable total in a tinted foot. The quote
   script used to emit Bootstrap grid markup (`row`/`col-8`/`text-end`) which
   no brand page ships a stylesheet for, so the breakdown rendered as a column
   of bare text — it now emits .sb-fare__line instead. */
.sb-fare{
  background:var(--surface-card);border-radius:var(--radius-lg);
  box-shadow:var(--shadow-card);overflow:hidden;
}
.sb-fare__trip{margin:0;padding:6px 24px 12px}
.sb-fare__row{
  display:flex;align-items:baseline;justify-content:space-between;gap:16px;
  padding:13px 0;border-bottom:1px solid var(--border-subtle);
}
.sb-fare__row:last-child{border-bottom:0}
.sb-fare__trip dt{font:var(--text-subtitle);color:var(--text-muted)}
.sb-fare__trip dd{
  margin:0;font:var(--text-h4);font-size:16px;color:var(--text-heading);
  text-align:right;overflow-wrap:anywhere;
}

/* Breakdown sits on the sand tint so it reads as the itemised part of the
   card rather than more trip facts. `.sb-page h2.` qualified: `.sb-home h2`
   is (0,1,1) and would otherwise keep its own margin and heading colour. */
.sb-fare__break{
  padding:16px 24px 18px;background:var(--sand-100);
  border-top:1px solid var(--border-subtle);
}
.sb-page h2.sb-fare__title{
  margin:0 0 6px;font:var(--text-caption);font-weight:600;
  letter-spacing:.08em;text-transform:uppercase;color:var(--text-muted);
}
.sb-fare__line{
  display:flex;align-items:center;justify-content:space-between;gap:16px;
  padding:9px 0;font:var(--text-body);color:var(--text-body-color);
}
.sb-fare__line + .sb-fare__line{border-top:1px dashed var(--sand-500)}
.sb-fare__line span:last-child{
  font:var(--text-subtitle);color:var(--text-heading);
  font-variant-numeric:tabular-nums;white-space:nowrap;
}
/* a surcharge is the one line a guest did not choose — name it in coral so it
   is obvious at a glance which part of the fare is conditional */
.sb-fare__line--extra span:first-child{color:var(--coral-600)}
.sb-fare__line--extra span:last-child{color:var(--coral-600)}

/* Placeholder bars, rendered server-side one per seat, so the card does not
   jump height when the quote lands. */
.sb-fare__line--wait span{
  display:block;height:11px;border-radius:var(--radius-pill);
  background:var(--sand-500);opacity:.5;animation:sb-fare-pulse 1.2s var(--ease-brand) infinite;
}
.sb-fare__line--wait span:first-child{width:84px}
.sb-fare__line--wait span:last-child{width:64px}
@keyframes sb-fare-pulse{50%{opacity:.22}}
@media (prefers-reduced-motion:reduce){
  .sb-fare__line--wait span{animation:none}
}

.sb-fare__total{
  display:flex;align-items:center;justify-content:space-between;gap:16px;
  padding:15px 24px;background:var(--blue-100);
}
.sb-fare__totallabel{font:var(--text-h4);color:var(--blue-800)}
.sb-fare__amount{
  margin:0;font:600 26px/1.1 var(--font-display);color:var(--blue-600);
  font-variant-numeric:tabular-nums;white-space:nowrap;
}

/* ---- shuttle date picker (shuttleinfo) ----------------------------------
   Replaces a `.sb-table` whose cells held .sb-btn--ghost buttons carrying an
   inline style.cssText — a departure calendar rendered as a spreadsheet. A
   month is a grid, not tabular data: there is no row/column relationship to
   preserve, so this is a plain CSS grid and the weekday labels are captions
   rather than <th>s. */
.sb-cal{
  background:var(--surface-card);border-radius:var(--radius-lg);
  box-shadow:var(--shadow-card);overflow:hidden;
}
.sb-cal__nav{
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:12px 14px;border-bottom:1px solid var(--border-subtle);
}
.sb-cal__month{font:var(--text-h4);color:var(--text-heading)}
/* `.sb-home button.` qualified: .sb-btn--ghost sets its own padding and the
   arrows need to be square, not text-width */
.sb-home button.sb-cal__arrow{
  flex:none;width:38px;height:38px;padding:0;justify-content:center;
  border-radius:50%;
}
.sb-cal__grid{display:grid;grid-template-columns:repeat(7,1fr);gap:6px;padding:12px}
.sb-cal__dow{
  font:var(--text-caption);font-weight:600;color:var(--text-muted);
  text-align:center;text-transform:uppercase;letter-spacing:.06em;padding-bottom:2px;
}
/* a day with no bookable departure: still numbered, so the month stays
   readable as a calendar, but plainly not a target */
.sb-cal__off{
  display:flex;align-items:center;justify-content:center;min-height:60px;
  font:var(--text-body);color:var(--gray-300);
}
.sb-home button.sb-cal__day{
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;
  width:100%;min-height:60px;padding:7px 2px;cursor:pointer;
  border:1px solid var(--sand-500);border-radius:var(--radius-md);
  background:var(--sand-100);color:var(--text-heading);
  transition:transform var(--dur-fast) var(--ease-brand),
             box-shadow var(--dur-fast) var(--ease-brand),
             border-color var(--dur-fast) var(--ease-brand);
}
.sb-cal__daynum{font:var(--text-h4);font-size:17px;line-height:1}
.sb-cal__dayprice{font:var(--text-small);color:var(--green-600);white-space:nowrap}
.sb-home button.sb-cal__day:hover{
  transform:translateY(-1px);border-color:var(--green-500);box-shadow:var(--shadow-card);
}
/* the picked day. Before this the grid gave no feedback at all that a click
   had landed — the departure list appeared further down the page and that was
   the only sign. */
.sb-home button.sb-cal__day.is-selected{
  background:var(--green-100);border-color:var(--green-500);
  box-shadow:0 0 0 2px var(--green-300);
}
.sb-home button.sb-cal__day.is-selected .sb-cal__dayprice{color:var(--green-800)}
@media (prefers-reduced-motion:reduce){
  .sb-home button.sb-cal__day{transition:none}
  .sb-home button.sb-cal__day:hover{transform:none}
}

/* ---- departure list ------------------------------------------------------
   One row per sailing on the chosen day. Was a flex row with a bottom border
   and another inline cssText; the seats-left count now reads as a pill so a
   nearly-full departure is visible without reading the sentence. */
.sb-slots{display:grid;gap:10px}
.sb-slot{
  display:flex;align-items:center;gap:14px;flex-wrap:wrap;
  padding:13px 16px;background:var(--surface-card);
  border:1px solid var(--border-subtle);border-radius:var(--radius-md);
}
.sb-slot--off{background:var(--gray-100)}
.sb-slot__time{font:var(--text-h4);font-size:18px;color:var(--text-heading);min-width:62px}
.sb-slot--off .sb-slot__time{color:var(--text-muted)}
.sb-slot__seats{
  padding:3px 10px;border-radius:var(--radius-pill);font:var(--text-small);
}
.sb-slot__seats--ok{background:var(--green-100);color:var(--green-800)}
.sb-slot__seats--low{background:var(--coral-100);color:var(--coral-800)}
.sb-slot__seats--off{background:var(--gray-100);color:var(--gray-500)}
.sb-slot__price{
  margin-left:auto;font:var(--text-subtitle);font-weight:600;color:var(--text-heading);
  white-space:nowrap;
}
.sb-slot--off .sb-slot__price{color:var(--text-muted)}
.sb-slot .sb-btn{flex:none}
.sb-slot.is-selected{border-color:var(--green-500);box-shadow:0 0 0 2px var(--green-300)}

@media (max-width:520px){
  .sb-cal__grid{gap:4px;padding:10px}
  .sb-cal__off,.sb-home button.sb-cal__day{min-height:50px}
  .sb-cal__daynum{font-size:14px}
  .sb-cal__dayprice{font-size:10px}
  .sb-cal__dow{font-size:10px}
  /* price and button take their own row rather than crushing the time */
  .sb-slot__price{margin-left:0}
  .sb-slot .sb-btn{width:100%;justify-content:center}
}

/* ---- error pages (404, 100) --------------------------------------------- */
.sb-error{
  min-height:60vh;display:flex;align-items:center;justify-content:center;
  padding:64px var(--gutter);background:var(--sand-100);text-align:center;
}
.sb-error__inner{max-width:560px}
.sb-error__code{
  font:600 96px/1 var(--font-display);color:var(--green-500);letter-spacing:-2px;
}
.sb-error h1{font:var(--text-h2);margin-top:12px}
.sb-error p{font:var(--text-body);font-size:15px;color:var(--text-muted);margin-top:10px}
.sb-error img{margin:0 auto 20px;max-height:150px;width:auto}
.sb-error .sb-linklist{
  max-width:320px;margin:24px auto 0;text-align:left;
}
.sb-error__cta{margin-top:24px;display:inline-flex}

/* ---- 404 recovery -------------------------------------------------------
   A 404 is usually reached from a stale link someone was sent, so the page
   has to be a junction rather than an apology: a search box wired to the
   cross-product search, then the four product lines as cards, then the
   quieter routes (contact, bookings, home).

   Everything here is a --recover modifier or an .sb-error__* addition, never
   a change to the base .sb-error rules - 100.jsp (the unpublished-product
   notice) shares those and must keep its narrow, centred shape. */
.sb-error__inner--recover{max-width:900px}
/* the mascot above is cropped flush to its box, so the eyebrow needs its own
   clearance or it reads as sitting on Boki's feet */
.sb-error__eyebrow{
  font:700 15px/1 var(--font-display);color:var(--green-600);letter-spacing:3px;
  margin-top:10px;
}
/* Boki cropped to bust height over a soft disc, the same treatment the
   no-results states use - a 404 and an empty search are the same moment for
   the visitor, so they should look alike. */
.sb-error__art{position:relative;width:190px;height:150px;margin:0 auto;overflow:hidden}
.sb-error__art::before{
  content:"";position:absolute;inset:0;
  background:radial-gradient(circle at 50% 38%, var(--green-100) 0%, transparent 70%);
}
/* explicit height, and position:relative to sit above the disc: the base
   `.sb-error img` caps max-height at 150px, which would shrink the crop */
.sb-error__art img{position:relative;height:230px;max-height:none;margin:0 auto;display:block}

.sb-error__search{display:flex;gap:8px;max-width:520px;margin:22px auto 0}
/* min-height, not height: the phone breakpoint turns this row into a column,
   where `flex:1` resolves flex-basis:0 against the HEIGHT and would collapse
   a fixed-height field to a hairline. */
.sb-error__search input{
  flex:1;min-width:0;min-height:46px;padding:0 14px;
  border:1px solid var(--border-subtle);border-radius:var(--radius-md);
  background:var(--surface-card);font:var(--text-body);font-size:15px;
}
.sb-error__search input:focus{
  outline:none;border-color:var(--blue-500);box-shadow:0 0 0 3px var(--blue-100);
}
.sb-error__search button{
  flex:none;min-height:46px;padding:0 22px;border:0;border-radius:var(--radius-md);
  background:var(--action-cta);color:var(--text-inverse);cursor:pointer;
  font:600 15px/1 var(--font-display);
}
.sb-error__search button:hover{background:var(--action-cta-hover)}

/* 100.jsp only: its message names one product, so the card row needs a line
   that pivots away from it. The 404's message is already generic and goes
   straight to the cards. */
.sb-error__browse{
  margin-top:24px;font:var(--text-small);color:var(--text-muted);
  text-transform:uppercase;letter-spacing:.5px;
}
.sb-error__browse + .sb-error__grid{margin-top:12px}

.sb-error__grid{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-top:26px}
.sb-error__card{
  background:var(--surface-card);border:1px solid var(--border-subtle);
  border-radius:var(--radius-lg);padding:18px 14px;display:block;color:inherit;
  transition:transform var(--dur-med) var(--ease-brand),
             box-shadow var(--dur-med) var(--ease-brand);
}
.sb-error__card:hover{transform:translateY(-2px);box-shadow:var(--shadow-card)}
/* 100.jsp marks the card matching the product the visitor came for, so the
   most likely replacement is the one their eye lands on. Border and tint
   only - not the green CTA fill, which would read as "this is the button"
   when the other three are equally clickable. */
.sb-error__card--match{
  border-color:var(--green-500);background:var(--green-100);
}
.sb-error__card--match .sb-error__disc{background:var(--surface-card)}
.sb-error__disc{
  width:42px;height:42px;border-radius:50%;background:var(--green-100);
  display:inline-flex;align-items:center;justify-content:center;
}
.sb-error__disc .sb-icon{width:21px;height:21px;color:var(--green-600)}
.sb-error__card h2{font:var(--text-h4);font-size:15px;margin-top:11px}
/* (0,2,0) so it outranks the base `.sb-error p` (0,1,1) muted 15px body */
.sb-error .sb-error__card p{font:var(--text-caption);color:var(--text-muted);margin-top:4px}

.sb-error__foot{
  margin-top:26px;padding-top:18px;border-top:1px solid var(--sand-500);
  font:var(--text-caption);color:var(--text-muted);
}
.sb-error__foot a{color:var(--text-link);text-decoration:underline;margin:0 5px}

/* ---- payment result (transaction.jsp) -----------------------------------
   The gateway returns one of three outcomes and the page used to print a
   single .sb-alert into an otherwise empty column, so the footer began right
   under it. Someone who has just been charged gets a card instead: Boki in a
   pose that matches the news, the message, the booking code they will want to
   note down, and one clear next step.

   The tone classes only colour the mascot's disc and the reference chip. The
   message keeps whatever markup its own string carries - all three L10N
   strings are a bold first line plus a <small> body, which is already the
   hierarchy this layout wants. */
.sb-result{
  max-width:560px;margin:0 auto;text-align:center;
  background:var(--surface-card);border:1px solid var(--border-subtle);
  border-radius:var(--radius-lg);box-shadow:var(--shadow-card);
  padding:36px 28px 30px;
}
.sb-result__art{
  width:132px;height:132px;margin:0 auto 6px;border-radius:50%;
  display:flex;align-items:flex-end;justify-content:center;overflow:hidden;
}
/* height (not max-height) so the pose fills its disc, and position over the
   tinted background rather than being clipped by .sb-home img{max-width:100%} */
.sb-result__art img{height:168px;width:auto;margin:0;display:block}
.sb-result--ok .sb-result__art{background:var(--green-100)}
.sb-result--info .sb-result__art{background:var(--blue-100)}
.sb-result--bad .sb-result__art{background:var(--coral-100)}

/* the string's own first line is the headline; <small> is the explanation */
.sb-result__msg{font:var(--text-h3);color:var(--text-heading);margin-top:8px}
.sb-result__msg small{
  display:block;margin-top:10px;
  font:var(--text-body);font-size:15px;color:var(--text-muted);
}
.sb-result__msg a{color:var(--text-link);text-decoration:underline}
.sb-result--ok .sb-result__msg{color:var(--green-800)}
.sb-result--bad .sb-result__msg{color:var(--coral-800)}

/* The reference the guest will quote back to us. It was only ever inside a
   link's href before, so there was nothing to write down. */
.sb-result__ref{
  display:inline-flex;align-items:baseline;gap:10px;margin-top:20px;
  padding:10px 16px;border-radius:var(--radius-md);background:var(--sand-100);
  border:1px solid var(--border-subtle);
}
.sb-result__ref span{font:var(--text-caption);color:var(--text-muted)}
.sb-result__ref strong{
  font:600 18px/1 var(--font-display);color:var(--text-heading);
  letter-spacing:1px;
}

.sb-result__actions{
  display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-top:24px;
}
.sb-result__actions .sb-btn{padding:11px 20px}
/* .sb-btn--ghost is transparent with border:0, invisible on the white card */
.sb-result__actions .sb-btn--ghost{
  background:var(--surface-card);border:1px solid var(--border-subtle);
}
.sb-result__actions .sb-btn--ghost:hover{background:var(--sand-100)}

@media (max-width:420px){
  .sb-result{padding:28px 18px 24px}
  .sb-result__actions{flex-direction:column}
  .sb-result__actions .sb-btn{justify-content:center}
}

@media (max-width:767px){
  .sb-error__grid{grid-template-columns:repeat(2,1fr)}
}
@media (max-width:420px){
  .sb-error__grid{grid-template-columns:1fr}
  /* stacked, the button would otherwise squeeze the field to nothing */
  .sb-error__search{flex-direction:column}
  .sb-error__search button{width:100%}
}

/* ---- checkout (bookCar and its bookTour/bookHoliday/… siblings) ----------
   The last step of the funnel. One column: the progress strip, then the
   summary card so the guest can check what they are paying for before filling
   anything in, then the form as numbered .sb-formsec blocks.

   `label.error` is generated by jQuery Validate, which these pages keep — the
   rule set (payMethod, terms, …) is part of the server contract and could not
   be swapped for native validation. Its errorPlacement inserts the label after
   the field WRAPPER, which is why the two container rules at the bottom exist:
   dropped into a grid, the message would otherwise claim a column of its own
   instead of sitting under the field it belongs to. */
.sb-steps{display:flex;gap:10px;list-style:none;margin:0 0 28px;padding:0}
.sb-steps li{flex:1;text-align:center;font:var(--text-caption);color:var(--text-muted)}
.sb-steps li::after{
  content:"";display:block;height:4px;margin-top:9px;
  border-radius:var(--radius-pill);background:var(--gray-300);
}
.sb-steps li.is-on{font:var(--text-subtitle);font-weight:600;color:var(--green-800)}
.sb-steps li.is-on::after{background:var(--green-500)}

.sb-checkout__edit{display:flex;justify-content:flex-end;margin-top:14px}
/* Sits under the summary card's Change button; 20px read as if the two
   belonged together, so the required-field note gets its own air. */
/* `p.` qualified: brand-home.css sets `.sb-home p{margin:0}` at (0,1,1), which
   beats a bare class — the margin silently did nothing. */
.sb-page p.sb-checkout__req{
  font:var(--text-caption);color:var(--text-muted);text-align:right;margin:34px 0 14px;
}
.sb-checkout__row{margin-top:16px}
.sb-checkout__sub{margin-top:8px}
.sb-page p.sb-checkout__hint{
  font:var(--text-caption);color:var(--text-muted);
  text-align:center;margin-top:20px;
}

/* Card-processing fee, revealed under the summary while "pay by card" is the
   selected method. Same .sb-kv rows, tinted so the payable total reads as an
   addendum to the price above rather than a competing one. */
.sb-kv--fee{margin-top:10px;background:var(--blue-100)}
.sb-kv--fee .sb-kv__amount{color:var(--blue-800)}

/* Footnote for the "#" marker on a billed-later option. `p.` qualified for the
   same reason as __req above. */
.sb-page p.sb-checkout__foot{
  font:var(--text-caption);color:var(--text-muted);margin-top:10px;
}

/* The "we still need these details from you" list inside section 5. The legacy
   page shipped a <style> block resetting ul.normalUL because the theme killed
   list markers globally; the brand layers never did, so this only needs the
   indent back. */
.sb-checkout__needs{
  list-style:disc;padding-left:22px;margin:4px 0 18px;
  font:var(--text-body);color:var(--text-body-color);
}
.sb-checkout__needs li{margin-bottom:4px}

/* "What happens next" — reassurance directly below the commit button. */
.sb-checkout__next{
  background:var(--surface-card);border-radius:var(--radius-lg);
  box-shadow:var(--shadow-card);padding:22px;margin-top:26px;
}
.sb-checkout__next h2{font:var(--text-h4);margin-bottom:14px}
.sb-checkout__step{display:flex;align-items:flex-start;gap:10px;margin-bottom:10px}
.sb-checkout__stepn{
  flex:none;width:22px;height:22px;border-radius:50%;
  background:var(--action-primary);color:var(--text-inverse);
  font:700 12px/22px var(--font-display);text-align:center;
}
.sb-home a.sb-checkout__wa{
  display:inline-flex;align-items:center;gap:8px;margin-top:6px;
  padding-top:12px;border-top:1px solid var(--border-subtle);width:100%;
  font:var(--text-subtitle);font-weight:600;color:var(--green-600);
}

/* Checkout actions: Cancel left, Submit right — the DOM order matches, so the
   tab order does too. Both are sized as thumb targets rather than inheriting
   the compact default .sb-btn padding.

   Radius is inherited from .sb-btn (--radius-md) — no override needed now
   that .sb-btn--primary no longer forces a pill.

   justify-content is set explicitly: .sb-btn is an inline-flex that does not
   centre, so once the committing button gained a min-width its label sat
   against the left edge. */
.sb-form__foot--split{justify-content:space-between;gap:16px;margin-top:30px}
.sb-form__foot--split .sb-btn{
  min-height:52px;padding:14px 32px;font-size:16px;
  justify-content:center;
}
.sb-form__foot--split .sb-btn--quiet{
  background:var(--surface-card);
  border:1px solid var(--gray-300);
  color:var(--text-body-color);
}
.sb-form__foot--split .sb-btn--quiet:hover{
  background:var(--gray-100);border-color:var(--gray-500);color:var(--text-heading);
}
/* the committing action carries a little lift, tinted to whichever variant
   it is — coral for a true CTA, green when a page uses the plain primary */
.sb-form__foot--split .sb-btn--primary,
.sb-form__foot--split .sb-btn--cta{min-width:180px}
.sb-form__foot--split .sb-btn--primary{box-shadow:0 2px 10px rgba(108,194,67,.32)}
.sb-form__foot--split .sb-btn--primary:hover{box-shadow:0 4px 14px rgba(108,194,67,.42)}
.sb-form__foot--split .sb-btn--cta{box-shadow:0 2px 10px rgba(255,106,61,.32)}
.sb-form__foot--split .sb-btn--cta:hover{box-shadow:0 4px 14px rgba(255,106,61,.42)}
@media (max-width:767px){
  /* share the row equally instead of hugging their text */
  .sb-form__foot--split{gap:12px}
  .sb-form__foot--split .sb-btn{flex:1;padding:14px 12px}
}
.sb-req{color:var(--coral-600)}
.sb-strike{color:var(--text-muted);text-decoration:line-through}

.sb-formsec > h2 .sb-formsec__n{
  display:inline-flex;align-items:center;justify-content:center;
  width:26px;height:26px;margin-right:10px;border-radius:50%;
  background:var(--green-100);color:var(--green-800);
  font:var(--text-subtitle);font-weight:600;vertical-align:2px;
}

/* dismiss control on a page-level alert (replaces bootstrap's .btn-close) */
.sb-alert__x{
  margin-left:auto;flex:none;padding:2px;border:0;background:transparent;
  color:inherit;cursor:pointer;border-radius:var(--radius-sm);
}
.sb-alert__x .sb-icon{width:16px;height:16px}

/* ---- invalid field state ------------------------------------------------
   TWO validation builds are in play and they do not agree on class names:

   * stock jQuery Validate      -> errorClass "error",   errorElement <label>
   * the sky-forms 1.11.0 build -> errorClass "invalid", errorElement <em>,
     and it ALSO stamps `state-error` on the field's parent
     (assets2/plugins/sky-forms/version-2.0.1/js/jquery.validate.min.js)

   The booking pages load the sky-forms build, so styling only `.error` hit
   nothing at all and invalid fields kept the plain grey/blue treatment. Both
   conventions are covered here, plus `.sb-field.is-invalid` for the vanilla
   path in brandHelpBox.jsp.

   Blue is the FOCUS colour, and validate focuses the first invalid field on
   submit — so the error state has to win on :focus too, or the field reads as
   "active" rather than "wrong". The light red fill makes an unfocused invalid
   field obvious without relying on a 1px border. */
.sb-page em.invalid,
.sb-page label.error{
  display:block;margin-top:6px;font:var(--text-caption);font-style:normal;
  color:var(--coral-600);
}

.sb-page input.invalid:not([type="radio"]):not([type="checkbox"]),
.sb-page select.invalid,
.sb-page textarea.invalid,
.sb-page .state-error input:not([type="radio"]):not([type="checkbox"]),
.sb-page .state-error select,
.sb-page .state-error textarea,
.sb-page input.error:not([type="radio"]):not([type="checkbox"]),
.sb-page select.error,
.sb-page textarea.error,
.sb-field.is-invalid input:not([type="radio"]):not([type="checkbox"]),
.sb-field.is-invalid select,
.sb-field.is-invalid textarea{
  border-color:var(--coral-500);
  background:var(--coral-100);
}
.sb-page input.invalid:focus,
.sb-page select.invalid:focus,
.sb-page textarea.invalid:focus,
.sb-page .state-error input:focus,
.sb-page .state-error select:focus,
.sb-page .state-error textarea:focus,
.sb-page input.error:focus,
.sb-page select.error:focus,
.sb-page textarea.error:focus,
.sb-field.is-invalid input:focus,
.sb-field.is-invalid select:focus,
.sb-field.is-invalid textarea:focus{
  border-color:var(--coral-600);
  box-shadow:0 0 0 3px var(--coral-300);
}
/* A radio or checkbox has no usable border or fill, so ring it instead. This
   is what marks the unanswered "How to pay" and "I agree" groups. */
.sb-page input[type="radio"].invalid,
.sb-page input[type="checkbox"].invalid,
.sb-page input[type="radio"].error,
.sb-page input[type="checkbox"].error{
  outline:2px solid var(--coral-500);outline-offset:2px;
  accent-color:var(--coral-500);
}
/* the plugin also stamps a success state; keep it neutral rather than
   painting every completed field green */
.sb-page .state-success input,
.sb-page .state-success select,
.sb-page .state-success textarea{background:var(--surface-page)}

.sb-form__grid > em.invalid,
.sb-checks > em.invalid,
.sb-form__grid > label.error,
.sb-checks > label.error{grid-column:1 / -1;margin-top:0}

/* ---- responsive --------------------------------------------------------- */
@media (max-width:1100px){
  .sb-quotes{grid-template-columns:repeat(2,1fr)}
}
@media (max-width:900px){
  .sb-withaside{grid-template-columns:1fr;gap:28px}
  .sb-aside{position:static}
  .sb-feat__grid,.sb-feat__grid--2{grid-template-columns:1fr}
  .sb-duo,.sb-duo--flip{grid-template-columns:1fr;gap:24px}
  .sb-duo--flip > :first-child{order:0}
  .sb-form__grid{grid-template-columns:1fr}
}
@media (max-width:767px){
  .sb-pagehero{padding:32px var(--gutter) 36px}
  .sb-pagehero::before{                 /* horizontal fade leaves no photo this narrow */
    background:linear-gradient(180deg,
      rgba(255,255,255,.95) 0%,
      rgba(255,255,255,.84) 45%,
      rgba(255,255,255,.4) 100%);
  }
  .sb-content{padding:32px var(--gutter)}
  .sb-linklist--cols{grid-template-columns:1fr}
  .sb-quotes{grid-template-columns:1fr}
  .sb-form{padding:20px}
  .sb-authcard__boki{height:130px;top:-72px;right:14px}
  .sb-checks{grid-template-columns:1fr}
  .sb-choicerow{display:block}
  .sb-choices{margin-top:8px}
  .sb-kv{padding:8px 18px}
  .sb-kv__row{display:block}
  .sb-kv dd{text-align:left;margin-top:2px}
  /* only the trip facts stack — a fare line is a label/amount pair and stays
     on one row however narrow the screen gets */
  .sb-fare__trip{padding:4px 18px 10px}
  .sb-fare__trip .sb-fare__row{display:block}
  .sb-fare__trip dd{text-align:left;margin-top:2px}
  .sb-fare__break{padding:14px 18px 16px}
  .sb-fare__total{padding:14px 18px}
  .sb-fare__amount{font-size:23px}
  .sb-error__code{font-size:72px}
}

/* ============ Free eSIM dashboard (myBooking) ============ */
.sb-esimdash{background:linear-gradient(135deg,#0d3b66,#1d6fa5);border-radius:14px;padding:24px;color:#fff;margin-bottom:36px}
.sb-esimdash__head{display:flex;align-items:center;gap:10px;margin-bottom:16px;flex-wrap:wrap}
.sb-esimdash__head h2{font-size:20px;font-weight:700;color:#fff;margin:0}
.sb-esimdash__head .sb-icon{width:26px;height:26px}
.sb-esimdash__free{flex-shrink:0;background:#f39c12;color:#fff;font-weight:800;font-size:12px;letter-spacing:2px;padding:4px 12px;border-radius:20px}
.sb-esimdash__labels{display:flex;justify-content:space-between;font-size:14px;margin-bottom:6px}
.sb-esimdash__bar{background:rgba(255,255,255,.25);border-radius:8px;height:12px;overflow:hidden}
.sb-esimdash__fill{background:#ffd700;height:100%;border-radius:8px;transition:width .4s}
.sb-esimdash__msg{margin:10px 0 0;font-size:15px;font-weight:600}
.sb-esimdash__guest{background:rgba(255,255,255,.12);border:1px solid rgba(255,255,255,.25);border-radius:10px;padding:14px;margin-top:16px;display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}
.sb-esimdash__guest p{margin:0}
.sb-esimdash__actions{display:flex;gap:10px;margin-top:16px;flex-wrap:wrap}
.sb-esimdash__list{background:#fff;border-radius:10px;padding:12px;margin-top:14px;color:#333}
.sb-esimdash__variant{display:flex;justify-content:space-between;align-items:center;gap:10px;padding:8px 6px;border-bottom:1px solid #eee;flex-wrap:wrap}
.sb-esimdash__variant:last-child{border-bottom:0}
.sb-esimdash__mine{margin-top:18px}
.sb-esimdash__mine h3{font-size:16px;color:#fff;margin:0 0 8px}
.sb-esimdash__row{display:flex;align-items:center;gap:10px;background:rgba(255,255,255,.12);border-radius:8px;padding:10px;margin-bottom:8px;flex-wrap:wrap}
.sb-esimdash__code{font-weight:700}
.sb-esimdash__ac{background:#fff;color:#0d3b66;border-radius:6px;padding:4px 8px;word-break:break-all;max-width:100%}
.sb-esimdash__actioncell{margin-left:auto;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.sb-esimdash .sb-btn:not([class*="sb-btn--"]){background:rgba(255,255,255,.16);color:#fff;border:1px solid rgba(255,255,255,.35)}
@media (max-width:767px){.sb-esimdash{padding:16px}}

/* ============ Free eSIM promo panel (*info pages) ============ */
.sb-esim{background:linear-gradient(135deg,#0d3b66,#1d6fa5);border-radius:14px;padding:24px;color:#fff;box-shadow:0 4px 20px rgba(29,111,165,.35)}
.sb-esim__top{display:flex;align-items:center;gap:16px;flex-wrap:wrap}
.sb-esim__wifi{width:44px;height:44px;flex-shrink:0}
.sb-esim__title{flex:1;min-width:200px}
.sb-esim__title h2{font-size:20px;font-weight:700;color:#fff;margin:6px 0 2px}
.sb-esim__badge{background:#f39c12;color:#fff;font-weight:800;font-size:12px;letter-spacing:2px;padding:4px 12px;border-radius:20px;display:inline-block}
.sb-esim__plan{margin:0;font-size:15px;opacity:.9}
.sb-esim__worth{background:rgba(255,255,255,.15);border:1px solid rgba(255,255,255,.3);border-radius:10px;padding:10px 16px;color:#ffd700;font-size:20px;font-weight:800;text-shadow:0 0 12px rgba(255,215,0,.4)}
.sb-esim__desc{margin:14px 0 0;font-size:14px;opacity:.95}
.sb-esim__feats{display:flex;margin-top:16px}
.sb-esim__feat{flex:1;display:flex;align-items:center;gap:8px;padding:0 12px;border-right:1px solid rgba(255,255,255,.15);font-size:13px}
.sb-esim__feat:first-child{padding-left:0}
.sb-esim__feat:last-child{border-right:0}
.sb-esim__feat .sb-icon{width:20px;height:20px;flex-shrink:0}
.sb-esim__cta{margin-top:16px;text-align:right}
@media (max-width:767px){
	.sb-esim{padding:16px}
	.sb-esim__top{justify-content:center;text-align:center}
	.sb-esim__feats{flex-direction:column;gap:10px}
	.sb-esim__feat{border-right:0;border-bottom:1px solid rgba(255,255,255,.15);padding:0 0 10px}
	.sb-esim__feat:last-child{border-bottom:0}
	.sb-esim__cta{text-align:center}
}
