/* ==========================================================================
   Gone Caribe — private day tours, Willemstad, Curaçao

   THE BRIEF. Their live site is a Squarespace build whose homepage renders
   FOURTEEN WORDS. Behind an overlay menu that the crawler cannot open sit
   seventy URLs: nine priced tours with full stop-by-stop itineraries, an
   inclusions and exclusions list for each, an FAQ, and a build-your-own page
   naming 21 beaches, 13 plantation houses and 14 activities. All of it good,
   none of it findable. It scores 38/100.

   So this is not a writing job, it is an ARRANGEMENT job. The content exists.
   The task is to get nine products priced within twenty dollars of each other
   onto one page in a shape a person can compare.

   THAT DECIDES THE LAYOUT. One card, repeated eight times, every one with the
   price and the clock in the same place. The route underneath as a numbered
   list, because their itineraries are ordered and the order is the product.
   The cheapest and shortest tour is lifted out above the grid: it is two
   hours, it starts downtown, and it is the only thing a cruise passenger with
   half a day ashore can actually buy.

   REVISION, 2026-09-23. The owner looked at the first build and said it was
   "a bit too generic — add some Caribbean flair", and asked for poppier and
   simpler. Read as three instructions, in this order of authority:

     SIMPLER first. Fewer elements, not more. Eight eyebrow labels became
     one, four bordered cards became four rules, a nine-element fact list in
     the hero became one line, and two blocks that repeated links or pictures
     already on the page came off entirely. Nothing factual left with them.

     POPPIER second. Bigger, heavier headings; the price — which is the axis
     a visitor actually compares these nine tours on — set three steps up the
     scale in the action colour; saturated accents instead of restrained
     ones; one warm colour block at the conversion point.

     FLAIR last, and only from Curaçao. No palms, no brochure pastel. The
     colour comes off the Handelskade, which is a photograph THEY published
     and the exact row of houses their own downtown walking tour walks past.

   COLOUR is measured, not chosen. Every value below was sampled with sharp
   out of a file in img/, and the pixel count is the evidence:

     THE HANDELSKADE FAÇADES  — img/handelskade.webp, the façade band
       #669BE8  cobalt        1,929px of the crop (17.1%)
       #EAB9C8  rose          3,444px (22.8%)
       #DBDAAB  butter        3,748px  (9.8%)
       #F5895A  coral         1,515px (12.8%)
       #DB6D4D  roof tile       933px (20.8%)
       #BCD7B6  mint            396px  (4.7%)
       #FAA267  mango         1,225px (20.6%)
     THE WATER
       #89C7D8  the shallows in their cover photograph   3,216px  (5.3%)
       #5B9994  the same bay, deeper                     1,770px  (2.9%)
       #475C76  the Sint Annabaai, under the Handelskade 17,237px (9.1%)
     THE TWO THAT WERE ALREADY HERE
       #E6D945  the yellow wall their pineapple is shot against  5,808px (18.0%)
       #D97735  the cracked red earth of the Bahada di Daniel trail 1,616px (5.0%)

   Those thirteen are FILLS. None of them carries text on a light ground and
   none of them is allowed to. Text takes the two derived values:

     --sea   #006D77  the two water samples average hue 184°; this is that
                      hue at full chroma, at the lightness AA allows.
                      6.08 on white, 5.82 on the page ground.
     --tile  #B23A12  the roof-tile hue (14°) at 81% chroma, darkened the
                      same way. 5.99 on white, 5.73 on the page ground.

   Every pair is computed, in BOTH themes, and the recurring failure in
   ../../CLAUDE.md — a token colour on a fixed ground — is why the hero and
   the kade stripe below use fixed values and the warm band at the foot of
   the page is a THEMED PAIR (--band / --band-ink) rather than a fixed
   colour with --ink on top of it.

   Light is the design. Dark is the option — an island tour site is read in
   daylight, on a phone, outdoors.

   TYPE — three faces, one job each, and the job is written here:

     Bricolage Grotesque   headings. Nothing else. Ever. Heavier than before:
                           800 for the h1, 700 for everything under it.
     Plus Jakarta Sans     everything read at length, and every label.
     DM Mono               FIGURES ONLY — .fig. Prices, durations, clock
                           times, the telephone number. 20 elements on the
                           page against several hundred of the body face.

   The mono face is confined to .fig for the reason TDS failed review: there,
   a monospace had quietly become the default for fifteen different LABEL
   roles and the verdict was "too many elements fighting for attention". Nine
   prices within twenty dollars of each other genuinely do read better as
   measurements. A label does not. If .fig ever appears on something that is
   not a number, take it off.
   ========================================================================== */

:root {
  color-scheme: light;

  /* ---- the measured colours that the stylesheet actually uses. FILLS ONLY.
         The other five measured values in the table above are not tokens:
         #E6D945, #89C7D8 and rgb(4 34 36) appear in the hero as LITERALS,
         because the hero's ground is a photograph rather than a theme and a
         token there would flip underneath fixed type — which is the one
         failure ../../CLAUDE.md keeps coming back to. #5B9994 and #D97735
         are evidence for --sea and the old --sun; nothing paints with them. */
  --kade-cobalt: #669BE8;
  --kade-rose:   #EAB9C8;
  --kade-butter: #DBDAAB;
  --kade-coral:  #F5895A;
  --kade-tile:   #DB6D4D;
  --kade-mint:   #BCD7B6;
  --kade-mango:  #FAA267;
  --knip:        #89C7D8;

  /* ---- the working set ---- */
  --ground:    #FCFAF2;   /* their beach sand #E7D8B8 at 15% over white */
  --surface:   #FFFFFF;
  --surface-2: #F4F1E1;
  --ink:       #12211F;
  --ink-2:     #374A47;
  --muted:     #556763;
  --line:      #E6E2D0;
  --line-2:    #CDC8B3;

  --sea:       #006D77;   /* action. 6.08 on white, 5.82 on --ground */
  --sea-ink:   #00525A;   /* action, hovered */
  --sea-on:    #FFFFFF;
  --tile:      #B23A12;   /* accent text. 5.99 on white, 5.73 on --ground */

  /* The warm block at the conversion point: their pineapple wall #E6D945 at
     24% over white. A THEMED PAIR, not a fixed colour — see the note in the
     header about --ink on a fixed light ground. */
  --band:      #F9F6D2;
  --band-ink:  #12211F;
  --band-line: #E4DFAA;

  --good:      #14654A;
  --warn:      #9C400B;

  --display: "Bricolage Grotesque", "Segoe UI", system-ui, sans-serif;
  --body:    "Plus Jakarta Sans", "Segoe UI", system-ui, -apple-system, sans-serif;
  --fig:     "DM Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;

  /* The whole scale is declared here, and the chat token map below declares
     every step it reads as well. font-size INHERITS, so an undefined step
     does not fall back to a default — it falls back to the parent size, and
     the panel silently renders flat. That is exactly what happened on
     Elegance and Body Fit. */
  --f-100: .75rem;    --f-200: .8125rem;  --f-300: .875rem;
  --f-400: .9375rem;  --f-500: 1rem;      --f-600: 1.125rem;
  --f-700: 1.25rem;   --f-800: 1.5rem;    --f-900: 1.875rem;
  --f-1000: 2.375rem;

  --ls-label: .13em;
  --ls-caps:  .04em;

  --pad: clamp(1.25rem, 4vw, 3rem);
  --gap: clamp(3.5rem, 6.5vw, 5.75rem);
  --wrap: 78rem;
  --r: 14px;
  --r-sm: 9px;

  /* The Handelskade, as a rule. Seven façade colours measured off their own
     photograph of it, hard-edged, no blending. It appears exactly twice —
     the foot of the hero and the head of the footer — and it carries no
     text, in either theme, which is why it can use the measured values raw. */
  --kade: linear-gradient(90deg,
    var(--kade-butter) 0 14.2857%, var(--kade-cobalt) 0 28.5714%,
    var(--kade-rose)   0 42.8571%, var(--kade-coral)  0 57.1428%,
    var(--kade-tile)   0 71.4285%, var(--kade-mint)   0 85.7142%,
    var(--kade-mango)  0 100%);
}

/* Dark is opted into, never sniffed from the OS — the toggle writes
   data-theme and it is remembered. The hero keeps a fixed dark scrim either
   way, because a photograph needs one.

   The thirteen measured colours do NOT flip: they are the photographs, and a
   façade is the colour it is painted at three in the afternoon regardless of
   what a visitor set their phone to. Only the derived text colours flip. */
:root[data-theme="dark"] {
  color-scheme: dark;
  --ground:    #0C1A19;
  --surface:   #132523;
  --surface-2: #1B302E;
  --ink:       #F1F7F4;
  --ink-2:     #C3D4D0;
  --muted:     #8AA09B;
  --line:      #23413E;
  --line-2:    #33574F;

  --sea:       #5FD0CE;   /* 9.68 on --ground, 8.66 on --surface */
  --sea-ink:   #8AE0DE;
  --sea-on:    #04201F;
  --tile:      #FAA267;   /* the measured mango façade. 8.4 on --ground */

  --band:      #1B302E;
  --band-ink:  #F1F7F4;
  --band-line: #33574F;

  --good:      #63C99D;
  --warn:      #EDA96F;
}

*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--ground); color: var(--ink);
  font-family: var(--body); font-size: var(--f-500); line-height: 1.66;
  -webkit-font-smoothing: antialiased;
}

img { max-inline-size: 100%; block-size: auto; display: block; }
a { color: inherit; text-decoration-thickness: 1px; text-underline-offset: 3px; }
:where(a, button, input, select, textarea, summary):focus-visible {
  outline: 2px solid var(--sea); outline-offset: 3px; border-radius: 3px;
}

/* Bolder than the first build, deliberately: this is a business selling a
   holiday and the previous setting was too polite for what it is selling.
   The h1 goes to 800 and up to 5rem; everything under it to 700. The step
   between an h1 and an h2 is now large enough to be a hierarchy rather than
   a nuance, which is what lets the eyebrow labels come off. */
h1, h2, h3 { font-family: var(--display); font-weight: 700; margin: 0; text-wrap: balance; }
.h-hero { font-size: clamp(2.75rem, 6.6vw, 5.1rem); line-height: .98; letter-spacing: -.038em; font-weight: 800; }
.h-lg   { font-size: clamp(2rem, 4.1vw, 3.1rem); line-height: 1.06; letter-spacing: -.032em; }
.h-sm   { font-size: var(--f-700); line-height: 1.24; letter-spacing: -.018em; }

p, li, dd { text-wrap: pretty; }
p { margin: 0; }

/* The one job of the mono face. Nothing but numbers. */
.fig { font-family: var(--fig); font-variant-numeric: tabular-nums; letter-spacing: -.02em; }

.skip { position: absolute; inset-inline-start: -9999px; inset-block-start: 0; z-index: 200;
        background: var(--sea); color: var(--sea-on); padding: .8rem 1.2rem; font-weight: 700; }
.skip:focus { inset-inline-start: 0; }
.wrap { inline-size: 100%; max-inline-size: var(--wrap); margin-inline: auto; padding-inline: var(--pad); }

/* TWO on the whole page now — the hero kicker and the one over the walking
   tour, which carries facts (two hours, on foot, downtown) rather than
   repeating the heading under it. There were eight; seven said in small caps
   what the h2 underneath said in full, which is the ceremony ../../CLAUDE.md
   calls the tell of a 2010s page. */
.label {
  font-family: var(--body); font-weight: 800; font-size: var(--f-300);
  letter-spacing: var(--ls-label); text-transform: uppercase; color: var(--sea);
}
.note { margin-block-start: clamp(1.5rem, 2.8vw, 2rem); font-size: var(--f-400); color: var(--muted); max-inline-size: 62ch; }

/* Honeypot. Off-screen rather than display:none — a bot that renders the page
   skips a hidden field and fills a visible one. */
.hp { position: absolute; inset-inline-start: -9999px; inline-size: 1px; overflow: hidden; }

/* ---------- header ----------
   Breakpoint RE-MEASURED in Chrome after this revision, and this time IN ALL
   THREE LOCALES, which is the part the first build got wrong. It measured
   English, got 1156px, and set 1220 — but French is not English:

     locale   brand   nav    lang  theme  CTA    total with gaps + padding
     EN       163.4   436.1  98.5  34     136.1  1036
     NL       163.4   440.1  98.5  34     141.7  1046
     FR       163.4   599.3  98.5  34     195.1  1258

   "Réserver une excursion" is 59px wider than "Request a tour" and the four
   French nav items are 163px wider than the English ones, so a breakpoint
   measured on English puts the French header into a 6px horizontal scroll
   across every width between the breakpoint and about 1290. Seen on /fr/ at
   1200 during this revision.

   Two changes, together: a fourth nav item ("Plan ahead") came off — the
   section is still there and still scrolled to, and three items is simpler
   anyway — and the breakpoint is now taken from FRENCH, the widest locale,
   not from English. Three items, French: 1073px at a 1181 viewport.
   1180 leaves 108px of air. Verified at 1181 and 1179 in all three. */

.hdr {
  position: sticky; inset-block-start: 0; z-index: 60;
  background: color-mix(in srgb, var(--ground) 85%, transparent);
  backdrop-filter: blur(14px) saturate(1.25);
  border-block-end: 1px solid var(--line);
}
.hdr .wrap { display: flex; align-items: center; gap: clamp(.75rem, 2.2vw, 1.75rem); min-block-size: 68px; }

.brand { flex: none; display: inline-flex; align-items: center; gap: .6rem; text-decoration: none; }
.brand__mark { inline-size: 40px; block-size: 40px; }
.brand__mark--light { display: none; }
:root[data-theme="dark"] .brand__mark { display: none; }
:root[data-theme="dark"] .brand__mark--light { display: block; }
.brand__name { font-family: var(--display); font-weight: 800; font-size: var(--f-700); letter-spacing: -.028em; }

.nav { display: flex; gap: clamp(.9rem, 1.7vw, 1.6rem); margin-inline-start: auto; }
.nav a { font-size: var(--f-500); font-weight: 600; color: var(--ink-2); text-decoration: none; padding-block: .3rem; position: relative; white-space: nowrap; }
.nav a::after {
  content: ""; position: absolute; inset-inline: 0; inset-block-end: -2px; block-size: 2px;
  background: var(--sea); scale: 0 1; transform-origin: left;
  transition: scale .28s cubic-bezier(.2,.7,.3,1);
}
.nav a:hover { color: var(--ink); }
.nav a:hover::after, .nav a[aria-current="true"]::after { scale: 1 1; }
.nav a[aria-current="true"] { color: var(--ink); }

.hdr__tools { display: flex; align-items: center; gap: .5rem; flex: none; }
.lang { display: inline-flex; gap: .1rem; font-size: var(--f-200); font-weight: 700; letter-spacing: .06em; }
.lang a { padding: .3rem .42rem; border-radius: 6px; text-decoration: none; color: var(--muted); }
.lang a:hover { color: var(--ink); }
.lang a[aria-current="page"] { background: var(--surface-2); color: var(--ink); }

.icon-btn {
  inline-size: 34px; block-size: 34px; display: grid; place-items: center;
  background: transparent; color: var(--muted); border: 0; border-radius: 50%; cursor: pointer;
  transition: color .2s ease, background .2s ease;
}
.icon-btn:hover { color: var(--ink); background: var(--surface-2); }
.icon-btn svg { inline-size: 17px; block-size: 17px; }

@media (max-width: 1180px) {
  .hdr .wrap { flex-wrap: wrap; min-block-size: 60px; padding-block: .5rem; }
  .nav { order: 3; inline-size: 100%; margin-inline-start: 0; overflow-x: auto; scrollbar-width: none; gap: 1.3rem; padding-block-end: .3rem; }
  .nav::-webkit-scrollbar { display: none; }
  .hdr__tools { margin-inline-start: auto; }
}

/* On a phone the wordmark goes and the roundel stays.

   Measured at 390: the mark, the wordmark, three language links, the theme
   button and the CTA add up to 479px against 350px of usable width, so the
   header wrapped to THREE sticky rows — 155px, eighteen per cent of an
   iPhone screen, permanently. Dropping the wordmark takes it to 364 and two
   rows. Nothing is lost to a screen reader: the link already carries
   aria-label="Gone Caribe, home", and with no visible text there is no
   label-in-name conflict to create. */
/* Re-measured on this revision, and it needed three more lines. At a 390
   viewport the usable width inside .hdr .wrap is 335px, NOT 350 — a 15px
   scrollbar is deducted before the padding is. The bolder CTA and the pill
   radius took the brand + tools row to 336.63px, which is 1.6px too wide, so
   the header silently went back to THREE sticky rows at 165px. Trimming the
   row gap, the language links and the small button's side padding brings it
   to 319.6px and two rows. Measured, not guessed: 40 + 9.6 + 270 = 319.6. */
@media (max-width: 560px) {
  .brand__name { display: none; }
  .hdr .wrap { gap: .6rem; }
  .lang a { padding: .3rem .3rem; }
  .btn--sm { padding: .55rem .9rem; }
}
/* And once more for the narrow end. Measured header heights, per locale:

     viewport   EN    NL    FR
       465     110   110   110
       415     109   109   109
       375     109   109   157
       360     109   109   157
       345     157   157   157

   Three rows below those thresholds, and left that way on purpose. The
   French header cannot fit "Réserver une excursion" — 195px against the
   English 136 — plus three language links, the theme button and the roundel
   into the 335px a 375px phone actually gives .hdr .wrap. The alternatives
   are hiding the language switcher on a trilingual site, on the device most
   of its visitors hold, or shortening a call to action that is written in
   their own register. 48px of chrome is the cheaper of the three. */
@media (max-width: 430px) {
  .hdr .wrap { gap: .5rem; }
  .lang { font-size: var(--f-100); }
  .lang a { padding: .3rem .25rem; }
  .btn--sm { padding: .55rem .8rem; }
}

/* ---------- buttons ---------- */

.btn {
  display: inline-flex; align-items: center; gap: .5rem;
  font-family: var(--body); font-weight: 800; font-size: var(--f-500);
  padding: .85rem 1.55rem; border-radius: 999px;
  border: 1px solid transparent; cursor: pointer; text-decoration: none;
  transition: background .2s ease, color .2s ease, border-color .2s ease;
}
.btn--solid { background: var(--sea); color: var(--sea-on); }
.btn--solid:hover { background: var(--sea-ink); }
.btn--ghost { border-color: color-mix(in srgb, currentColor 32%, transparent); }
.btn--ghost:hover { border-color: currentColor; }
.btn--sm { padding: .55rem 1.1rem; font-size: var(--f-300); }

/* ---------- sections ---------- */

.sec { padding-block: var(--gap); }
.sec--tight { padding-block: clamp(2.5rem, 4.5vw, 3.75rem); }
.sec--raise { background: var(--surface); border-block: 1px solid var(--line); }
/* The tour grid only. Its cards lost their borders, so the cards are white
   and the ground under them is the warm one — otherwise eight borderless
   white panels on near-white paper stop being eight things. */
.sec--sand { background: var(--surface-2); border-block: 1px solid var(--line); }
/* The one warm block on the page, and it is at the conversion point. */
.sec--band { background: var(--band); color: var(--band-ink); border-block: 1px solid var(--band-line); }
.sec__head { max-inline-size: 46rem; margin-block-end: clamp(2rem, 3.4vw, 3rem); }
.sec__head .label { display: block; margin-block-end: .85rem; }
.sec__head h2 + .lede { margin-block-start: 1.15rem; }
.lede { font-size: var(--f-700); color: var(--ink-2); line-height: 1.55; max-inline-size: 58ch; }
.sec--band .lede { color: var(--band-ink); }

/* ---------- hero ---------- */

.hero { position: relative; isolation: isolate; }
.hero__pic { position: absolute; inset: 0; z-index: -2; }
.hero__pic img { inline-size: 100%; block-size: 100%; object-fit: cover; object-position: 50% 62%; }

/* The scrim is FIXED, not themed. Their cover photograph is bright sky and
   pale limestone; light type on a darkened frame reads in both themes, and a
   pale veil with dark type over it does not.

   ONE LAYER, horizontal only: dark under the copy column, clear over the
   right where the pineapple is. There is deliberately NO vertical fade into
   the page ground.

   The first build had one — transparent to --ground over the last third —
   and it put the three figures at the foot of the hero ($40 / 9 / 5, with
   their labels) onto pale limestone in near-white type. Unreadable, and it
   is the recurring failure named in ../../CLAUDE.md: light text on a light
   ground. Caught on the first render.

   So the hero is a self-contained dark block and meets the paper below it
   with a clean edge — with the kade stripe on that edge.

   THE ALPHAS ARE COMPUTED, NOT EYEBALLED. The scrim is rgb(4 34 36), the
   Sint Annabaai #475C76 taken to the darkness a scrim needs. The worst pixel
   their photograph can put under the type is a white cloud, so every alpha
   was checked against #FFFFFF:

     alpha  composite  #FFFFFF text   --pineapple #E6D945
      .86    #253F40      11.0 : 1        7.5 : 1
      .80    #364E50       8.9 : 1        6.1 : 1
      .76    #405759       7.7 : 1        5.3 : 1

   Then the right-hand edge of every text run in the hero was MEASURED in
   Chrome with Range.getClientRects, at six widths, rather than estimated:

     1600  51.4%      1280  51.8%      1000  57.6%
     1440  51.6%      1100  53.6%       920  62.3%

   The horizontal scrim holds .80 or better out to 60% and only then falls
   away over the pineapple, where nothing is written — so every one of those
   runs sits on .80 or darker. The 920 row is why the vertical scrim now
   takes over at 1000 rather than at 900: below about 1000 the copy column is
   a bigger fraction of the screen than the gradient allows for, and the
   vertical version is safe at every width.

   The previous build ran .93 across the whole copy column, which is four
   times more veil than the arithmetic asks for and threw away the turquoise
   that is the reason the photograph is here. */
.hero::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(100deg,
    rgb(4 34 36 / .88) 0%,
    rgb(4 34 36 / .84) 34%,
    rgb(4 34 36 / .80) 60%,
    rgb(4 34 36 / .40) 74%,
    rgb(4 34 36 / .12) 88%,
    rgb(4 34 36 / .04) 100%);
}

/* The Handelskade, on the edge where the photograph meets the paper. No
   markup, no text on it, both themes identical — it is a picture, not a
   surface. */
.hero::before {
  content: ""; position: absolute; z-index: 1;
  inset-inline: 0; inset-block-end: 0; block-size: 8px;
  background: var(--kade);
}

@media (max-width: 1000px) {
  /* No room for a clear half, so the scrim goes vertical — and still never
     falls below .80 anywhere type can reach. */
  .hero::after { background: linear-gradient(180deg, rgb(4 34 36 / .88) 0%, rgb(4 34 36 / .80) 55%, rgb(4 34 36 / .86) 100%); }
  .hero__pic img { object-position: 62% 60%; }
}

.hero .wrap { padding-block: clamp(4.5rem, 12vw, 8.5rem) clamp(3.25rem, 7vw, 5.25rem); min-block-size: min(76svh, 660px); display: flex; flex-direction: column; justify-content: center; }
.hero h1 { margin-block-start: 1.15rem; max-inline-size: 13ch; }
.hero h1 em { font-style: normal; display: block; }
.hero__sub { margin-block-start: 1.5rem; max-inline-size: 38ch; font-size: var(--f-700); line-height: 1.5; }
.hero__cta { display: flex; gap: .7rem; flex-wrap: wrap; margin-block-start: 2rem; }

/* What is left of the three-fact list: one line, one figure. */
.hero__price { display: flex; flex-wrap: wrap; align-items: baseline; gap: .1rem .7rem; margin-block-start: clamp(1.75rem, 3vw, 2.4rem); }
.hero__price b { font-size: var(--f-900); font-weight: 500; line-height: 1; }
.hero__price span { font-size: var(--f-400); }

/* These MUST come after the rules above. The hero sits on a FIXED dark scrim,
   so its type takes fixed light values rather than theme tokens — and these
   are single-class selectors, so at equal specificity source order is the
   whole fight. Placed before, the block is dead code and the copy renders in
   --ink-2: dark slate on a dark scrim. This exact bug shipped once already. */
.hero .wrap { color: #FFFFFF; }
.hero .label { color: #89C7D8; }          /* the measured shallows, raw */
.hero h1 em { color: #E6D945; }           /* the measured pineapple wall, raw */
.hero__sub { color: #DCEAE7; }
.hero__price b { color: #E6D945; }
.hero__price span { color: #C8DCD8; }
.hero .btn--ghost { color: #FFFFFF; border-color: rgb(255 255 255 / .45); }
.hero .btn--ghost:hover { border-color: #FFFFFF; background: rgb(255 255 255 / .1); }
.hero .btn--solid { background: #E6D945; color: #12211F; }
.hero .btn--solid:hover { background: #F2E870; }

@media (prefers-reduced-motion: no-preference) {
  .hero .label, .hero h1, .hero__sub, .hero__cta, .hero__price { animation: rise .6s cubic-bezier(.2,.7,.3,1) both; }
  .hero h1 { animation-delay: .05s; }
  .hero__sub { animation-delay: .1s; }
  .hero__cta { animation-delay: .15s; }
  .hero__price { animation-delay: .2s; }
  @keyframes rise { from { opacity: 0; translate: 0 12px; } to { opacity: 1; translate: 0 0; } }
}

/* ---------- what private means ----------
   Four bordered cards became four rules. Each rule is one measured
   Handelskade façade — butter, cobalt, coral, mint — so the row reads as the
   waterfront rather than as a component library. The colours are fills and
   never carry text, so they can be the raw measured values in both themes. */

.pillars__h { max-inline-size: 22ch; margin-block-end: clamp(2rem, 3.4vw, 2.75rem); }
.pillars { display: grid; gap: clamp(1.4rem, 2.6vw, 2.25rem); grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 1000px) { .pillars { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px)  { .pillars { grid-template-columns: 1fr; } }
.pillar { border-block-start: 6px solid var(--kade-butter); padding-block-start: 1rem; }
.pillar:nth-child(2) { border-block-start-color: var(--kade-cobalt); }
.pillar:nth-child(3) { border-block-start-color: var(--kade-coral); }
.pillar:nth-child(4) { border-block-start-color: var(--kade-mint); }
.pillar h3 { font-size: var(--f-700); margin-block-end: .5rem; }
.pillar p { color: var(--ink-2); font-size: var(--f-400); line-height: 1.6; }

/* ---------- the featured walking tour ---------- */

/* The photograph goes ACROSS THE TOP, not beside the text.

   It is the Handelskade waterfront at 1600 x 659 — a 2.4:1 panorama, and the
   whole subject of it is a long horizontal row of houses. Dropped into a
   half-width column it came out 400px wide and floated in a column of white
   space twice its height, while the thing it is a picture of was too small to
   see. Cropping it to fit a column would cut the row, which is the picture.
   So it gets the full width it was shot at, and the text sits under it. */
.feature { display: grid; gap: clamp(1.5rem, 3vw, 2.25rem); }
.feature__pic { margin: 0; order: -1; }
/* Capped: the source file is 1600px and nothing here is ever upscaled, so
   the frame is centred rather than stretched past what the photograph has. */
.feature__pic img { inline-size: 100%; max-inline-size: 1600px; margin-inline: auto; border-radius: var(--r); }

/* Under the panorama the text runs in two columns rather than one long
   measure — heading, lede and price on the left; the route, what is in it,
   and the button on the right.

   Two blocks side by side, not eight items auto-placed into a grid.

   The first attempt gave each child an explicit grid-column and let the rows
   fall where they liked. Auto-placement put the heading "What you walk past"
   on row 1 and the chips it introduces on row 4, with three empty rows of
   nothing in between — a heading orphaned from its own list. Two containers
   have no rows to disagree about. */
.feature__body { display: grid; gap: clamp(1.5rem, 3vw, 2.5rem) clamp(2rem, 4vw, 3.5rem); grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr); align-items: start; }
@media (max-width: 860px) { .feature__body { grid-template-columns: 1fr; } }

.feature__main .label { display: block; margin-block-end: .85rem; }
.feature__main h2 { margin-block-end: 1rem; }
.feature__side .h-sm { margin-block-end: .8rem; }
.feature__side .btn { margin-block-start: 1.5rem; }

/* Two hairlines became none. The $40 carries it on its own at --f-1000, in
   the action colour, which is exactly the job: this is the figure a cruise
   passenger with six hours ashore is looking for. */
.pricebar {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .3rem .9rem;
  margin-block-start: 1.6rem;
}
.pricebar__fig { font-size: var(--f-1000); font-weight: 500; color: var(--sea); line-height: 1; }
.pricebar__unit { font-size: var(--f-300); color: var(--muted); }
.pricebar__time { margin-inline-start: auto; font-size: var(--f-400); color: var(--ink-2); }

.chips { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: .4rem; }
.chips li {
  font-size: var(--f-300); font-weight: 500; color: var(--ink-2);
  background: var(--surface-2);
  border-radius: 999px; padding: .34rem .85rem;
}

.inex { margin-block-start: 1.5rem; display: grid; gap: .6rem; grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 520px) { .inex { grid-template-columns: 1fr; } }
.inex > div { display: grid; gap: .2rem; }
.inex b { font-size: var(--f-200); letter-spacing: var(--ls-label); text-transform: uppercase; color: var(--muted); font-weight: 800; }
.inex span { font-size: var(--f-400); color: var(--ink-2); }

/* ---------- the tour grid ----------
   EIGHT items, and the columns are STATED. auto-fit resolving eight to three
   leaves two orphans on the last row, which is the bug in ../CLAUDE.md. Two
   columns is also simply right: each card carries a five-step route, and
   three of those side by side is a wall. */

.tours { display: grid; gap: 1.15rem; grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 900px) { .tours { grid-template-columns: 1fr; } }

/* The border on every side is gone. A card is now a fill, and the only line
   left on it is the one that separates the name and the price from the
   route — which is the line that does work. What replaces the ceremony is
   the PRICE, three steps up the scale in the action colour, because price
   and duration are the two things a visitor is here to line up. */
.tour {
  container-type: inline-size;
  background: var(--surface); border-radius: var(--r);
  padding: clamp(1.3rem, 2.4vw, 1.8rem);
  display: flex; flex-direction: column; gap: 1rem;
  transition: translate .2s ease, box-shadow .2s ease;
}
.tour:hover { translate: 0 -3px; box-shadow: 0 10px 28px rgb(18 33 31 / .09); }
:root[data-theme="dark"] .tour:hover { box-shadow: 0 10px 28px rgb(0 0 0 / .4); }

/* The one card on the grid that is not a fixed itinerary. Marked with the
   accent rather than a different shape, so the grid keeps its rhythm. */
.tour--open { background: color-mix(in srgb, var(--kade-coral) 12%, var(--surface)); }
:root[data-theme="dark"] .tour--open { background: color-mix(in srgb, var(--kade-coral) 14%, var(--surface)); }

.tour__top { display: flex; flex-direction: column; gap: .35rem; padding-block-end: .9rem; border-block-end: 1px solid var(--line); }
.tour h3 { font-size: var(--f-800); line-height: 1.12; }
.tour__price { display: flex; flex-wrap: wrap; align-items: baseline; gap: .2rem .75rem; }
.tour__price > .fig:first-child { font-size: var(--f-900); font-weight: 500; line-height: 1.05; color: var(--sea); }
.tour__dur { font-size: var(--f-300); color: var(--muted); }

/* At card widths over 26rem the name and the price sit on one line; below
   that they stack. A container query, not a media query: the card does not
   know or care how wide the viewport is. */
@container (min-width: 26rem) {
  .tour__top { flex-direction: row; align-items: baseline; justify-content: space-between; gap: 1rem; }
  .tour__price { flex: none; flex-direction: column; align-items: flex-end; gap: .1rem; }
}

.route { margin: 0; padding-inline-start: 1.4rem; display: grid; gap: .55rem; font-size: var(--f-400); color: var(--ink-2); line-height: 1.55; }
.route::marker { color: var(--muted); }
.route li::marker { font-family: var(--fig); font-size: var(--f-200); color: var(--muted); }
.route b { color: var(--ink); font-weight: 700; }

.tour__lede { font-size: var(--f-400); color: var(--ink-2); line-height: 1.6; }

.tour__inc { font-size: var(--f-300); color: var(--muted); line-height: 1.55; margin-block-start: auto; padding-block-start: .3rem; }
.tour__inc b { color: var(--ink-2); font-weight: 700; }

/* Their own safety and fitness wording, quoted and given its own weight.
   Never softened, never extended — see ../CLAUDE.md for this trade. */
.tour__warn {
  font-size: var(--f-300); line-height: 1.55; color: var(--ink-2);
  border-inline-start: 4px solid var(--kade-tile);
  padding: .1rem 0 .1rem .85rem;
}
.tour__warn b { color: var(--ink); }

.tour__cta { align-self: flex-start; font-size: var(--f-400); font-weight: 800; color: var(--sea); text-decoration: none; padding-block: .2rem; border-block-end: 2px solid color-mix(in srgb, var(--sea) 35%, transparent); }
.tour__cta:hover { border-block-end-color: var(--sea); }

/* ---------- build your own ---------- */

.pick { display: grid; gap: clamp(1.75rem, 3.5vw, 2.75rem); grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 1000px) { .pick { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 620px)  { .pick { grid-template-columns: 1fr; } }
.pick__col .h-sm { margin-block-end: .85rem; }
.pick__col .h-sm ~ .h-sm { margin-block-start: 2rem; }
.pick__note { font-size: var(--f-300); color: var(--muted); line-height: 1.6; margin-block-end: 1rem; }

.pick__list { list-style: none; margin: 0; padding: 0; display: grid; gap: .3rem; font-size: var(--f-400); color: var(--ink-2); }
/* overflow-wrap is load-bearing, not tidiness. "Schildpaddenuitzichtpunt,
   Playa Choromba" is 205px wide and the two-column activities list gives it
   149px between roughly 1010 and 1130, so the Dutch page pushed .pick, then
   .wrap, then main, then the document, and the whole page scrolled sideways
   by 10px. English and French never showed it: no compound that long.
   hyphens:auto does the breaking politely; break-word guarantees it. */
.pick__list li { padding-inline-start: 1rem; position: relative; line-height: 1.45; overflow-wrap: break-word; hyphens: auto; }
.pick__list li::before { content: ""; position: absolute; inset-inline-start: 0; inset-block-start: .58em; inline-size: 6px; block-size: 6px; border-radius: 50%; background: var(--kade-coral); }
.pick__list i { font-style: normal; color: var(--muted); font-size: var(--f-200); }
.pick__list--two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 480px) { .pick__list--two { grid-template-columns: 1fr; } }

.pick__dl { margin: 0 0 .5rem; display: grid; gap: .25rem; }
.pick__dl dt { font-weight: 700; font-size: var(--f-400); }
.pick__dl dd { margin: 0 0 .9rem; font-size: var(--f-300); color: var(--ink-2); line-height: 1.55; }

/* ---------- gallery ----------
   SIX figures, three columns, stated. Each rises into place as it is
   scrolled to — a scroll-driven animation, so nothing runs off a timer and
   nothing runs at all under reduced motion. */

.gal { display: grid; gap: 1.1rem; grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 900px) { .gal { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px) { .gal { grid-template-columns: 1fr; } }
.gal figure { margin: 0; }
.gal img { inline-size: 100%; aspect-ratio: 3 / 2; object-fit: cover; border-radius: var(--r); }
.gal figcaption { margin-block-start: .65rem; font-size: var(--f-300); font-weight: 500; color: var(--ink-2); }

@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .gal figure {
      animation: gal-in linear both;
      animation-timeline: view();
      animation-range: entry 8% cover 26%;
    }
    @keyframes gal-in { from { opacity: 0; translate: 0 22px; } to { opacity: 1; translate: 0 0; } }
  }
}

/* ---------- plan ahead ---------- */

.plan { display: grid; gap: clamp(1.4rem, 2.6vw, 2.25rem); grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 860px) { .plan { grid-template-columns: 1fr; } }
/* Three more façades, same rule as the pillars row. */
.plan__item { border-inline-start: 5px solid var(--kade-mango); padding-inline-start: 1.15rem; }
.plan__item:nth-child(2) { border-inline-start-color: var(--kade-rose); }
.plan__item:nth-child(3) { border-inline-start-color: var(--knip); }
.plan__item h3 { font-size: var(--f-700); margin-block-end: .5rem; }
.plan__item p { color: var(--ink-2); font-size: var(--f-400); line-height: 1.6; }

/* ---------- who we are ---------- */

.who { display: grid; gap: clamp(1.75rem, 4vw, 3.25rem); grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); align-items: start; }
@media (max-width: 860px) { .who { grid-template-columns: 1fr; } }
.who__body { display: grid; gap: 1.1rem; }
.who__lede { font-family: var(--display); font-weight: 600; font-size: clamp(1.4rem, 2.6vw, 1.9rem); line-height: 1.24; letter-spacing: -.026em; }
.who__body p:not(.who__lede) { color: var(--ink-2); line-height: 1.68; }
.who__pic img { inline-size: 100%; border-radius: var(--r); }

.award {
  display: flex; align-items: center; gap: 1rem; text-decoration: none;
  margin-block-start: .6rem; padding: 1rem 1.15rem;
  background: var(--surface-2); border-radius: var(--r);
  transition: background .2s ease;
}
.award:hover { background: color-mix(in srgb, var(--kade-butter) 40%, var(--surface-2)); }
:root[data-theme="dark"] .award:hover { background: var(--line); }
.award__badge { inline-size: 56px; block-size: auto; flex: none; }
.award__badge--light { display: none; }
:root[data-theme="dark"] .award__badge { display: none; }
:root[data-theme="dark"] .award__badge--light { display: block; }
.award span { font-size: var(--f-300); color: var(--ink-2); line-height: 1.55; }

/* ---------- fact lists ---------- */

.facts { margin: 0; background: var(--surface); border-radius: var(--r); overflow: hidden; }
.sec--raise .facts { background: var(--ground); }
.facts > div { display: grid; grid-template-columns: minmax(0, 12rem) minmax(0, 1fr); gap: .25rem 1.5rem; padding: .95rem clamp(1.1rem, 2vw, 1.45rem); border-block-end: 1px solid var(--line); }
.facts > div:last-child { border-block-end: 0; }
.facts dt { font-size: var(--f-200); letter-spacing: var(--ls-label); text-transform: uppercase; color: var(--muted); font-weight: 700; padding-block-start: .15rem; }
.facts dd { margin: 0; font-size: var(--f-400); color: var(--ink-2); line-height: 1.6; }
.facts a { color: var(--sea); font-weight: 700; }
@media (max-width: 620px) { .facts > div { grid-template-columns: 1fr; } }
.facts--tight > div { grid-template-columns: minmax(0, 8rem) minmax(0, 1fr); }

/* ---------- the request form ---------- */

.contact { display: grid; gap: clamp(1.75rem, 4vw, 3.25rem); grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); align-items: start; }
@media (max-width: 900px) { .contact { grid-template-columns: 1fr; } }

.form { display: grid; gap: .95rem; }
.form__row { display: grid; gap: .95rem; grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 560px) { .form__row { grid-template-columns: 1fr; } }

.field { display: flex; flex-direction: column; gap: .3rem; }
.field label { font-size: var(--f-200); font-weight: 800; letter-spacing: var(--ls-label); text-transform: uppercase; color: var(--muted); }
.field .req { color: var(--tile); }
.field input, .field select, .field textarea {
  font-family: var(--body); font-size: var(--f-500); color: var(--ink);
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-sm);
  padding: .68rem .8rem; inline-size: 100%; min-block-size: 44px;
  transition: border-color .2s ease;
}
.field textarea { min-block-size: 6.5rem; resize: vertical; field-sizing: content; max-block-size: 15rem; }
.field input:hover, .field select:hover, .field textarea:hover { border-color: var(--line-2); }
.field input:focus, .field select:focus, .field textarea:focus { outline: none; border-color: var(--sea); }

/* Two routes to the same state. :user-invalid catches the browser's own
   verdict once a field has been touched and left; .invalid is what the script
   sets after a submit. Neither needs the other. */
.field:has(:user-invalid) input, .field:has(:user-invalid) select, .field:has(:user-invalid) textarea,
.field.invalid input, .field.invalid select, .field.invalid textarea { border-color: var(--warn); }

.field .err { font-size: var(--f-200); color: var(--warn); line-height: 1.4; }
/* Without this an empty slot reserves its line-height on every field. On
   Everts that came to 115px of invisible space down one panel. */
.field .err:empty { display: none; }

.form__foot { display: flex; flex-wrap: wrap; gap: .85rem 1.25rem; align-items: center; margin-block-start: .3rem; }
.form__foot p { font-size: var(--f-200); color: var(--muted); max-inline-size: 32ch; }

.status { border-inline-start: 2px solid var(--line); padding: .55rem 0 .55rem 1rem; font-size: var(--f-300); line-height: 1.6; display: none; }
.status.show { display: block; }
.status--ok { border-inline-start-color: var(--good); color: var(--good); }
.status--bad { border-inline-start-color: var(--warn); color: var(--warn); }
.status a { color: inherit; font-weight: 700; }

.reach { display: grid; gap: 1.25rem; align-content: start; }
.social { list-style: none; margin: 0; padding: 0; display: grid; gap: .45rem; font-size: var(--f-400); }
.social a { color: var(--ink-2); text-decoration: none; font-weight: 500; }
.social a:hover { color: var(--sea); }

/* ---------- footer ---------- */

/* The kade stripe again, at the other end of the page. Same gradient, same
   measured façades, no text on it. */
.ftr { position: relative; padding-block: clamp(2.75rem, 5vw, 3.75rem) 1.75rem; }
.ftr::before { content: ""; position: absolute; inset-inline: 0; inset-block-start: 0; block-size: 8px; background: var(--kade); }
.ftr .wrap { display: grid; gap: clamp(1.75rem, 3.5vw, 3rem); grid-template-columns: 2fr 1fr; }
@media (max-width: 620px) { .ftr .wrap { grid-template-columns: 1fr; } }
.ftr .brand__mark { inline-size: 52px; block-size: 52px; }
.ftr__blurb { margin-block-start: .9rem; color: var(--muted); font-size: var(--f-400); max-inline-size: 32ch; line-height: 1.6; }
.ftr__h { font-family: var(--body); font-size: var(--f-200); letter-spacing: var(--ls-label); text-transform: uppercase; font-weight: 800; color: var(--muted); margin: 0 0 .9rem; }
.ftr ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }
.ftr a { color: var(--ink-2); text-decoration: none; font-size: var(--f-400); }
.ftr a:hover { color: var(--sea); }
.ftr__legal { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: .5rem 2rem; padding-block-start: 1.75rem; margin-block-start: .75rem; border-block-start: 1px solid var(--line); font-size: var(--f-200); color: var(--muted); }

/* ---------- 404 ---------- */
.notfound { display: grid; place-items: center; min-block-size: 58svh; }
.notfound > div { max-inline-size: 44ch; }
.notfound .lede { margin-block-start: 1.05rem; }
.notfound .hero__cta { display: flex; gap: .6rem; flex-wrap: wrap; margin-block-start: 1.75rem; }

/* ---------- chat ----------
   An assistant that answers from what this business has published, and a
   button that hands the whole conversation over the moment it cannot. Front
   end here; the model, the guard rails and the knowledge base are in
   functions/api/chat.js and functions/_kb.js.

   SHARED COMPONENT. Everything below the token map is byte-identical on every
   site in workshop/ — only the map is per-site, and it only ever maps this
   site's own tokens onto the --chat-* names. If a change is needed, change it
   in all of them, or it stops being shared.

   The panel is non-modal on purpose: it opens beside the page rather than
   over it, so nothing behind it is blocked and no focus trap is needed.
   Escape closes it, a click outside closes it, and focus returns to the
   launcher. */

:root {
  --chat-accent:    var(--sea);
  --chat-accent-on: var(--sea-on);
  --chat-surface:   var(--surface);
  --chat-raise:     var(--surface-2);
  --chat-ink:       var(--ink);
  --chat-ink-2:     var(--ink-2);
  --chat-muted:     var(--muted);
  --chat-line:      var(--line);
  --chat-line-soft: var(--line);
  --chat-warn:      var(--warn);
  --chat-display:   var(--display);
  --chat-body:      var(--body);
  --chat-r: 16px;
  --chat-r-sm: 5px;
  --chat-r-pill: 999px;
}

.chat-launch {
  position: fixed; z-index: 70;
  right: clamp(1rem, 3vw, 2rem); bottom: clamp(1rem, 3vw, 2rem);
  display: inline-flex; align-items: center; gap: .55rem;
  padding: .8rem 1.25rem .8rem 1rem;
  background: var(--chat-accent); color: var(--chat-accent-on);
  border: 0; border-radius: var(--chat-r-pill); cursor: pointer;
  box-shadow: 0 4px 12px rgb(0 0 0 / .18), 0 18px 44px rgb(0 0 0 / .22);
  font-family: var(--chat-body); font-weight: 600; font-size: var(--f-400);
  letter-spacing: var(--ls-caps, .04em); line-height: 1;
  transition: filter .22s ease, translate .22s cubic-bezier(.2,.7,.3,1), box-shadow .22s ease;
}
.chat-launch:hover { filter: brightness(1.08); translate: 0 -2px; box-shadow: 0 6px 16px rgb(0 0 0 / .22), 0 26px 60px rgb(0 0 0 / .28); }
.chat-launch svg { width: 22px; height: 22px; flex: none; }

/* Arrives with one small rise so the eye catches it, then never moves again. */
@media (prefers-reduced-motion: no-preference) {
  .chat-launch { animation: launch-in .5s cubic-bezier(.2,.7,.3,1) 1.1s both; }
  @keyframes launch-in { from { opacity: 0; translate: 0 14px; } to { opacity: 1; translate: 0 0; } }
}

.chat-launch[aria-expanded="true"] .chat-launch__open,
.chat-launch[aria-expanded="false"] .chat-launch__close { display: none; }
.chat-launch[aria-expanded="true"] .chat-launch__label { display: none; }
.chat-launch[aria-expanded="true"] { padding: .8rem 1rem; }

@media (max-width: 560px) {
  .chat-launch__label { display: none; }
  .chat-launch { padding: .8rem .95rem; }
}

/* ---- the panel ----
   A conversation, so it needs a fixed frame with the log scrolling inside it.
   Left to grow with its content a chat panel walks off the top of the screen
   after four exchanges.

   Rows: head / log / composer / handoff / status / disclosure. The log is the
   only one that flexes, and minmax(0,1fr) is what actually lets it scroll —
   1fr alone takes its minimum from the content and the panel overflows. */

.chat {
  position: fixed; z-index: 71;
  right: clamp(1rem, 3vw, 2rem);
  bottom: calc(clamp(1rem, 3vw, 2rem) + 66px);
  width: min(24rem, calc(100vw - 2rem));
  height: min(34rem, calc(100svh - clamp(1rem, 3vw, 2rem) - 94px));
  background: var(--chat-surface); color: var(--chat-ink);
  border: 1px solid var(--chat-line); border-radius: var(--chat-r);
  box-shadow: 0 8px 22px rgb(0 0 0 / .14), 0 32px 80px rgb(0 0 0 / .22);
  overflow: hidden;
  display: none; grid-template-rows: auto minmax(0, 1fr) auto auto auto auto;
  opacity: 0; translate: 0 12px; scale: .98;
  transform-origin: bottom right;
  transition: opacity .26s ease, translate .26s cubic-bezier(.2,.7,.3,1),
              scale .26s cubic-bezier(.2,.7,.3,1), display .26s allow-discrete;
}
.chat.is-open { display: grid; opacity: 1; translate: 0 0; scale: 1; }
@starting-style { .chat.is-open { opacity: 0; translate: 0 12px; scale: .98; } }

.chat__head { padding: 1rem 1.15rem .8rem; border-bottom: 1px solid var(--chat-line-soft); }
.chat__title { font-family: var(--chat-display); font-size: var(--f-700); line-height: 1.15; margin: 0 0 .25rem; }
.chat__intro { font-size: var(--f-200); color: var(--chat-muted); margin: 0; line-height: 1.5; }

/* ---- the log ---- */

.chat__log {
  overflow-y: auto; overscroll-behavior: contain;
  padding: .95rem 1.15rem;
  display: flex; flex-direction: column; gap: .5rem;
  scrollbar-width: thin; scrollbar-color: var(--chat-line) transparent;
}

.msg {
  max-width: 88%;
  padding: .55rem .8rem;
  border-radius: var(--chat-r);
  font-size: var(--f-400); line-height: 1.55;
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.msg--bot { align-self: flex-start; background: var(--chat-raise); border-bottom-left-radius: var(--chat-r-sm); }
.msg--me  { align-self: flex-end; background: var(--chat-accent); color: var(--chat-accent-on); border-bottom-right-radius: var(--chat-r-sm); }

.msg--note {
  align-self: stretch; max-width: none; text-align: center;
  background: none; padding: .15rem 0;
  font-size: var(--f-100); color: var(--chat-muted); line-height: 1.5;
}

/* Each turn arrives with one small rise. Reduced motion gets it instantly. */
@media (prefers-reduced-motion: no-preference) {
  .msg { animation: msg-in .26s cubic-bezier(.2,.7,.3,1) both; }
  @keyframes msg-in { from { opacity: 0; translate: 0 6px; } to { opacity: 1; translate: 0 0; } }
}

.msg--typing { display: inline-flex; gap: .3rem; align-items: center; padding: .75rem .8rem; }
.msg--typing i { width: 6px; height: 6px; border-radius: 50%; background: var(--chat-muted); display: block; }
@media (prefers-reduced-motion: no-preference) {
  .msg--typing i { animation: blip 1.1s ease-in-out infinite; }
  .msg--typing i:nth-child(2) { animation-delay: .16s; }
  .msg--typing i:nth-child(3) { animation-delay: .32s; }
  @keyframes blip { 0%, 60%, 100% { opacity: .3; translate: 0 0; } 30% { opacity: 1; translate: 0 -3px; } }
}

/* ---- the composer ---- */

.chat__composer {
  display: flex; gap: .55rem; align-items: end;
  padding: .65rem 1.15rem;
  border-top: 1px solid var(--chat-line-soft);
}
.chat__composer textarea {
  flex: 1;
  font-family: var(--chat-body); font-size: var(--f-400); color: var(--chat-ink);
  background: transparent; border: 0; border-bottom: 1px solid var(--chat-line);
  border-radius: 0; padding: .45rem 0; resize: none;
  min-height: 2.3rem; max-height: 6.5rem; field-sizing: content;
  transition: border-color .25s ease;
}
.chat__composer textarea:hover { border-bottom-color: var(--chat-muted); }
.chat__composer textarea:focus { outline: none; border-bottom-color: var(--chat-accent); border-bottom-width: 2px; }

.chat__send {
  flex: none; width: 36px; height: 36px; margin-bottom: .15rem;
  display: grid; place-items: center;
  background: var(--chat-accent); color: var(--chat-accent-on);
  border: 0; border-radius: 50%; cursor: pointer;
  transition: filter .2s ease, opacity .2s ease;
}
.chat__send svg { width: 17px; height: 17px; }
.chat__send:hover { filter: brightness(1.08); }
.chat__send:disabled { opacity: .35; cursor: default; }

/* ---- handing over ---- */

.chat__foot {
  display: flex; align-items: center; gap: .4rem .75rem; flex-wrap: wrap;
  padding: 0 1.15rem .8rem;
}
.chat__handoff {
  font-family: var(--chat-body); font-size: var(--f-200); font-weight: 600;
  letter-spacing: .02em;
  background: transparent; color: var(--chat-accent);
  border: 1px solid color-mix(in oklab, var(--chat-accent) 45%, transparent);
  border-radius: var(--chat-r-pill); padding: .38rem .8rem; cursor: pointer;
  transition: background .2s ease, border-color .2s ease;
}
.chat__handoff:hover { background: color-mix(in oklab, var(--chat-accent) 12%, transparent); border-color: var(--chat-accent); }
.chat__handoff:disabled { opacity: .45; cursor: default; }
.chat__call { font-size: var(--f-100); color: var(--chat-muted); }
.chat__call:hover { color: var(--chat-ink); }

.chat__ho { padding: 0 1.15rem .8rem; border-top: 1px solid var(--chat-line-soft); }
.chat__ho[hidden] { display: none; }
.chat__ho-note { font-size: var(--f-200); color: var(--chat-ink-2); margin: .8rem 0 .55rem; line-height: 1.5; }

.chat__field { display: flex; flex-direction: column; gap: .25rem; margin-bottom: .55rem; }
.chat__field label {
  font-family: var(--chat-body); font-size: var(--f-100); font-weight: 600;
  letter-spacing: var(--ls-label, .1em); text-transform: uppercase; color: var(--chat-muted);
}
.chat__field input, .chat__field textarea {
  font-family: var(--chat-body); font-size: var(--f-400); color: var(--chat-ink);
  background: transparent; border: 0; border-bottom: 1px solid var(--chat-line);
  border-radius: 0; padding: .4rem 0; width: 100%; resize: none;
  transition: border-color .25s ease;
}
.chat__field textarea { min-height: 2.6rem; max-height: 6rem; field-sizing: content; }
.chat__field input:focus, .chat__field textarea:focus { outline: none; border-bottom-color: var(--chat-accent); border-bottom-width: 2px; }
.chat__field.invalid input, .chat__field.invalid textarea { border-bottom-color: var(--chat-warn); }
.chat__err { font-size: var(--f-100); color: var(--chat-warn); line-height: 1.4; }
.chat__err:empty { display: none; }

.chat__submit {
  width: 100%; margin-top: .2rem;
  font-family: var(--chat-body); font-size: var(--f-300); font-weight: 600;
  letter-spacing: var(--ls-caps, .04em);
  background: var(--chat-accent); color: var(--chat-accent-on);
  border: 0; border-radius: var(--chat-r-pill); padding: .6rem 1rem; cursor: pointer;
  transition: filter .2s ease;
}
.chat__submit:hover { filter: brightness(1.08); }
.chat__submit:disabled { opacity: .5; cursor: default; }

.chat__status {
  margin: 0 1.15rem .8rem; padding: .45rem 0 .45rem .85rem;
  border-left: 2px solid var(--chat-warn);
  font-size: var(--f-200); line-height: 1.55; color: var(--chat-warn);
  display: none;
}
.chat__status.show { display: block; }
.chat__status a { color: inherit; font-weight: 600; }

/* The one honest thing a chatbot owes anybody. */
.chat__disclosure {
  padding: 0 1.15rem .75rem; margin: 0;
  font-size: var(--f-100); color: var(--chat-muted); line-height: 1.5;
}

/* For labels a screen reader needs and the eye does not. */
.chat-vh { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }

@media (max-width: 560px) {
  .chat {
    left: 1rem; right: 1rem; width: auto;
    bottom: calc(1rem + 62px);
    height: min(30rem, calc(100svh - 1rem - 86px));
  }
}
