/* ============================================================
   Lukman Batik Workshop — styles
   v2 "IDEAFEST": full restyle to the ideafest.id design language,
   requested 2026-08-11. Values were read off the live site, not guessed:
     · monospace type everywhere, UPPERCASE, wide tracking
     · ZERO border-radius — every corner sharp, buttons are outlined boxes
     · flat saturated colour blocks (orange #FE9D00, hot pink, grey) that
       switch abruptly full-bleed, instead of one calm ground
     · no shadows at all; hairline rules do the separating
     · sticky top nav AND a black sticky bottom bar

   v1 is the previous "amber editorial" design, preserved verbatim in
   design-versions/v1-amber/ — see design-versions/README.md to switch back. The instructor dashboard (class/) is NOT
   restyled; it is an internal tool and was out of scope.
   ============================================================ */

:root {
  /* --- IdeaFest palette, sampled from the live site --- */
  --if-orange: #E8A33D;   /* KUNYIT — turmeric dye. Was IdeaFest #FE9D00 until 2026-08-17 */
  --if-pink:   #26456E;   /* INDIGO — the accent. Name kept; it is no longer pink. */
  /* The button ROLLOVER stays the original hot pink, on Agung's call (2026-08-20), even
     though the accent moved to indigo. Its own token so the two can never be confused:
     changing the accent must not change the rollover, and vice versa.
     Pairs with BLACK text — measured: black on #EC008C is 4.57:1 (AA), white is 4.25:1 (fails). */
  --rollover:  #EC008C;
  --if-yellow: #F3EAD8;   /* CREAM — undyed cloth, the CTA band */
  --if-grey:   #DBDBDA;   /* neutral block */
  --if-navy:   #0A0C1B;   /* near-black with a blue cast — nav, bottom bar */

  --bg:        #FFFFFF;   /* white ground; colour arrives in blocks, not the page */
  --ink:       #0A0C1B;
  --ink-2:     #0A0C1B;
  --text:      #0A0C1B;
  --muted:     #55585F;
  --muted-2:   #7A7D85;
  --faint:     #9A9DA4;
  --line:      #0A0C1B;   /* borders are INK now, not tinted grey — sharp and visible */
  --hairline:  #C9C9C8;
  --card:      #FFFFFF;
  --cream:     #DBDBDA;
  --hover:     #F2F2F1;
  --accent:    #E8A33D;
  --accent-2:  #E8A33D;
  --accent-3:  #C4842A;
  /* Flat, not gradient — IdeaFest never blends a fill. */
  --grad: #E8A33D;
  --gold:      #E8A33D;
  /* Mono is the whole personality. Roboto Mono is what ideafest.id uses; the stack
     falls back to the platform monospace so the page still reads correctly offline. */
  --sans: 'Roboto Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --serif: 'Roboto Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  /* Headlines. Caveat Bold (2026-08-14) — informal handwriting, deliberately at odds with
     the mono body, which is where the character comes from. Has a real 700. */
  --display: 'Caveat', 'Bradley Hand', 'Segoe Script', cursive;
  --radius: 0px;
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  font-family: var(--sans);
  background-color: var(--bg);
  /* The batik truntum page texture is gone: IdeaFest lays flat colour, never a pattern
     behind everything. The motif survives where it still earns its place — the kawung
     bullets and the groups card. */
  color: var(--text);
  -webkit-font-smoothing: antialiased;
  /* room for the sticky bottom bar so it never covers the footer's last line */
  padding-bottom: 44px;
}

/* Recia ships a single weight — never fake-bold it, so 500 is enforced. */
h1, h2, h3 {
  font-family: var(--serif);
  font-weight: 500 !important;
  letter-spacing: -.015em;
  line-height: 1.08;
}

a { color: var(--muted); text-decoration: none; }
a:hover { color: var(--text); }

input, textarea, button { font-family: inherit; }
input:focus, textarea:focus { border-color: var(--ink) !important; }
/* keyboard users had nothing but the browser default ring — give focus a designed,
   visible state. :focus-visible so a mouse click never shows it. */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* the topbar is sticky, so an anchor jump would tuck the section heading underneath it */
html { scroll-behavior: smooth; }
#plan, #faq, #location, #reviews, #kids, #book { scroll-margin-top: 84px; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
::placeholder { color: var(--muted-2); }

img { max-width: 100%; }

.wrap { max-width: 1160px; margin: 0 auto; padding: 0 18px; }
.wrap-narrow { max-width: 1120px; margin: 0 auto; }
.hidden { display: none !important; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---------- animations ---------- */
@keyframes lbw-rise { from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: translateY(0); } }
@keyframes lbw-marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }

/* ---------- shared button transitions ---------- */
button, a { transition: filter .18s ease, transform .18s ease, box-shadow .18s ease, background-color .18s ease; }
button:hover, a[href]:hover { filter: brightness(.93); transform: translateY(-3px); box-shadow: 0 12px 26px -10px rgba(20,22,28,.4); }
button:active, a[href]:active { filter: brightness(.85); transform: translateY(-1px); }
button:disabled { filter: none !important; transform: none !important; box-shadow: none !important; cursor: not-allowed; }
/* opt certain UI chrome out of the lift effect */
.no-lift:hover, .no-lift:active { transform: none; box-shadow: none; filter: none; }

/* ---------- scroll reveal (progressive — only hides when JS adds .anim) ---------- */
.anim .reveal { opacity: 0; transform: translateY(22px); transition: opacity .7s cubic-bezier(.22,1,.36,1), transform .7s cubic-bezier(.22,1,.36,1); }
.anim .reveal.in { opacity: 1; transform: none; }
.anim .reveal-cards > * { opacity: 0; transform: translateY(24px); transition: opacity .6s cubic-bezier(.22,1,.36,1), transform .6s cubic-bezier(.22,1,.36,1); }
.anim .reveal-cards.in > * { opacity: 1; transform: none; }
.anim .reveal-cards.in > *:nth-child(2) { transition-delay: .08s; }
.anim .reveal-cards.in > *:nth-child(3) { transition-delay: .16s; }
.anim .reveal-cards.in > *:nth-child(4) { transition-delay: .24s; }
.anim .reveal-cards.in > *:nth-child(5) { transition-delay: .32s; }
.anim .reveal-cards.in > *:nth-child(6) { transition-delay: .40s; }
@media (prefers-reduced-motion: reduce) {
  .anim .reveal, .anim .reveal.in,
  .anim .reveal-cards > *, .anim .reveal-cards.in > * { opacity: 1 !important; transform: none !important; transition: none !important; }
}

/* ============ TOP BAR ============ */
.topbar {
  position: sticky; top: 0; z-index: 50;
  background: rgba(255,255,255,.92);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--hairline);
}
.topbar-logo img { filter: none; } /* natural (dark) logo on the light bar */
.topbar .wrap { display: flex; align-items: center; gap: 6px; }
.topbar-logo { display: flex; align-items: center; margin-right: auto; padding: 8px 0; }
.topbar-logo img { height: 51px; width: auto; display: block; }
/* section anchors + the outbound Blog link */
.topnav { display: flex; align-items: center; gap: 4px; margin-right: 14px; }
.topnav-link {
  padding: 8px 13px; border-radius: 999px; font-weight: 700; font-size: 14px;
  color: var(--text); transition: background-color .18s ease, color .18s ease;
}
.topnav-link:hover { color: var(--ink); background: var(--cream); }
.topnav-blog { color: var(--accent-3); }
.topnav-blog:hover { color: #fff; background: var(--accent); }
.lang-toggle {
  display: flex; align-items: center; gap: 2px; background: #fff;
  border: 1px solid var(--line); border-radius: 999px; padding: 3px; margin-right: 10px;
}
.lang-btn {
  background: transparent; color: var(--muted); border: none; cursor: pointer;
  font: inherit; font-weight: 800; font-size: 12px; padding: 5px 11px; border-radius: 999px;
}
.lang-btn.active { background: var(--ink-2); color: #fff; }
.tab-btn {
  background: transparent; border: none; cursor: pointer; font: inherit;
  font-weight: 800; font-size: 14px; padding: 16px 10px 13px; color: var(--muted-2);
  border-bottom: 3px solid transparent; transition: color .15s ease;
}
.tab-btn.active { color: var(--ink-2); border-bottom-color: var(--ink); }
.portal-link {
  display: inline-flex; align-items: center; gap: 7px; color: var(--ink-2); font-weight: 800;
  font-size: 14px; padding: 9px 16px; border: 1px solid var(--line); border-radius: 999px; background: #fff;
}
.portal-link:hover { color: var(--ink-2); background: var(--hover); }

/* ============ HERO — full-bleed blurred photo panel ============ */
.hero { padding: 14px 18px 0; }
.hero-panel {
  position: relative; max-width: 1160px; margin: 0 auto;
  border-radius: 24px; overflow: hidden;
  min-height: clamp(680px, 88vh, 960px);
  display: flex; flex-direction: column; justify-content: flex-end;
  background: var(--ink);
}
/* layer 1 — the photo carousel fills the panel behind everything */
.hero-bg { position: absolute; inset: 0; overflow: hidden; }
.hero-bg-inner { position: absolute; inset: 0; overflow: hidden; }
.hero-bg .carousel-track { width: 100%; height: 100%; }
.hero-bg .carousel-slide { aspect-ratio: auto; height: 100%; overflow: hidden; }
/* layer 2 — vertical slide rail (no arrows in this design) */
.hero-rail {
  position: absolute; top: 50%; right: clamp(16px, 2vw, 26px); transform: translateY(-50%);
  z-index: 3; display: flex; flex-direction: column; align-items: center; gap: 10px;
}
.hero-rail .dot { width: 8px; height: 8px; background: rgba(255,255,255,.4); }
.hero-rail .dot.active { width: 8px; height: 30px; background: #FF7A18; }
/* layer 3 — only the bottom third of the photo is softened, masked so the
   blur has no hard top edge */
.hero-blur {
  position: absolute; left: 0; right: 0; bottom: 0; height: 36%; z-index: 1; pointer-events: none;
  -webkit-backdrop-filter: blur(16px); backdrop-filter: blur(16px);
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 26%, #000 100%);
          mask-image: linear-gradient(180deg, transparent 0%, #000 26%, #000 100%);
}
/* layer 4 — scrim, layer 5 — vignette */
.hero-scrim {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: linear-gradient(180deg, rgba(20,22,28,.30) 0%, rgba(20,22,28,.06) 34%, rgba(20,22,28,.52) 66%, rgba(20,22,28,.86) 100%);
}
.hero-vignette { position: absolute; inset: 0; z-index: 1; pointer-events: none; box-shadow: inset 0 0 120px 6px rgba(20,22,28,.3); }
/* layer 6 — content */
.hero-content { position: relative; z-index: 2; padding: clamp(28px, 4vw, 54px); }
.eyebrow {
  display: inline-flex; align-items: center; gap: 10px; padding: 0;
  border: none; font-weight: 600;
  font-size: 11px; letter-spacing: .3em; color: rgba(255,255,255,.72); margin-bottom: 20px; text-transform: uppercase;
}
.eyebrow svg { stroke: var(--accent-2); }
.hero h1 {
  letter-spacing: -.015em; line-height: 1.05; margin: 0;
  font-size: clamp(36px, 4.8vw, 66px); color: #fff; text-wrap: balance;
}
.hero h1 .accent { color: var(--accent-2); }
.hero-divider { height: 1px; background: rgba(255,255,255,.22); margin: clamp(26px, 3.4vw, 44px) 0 clamp(20px, 2.6vw, 30px); }
/* duration · price · what's included — the three facts a traveller scans for, and the
   only copy left in the hero on a phone once .hero-sub is hidden */
.hero-facts {
  font-weight: 700; font-size: 14.5px; letter-spacing: .005em;
  color: rgba(255,255,255,.92); margin: 0 0 clamp(16px, 2vw, 22px);
}
.hero-bottom { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 28px; }
.hero-sub {
  flex: 1 1 320px; max-width: 52ch; margin: 0; font-size: clamp(15px, 1.5vw, 17px);
  line-height: 1.65; color: rgba(255,255,255,.82); text-wrap: pretty;
}
.hero-cta { display: flex; gap: 14px; flex-wrap: wrap; align-items: center; }

.btn {
  display: inline-flex; align-items: center; gap: 9px; font-weight: 600;
  border: none; cursor: pointer; border-radius: 999px;
}
.btn-primary { background: var(--grad); color: #fff; font-size: 15px; font-weight: 700; padding: 16px 30px; }
/* glass pill — reads on the dark hero photo */
.btn-glass {
  background: rgba(255,255,255,.1); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  color: #fff; font-size: 15px; padding: 15px 26px; border: 1px solid rgba(255,255,255,.3);
}
.btn-glass:hover { color: #fff; }
.btn-pill { border-radius: 999px; }

/* ---------- rollover fill: buttons sweep to orange + white text on hover ---------- */
.btn, .pkg-book, .cta-big, .nav-next { position: relative; overflow: hidden; z-index: 0; transition: color .3s ease; }
.btn::before, .pkg-book::before, .cta-big::before, .nav-next::before {
  content: ""; position: absolute; inset: 0; z-index: -1; background: var(--grad);
  transform: translateY(101%); transition: transform .4s cubic-bezier(.45,.05,.2,1);
}
.btn:hover::before, .pkg-book:hover::before, .cta-big:hover::before, .nav-next:hover::before { transform: translateY(0); }
.btn:hover, .pkg-book:hover, .cta-big:hover, .nav-next:hover { color: #fff; }
/* keep outlines from clashing once the orange fills in */
.btn-glass:hover, .pkg-book.outline:hover, .groups-btn-alt:hover { border-color: #FF7A18; }

/* Google rating cluster — sits between the two hero buttons */
/* The rating cluster is now the only route to the Google listing, so it carries full
   glass-pill weight (matching what the old "Read Google Reviews" button had) rather
   than the quieter treatment it needed while sharing the row with that button. It
   picks up the global a[href] hover lift automatically. */
.rev-summary {
  display: inline-flex; align-items: center; gap: 12px; color: #fff;
  padding: 9px 22px 9px 16px; border-radius: 999px; cursor: pointer;
  background: rgba(255,255,255,.1); border: 1px solid rgba(255,255,255,.3);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
}
.rev-summary:hover { background: rgba(255,255,255,.17); border-color: #FF7A18; }
.rev-summary .rev-row { display: flex; align-items: center; gap: 8px; }
.rev-rating { font-weight: 800; font-size: 20px; line-height: 1; color: #fff; }
.rev-stars { display: flex; gap: 2px; }
.rev-stars svg { width: 14px; height: 14px; fill: var(--accent-2); }
.rev-basedon { font-size: 12.5px; color: rgba(255,255,255,.6); margin-top: 3px; }

/* ---------- carousels ---------- */
.carousel {
  position: relative; border-radius: 16px; overflow: hidden;
  box-shadow: 0 14px 34px -16px rgba(20,22,28,.3);
}
.carousel-track { display: flex; transition: transform .7s cubic-bezier(.4,0,.2,1); }
/* min-width:0 is load-bearing — without it a flex slide grows to the photo's
   intrinsic width and the track lands between slides */
.carousel-slide { flex: 0 0 100%; width: 100%; min-width: 0; aspect-ratio: 4/5; }
.carousel-arrow {
  position: absolute; top: 50%; transform: translateY(-50%); width: 44px; height: 44px;
  border-radius: 50%; border: none; background: rgba(255,255,255,.9); color: var(--ink);
  display: grid; place-items: center; cursor: pointer; z-index: 2;
}
.carousel-arrow.prev { left: 12px; }
.carousel-arrow.next { right: 12px; }
.dots { display: flex; justify-content: center; gap: 8px; margin-top: 14px; }
.dot {
  width: 8px; height: 8px; border-radius: 999px; background: var(--line);
  border: none; padding: 0; cursor: pointer; transition: all .3s ease;
}
.dot.active { width: 22px; background: var(--ink); }

/* ---------- image placeholder panels ---------- */
/* image wells stay a flat neutral until real photos are dropped in */
.img-slot {
  width: 100%; height: 100%; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 10px; text-align: center;
  background: var(--cream); color: var(--muted); padding: 18px;
}
.img-slot.on-light {
  background: var(--cream); color: var(--muted);
  box-shadow: inset 0 0 0 1px rgba(20,22,28,.06);
}
.img-slot svg { opacity: .55; }
.img-slot .slot-label { font-weight: 700; font-size: 13px; letter-spacing: .02em; }
.img-slot .slot-tag { font-size: 11px; letter-spacing: .18em; text-transform: uppercase; opacity: .6; }
.img-slot img,
.slot-photo { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ============ TESTIMONIALS MARQUEE ============ */
/* the 64px bottom padding existed to seat the "Book Your Spot" button under the
   marquee; with that gone, only enough is left to keep the cards' shadows from being
   clipped by overflow:hidden — the spacing to the next section is #plan's own padding */
.reviews-section { padding: 26px 18px 12px; overflow: hidden; background: transparent; }
.marquee-clip { position: relative; margin: 0 -18px; }
/* the negative delay starts the loop mid-cycle so the left edge is never empty */
.marquee-track {
  display: flex; align-items: flex-start; gap: 22px; width: max-content;
  padding: 6px 18px; animation: lbw-marquee 55s linear -22s infinite;
}
.marquee-track:hover { animation-play-state: paused; }
.review-card {
  flex: 0 0 340px; width: 340px; border: 1px solid var(--hairline); border-radius: 22px;
  padding: 26px; background: #fff;
  display: flex; flex-direction: column; gap: 13px; box-shadow: 0 10px 30px -18px rgba(20,22,28,.22);
}
.review-head { display: flex; align-items: center; gap: 11px; }
.review-avatar {
  width: 42px; height: 42px; border-radius: 50%; color: #fff; font-weight: 800;
  font-size: 17px; display: grid; place-items: center; flex-shrink: 0;
}
.review-name { font-weight: 800; font-size: 15px; }
.review-when { font-size: 12px; color: var(--muted-2); }
.review-text { margin: 0; font-size: 14px; line-height: 1.55; color: var(--text); }
.review-toggle {
  align-self: flex-start; background: none; border: none; padding: 0; cursor: pointer;
  font: inherit; font-weight: 800; font-size: 13px; color: var(--muted);
}

/* ============ SECTION HEADERS ============ */
.section { padding: clamp(74px, 9vw, 120px) 18px; }
.section-head { text-align: center; margin-bottom: 32px; }
.section-eyebrow { font-weight: 600; font-size: 11px; letter-spacing: .26em; color: var(--muted-2); margin-bottom: 16px; text-transform: uppercase; }
.section-head h2 { letter-spacing: -.02em; font-size: clamp(36px, 5.4vw, 66px); line-height: 1.05; margin: 0; }

/* ============ PACKAGES ============ */
.pkg-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 18px; padding: 8px 0 4px;
}
.lbw-pkg-card {
  border-radius: 22px; overflow: hidden; background: #fff; display: flex; flex-direction: column;
  box-shadow: 0 12px 30px -14px rgba(20,22,28,.24);
  transition: transform .2s cubic-bezier(.2,0,0,1), box-shadow .2s cubic-bezier(.2,0,0,1);
}
.lbw-pkg-card.featured { border: 2px solid var(--ink); border-radius: 22px; box-shadow: 0 18px 42px -16px rgba(240,112,13,.4); position: relative; }
@media (hover: hover) {
  .lbw-pkg-card:hover { transform: translateY(-4px); box-shadow: 0 18px 40px -16px rgba(20,22,28,.34); }
}
.pkg-badge {
  position: absolute; top: 14px; left: 14px; z-index: 2; background: var(--accent); color: #fff;
  font-weight: 600; font-size: 11px; letter-spacing: .12em; padding: 7px 14px; border-radius: 999px;
}
.scrub {
  position: relative; width: 100%; aspect-ratio: 4/3; background: var(--cream); overflow: hidden;
}
.scrub-frame { position: absolute; inset: 0; opacity: 0; transition: opacity .18s ease; pointer-events: none; }
.scrub-frame.active { opacity: 1; pointer-events: auto; }
.scrub-dots { position: absolute; left: 0; right: 0; bottom: 10px; display: flex; justify-content: center; gap: 5px; pointer-events: none; }
.scrub-dot { width: 7px; height: 7px; border-radius: 999px; background: rgba(255,255,255,.5); transition: all .18s ease; }
.scrub-dot.active { width: 18px; background: rgba(255,255,255,.95); }
.pkg-body { padding: 22px; display: flex; flex-direction: column; flex: 1; }
.pkg-body h3 { font-size: 22px; margin: 0 0 8px; letter-spacing: -.015em; }
.pkg-desc { margin: 0; color: var(--muted); font-size: 14px; line-height: 1.5; }
.pkg-note { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); font-style: italic; font-size: 12.5px; font-weight: 600; margin-top: 16px; }
.pkg-price { display: flex; align-items: baseline; gap: 6px; margin-top: 6px; }
.pkg-price .amount { font-weight: 900; font-size: 26px; color: var(--muted); }
.pkg-price .per { color: var(--muted-2); font-weight: 600; font-size: 13px; }
.pkg-spacer { flex: 1; min-height: 18px; }
.pkg-book {
  display: flex; align-items: center; justify-content: center; gap: 8px; font-weight: 800; font-size: 15px;
  padding: 13px; border-radius: 999px; cursor: pointer;
}
.pkg-book.outline { background: transparent; color: var(--ink); border: 2px solid var(--ink); }
.pkg-book.solid { background: var(--grad); color: #fff; border: none; font-weight: 600; }
.pkg-includes { display: flex; align-items: center; gap: 9px; margin-top: 16px; color: var(--muted); font-size: 14px; font-weight: 600; }

/* Corporate / large-group inquiry card */
.groups-card {
  position: relative; overflow: hidden;
  display: flex; flex-direction: row-reverse; align-items: center; gap: clamp(24px, 4vw, 44px); flex-wrap: wrap;
  margin-top: 40px; background: var(--ink); color: #fff;
  border-radius: 20px; padding: clamp(26px, 4vw, 44px);
  box-shadow: 0 24px 50px -24px rgba(20,22,28,.5);
}
/* Batik truntum woven into the card background: four-petal kembang stars on a diagonal
   half-drop lattice, with the small scattered dots the motif is known for. Was a grid of
   plain circles, which read as generic dots rather than batik. Keep the opacity low —
   this sits behind white text, so contrast matters more than the pattern does. */
.groups-card::before {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background-image: url("data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20width='80'%20height='80'%20viewBox='0%200%2080%2080'><g%20fill='none'%20stroke='%23ffffff'%20stroke-width='1.15'><path%20d='M20,10Q24,16,30,20Q24,24,20,30Q16,24,10,20Q16,16,20,10Z'/><path%20d='M60,50Q64,56,70,60Q64,64,60,70Q56,64,50,60Q56,56,60,50Z'/></g><g%20fill='%23ffffff'><circle%20cx='20'%20cy='20'%20r='1.7'/><circle%20cx='60'%20cy='60'%20r='1.7'/><circle%20cx='60'%20cy='20'%20r='1.6'/><circle%20cx='20'%20cy='60'%20r='1.6'/><circle%20cx='53'%20cy='13'%20r='1'/><circle%20cx='67'%20cy='13'%20r='1'/><circle%20cx='53'%20cy='27'%20r='1'/><circle%20cx='67'%20cy='27'%20r='1'/><circle%20cx='13'%20cy='53'%20r='1'/><circle%20cx='27'%20cy='53'%20r='1'/><circle%20cx='13'%20cy='67'%20r='1'/><circle%20cx='27'%20cy='67'%20r='1'/></g></svg>");
  background-size: 78px 78px; opacity: .12;
}
.groups-card > * { position: relative; z-index: 1; }
.groups-main { flex: 1 1 340px; display: flex; align-items: flex-start; gap: 22px; }
.groups-text { flex: 1 1 auto; }
.groups-title { font-family: var(--sans); font-weight: 800; letter-spacing: -.01em; font-size: clamp(24px, 3.2vw, 36px); margin: 6px 0 8px; color: #fff; }
.groups-desc { margin: 0 0 22px; color: rgba(233,223,212,.8); font-size: 16px; line-height: 1.55; max-width: 52ch; }
.groups-actions { display: flex; flex-wrap: wrap; gap: 12px; }
.groups-btn { display: inline-flex; align-items: center; gap: 9px; font-size: 15px; padding: 15px 28px; }
/* secondary "Email Us" button — outline on the dark card */
.groups-btn-alt { background: transparent; color: #fff; border: 1.5px solid rgba(255,255,255,.28); }
.groups-btn-alt:hover { border-color: var(--accent); }
/* carousel column */
.groups-media { flex: 1 1 340px; max-width: 460px; }
.groups-carousel {
  border-radius: 16px; border: 1px solid rgba(255,255,255,.1);
  box-shadow: 0 16px 40px -18px rgba(0,0,0,.6);
}
.groups-carousel .carousel-slide { aspect-ratio: 4/3; }
.groups-media .dots { margin-top: 12px; }
.groups-media .dot { background: rgba(255,255,255,.32); }
.groups-media .dot.active { background: var(--gold); }

/* ============ KIDS / FAMILY ============ */
.kids-grid {
  max-width: 1120px; margin: 0 auto; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 36px; align-items: center;
}
.kids-benefits { display: flex; flex-direction: column; gap: 14px; }
.benefit { display: flex; align-items: flex-start; gap: 12px; }
/* Kawung bullet: the motif IS the bullet, so no badge behind it — a dark disc would
   swallow the shape at this size. currentColor carries the orange down into the SVG. */
.benefit .kawung {
  flex-shrink: 0; width: 26px; height: 26px; color: var(--accent);
  display: grid; place-items: center; margin-top: -1px;
}
.benefit span.text { font-size: 16px; line-height: 1.5; color: var(--text); font-weight: 600; }
.kids-heading { font-weight: 600; letter-spacing: -.015em; font-size: clamp(30px, 4.4vw, 50px); margin: 0 0 20px; }

/* ============ FAQ ============ */
.faq-section { background: #fff; }
.faq-list { max-width: 760px; margin: 0 auto; display: flex; flex-direction: column; gap: 12px; }
.faq-item {
  border-radius: 14px; background: #fff; overflow: hidden;
  box-shadow: 0 8px 24px -14px rgba(20,22,28,.22); transition: box-shadow .18s ease;
}
.faq-q {
  display: flex; align-items: center; gap: 14px; width: 100%; background: transparent;
  border: none; cursor: pointer; font: inherit; text-align: left; padding: 24px 26px;
  font-weight: 800; font-size: 17px; color: var(--ink); transition: background .18s ease;
}
.faq-q:hover { background: var(--hover) !important; }
.faq-q .icon { flex-shrink: 0; transition: transform .4s cubic-bezier(.22,1,.36,1); color: var(--ink); }
.faq-item.open .faq-q .icon { transform: rotate(135deg); color: var(--accent); }
/* collapsible wrapper — JS animates its height between 0 and the content height */
.faq-a-wrap { height: 0; overflow: hidden; transition: height .42s cubic-bezier(.22,1,.36,1); will-change: height; }
.faq-a {
  padding: 2px 26px 26px; font-size: 15px; line-height: 1.6; color: var(--muted);
  opacity: 0; transform: translateY(-6px);
  transition: opacity .38s ease .04s, transform .45s cubic-bezier(.22,1,.36,1) .04s;
}
.faq-item.open .faq-a { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .faq-a-wrap, .faq-a, .faq-q .icon { transition: none; }
}

/* ============ BOOKING CTA BAND ============ */
/* The section is now only spacing + page background; the dark panel, the photo and the
   radius all live on .cta-card so it reads as a contained card like .groups-card. */
.cta-band { position: relative; padding: clamp(66px, 8vw, 104px) 18px; background: transparent; }
.cta-card {
  /* 1120px, not 1160 — matches .wrap-narrow so this card's edges line up with .groups-card */
  position: relative; overflow: hidden; max-width: 1120px; margin: 0 auto;
  border-radius: 20px; background: var(--ink);
  padding: clamp(52px, 7vw, 88px) clamp(20px, 4vw, 44px);
  box-shadow: 0 24px 50px -24px rgba(20, 22, 28, .5);
}
.cta-band .cta-bg { position: absolute; inset: 0; z-index: 0; }
.cta-band .cta-bg-img { width: 100%; height: 100%; object-fit: cover; display: block; }
.cta-band .cta-overlay { position: absolute; inset: 0; z-index: 1; background: rgba(20,22,28,.72); pointer-events: none; }
.cta-inner { position: relative; z-index: 2; max-width: 900px; margin: 0 auto; text-align: center; }
.cta-inner h2 { font-weight: 600; letter-spacing: -.015em; font-size: clamp(38px, 5.5vw, 66px); color: #fff; margin: 0 0 14px; text-wrap: balance; }
.cta-inner p { color: rgba(255,255,255,.72); font-size: 17px; line-height: 1.55; max-width: 50ch; margin: 0 auto 30px; }
.cta-big {
  display: inline-flex; align-items: center; gap: 12px; background: var(--grad); color: #fff;
  font-weight: 600; font-size: clamp(18px, 2.6vw, 24px); padding: 20px 42px; border: none;
  border-radius: 999px; cursor: pointer;
}

/* ============ LOCATION ============ */
.loc-section { background: transparent; border-top: 1px solid var(--line); }
.loc-grid { max-width: 1120px; margin: 0 auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 24px; align-items: stretch; }
.loc-details { display: flex; flex-direction: column; gap: 20px; }
.loc-row { display: flex; align-items: flex-start; gap: 13px; }
.loc-ico { flex-shrink: 0; display: grid; place-items: center; padding-top: 2px; }
.loc-label { font-weight: 700; font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--muted-2); }
.loc-value { font-size: 17px; color: var(--ink); font-weight: 700; margin-top: 3px; }
a.loc-value { display: inline-block; }
.loc-directions {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px; align-self: flex-start;
  background: var(--grad); color: #fff; font-weight: 600; font-size: 15px; padding: 14px 28px; border-radius: 999px;
}
.map-box {
  position: relative; min-height: 320px; border-radius: 16px; overflow: hidden;
  border: 2px solid var(--text); box-shadow: 0 16px 40px -18px rgba(198,86,10,.45);
}
.map-box iframe {
  width: 100%; height: 100%; min-height: 320px; border: 0; display: block;
  filter: sepia(.45) saturate(1.35) hue-rotate(-18deg) contrast(1.02);
}
.map-box .map-tint { position: absolute; inset: 0; background: var(--text); mix-blend-mode: multiply; opacity: .14; pointer-events: none; }
.map-box .map-ring { position: absolute; inset: 0; box-shadow: inset 0 0 0 1px rgba(178,74,30,.35); pointer-events: none; }

/* ============ FOOTER ============ */
.site-footer { background: var(--ink); padding: 0 24px 34px; border-top: 1px solid rgba(255,255,255,.08); }
.site-footer .inner { max-width: 1000px; margin: 0 auto; padding-top: 40px; }
.site-footer .brand { font-weight: 800; font-size: 22px; color: #fff; }
.site-footer .loc { display: flex; align-items: center; gap: 8px; color: rgba(255,255,255,.72); font-size: 14px; margin-top: 8px; }
/* brand left, contact details right — the footer is where people hunt for the number */
.foot-top {
  display: flex; flex-wrap: wrap; justify-content: space-between; align-items: flex-start;
  gap: 26px; padding-bottom: 26px; border-bottom: 1px solid rgba(255,255,255,.1);
}
.foot-contact { display: flex; flex-direction: column; gap: 11px; }
.foot-line { display: flex; align-items: center; gap: 10px; color: rgba(255,255,255,.8); font-size: 14.5px; }
a.foot-line:hover { color: #fff; }
.foot-hours { color: rgba(255,255,255,.6); }
@media (max-width: 620px) {
  .foot-top { flex-direction: column; align-items: flex-start; }
}

/* ---------- floating WhatsApp bubble ---------- */
.wa-bubble {
  position: fixed; right: 18px; bottom: 18px; z-index: 60;
  display: inline-flex; align-items: center; gap: 10px;
  background: #25D366; color: #fff; font-weight: 700; font-size: 15px;
  padding: 13px 20px 13px 16px; border-radius: 999px;
  box-shadow: 0 14px 30px -10px rgba(20,22,28,.5);
  transition: transform .2s cubic-bezier(.2,0,0,1), box-shadow .2s ease, background-color .2s ease;
}
.wa-bubble:hover { color: #fff; background: #1EBE5B; transform: translateY(-3px); box-shadow: 0 20px 38px -12px rgba(20,22,28,.55); }
.wa-bubble svg { flex-shrink: 0; }
@media (max-width: 560px) {
  /* icon only on phones — a labelled pill would cover the content */
  .wa-bubble { right: 14px; bottom: 14px; padding: 14px; }
  .wa-bubble-label { display: none; }
}

/* ============ DASHBOARD — LOGIN ============ */
.login-wrap { max-width: 420px; margin: 0 auto; padding: 64px 20px; }
.login-card { background: #fff; border: 1px solid var(--line); border-radius: 18px; padding: 36px 30px; }
.login-icon { width: 52px; height: 52px; border-radius: 14px; background: var(--ink); display: grid; place-items: center; margin-bottom: 18px; }
.login-card h2 { font-size: 24px; margin: 0 0 4px; letter-spacing: -.02em; }
.login-card .sub { margin: 0 0 24px; color: var(--muted); font-size: 14px; }
.field-label { display: block; font-weight: 800; font-size: 13px; margin-bottom: 8px; }
.field-label .req { color: #C0392B; }
.field-label .opt { color: var(--muted-2); font-weight: 500; font-size: 12px; }
.field-err { margin-top: 6px; font-size: 12.5px; font-weight: 600; color: #C0392B; }
.pkg-rule-note { font-size: 12.5px; line-height: 1.45; color: #8A5A1C; font-weight: 600; background: #FBEFDD; border: 1px solid #F0DCC0; border-radius: 10px; padding: 9px 12px; }

/* ============ SESSIONS / AVAILABILITY ============ */
.sessions-head { max-width: 640px; margin: 0 auto 42px; text-align: center; }
.sessions-head h2 { font-size: clamp(28px, 4vw, 42px); margin: 6px 0 14px; }
.sessions-sub { color: var(--muted); font-size: 16px; line-height: 1.6; margin: 0; }
/* Two slots since 2026-08-08 (was 3). NOTE: inside .plan-sec this max-width is
   overridden to none, so the two cards span the full container there. */
.sessions-grid { max-width: 1000px; margin: 0 auto; display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; }
.session-card {
  background: #fff; border: 1px solid var(--hairline); border-radius: 22px;
  padding: 28px 26px; box-shadow: 0 14px 34px -22px rgba(20,22,28, .4);
  transition: transform .2s cubic-bezier(.2,0,0,1), box-shadow .2s cubic-bezier(.2,0,0,1);
  /* icon beside the title+time, description spans full width below */
  display: grid; grid-template-columns: auto 1fr;
  grid-template-areas: "ico name" "ico time" "desc desc";
  column-gap: 16px; row-gap: 1px; align-content: start;
}
.session-card:hover { transform: translateY(-3px); box-shadow: 0 22px 44px -22px rgba(20,22,28, .5); }
.session-ico { grid-area: ico; align-self: center; width: 54px; height: 54px; border-radius: 15px; display: grid; place-items: center; }
.session-name { grid-area: name; align-self: end; font-family: var(--sans); font-weight: 400; font-size: 24px; color: var(--ink); }
.session-time { grid-area: time; align-self: start; font-weight: 800; font-size: 14px; letter-spacing: .02em; }
/* three batik-dye tones: kunyit (gold), soga (terracotta), indigo */
.plan-sec .session-card:nth-child(1) .session-ico { background: #F6E7C4; color: #D9922E; }
.plan-sec .session-card:nth-child(1) .session-time { color: #D9922E; }
.plan-sec .session-card:nth-child(2) .session-ico { background: #FCE6D2; color: #C2560F; }
.plan-sec .session-card:nth-child(2) .session-time { color: #C2560F; }
.plan-sec .session-card:nth-child(3) .session-ico { background: #DADEEE; color: #3B4A72; }
.plan-sec .session-card:nth-child(3) .session-time { color: #3B4A72; }
.session-desc { grid-area: desc; color: var(--muted); font-size: 14.5px; line-height: 1.55; margin: 16px 0 0; }
.sessions-foot { text-align: center; color: var(--muted-2); font-size: 13px; margin: 22px auto 0; max-width: 560px; }
@media (max-width: 760px) { .sessions-grid { grid-template-columns: 1fr; } }

/* ============ PLAN — time + package unified into one section ============ */
.plan-sec { padding: clamp(66px, 8vw, 104px) 18px; }
.plan-head { margin-bottom: 6px; }
.plan-sub { color: var(--muted); font-size: 16px; line-height: 1.6; max-width: 620px; margin: 12px auto 0; }
.plan-block { margin-top: 44px; }
.plan-subhead {
  display: flex; align-items: center; justify-content: center; gap: 13px;
  font-family: var(--sans); letter-spacing: -.01em;
  font-size: clamp(22px, 2.8vw, 30px); color: var(--ink); margin: 0 0 24px;
}
.plan-num {
  flex-shrink: 0; width: 30px; height: 30px; border-radius: 50%;
  background: var(--accent); color: #fff; display: grid; place-items: center;
  font-family: var(--sans); font-weight: 800; font-size: 15px;
}
/* Both rows span the full container: packages 3-up, sessions 2-up (since 2026-08-08).
   The session cards are wider than the package cards by design — the row edges line
   up, which is what reads as aligned; matching card widths would leave dead space. */
.plan-sec .sessions-grid { max-width: none; margin: 0; }
.plan-sec .pkg-grid { grid-template-columns: repeat(3, 1fr); }
.plan-sec .pkg-includes { justify-content: center; margin-top: 16px; }
@media (max-width: 760px) {
  .plan-sec .sessions-grid, .plan-sec .pkg-grid { grid-template-columns: 1fr; }
}
.login-input {
  width: 100%; padding: 13px 15px; border: 2px solid var(--line); border-radius: 12px;
  font-size: 15px; background: #fff; outline: none; margin-bottom: 12px;
}
.chip-row { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 18px; }
.name-chip {
  background: #fff; color: var(--text); border: 1px solid var(--line); cursor: pointer; font: inherit;
  font-weight: 800; font-size: 13px; padding: 7px 13px; border-radius: 999px;
}
.name-chip.active { background: var(--ink); color: #fff; border-color: var(--ink); }
.login-submit {
  width: 100%; background: var(--ink); color: #fff; font-weight: 800; font-size: 16px;
  padding: 15px; border: none; border-radius: 12px; cursor: pointer;
}
.login-submit:disabled { background: var(--line); cursor: not-allowed; }

/* ============ DASHBOARD ============ */
.dash-header { background: #fff; border-bottom: 1px solid var(--line); }
.dash-header .wrap { padding: 20px 18px; display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.dash-welcome { margin-right: auto; }
.dash-welcome .hi { font-size: 13px; color: var(--muted); font-weight: 600; }
.dash-welcome .who { font-weight: 900; font-size: 22px; letter-spacing: -.01em; }
.dash-add {
  display: inline-flex; align-items: center; gap: 8px; background: var(--grad); color: #fff;
  font-weight: 600; font-size: 14px; padding: 12px 18px; border: none; border-radius: 999px; cursor: pointer;
}
.dash-logout {
  display: inline-flex; align-items: center; gap: 8px; background: #fff; color: var(--ink-2);
  font-weight: 800; font-size: 14px; padding: 11px 18px; border: 2px solid var(--line); border-radius: 999px; cursor: pointer;
}
.dash-body { max-width: 1160px; margin: 0 auto; padding: 26px 18px 64px; }
.filter-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 22px; }
.filter-btn {
  background: #fff; color: var(--text); border: 1px solid var(--line); cursor: pointer; font: inherit;
  font-weight: 800; font-size: 14px; padding: 10px 16px; border-radius: 999px; transition: all .15s ease;
}
.filter-btn.active { background: var(--ink-2); color: #fff; border-color: var(--ink-2); }
.dash-cols { display: grid; grid-template-columns: minmax(0, 420px) minmax(0, 1fr); gap: 26px; align-items: start; }

/* calendar */
.cal-card { background: #fff; border: 1px solid var(--line); border-radius: 18px; padding: 20px; }
.cal-head { display: flex; align-items: center; gap: 12px; margin-bottom: 18px; }
.cal-month { font-weight: 900; font-size: 18px; letter-spacing: -.01em; margin-right: auto; text-transform: capitalize; }
.cal-nav {
  width: 38px; height: 38px; display: inline-flex; align-items: center; justify-content: center;
  background: #fff; border: 1px solid var(--line); border-radius: 10px; cursor: pointer; color: var(--text);
}
.cal-nav:hover { background: var(--hover); }
.cal-weekdays, .cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; }
.cal-weekdays { margin-bottom: 6px; }
.cal-weekday { text-align: center; font-weight: 800; font-size: 11px; color: var(--muted-2); padding: 4px 0; }
.cal-cell {
  position: relative; aspect-ratio: 1; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 4px; border: none; border-radius: 10px; font: inherit; font-weight: 800;
  font-size: 15px; background: transparent; color: var(--text); cursor: pointer; transition: all .15s ease;
}
.cal-cell.empty { visibility: hidden; }
.cal-cell.booked { background: var(--cream); color: var(--muted); }
.cal-cell.today { box-shadow: inset 0 0 0 2px var(--ink-2); }
/* Days that already happened — dimmed, but still clickable (photos get shared and
   payments get marked after the session, so the past must stay reachable). The
   selected/today rules below still win, so a past day you open is clearly readable. */
.cal-cell.past { opacity: .38; }
.cal-cell.past:hover { opacity: .72; }
.cal-cell.selected { background: var(--ink); color: #fff; opacity: 1; }
.cal-count { font-size: 10px; font-weight: 800; line-height: 1; padding: 2px 6px; border-radius: 999px; background: var(--muted); color: #fff; }

/* ---- Workshop-day indicators -------------------------------------------------
   Applied only to days that hold sessions. The badge colour carries the meaning, so
   it still reads when the cell is selected (dark) or dimmed (past). Ordered by how
   urgently the studio must act: TOMORROW is loudest — it is the one you prepare for
   — then today, then upcoming, and the past recedes.                              */
.cal-cell.has-tomorrow { background: #FDF0E4; box-shadow: inset 0 0 0 2px var(--accent); }
.cal-cell.has-tomorrow .cal-count { background: var(--accent); }
.cal-cell.has-today   .cal-count { background: var(--ink); }
.cal-cell.has-upcoming .cal-count { background: #2F6F4F; }   /* green = booked and ahead */
.cal-cell.has-past     .cal-count { background: var(--muted-2); }
/* The selected day is a dark tile; a solid badge on it would fight the fill, except for
   tomorrow, whose orange must survive being selected. */
.cal-cell.selected .cal-count { background: rgba(255,255,255,.28); }
.cal-cell.selected.has-tomorrow { background: var(--ink); box-shadow: inset 0 0 0 2px var(--accent); }
.cal-cell.selected.has-tomorrow .cal-count { background: var(--accent); }

/* Status chip beside the day-panel date */
.day-phase {
  display: inline-flex; align-items: center; height: 22px; padding: 0 10px; border-radius: 999px;
  font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; white-space: nowrap;
}
.day-phase.tomorrow { background: var(--accent); color: #fff; }
.day-phase.today    { background: var(--ink); color: #fff; }
.day-phase.upcoming { background: #E4F0E9; color: #2F6F4F; }
.day-phase.past     { background: #ECEDEF; color: var(--muted-2); }
.cal-legend { display: flex; align-items: center; gap: 16px; margin-top: 18px; padding-top: 16px; border-top: 1px solid #F0F1F3; flex-wrap: wrap; }
.cal-legend .lg { display: flex; align-items: center; gap: 8px; }
.cal-legend .lg-count { min-width: 18px; height: 16px; padding: 0 5px; display: inline-flex; align-items: center; justify-content: center; border-radius: 5px; background: var(--muted); color: #fff; font-size: 10px; font-weight: 800; }
.cal-legend .lg-today { width: 16px; height: 16px; border-radius: 5px; box-shadow: inset 0 0 0 2px var(--ink-2); }
/* Legend swatches mirror the badge colours above — same meaning, same colour. */
.cal-legend .lg-swatch { width: 16px; height: 16px; border-radius: 5px; }
.cal-legend .lg-tomorrow { background: var(--accent); }
.cal-legend .lg-upcoming { background: #2F6F4F; }
.cal-legend .lg-past { background: var(--muted-2); }
.cal-legend .lg-text { font-size: 12px; color: var(--muted); font-weight: 700; }

/* day sessions */
.day-head { display: flex; align-items: center; gap: 11px; margin-bottom: 14px; }
.day-title { font-weight: 900; font-size: 18px; letter-spacing: -.01em; }
.day-rule { flex: 1; height: 1px; background: var(--line); }
.day-count { font-size: 12px; color: var(--muted-2); font-weight: 700; }
.day-empty { background: #fff; border: 1px dashed var(--line); border-radius: 16px; text-align: center; padding: 54px 20px; color: var(--muted-2); }
.day-empty .big { font-weight: 800; font-size: 15px; color: var(--muted); }
.day-empty .small { font-size: 13px; margin-top: 4px; }
.day-list { display: flex; flex-direction: column; gap: 16px; }
.booking-row { padding: 22px 24px; background: #fff; border-radius: 16px; border: 1px solid var(--line); }
.booking-top { display: flex; align-items: flex-start; gap: 12px; }
.booking-time { font-weight: 900; font-size: 30px; letter-spacing: -.02em; line-height: 1.05; margin-right: auto; }
.pay-chip { font-weight: 800; font-size: 13px; padding: 6px 15px; border-radius: 999px; }
.pay-chip.paid { background: #DCFCE7; color: #15803D; }
.pay-chip.unpaid { background: var(--cream); color: var(--text); }
.booking-client-row { display: flex; align-items: center; gap: 10px; margin-top: 12px; flex-wrap: wrap; }
.booking-client { font-weight: 900; font-size: 24px; letter-spacing: -.02em; }
.code-btn {
  display: inline-flex; align-items: center; gap: 7px; font-family: ui-monospace, 'SF Mono', Menlo, monospace;
  font-weight: 800; font-size: 14px; letter-spacing: .03em; color: var(--muted); background: var(--bg);
  padding: 6px 12px; border: none; border-radius: 9px; cursor: pointer;
}
.code-btn:hover { background: var(--cream); }
.booking-pkg { font-size: 15px; color: var(--muted); margin-top: 3px; }
.booking-bottom { display: flex; align-items: flex-end; gap: 16px; margin-top: 18px; flex-wrap: wrap; }
.booking-meta { margin-right: auto; display: flex; flex-direction: column; gap: 6px; }
.meta-line { display: flex; align-items: center; gap: 9px; font-size: 15px; color: var(--text); }
.meta-line.suggest { font-size: 14px; color: #B45309; font-weight: 700; }
.meta-line.note { font-size: 15px; color: var(--muted); font-style: italic; }
.booking-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; justify-content: flex-end; }
/* on a phone the avatars and the buttons cannot share a line — let the buttons take
   their own full-width row rather than squeezing into 120px each */
@media (max-width: 620px) {
  .booking-bottom { flex-direction: column; align-items: stretch; }
  .booking-bottom .booking-actions { margin-left: 0; justify-content: flex-start; }
}
.act-btn { display: inline-flex; align-items: center; gap: 7px; font-weight: 800; font-size: 15px; border-radius: 999px; cursor: pointer; white-space: nowrap; }
.act-cta { background: var(--ink); color: #fff; padding: 13px 26px; border: 2px solid var(--text); }
.act-cta.mine { background: #fff; color: var(--text); padding: 12px 24px; border: 2px solid var(--ink); }
.act-btn:disabled { background: var(--hover); color: var(--muted-2); border: 2px solid var(--hover); }
.act-wa { background: #25D366; color: #fff; border: none; padding: 12px 20px; }
.act-wa:hover { color: #fff; }
/* Cancel is destructive-looking but reversible, so it reads as a quiet outline rather
   than a red block — it sits next to WhatsApp and Share Photos and must not shout. */
/* ---------- ⋮ card menu: occasional + consequential actions ---------- */
.card-menu { position: relative; flex-shrink: 0; }
.menu-btn {
  display: grid; place-items: center; width: 32px; height: 32px; border-radius: 999px;
  background: transparent; border: none; color: var(--muted-2); cursor: pointer;
}
.menu-btn:hover { background: var(--hover); color: var(--ink); }
.menu-pop {
  position: absolute; top: 38px; right: 0; z-index: 20; min-width: 210px;
  background: #fff; border: 1px solid var(--hairline); border-radius: 14px; padding: 6px;
  box-shadow: 0 18px 40px -16px rgba(20,22,28,.45);
}
.menu-item {
  display: flex; align-items: center; gap: 11px;
  width: 100%; text-align: left; background: none; border: none;
  font: inherit; font-weight: 700; font-size: 14.5px; color: var(--text);
  padding: 11px 13px; border-radius: 9px; cursor: pointer;
}
/* icon sits quieter than the label; the danger item inherits red instead */
.menu-ico { flex-shrink: 0; color: var(--muted-2); }
.menu-item:hover .menu-ico { color: var(--ink); }
.menu-item.danger .menu-ico,
.menu-item.danger:hover .menu-ico { color: #B42318; }
.menu-item:hover { background: var(--hover); }
.menu-item.danger { color: #B42318; }
.menu-item.danger:hover { background: #FEF3F2; }
.menu-sep { height: 1px; background: var(--hairline); margin: 5px 3px; }
.pay-chip.cancelled { background: #FEF3F2; color: #B42318; }
/* the cancelled block: still readable for reference, clearly not part of the day's work */
.cancelled-block { margin-top: 22px; }
.cancelled-head {
  font-weight: 800; font-size: 12px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--muted-2); margin-bottom: 10px;
}
.booking-row.is-cancelled { background: var(--hover); border-style: dashed; }
.booking-row.is-cancelled .booking-client,
.booking-row.is-cancelled .booking-time { text-decoration: line-through; color: var(--muted); }
.booking-row.is-cancelled .booking-pkg { color: var(--muted-2); }
/* T-shirt size picker (booking step 2) */
.tshirt-sizes { margin-top: 16px; background: var(--hover); border: 1px solid var(--line); border-radius: 14px; padding: 18px; }
.tshirt-sizes.hidden { display: none; }
.size-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
.size-group + .size-group { margin-top: 16px; }
.size-group-label { font-weight: 800; font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--faint); margin-bottom: 10px; }
.size-pills { display: flex; flex-wrap: wrap; gap: 9px; }
/* inactive size = clean outlined pill (tap to add the first one) */
.size-pill { display: inline-flex; align-items: center; border: 2px solid var(--line); border-radius: 999px; background: #fff; color: var(--ink-2); font: inherit; font-weight: 800; font-size: 14px; cursor: pointer; padding: 10px 18px; transition: transform .15s cubic-bezier(.2,0,0,1), border-color .15s ease, box-shadow .15s ease; }
button.size-pill:hover:not(:disabled) { border-color: var(--ink); transform: translateY(-2px); box-shadow: 0 8px 18px -10px rgba(20,22,28,.5); }
button.size-pill:disabled { opacity: .35; cursor: not-allowed; }
/* active size = filled pill with inline − / count / + */
.size-pill.on { padding: 4px 5px; gap: 2px; background: var(--ink); border-color: var(--ink); color: #fff; }
.sp-mid { padding: 0 8px; min-width: 40px; text-align: center; white-space: nowrap; }
.sp-mid b { font-weight: 900; }
.sp-btn { width: 30px; height: 30px; border-radius: 50%; border: none; background: rgba(255,255,255,.16); color: #fff; font-weight: 800; font-size: 18px; line-height: 1; cursor: pointer; display: grid; place-items: center; transition: background .15s ease; }
.sp-btn:hover:not(:disabled) { background: rgba(255,255,255,.34); }
.sp-btn:disabled { opacity: .35; cursor: not-allowed; }
.size-hint { margin-top: 12px; font-size: 13px; color: var(--accent); font-weight: 600; }

/* admin instructor assignment — batik-dye avatars */
/* Instruktur on the left, actions pushed right, sharing one line. The divider moved
   from .assign-block to this wrapper so it spans the full card width. */
.booking-bottom {
  display: flex; align-items: flex-end; justify-content: space-between; gap: 18px;
  flex-wrap: wrap; margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--line);
}
.booking-bottom .booking-actions { margin-left: auto; }
.assign-block { flex: 0 1 auto; }
.assign-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
.assign-label { font-weight: 800; font-size: 12px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted-2); }
.assign-cap { font-weight: 800; font-size: 12px; color: var(--muted-2); background: var(--hover); border-radius: 999px; padding: 3px 11px; letter-spacing: .02em; transition: background .18s ease, color .18s ease; }
.assign-cap.full { background: var(--ink); color: #fff; }
.assign-avatars { display: flex; gap: 20px; }
.ava { display: flex; flex-direction: column; align-items: center; gap: 8px; background: none; border: none; padding: 0; cursor: pointer; font: inherit; width: 52px; }
.ava-circle { position: relative; width: 46px; height: 46px; border-radius: 50%; display: grid; place-items: center; font-weight: 800; font-size: 17px; color: var(--c); background: #fff; border: 2px solid var(--c); transition: transform .16s cubic-bezier(.2,0,0,1), box-shadow .16s ease, background .16s ease, color .16s ease; }
.ava:hover:not(.blocked) .ava-circle { transform: translateY(-3px); box-shadow: 0 10px 20px -8px var(--c); }
.ava:active:not(.blocked) .ava-circle { transform: translateY(-1px); }
.ava.on .ava-circle { background: var(--c); color: #fff; }
.ava-check { position: absolute; right: -3px; bottom: -3px; width: 18px; height: 18px; border-radius: 50%; background: var(--c); border: 2px solid #fff; display: grid; place-items: center; }
.ava-name { font-weight: 700; font-size: 12.5px; color: var(--muted); transition: color .16s ease; }
.ava.on .ava-name { color: var(--ink); }
.ava.blocked { opacity: .32; cursor: not-allowed; }
.ava:focus-visible .ava-circle { outline: 2px solid var(--ink); outline-offset: 2px; }

/* ============ MODALS ============ */
.modal-overlay {
  position: fixed; inset: 0; z-index: 200; background: rgba(20,18,16,.55);
  overflow-y: auto; padding: 20px; display: flex; align-items: flex-start; justify-content: center;
}
.modal-shell { position: relative; width: 100%; max-width: 760px; margin: auto; }
.modal-shell.narrow { max-width: 520px; }
.modal-close {
  position: absolute; top: 10px; right: 10px; z-index: 3; width: 40px; height: 40px;
  display: grid; place-items: center; background: var(--ink-2); color: #fff; border: none; border-radius: 50%; cursor: pointer;
}
.modal-card {
  background: var(--bg); border-radius: 24px; padding: clamp(20px, 4vw, 40px);
  animation: lbw-rise .35s cubic-bezier(.2,0,0,1) both; box-shadow: 0 40px 90px -30px rgba(30,20,12,.6);
}
.modal-card h2 { letter-spacing: -.02em; font-size: clamp(22px, 4vw, 34px); margin: 0 0 4px; }
.modal-sub { margin: 0 0 20px; color: var(--muted); font-size: 14px; }

/* stepper */
.stepper { display: flex; align-items: center; gap: 6px; margin-bottom: 24px; }
.step-dot { flex: 0 0 auto; width: 30px; height: 30px; border-radius: 50%; display: grid; place-items: center; font-weight: 800; font-size: 14px; background: var(--line); color: var(--muted-2); transition: all .2s ease; }
/* the step you are ON — ink circle showing its number */
.step-dot.active { background: var(--ink); color: #fff; }
/* steps you have FINISHED — amber circle with a tick, so progress is readable at a
   glance instead of every reached step looking identical */
.step-dot.done { background: var(--accent); color: #fff; }
.step-dot.done svg { display: block; }
.step-label { flex: 1; min-width: 8px; font-weight: 800; font-size: 12px; color: var(--muted-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.step-label.active { color: var(--ink-2); }
.step-bar { flex: 1 1 auto; height: 3px; border-radius: 999px; min-width: 10px; background: var(--line); transition: all .2s ease; }
/* the connector matches the ticks it joins */
.step-bar.done { background: var(--accent); }
.step-bar.last { flex: 0 0 0; min-width: 0; }

.notice { display: flex; gap: 12px; align-items: flex-start; background: var(--cream); border-radius: 14px; padding: 14px 16px; margin-bottom: 24px; }
.notice .txt { font-size: 14px; line-height: 1.5; color: #7a4d00; font-weight: 600; }

/* Exactly two fields per line, never three. auto-fit used to pack as many 180px columns
   as fitted, which on the wide inquiry modal produced 3-across rows that read as clutter. */
.form-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }
@media (max-width: 560px) { .form-grid { grid-template-columns: 1fr; } }
.form-block { margin-bottom: 20px; }
.form-input {
  width: 100%; padding: 13px 15px; border: none; border-radius: 12px; font-size: 15px;
  background: #fff; outline: none;
}
.form-input.with-icon { padding-left: 42px; }
.input-wrap { position: relative; }
.input-wrap svg.lead { position: absolute; left: 14px; top: 50%; transform: translateY(-50%); pointer-events: none; }
textarea.form-input { resize: vertical; line-height: 1.5; }

/* WhatsApp field: country-code <select> welded to the number input so the pair reads as
   one control. The select is sized to its content (max-width caps the long names some
   browsers show when collapsed) and the input takes the rest — min-width:0 is required,
   or the input's intrinsic width wins and pushes the row wider than its column. */
.phone-wrap {
  display: flex; align-items: stretch; background: #fff; border-radius: 12px; overflow: hidden;
}
.phone-dial {
  flex: 0 0 auto; max-width: 118px; border: none; outline: none; background: transparent;
  padding: 13px 6px 13px 13px; font-size: 15px; font-family: inherit; color: var(--ink);
  cursor: pointer; -webkit-appearance: none; appearance: none;
  /* room for the chevron drawn as a background image (native arrow is hidden above) */
  padding-right: 24px;
  background-image: url("data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20width='12'%20height='12'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%236A7078'%20stroke-width='3'%20stroke-linecap='round'%20stroke-linejoin='round'><polyline%20points='6%209%2012%2015%2018%209'/></svg>");
  background-repeat: no-repeat; background-position: right 8px center;
}
.phone-dial:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 12px 0 0 12px; }
.phone-num {
  flex: 1 1 auto; min-width: 0; border: none; outline: none; background: transparent;
  padding: 13px 15px 13px 6px; font-size: 15px; font-family: inherit;
}
/* hairline between the two halves */
.phone-wrap .phone-num { box-shadow: inset 1px 0 0 rgba(20,22,28,.10); }
.field-hint { font-size: 12px; color: var(--muted-2); margin-top: 7px; line-height: 1.45; }
/* iOS Safari sizes a date input from its CONTENT, so an empty one collapses to a
   narrow pill instead of filling the row, and its value renders centred. Resetting
   the native appearance and pinning the box makes it match the text fields exactly
   (13 + 20 + 13 = 46px). min-width:0 stops the intrinsic min-content width winning
   inside the grid; min-height guards against iOS collapsing the box once appearance
   is reset. */
input[type="date"].form-input {
  cursor: pointer;
  -webkit-appearance: none; appearance: none;
  display: block; width: 100%; min-width: 0;
  /* explicit height, not min-height: the inner ::-webkit-datetime-edit renders 2px
     taller than a text field's content box, which shows up in the inquiry modal
     where this field sits in a row beside text inputs */
  line-height: 20px; height: 46px;
  font-family: inherit; font-size: 15px; color: var(--text); text-align: left;
}
/* On iOS the value lives in this pseudo-element, which shrinks to its own content and
   is then centred in the box — text-align on the input alone does nothing. It has to
   be stretched to full width, and it takes the iOS accent colour unless told otherwise. */
input[type="date"].form-input::-webkit-date-and-time-value {
  text-align: left; margin: 0; width: 100%; color: var(--text);
}
input[type="date"].form-input::-webkit-datetime-edit {
  padding: 0; text-align: left; width: 100%; color: var(--text);
}
input[type="date"].form-input::-webkit-calendar-picker-indicator { cursor: pointer; }
/* ---------- Preferred Date — a two-line button (booking form only) ----------
   iOS renders an empty date input completely blank, so instead of dressing up the
   native control we show our own title + hint and lay the real input over the whole
   card at opacity 0. Tapping anywhere opens the native picker, which works on older
   iOS Safari where showPicker() does not exist. The inquiry modal keeps the plain
   .form-input date field — it shares a grid row with text inputs. */
.date-picker {
  position: relative; display: grid; grid-template-columns: auto 1fr auto;
  align-items: center; column-gap: 14px;
  background: #fff; border: 1px solid var(--hairline); border-radius: 16px;
  padding: 15px 18px; cursor: pointer;
  transition: border-color .2s ease, box-shadow .2s ease;
}
.date-picker:hover { border-color: var(--accent-2); box-shadow: 0 10px 24px -18px rgba(20,22,28,.5); }
.date-picker-ico { flex-shrink: 0; }
.date-picker-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.date-picker-title {
  font-family: var(--sans); font-weight: 800; font-size: 17px; letter-spacing: -.01em;
  color: var(--ink); line-height: 1.2;
}
.date-picker-sub { font-size: 13px; color: var(--muted-2); line-height: 1.35; }
.date-picker-chev { color: var(--muted-2); flex-shrink: 0; }
/* the real control — invisible, but it is what the tap actually lands on */
.date-picker input[type="date"] {
  position: absolute; inset: 0; width: 100%; height: 100%; margin: 0;
  opacity: 0; border: none; background: none; cursor: pointer;
  -webkit-appearance: none; appearance: none;
}

.choice-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.choice-grid-auto { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 14px; }
.choice-card {
  display: flex; align-items: center; gap: 12px; padding: 15px 16px; border-radius: 14px;
  border: 2px solid var(--line); background: #fff; color: var(--ink-2); cursor: pointer; text-align: left;
  width: 100%; font: inherit; transition: all .18s cubic-bezier(.2,0,0,1);
}
.choice-card .col { display: flex; flex-direction: column; line-height: 1.25; }
.choice-card .t { font-weight: 800; font-size: 15px; }
.choice-card .d { font-size: 13px; opacity: .85; }
.choice-card.active { border-color: var(--ink); background: var(--ink); color: #fff; }
.choice-card.disabled { opacity: .45; cursor: not-allowed; pointer-events: none; }

.pkg-picker { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 14px; }
.wpkg-card { border: 2px solid transparent; background: #fff; border-radius: 14px; overflow: hidden; display: flex; flex-direction: column; text-align: left; }
.wpkg-card.active { border-color: var(--ink); }
.wpkg-card.disabled { opacity: .5; pointer-events: none; }
.wpkg-body { padding: 13px 15px; display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.wpkg-name { display: flex; flex-direction: column; line-height: 1.25; }
.wpkg-name .t { font-weight: 800; font-size: 15px; }
.wpkg-name .s { font-size: 13px; color: var(--muted-2); }
.qty { display: flex; align-items: center; gap: 2px; background: var(--cream); flex-shrink: 0; border-radius: 8px; }
.qty button { width: 34px; height: 34px; border: none; background: transparent; color: var(--text); cursor: pointer; display: grid; place-items: center; font: inherit; }
.qty .n { min-width: 24px; text-align: center; font-weight: 900; font-size: 16px; }
.total-bar { margin-top: 14px; display: flex; align-items: center; justify-content: space-between; gap: 12px; background: var(--ink-2); color: #fff; padding: 13px 18px; border-radius: 4px; }
.total-bar .lbl { font-weight: 800; font-size: 14px; }
.total-bar .val { font-weight: 900; font-size: 18px; color: var(--gold); }

.summary { border-radius: 14px; overflow: hidden; margin-bottom: 22px; }
.summary-head { background: var(--ink-2); color: #fff; font-weight: 800; font-size: 14px; padding: 13px 18px; }
.summary-body { padding: 16px 18px; display: flex; flex-direction: column; gap: 10px; }
.summary-row { display: flex; justify-content: space-between; gap: 12px; font-size: 14px; }
.summary-row .k { color: var(--muted); }
.summary-row .v { font-weight: 700; text-align: right; }
.summary-rule { height: 1px; background: var(--line); margin: 4px 0; }
.summary-total { display: flex; justify-content: space-between; gap: 12px; align-items: baseline; }
.summary-total .k { font-weight: 800; font-size: 15px; }
.summary-total .v { font-weight: 900; font-size: 22px; color: var(--muted); }

.pay-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin-bottom: 16px; }
.pay-card {
  display: flex; align-items: center; gap: 11px; padding: 14px 15px; border-radius: 14px;
  border: 2px solid var(--line); background: #fff; color: var(--ink-2); cursor: pointer; text-align: left;
  width: 100%; font: inherit; transition: all .18s cubic-bezier(.2,0,0,1);
}
.pay-card.active { border-color: var(--ink); background: var(--ink); color: #fff; }
.pay-card .emoji { font-size: 22px; line-height: 1; }
.pay-card .col { display: flex; flex-direction: column; line-height: 1.2; }
.pay-card .n { font-weight: 800; font-size: 14px; }
.pay-card .d { font-size: 12px; opacity: .8; }
.pay-detail { background: var(--cream); border-radius: 12px; padding: 15px 17px; margin-bottom: 22px; font-size: 14px; line-height: 1.55; color: var(--text); white-space: pre-line; }

.review-rows { padding: 6px 18px; }
.review-line { display: flex; justify-content: space-between; gap: 14px; padding: 11px 0; border-bottom: 1px solid var(--hairline); font-size: 14px; }
.review-line .k { color: var(--muted); flex: 0 0 auto; }
.review-line .v { font-weight: 700; text-align: right; }
.review-total { display: flex; justify-content: space-between; gap: 14px; align-items: baseline; padding: 14px 0 12px; }
.review-total .k { font-weight: 800; font-size: 15px; }
.review-total .v { font-weight: 900; font-size: 22px; color: var(--muted); }
.review-hint { margin: 0 0 18px; font-size: 13px; color: var(--muted-2); line-height: 1.45; }

.success { text-align: center; padding: 32px 24px; background: #fff; border-radius: 20px; box-shadow: 0 20px 48px -22px rgba(20,22,28,.34); }
.success .badge { width: 76px; height: 76px; border-radius: 50%; background: #DCFCE7; display: grid; place-items: center; margin: 0 auto 18px; }
.success h3 { font-weight: 900; font-size: 27px; margin: 0 0 8px; font-family: var(--sans); }
.success p { margin: 0 auto 22px; max-width: 42ch; font-size: 15px; color: var(--muted); line-height: 1.55; }
.success .ref { display: flex; flex-direction: column; align-items: center; gap: 16px; background: var(--bg); border-radius: 20px; padding: 26px 22px; max-width: 340px; margin: 0 auto 24px; }
.success .ref .lbl { font-size: 11px; font-weight: 800; letter-spacing: .14em; text-transform: uppercase; color: var(--faint); }
.success .ref .code { font-weight: 900; font-size: clamp(34px, 9vw, 46px); letter-spacing: .06em; color: var(--muted); line-height: 1; }
.success .ref .qr { background: #fff; border-radius: 16px; padding: 14px; box-shadow: 0 8px 22px -12px rgba(20,22,28,.3); }
.success .ref .qr img { display: block; width: 180px; height: 180px; border-radius: 6px; }
.success .ref .scan { font-size: 12px; color: var(--muted-2); font-weight: 600; }
.success .done {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px; background: var(--grad);
  color: #fff; font-weight: 600; font-size: 16px; padding: 15px 34px; border: none; border-radius: 999px; cursor: pointer;
}

.pay-error { display: flex; align-items: flex-start; gap: 10px; background: #FDECEC; color: #B42318; font-weight: 700; font-size: 14px; line-height: 1.5; padding: 13px 15px; margin-bottom: 14px; border-radius: 12px; }
.confirm-btn {
  width: 100%; display: flex; align-items: center; justify-content: center; gap: 12px; background: var(--ink);
  color: #fff; font-weight: 900; font-size: clamp(17px, 2.4vw, 21px); padding: 19px; border: none; border-radius: 15px; cursor: pointer;
}
.pay-foot { text-align: center; margin: 12px 0 0; font-size: 12.5px; color: var(--muted-2); line-height: 1.4; }

.modal-nav { display: flex; gap: 12px; margin-top: 26px; }
.nav-back {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: 8px; background: #fff; color: var(--ink-2);
  font-weight: 800; font-size: 15px; padding: 15px 22px; border: none; border-radius: 14px; cursor: pointer;
}
.nav-back:hover { background: var(--hover); }
.nav-next {
  flex: 1 1 auto; display: inline-flex; align-items: center; justify-content: center; gap: 10px; background: var(--ink);
  color: #fff; font-weight: 900; font-size: 16px; padding: 15px 22px; border: none; border-radius: 14px; cursor: pointer;
}
.nav-next:disabled { background: var(--line); }

/* photos modal */
.photo-title { display: flex; align-items: center; gap: 12px; margin-bottom: 6px; }
.photo-title .ico { width: 44px; height: 44px; border-radius: 12px; background: var(--ink); display: grid; place-items: center; flex-shrink: 0; }
.photo-title h2 { font-weight: 900; letter-spacing: -.02em; font-size: clamp(20px, 4vw, 26px); margin: 0; font-family: var(--sans); }
.photo-title .sub { font-size: 13px; color: var(--muted); font-weight: 600; }
.photo-info { background: #fff; border-radius: 16px; padding: 15px 17px; margin: 18px 0; }
.photo-info .r { display: flex; align-items: center; gap: 10px; font-size: 15px; }
.photo-info .r + .r { margin-top: 9px; font-size: 14px; color: var(--muted); }
.photo-info .name { font-weight: 900; }
.photo-info .ses { margin-left: auto; font-size: 12px; font-weight: 800; color: var(--muted); background: var(--cream); padding: 3px 10px; border-radius: 999px; }
.folder-name { display: flex; align-items: center; gap: 9px; background: #fff; border-radius: 12px; padding: 11px 14px; box-shadow: inset 0 0 0 2px var(--line); }
.folder-name span { font-size: 14px; font-weight: 700; color: var(--text); word-break: break-word; }
.create-btn {
  width: 100%; display: inline-flex; align-items: center; justify-content: center; gap: 8px; margin-top: 10px;
  background: var(--ink-2); color: #fff; font-weight: 800; font-size: 15px; padding: 13px 20px; border: none; border-radius: 999px; cursor: pointer;
}
.photo-url {
  width: 100%; padding: 13px 15px; border: none; border-radius: 12px; font-size: 15px; background: #fff;
  box-shadow: inset 0 0 0 2px var(--line); box-sizing: border-box;
}
.photo-help { font-size: 12px; color: var(--muted-2); margin-top: 8px; line-height: 1.5; }
.photo-ok { display: flex; align-items: center; gap: 10px; background: #DCFCE7; color: #166534; border-radius: 12px; padding: 12px 14px; font-size: 14px; font-weight: 800; margin-top: 14px; }
.photo-err { background: #FEE2E2; color: #991B1B; border-radius: 12px; padding: 11px 14px; font-size: 13px; font-weight: 600; margin-top: 14px; }
.photo-actions { display: flex; gap: 12px; margin-top: 20px; }
.photo-cancel { flex: 0 0 auto; background: #fff; color: var(--ink-2); font-weight: 800; font-size: 15px; padding: 13px 22px; border: 2px solid var(--line); border-radius: 999px; cursor: pointer; }
.photo-send { display: inline-flex; align-items: center; justify-content: center; gap: 8px; flex: 1 1 auto; background: var(--ink); color: #fff; font-weight: 800; font-size: 15px; padding: 13px 22px; border: none; border-radius: 999px; cursor: pointer; }
.photo-send:disabled { opacity: .5; }

/* ============ RESPONSIVE ============ */
@media (max-width: 860px) {
  .dash-cols { grid-template-columns: 1fr; }
}
/* ---------- responsive polish (tablet + mobile) ---------- */
@media (max-width: 900px) {
  /* the copy needs the full width once the panel narrows */
  .hero-sub { flex: 1 1 100%; }
  .hero-bottom { gap: 22px; }
}
@media (max-width: 560px) {
  .hero { padding: 10px 12px 0; }
  .hero-panel { border-radius: 20px; min-height: clamp(560px, 82vh, 760px); }
  .hero h1 { font-size: clamp(30px, 8.6vw, 42px); }
  .hero-divider { margin: 22px 0 18px; }
  /* the headline carries the message on a phone — the sub-copy only pushed the
     buttons further down the panel */
  .hero-sub { display: none; }
  .hero-cta { gap: 10px; width: 100%; }
  .hero-cta .btn { width: 100%; justify-content: center; } /* full-width tap targets */
  .rev-summary { width: 100%; justify-content: center; }
  /* no room for four links beside the logo — keep only Blog, which leaves the site */
  .topnav { margin-right: 8px; }
  .topnav-link:not(.topnav-blog) { display: none; }
  .topnav-blog { padding: 7px 12px; font-size: 13.5px; }
  .hero-rail { right: 10px; }
  .choice-grid-2 { grid-template-columns: 1fr; }
  .section { padding: 60px 18px; }
  .section-head h2 { font-size: clamp(30px, 8.6vw, 42px); }
  .plan-block { margin-top: 34px; }
  .plan-subhead { font-size: 22px; }
  .groups-card { padding: 26px 20px; }
  .cta-big { font-size: 19px; padding: 17px 30px; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .001ms !important; animation-iteration-count: 1 !important; scroll-behavior: auto !important; }
  .carousel-track { transition: none; }
}

/* ================================================================================
   IDEAFEST LAYER — the restyle proper (2026-08-11)
   Appended last on purpose so it overrides the v2 rules above without deleting
   them; removing this block returns the site to amber editorial.
   ================================================================================ */

/* ---- 1. Sharp corners everywhere. IdeaFest has no rounded anything except the
        occasional pill, so radius is zeroed globally and re-granted by hand. ---- */
*, *::before, *::after { border-radius: 0 !important; }
.dot, .wa-bubble, .lang-toggle, .lang-toggle * { border-radius: 999px !important; }

/* ---- 2. No shadows. Separation comes from hairlines and colour blocks. ---- */
*, *::before, *::after { box-shadow: none !important; text-shadow: none !important; }
/* the one exception: keep focus visible for keyboard users */
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 3px solid var(--if-pink) !important; outline-offset: 2px;
}

/* ---- 3. Typography: mono, uppercase, tracked. This is the loudest single change. ---- */
body, input, select, textarea, button { font-family: var(--sans); }
h1, h2, h3, h4,
.hero-title, .section-title, .kids-heading, .plan-subhead, .sec-head,
.modal-card h2, .cta-inner h2 {
  font-family: var(--sans) !important;
  font-weight: 700 !important;
  text-transform: uppercase;
  letter-spacing: -.01em;
  line-height: 1.05;
}
.section-eyebrow, .field-label, .day-phase, .pay-chip, .lg-text,
.session-name, .plan-subhead, .hero-eyebrow {
  text-transform: uppercase; letter-spacing: .12em;
}
p, li, .session-desc, .pkg-desc, .review-text { letter-spacing: .01em; }

/* ---- 4. Buttons: outlined rectangles, never filled gradients. ---- */
.btn, .pkg-book, .cta-big, .nav-next, .btn-primary, .book-btn, button.cta-big {
  background: transparent !important;
  color: var(--ink) !important;
  border: 2px solid var(--ink) !important;
  text-transform: uppercase; letter-spacing: .1em; font-weight: 700;
  transition: background .12s linear, color .12s linear;
}
/* the v2 sweep-fill ::before is meaningless without rounded fills — switch it off */
.btn::before, .pkg-book::before, .cta-big::before, .nav-next::before { display: none !important; }
/* on dark ground the resting outline has to invert or it disappears */
.groups-card .btn, .cta-card .btn, .cta-card .cta-big, .hero-panel .btn {
  color: #fff !important; border-color: #fff !important;
}

/* ---- 5. Flat colour blocks. Sections alternate instead of sharing one ground. ---- */
.plan-sec { background: var(--if-orange); }
.plan-sec .session-card, .plan-sec .pkg-card, .plan-sec .wpkg-card {
  background: #fff; border: 2px solid var(--ink);
}
.loc-section { background: var(--if-grey); }
.cta-band { background: var(--if-yellow); }
.cta-card { background: var(--ink); border: 2px solid var(--ink); }

/* ---- 6. Cards become bordered boxes, not floating panels. ---- */
.session-card, .pkg-card, .review-card, .faq-item, .groups-card, .loc-card, .modal-card {
  border: 2px solid var(--ink);
  background: #fff;
}
.groups-card { background: var(--ink); color: #fff; }
.review-card { border-width: 1px; }

/* ---- 7. Nav: sharp, mono, with a pink outlined CTA like IdeaFest's BUY TICKET. ---- */
.topbar { background: #fff; border-bottom: 2px solid var(--ink); }
.topnav-link { text-transform: uppercase; letter-spacing: .1em; font-weight: 700; font-size: 12px; }
.topnav-link.topnav-blog { color: var(--if-pink); }

/* ---- 8. The sticky black bottom bar — IdeaFest's most recognisable chrome. ---- */
.if-bottombar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
  background: var(--if-navy); color: #fff;
  display: flex; align-items: center; gap: 22px;
  padding: 0 18px; height: 44px; overflow-x: auto;
  font-size: 11px; letter-spacing: .16em; text-transform: uppercase; font-weight: 700;
  -webkit-overflow-scrolling: touch; scrollbar-width: none;
}
.if-bottombar::-webkit-scrollbar { display: none; }
.if-bottombar a { color: #fff; text-decoration: none; white-space: nowrap; }
.if-bottombar a:hover { color: var(--if-orange); }
.if-bottombar .if-sep { color: #55585F; }
.if-bottombar .if-right { margin-left: auto; color: var(--if-orange); white-space: nowrap; }
/* the floating WhatsApp bubble has to clear the new bar */
.wa-bubble { bottom: 60px !important; }

/* ---- 9. Hairline rules replace soft dividers. ---- */
.faq-item { border-bottom: 1px solid var(--ink); }
.booking-row, .day-list > * { border-color: var(--ink); }

@media (max-width: 560px) {
  .if-bottombar { gap: 16px; font-size: 10px; }
}

/* ---- 10. Contrast repair inside the flat colour blocks.
        The v2 palette leans on grey muted text, which is legible on a near-white
        ground but nearly vanishes on IdeaFest orange. Inside coloured blocks the
        secondary text becomes ink, not grey. Checked visually on the orange section. */
.plan-sec { color: var(--ink); }
.plan-sec .section-eyebrow,
.plan-sec .section-sub,
.plan-sec .sessions-foot,
.plan-sec .pkg-includes, .plan-sec p {
  color: var(--ink) !important; opacity: .82;
}
.plan-sec .plan-num {
  background: var(--ink) !important; color: var(--if-orange) !important;
  border: none !important;
}
/* On the grey kids block the same problem is milder but real. */
.loc-section .section-eyebrow { color: var(--ink) !important; opacity: .7; }

/* ---- 11. The yellow CTA band needs ink text, not white. ---- */
.cta-band .section-eyebrow { color: var(--ink) !important; }

/* ---- 12. …but the groups card is a DARK panel sitting inside that orange section,
        so rule 10 above turned its body copy ink-on-navy and made it disappear.
        Anything inside .groups-card keeps light text. */
.plan-sec .groups-card p,
.plan-sec .groups-card .groups-desc,
.plan-sec .groups-card .section-eyebrow {
  color: rgba(255,255,255,.82) !important; opacity: 1;
}
.plan-sec .groups-card .section-eyebrow { color: var(--if-orange) !important; }

/* ---- 13. Buttons the rule-4 selector missed (they use their own class names). ---- */
.loc-directions, .wpkg-card .qty button, .size-pill, .choice-card, .pay-card, .inq-send {
  border-radius: 0 !important;
}
.loc-directions {
  background: transparent !important; color: var(--ink) !important;
  border: 2px solid var(--ink) !important; text-transform: uppercase; letter-spacing: .1em; font-weight: 700;
}
/* Selected states inside the booking wizard: flat ink fill, no gradient. */
.choice-card.active, .pay-card.active, .size-pill.active {
  background: var(--ink) !important; color: #fff !important; border-color: var(--ink) !important;
}

/* ---- 14. Chubbo Bold for headlines (2026-08-14, Agung's request).
        Overrides rule 3, which had put mono on the headings too. Mono stays everywhere
        else — labels, body, chrome — so the contrast between display and UI is the point.
        Chubbo's letterforms are wide and heavy, so the tracking rule-3 added is dropped
        and the uppercase transform is kept only where it was already a label. */
h1, h2, h3,
.hero-title, .section-title, .kids-heading, .plan-subhead,
.modal-card h2, .cta-inner h2, .groups-card h3, .day-title {
  font-family: var(--display) !important;
  font-weight: 700 !important;
  letter-spacing: -.02em !important;
  line-height: 1.02;
}
/* Chubbo is a display face: at headline sizes it wants a touch more room than mono did. */
.hero-title, .section-title, .cta-inner h2 { line-height: 1.04; }

/* ---- 15. ONE rollover for every button: pink fill, BLACK text (2026-08-14).
        Previously the hover was defined in three places with two different text colours
        (white on light grounds, ink on dark) and they had already drifted. This single
        rule governs all of them, including the buttons that carry white outlines on the
        dark panels — on hover those also go pink/black, so the behaviour is identical
        wherever the button sits. Keep new buttons in this selector list. */
.btn:hover, .btn:focus-visible,
.pkg-book:hover, .pkg-book:focus-visible,
.cta-big:hover, .cta-big:focus-visible,
.nav-next:hover, .nav-next:focus-visible,
.loc-directions:hover, .loc-directions:focus-visible,
.inq-send:hover, .inq-send:focus-visible,
.groups-card .btn:hover, .cta-card .btn:hover, .cta-card .cta-big:hover, .hero-panel .btn:hover,
.if-bottombar a:hover {
  background: var(--rollover) !important;
  color: #000 !important;
  border-color: var(--rollover) !important;
}
/* icons inside a button follow the text colour, or they stay white on pink */
.btn:hover svg, .pkg-book:hover svg, .cta-big:hover svg, .nav-next:hover svg,
.loc-directions:hover svg, .inq-send:hover svg,
.groups-card .btn:hover svg, .cta-card .cta-big:hover svg, .hero-panel .btn:hover svg {
  stroke: #000 !important; fill: none !important;
}
/* …except icons that are drawn as solid shapes rather than strokes (the WhatsApp mark) */
.btn:hover svg[fill]:not([fill="none"]), .groups-card .btn:hover svg[fill]:not([fill="none"]) {
  fill: #000 !important; stroke: none !important;
}

/* ---- 16. Pink promoted from "hover only" to a working accent (2026-08-14).
        Orange is the ground; pink is what marks the small things ON it. Kept to
        genuinely small elements — numbers, bullets, times, badges, dots, chips — so it
        stays an accent and does not start competing with the orange blocks. */

/* The numbered step boxes: pink box, BLACK number (overrides rule 10). */
.plan-num {
  background: var(--if-pink) !important;
  color: #000 !important;
  border: none !important;
}

/* Kawung bullets on the kids list */
.benefit .kawung { color: var(--if-pink); }

/* Session times sit next to the session name — small, and the natural place for accent */
.session-time { color: var(--if-pink) !important; }

/* "MOST POPULAR" flag on the package card */
.pkg-badge { background: var(--if-pink) !important; color: #000 !important; }

/* Carousel dots: the active one is the small element that reads as "you are here" */
.dot.active { background: var(--if-pink) !important; }
.groups-media .dot.active { background: var(--if-pink) !important; }
.hero-rail .dot.active { background: var(--if-pink) !important; }

/* Eyebrow labels. This rule USED to list .reviews-sec / .faq-sec / .kids-section — three
   class names that appear nowhere in index.html, so it only ever coloured .loc-section and
   the hero. Correcting the names would have made pink apply for the first time, and pink
   #EC008C on white is 4.25:1 (fails AA) — so section eyebrows are handled by rule 51a
   instead and only the hero keeps pink here, where it sits on a dark photo. */
/* .hero-eyebrow deleted 2026-08-17 — the class never existed. See rule 57. */

/* Small meta chrome: the booking-code chip, field asterisks, list markers */
.code-btn, .field-label .req { color: var(--if-pink) !important; }
.loc-item .lbl, .loc-details .lbl { color: var(--if-pink) !important; }

/* Price figures — small, high-value, and currently muted grey */
.pkg-price .amount { color: var(--if-pink) !important; }

/* ---- 17. Hero 15% taller + rounded corners are back (2026-08-14).
        Rule 1 had zeroed radius globally to match IdeaFest; Agung asked to try rounded
        again, so the radius scale is reinstated here in one place. Rule 1 is left intact
        above — this simply re-grants radius, so flipping back to sharp means deleting
        rule 17's radius block, not hunting through the file. */
:root { --r-lg: 20px; --r-md: 14px; --r-sm: 10px; --r-xs: 8px; }

/* Hero: 680→782, 88→101vh, 960→1104  (each +15%) */
.hero-panel { min-height: clamp(782px, 101vh, 1104px) !important; }
@media (max-width: 900px) {
  /* mobile hero was 560/82vh/760 → +15% */
  .hero-panel { min-height: clamp(644px, 94vh, 874px) !important; }
}

/* Re-grant radius. Larger surfaces get the big radius, controls the small one.
   NOTE the real class names: the package card is .lbw-pkg-card (not .pkg-card) and its
   image carousel is .scrub / .scrub-frame. Guessed names silently do nothing — these were
   read out of the live DOM. */
.hero-panel, .groups-card, .cta-card, .session-card, .lbw-pkg-card, .review-card,
.faq-item, .modal-card, .loc-card, .groups-media, .slot-photo,
.map-box, .map-ring, .booking-row, .cal-card, .login-card {
  border-radius: var(--r-lg) !important;
}
.btn, .pkg-book, .cta-big, .nav-next, .loc-directions, .inq-send,
.choice-card, .pay-card, .wpkg-card, .form-input, .phone-wrap, .day-phase,
.pkg-badge, .plan-num, .code-btn, .menu-pop, .cal-cell, .size-pill, .sess-pill {
  border-radius: var(--r-md) !important;
}
.cal-count, .pay-chip, .lg-count, .lg-swatch, .lg-today { border-radius: var(--r-sm) !important; }
.if-bottombar { border-radius: 0 !important; }                            /* the bar is edge-to-edge chrome */

/* ---- 18. Round the things rule 17 missed (2026-08-14).
        Rule 17 had forced `img { border-radius: 0 }` on the theory that photos should stay
        square inside rounded frames — wrong here: a square photo inside a 20px card leaves
        visible hard corners at the top of every package card. Photos now inherit the card
        radius, and the remaining square chrome (Google pill, map, review avatars) is
        rounded too. */
/* The package card's photo strip sits flush at the top, so it rounds only its TOP
   corners — the card's own edge stays crisp where the two meet. .scrub is the strip,
   .scrub-frame each slide inside it. */
.lbw-pkg-card .scrub { border-radius: var(--r-lg) var(--r-lg) 0 0 !important; overflow: hidden; }
.lbw-pkg-card .scrub-frame, .lbw-pkg-card .scrub-frame img { border-radius: 0 !important; }
/* Standalone photos (hero slides, kids/groups carousels) are clipped by their frame. */
.groups-media { overflow: hidden; }
.slot-photo { border-radius: inherit !important; }

/* Google rating pill in the hero */
.rev-summary { border-radius: 999px !important; }
/* The map: round the box AND the iframe, or the iframe's square corners poke through */
.map-box, .map-box iframe { border-radius: var(--r-lg) !important; overflow: hidden; }
/* Review avatars back to full circles */
.review-avatar { border-radius: 50% !important; }
/* Carousel arrows and dots read better as circles at this size */
.carousel-arrow, .car-arrow, .groups-media .arrow { border-radius: 999px !important; }

/* ---- 19. Syne Mono headline corrections + idle icon colour (2026-08-14) ---- */

/* Syne Mono has NO bold. Rules 3/14 forced 700, which makes the browser synthesise a
   smeared faux-bold. Weight goes back to 400 and the headline holds its own on size. */
h1, h2, h3,
.hero-title, .section-title, .kids-heading, .plan-subhead,
.modal-card h2, .cta-inner h2, .groups-card h3, .day-title {
  font-family: var(--display) !important;
  font-weight: 400 !important;
  letter-spacing: -.005em !important;
}

/* Idle icons match their label. Several buttons hardcode stroke="#fff" in the markup —
   fine when the button was a filled orange pill, wrong now that idle buttons are outlined
   with ink text, which left invisible white arrows. currentColor makes the icon follow
   whatever the text is doing, idle or hover, without touching the HTML. */
.btn svg, .pkg-book svg, .cta-big svg, .nav-next svg, .loc-directions svg, .inq-send svg,
.groups-btn svg, .groups-btn-alt svg {
  stroke: currentColor !important;
}
/* Solid-shape marks (the WhatsApp glyph) are filled, not stroked — leave those alone. */
.btn svg[fill]:not([fill="none"]), .groups-btn svg[fill]:not([fill="none"]) {
  stroke: none !important; fill: currentColor !important;
}

/* ---- 20. Wavy section separators (2026-08-14).
        Two offset strokes — pink and chartreuse — tracing the same curve, the way
        ideafest.id divides its blocks. Transparent background on purpose: the strip sits
        between sections of different colours, so a filled wave would need a variant per
        boundary. preserveAspectRatio="none" lets one path stretch to any width. */
.wave-sep {
  display: block; width: 100%; height: clamp(46px, 6vw, 84px);
  background-repeat: no-repeat; background-position: center; background-size: 100% 100%;
  /* it is decoration between blocks, never a gap that swallows clicks */
  pointer-events: none;
}
/* On a coloured block the strip must not paint a white band — it is already transparent,
   so it simply inherits whatever sits behind it. Nudge it to overlap the seam slightly. */
.wave-sep.on-orange { background-color: var(--if-orange); }
.wave-sep.on-grey   { background-color: var(--if-grey); }
.wave-sep.on-white  { background-color: #fff; }

/* ---- 21. Make the wave the EDGE of the colour block, not a line floating above it.
        The strip's background-color is the section ABOVE; the SVG fills everything below
        the curve with the section BELOW, then draws the two strokes on top. That is why
        the fill colour is baked per variant — a data-URI cannot read a CSS variable.
        Pair an `on-*` (above) with a `to-*` (below) on each separator. */
.wave-sep.on-yellow { background-color: var(--if-yellow); }
/* the separator is the seam itself, so kill the gap its neighbours would add */
.wave-sep + section, .wave-sep + .section { margin-top: 0; }

/* ---- 22. Caveat headlines + wave without the drawn lines (2026-08-14) ---- */

/* Caveat is handwriting. Two things that were right for mono are wrong for it:
   UPPERCASE (a script face loses its legibility and its charm in caps) and negative
   tracking (the letters already join). It also runs small for its point size, so the
   headline scale is nudged up. Real 700 exists, so bold is genuine here. */
h1, h2, h3,
.hero-title, .section-title, .kids-heading, .plan-subhead,
.modal-card h2, .cta-inner h2, .groups-card h3, .day-title {
  font-family: var(--display) !important;
  font-weight: 700 !important;
  text-transform: none !important;
  letter-spacing: 0 !important;
  line-height: 1.06;
}
h1, .hero-title { font-size: clamp(46px, 7vw, 88px); }
h2, .section-title, .cta-inner h2, .kids-heading { font-size: clamp(36px, 5.2vw, 62px); }

/* Wave separators keep the curved colour edge but drop the pink/chartreuse strokes —
   the shape does the dividing; the lines were decoration on top of it. Same paths as
   rule 21 minus the two <path stroke> elements. */
/* A white-on-white separator now draws nothing at all, so it just adds dead space. */
.wave-sep.on-white.to-white { display: none; }

/* ---- 23. Kill the leftover section rules that cut across the wave seams (2026-08-14).
        .loc-section carried a border-top from the v2 design, where a hairline was how
        sections were separated. Now the wave does that job, so the straight line just
        slices through the curve a few pixels below it. It only became obvious because
        --line was changed to ink for this restyle. */
.loc-section { border-top: 0 !important; }
/* Same reasoning for any other full-width straight rule sitting at a section boundary. */
.section + .section { border-top: 0 !important; }
.wave-sep + section, .wave-sep + .section { border-top: 0 !important; }

/* ---- 24. Separator corrections (2026-08-14). Two bugs, one cause: the pairings were
        written from ASSUMED section colours. Measuring the real ones showed #kids is
        WHITE, not grey — rule 5's `.kids-section` selector never matched anything, since
        the element is <section class="section" id="kids">. That made the plan→kids wave
        fill grey against a white section, and put a grey band between two white ones.

        Real order: hero(white) reviews(white) plan(ORANGE) kids(white) faq(white)
                    cta(YELLOW) location(GREY)                                        */

/* Where both sides are the same colour there is nothing to divide. */
.wave-sep.same-colour { display: none !important; }

/* The thin light line along the top of a coloured block was the seam between the section
   and the separator: two boxes meeting at a fractional pixel let the page ground show
   through. Pulling the strip up 1px makes them overlap instead of abut. */
.wave-sep {
  margin-top: -1px;
  margin-bottom: -1px;
  line-height: 0;       /* the div is empty, but an inline-box would still reserve height */
  font-size: 0;
}
/* Belt and braces: the block that FOLLOWS a wave starts flush, so no ground shows. */
.wave-sep + section, .wave-sep + .section { border-top: 0 !important; margin-top: 0 !important; }

/* ---- 25. "Visit the Studio" goes maroon; map returns to normal (2026-08-14) ---- */
:root { --if-maroon: #2B2A4A; }   /* deep indigo replaces maroon (2026-08-17) */

.loc-section { background: var(--if-maroon) !important; }

/* On maroon everything light-on-dark. The v2 rules set ink/grey text, which would be
   unreadable here, so each text role is restated rather than relying on inheritance. */
.loc-section, .loc-section p, .loc-section .loc-details, .loc-section .loc-item {
  color: rgba(255,255,255,.86) !important;
}
.loc-section h2, .loc-section .section-title { color: #fff !important; }
.loc-section .section-eyebrow { color: var(--if-pink) !important; opacity: 1; }
/* the small ADDRESS / OPENING HOURS / WHATSAPP captions */
.loc-section .lbl, .loc-section .loc-item .lbl { color: rgba(255,255,255,.55) !important; }
/* the values people actually read */
.loc-section .val, .loc-section strong, .loc-section b { color: #fff !important; }
/* icons beside each line */
.loc-section svg { stroke: rgba(255,255,255,.7) !important; }
/* outlined button has to invert on a dark ground. NOTE: this used to claim "hover stays
   pink/black via rule 15" — it did not. `background: transparent !important` below ties on
   specificity with `.loc-directions:hover` (both 0,2,0) and sits LATER in the file, so it won
   and this was the one button that never went pink. Rule 62 restores it. */
.loc-section .loc-directions {
  color: #fff !important; border-color: #fff !important; background: transparent !important;
}
.loc-section .loc-directions svg { stroke: currentColor !important; }

/* Map: plain again. The sepia+hue-rotate filter and the multiply tint were what gave it
   the orange cast; the ring was an orange inset shadow (already suppressed globally). */
.map-box iframe { filter: none !important; }
.map-box .map-tint { display: none !important; }
.map-box .map-ring { display: none !important; }
.map-box { border-color: rgba(255,255,255,.35) !important; }

/* The wave above the location section must now land on maroon, not grey. */

/* ---- 26. Real class names for the location lines (2026-08-14).
        Rule 25 guessed `.lbl` / `.val`; the DOM actually uses .loc-label / .loc-value,
        so the values stayed ink-on-maroon and were effectively invisible. Measured, not
        assumed — same mistake as the .pkg-card/.kids-section guesses earlier. */
.loc-section .loc-value, .loc-section .loc-value span { color: #fff !important; }
.loc-section .loc-label { color: rgba(255,255,255,.58) !important; }

/* ---- 27. Marker-pen accent words inside headlines (2026-08-14).
        The word is chosen in the TRANSLATION (*asterisks* in the TT string), not in the
        HTML, because applyI18n rewrites textContent on every language switch — markup in
        index.html would simply be erased. setI18nText() turns *word* into this span.
        Permanent Marker ships one weight (400) — do not request bold. */
.marker {
  font-family: 'Permanent Marker', 'Caveat', cursive;
  font-weight: 400;           /* the face has no bold; 700 would be a faux-bold smear */
  color: var(--if-pink);
  /* Permanent Marker sits smaller and higher than Caveat at the same size, so it needs a
     nudge to sit on the same baseline as the words either side of it. */
  font-size: 1.06em;
  line-height: 1;
  display: inline-block;
  transform: rotate(-1.5deg);  /* a hand-drawn word is never perfectly level */
}
/* On the dark CTA card and the maroon block the pink loses contrast — go yellow there. */
.cta-card .marker, .loc-section .marker, .groups-card .marker { color: var(--if-yellow); }

/* ---- 28. Emphasis face swapped to Caveat Brush (2026-08-14).
        Also one weight only (400). Being from the Caveat family it shares the headline
        face's metrics, so the size/baseline nudge rule 27 needed for Permanent Marker is
        no longer right — it sits correctly at 1em, and the rotation is eased off because
        a brush face already looks hand-drawn without help. */
.marker {
  font-family: 'Caveat Brush', 'Caveat', cursive;
  font-size: 1em;
  transform: rotate(-1deg);
}

/* ---- 29. Subtle batik pattern across the whole page (2026-08-14).
        This was removed at the start of the restyle (IdeaFest never patterns its ground)
        and is now wanted back. It cannot simply live on <body>: the orange, yellow and
        maroon blocks paint their own background-color and would hide it. So the tile is
        applied as a background-IMAGE alongside each block's background-colour — that way
        it sits behind all content naturally, with no stacking-order games.

        "Random" from a repeating tile is a trick: one large 320px tile carrying motifs at
        different sizes, rotations and offsets, so the eye does not lock onto the grid.
        Three classical motifs are mixed — truntum (four-petal star), kawung (ovals) and
        scattered dots. Two variants: dark strokes for light grounds, white for dark. */
:root {
  --batik-dark: url("data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20width='320'%20height='320'%20viewBox='0%200%20320%20320'><g%20fill='none'%20stroke='%230A0C1B'%20stroke-width='1.4'><path%20d='M52,20%20Q60,32%2072,40%20Q60,48%2052,60%20Q44,48%2032,40%20Q44,32%2052,20%20Z'/><circle%20cx='52'%20cy='40'%20r='2'%20fill='%230A0C1B'%20stroke='none'/><ellipse%20cx='196'%20cy='72'%20rx='9'%20ry='15'%20transform='rotate(24%20196%2072)'/><ellipse%20cx='214'%20cy='72'%20rx='9'%20ry='15'%20transform='rotate(-24%20214%2072)'/><circle%20cx='128'%20cy='128'%20r='3'%20fill='%230A0C1B'%20stroke='none'/><path%20d='M268,150%20Q276,162%20288,170%20Q276,178%20268,190%20Q260,178%20248,170%20Q260,162%20268,150%20Z'/><ellipse%20cx='70'%20cy='226'%20rx='13'%20ry='21'%20transform='rotate(-14%2070%20226)'/><circle%20cx='70'%20cy='226'%20r='2.4'%20fill='%230A0C1B'%20stroke='none'/><path%20d='M172,244%20Q178,253%20187,259%20Q178,265%20172,274%20Q166,265%20157,259%20Q166,253%20172,244%20Z'/><circle%20cx='286'%20cy='40'%20r='2.2'%20fill='%230A0C1B'%20stroke='none'/><circle%20cx='24'%20cy='140'%20r='2.2'%20fill='%230A0C1B'%20stroke='none'/><circle%20cx='240'%20cy='290'%20r='2.6'%20fill='%230A0C1B'%20stroke='none'/><circle%20cx='118'%20cy='60'%20r='1.8'%20fill='%230A0C1B'%20stroke='none'/><ellipse%20cx='300'%20cy='232'%20rx='8'%20ry='13'%20transform='rotate(38%20300%20232)'/></g></svg>");
  --batik-light: url("data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20width='320'%20height='320'%20viewBox='0%200%20320%20320'><g%20fill='none'%20stroke='%23FFFFFF'%20stroke-width='1.4'><path%20d='M52,20%20Q60,32%2072,40%20Q60,48%2052,60%20Q44,48%2032,40%20Q44,32%2052,20%20Z'/><circle%20cx='52'%20cy='40'%20r='2'%20fill='%23FFFFFF'%20stroke='none'/><ellipse%20cx='196'%20cy='72'%20rx='9'%20ry='15'%20transform='rotate(24%20196%2072)'/><ellipse%20cx='214'%20cy='72'%20rx='9'%20ry='15'%20transform='rotate(-24%20214%2072)'/><circle%20cx='128'%20cy='128'%20r='3'%20fill='%23FFFFFF'%20stroke='none'/><path%20d='M268,150%20Q276,162%20288,170%20Q276,178%20268,190%20Q260,178%20248,170%20Q260,162%20268,150%20Z'/><ellipse%20cx='70'%20cy='226'%20rx='13'%20ry='21'%20transform='rotate(-14%2070%20226)'/><circle%20cx='70'%20cy='226'%20r='2.4'%20fill='%23FFFFFF'%20stroke='none'/><path%20d='M172,244%20Q178,253%20187,259%20Q178,265%20172,274%20Q166,265%20157,259%20Q166,253%20172,244%20Z'/><circle%20cx='286'%20cy='40'%20r='2.2'%20fill='%23FFFFFF'%20stroke='none'/><circle%20cx='24'%20cy='140'%20r='2.2'%20fill='%23FFFFFF'%20stroke='none'/><circle%20cx='240'%20cy='290'%20r='2.6'%20fill='%23FFFFFF'%20stroke='none'/><circle%20cx='118'%20cy='60'%20r='1.8'%20fill='%23FFFFFF'%20stroke='none'/><ellipse%20cx='300'%20cy='232'%20rx='8'%20ry='13'%20transform='rotate(38%20300%20232)'/></g></svg>");
}

/* Light ground (white sections inherit this through the body) */
body {
  background-color: #fff;
  background-image: var(--batik-dark);
  background-size: 320px 320px;
  /* NOT background-attachment: fixed — it forces a full-page repaint per scroll frame in
     mobile Safari, and the pattern is far too faint to be worth that cost. */
  opacity: 1;
}
/* The tile is drawn at full strength, so each surface dials it back to a whisper by
   overlaying its own colour at high alpha is not possible on a background-image —
   instead the motif opacity is baked by using a very low stroke contrast per ground. */
body { background-blend-mode: normal; }

/* Coloured blocks: same tile, painted with their own colour underneath. */
.plan-sec  { background-color: var(--if-orange) !important; background-image: var(--batik-dark);  background-size: 320px 320px; }
.cta-band  { background-color: var(--if-yellow) !important; background-image: var(--batik-dark);  background-size: 320px 320px; }
.loc-section { background-color: var(--if-maroon) !important; background-image: var(--batik-light); background-size: 320px 320px; }
.site-footer { background-color: var(--ink) !important; background-image: var(--batik-light); background-size: 320px 320px; }

/* Hold it to a whisper. Without this the motifs read as decoration competing with the
   content; at these values it is texture you notice only when you look for it. */
body { background-blend-mode: overlay; }
.plan-sec, .cta-band { background-blend-mode: soft-light; }
.loc-section, .site-footer { background-blend-mode: soft-light; }

/* ---- 30. Fix: the pattern was invisible on white (2026-08-14).
        `background-blend-mode: overlay` preserves highlights, so dark strokes over a white
        base blended to nothing — the texture showed on orange/yellow but the white half of
        the page had none. Blend modes are dropped for the light ground in favour of a tile
        whose stroke is ALREADY faint (#E6E6E3), which is predictable on any pale colour.
        The coloured blocks keep soft-light, where it demonstrably works. */
:root {
  --batik-faint: url("data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20width='320'%20height='320'%20viewBox='0%200%20320%20320'><g%20fill='none'%20stroke='%23E6E6E3'%20stroke-width='1.6'><path%20d='M52,20%20Q60,32%2072,40%20Q60,48%2052,60%20Q44,48%2032,40%20Q44,32%2052,20%20Z'/><circle%20cx='52'%20cy='40'%20r='2'%20fill='%23E6E6E3'%20stroke='none'/><ellipse%20cx='196'%20cy='72'%20rx='9'%20ry='15'%20transform='rotate(24%20196%2072)'/><ellipse%20cx='214'%20cy='72'%20rx='9'%20ry='15'%20transform='rotate(-24%20214%2072)'/><circle%20cx='128'%20cy='128'%20r='3'%20fill='%23E6E6E3'%20stroke='none'/><path%20d='M268,150%20Q276,162%20288,170%20Q276,178%20268,190%20Q260,178%20248,170%20Q260,162%20268,150%20Z'/><ellipse%20cx='70'%20cy='226'%20rx='13'%20ry='21'%20transform='rotate(-14%2070%20226)'/><circle%20cx='70'%20cy='226'%20r='2.4'%20fill='%23E6E6E3'%20stroke='none'/><path%20d='M172,244%20Q178,253%20187,259%20Q178,265%20172,274%20Q166,265%20157,259%20Q166,253%20172,244%20Z'/><circle%20cx='286'%20cy='40'%20r='2.2'%20fill='%23E6E6E3'%20stroke='none'/><circle%20cx='24'%20cy='140'%20r='2.2'%20fill='%23E6E6E3'%20stroke='none'/><circle%20cx='240'%20cy='290'%20r='2.6'%20fill='%23E6E6E3'%20stroke='none'/><circle%20cx='118'%20cy='60'%20r='1.8'%20fill='%23E6E6E3'%20stroke='none'/><ellipse%20cx='300'%20cy='232'%20rx='8'%20ry='13'%20transform='rotate(38%20300%20232)'/></g></svg>");
}
body {
  background-image: var(--batik-faint) !important;
  background-blend-mode: normal !important;
}

/* ---- 31. Emphasis face -> Knewave (2026-08-14). One weight (400), like every display
        face tried here. Knewave is a heavy brush script whose caps run tall, so it needs
        slightly LESS size than Caveat Brush beside Caveat, and no rotation — the letters
        already lean. */
.marker {
  font-family: 'Knewave', 'Caveat Brush', cursive;
  font-weight: 400;
  font-size: .88em;
  transform: none;
  letter-spacing: .01em;
}

/* ---- 32. Kill the hairline of the ABOVE colour at the bottom of each wave (2026-08-14).
        Until now the strip's background-COLOUR was the section above and the SVG filled
        everything BELOW the curve with the next colour. At the strip's bottom edge that
        fill has to land exactly on the last pixel row; subpixel rounding left a 1px sliver
        of the above-colour showing — the orange line.

        Inverted: the strip's background-colour is now the section BELOW (so its bottom
        edge is by definition the same colour as what follows — a seam there is invisible),
        and the SVG paints the ABOVE colour as the region from the top down to the curve.
        Any sliver now lands at the TOP edge, where margin-top:-1px already tucks it under
        the previous section. */
.wave-sep.to-orange { background-color: var(--if-orange) !important; }
.wave-sep.to-white  { background-color: #fff !important; }
.wave-sep.to-yellow { background-color: var(--if-yellow) !important; }
.wave-sep.to-grey   { background-color: var(--if-grey) !important; }
.wave-sep.to-maroon { background-color: var(--if-maroon) !important; }

/* The SVG now draws the TOP region (0,0 → across → down to the curve). */
.wave-sep.on-white {
  background-image: url("data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%202293%20296'%20preserveAspectRatio='none'><path%20d='M2292%200C2292.55%202.14209e-05%202293%200.447728%202293%201V207C2293%20215.284%202286.28%20222%202278%20222H2059.5C2055.36%20222%202052%20225.358%202052%20229.5C2052%20233.642%202048.64%20237%202044.5%20237H1967C1958.72%20237%201952%20230.284%201952%20222V204C1952%20195.716%201945.28%20189%201937%20189H1759.5C1755.91%20189%201753%20191.91%201753%20195.5C1753%20199.09%201750.09%20202%201746.5%20202H1665C1656.72%20202%201650%20208.716%201650%20217V222C1650%20230.284%201643.28%20237%201635%20237H1623.5C1616.6%20237%201611%20242.596%201611%20249.5C1611%20256.404%201605.4%20262%201598.5%20262H1416.5C1409.6%20262%201404%20256.404%201404%20249.5C1404%20242.596%201398.4%20237%201391.5%20237H1187.5C1180.6%20237%201175%20242.596%201175%20249.5C1175%20256.404%201169.4%20262%201162.5%20262H603C594.716%20262%20588%20255.284%20588%20247V245C588%20236.716%20581.284%20230%20573%20230H551C543.268%20230%20537%20223.732%20537%20216C537%20208.268%20530.732%20202%20523%20202H316.5C312.91%20202%20310%20199.09%20310%20195.5C310%20191.91%20307.09%20189%20303.5%20189H201C193.82%20189%20188%20194.82%20188%20202C188%20209.18%20182.18%20215%20175%20215H90C81.7157%20215%2075%20221.716%2075%20230V232C75%20240.284%2068.2843%20247%2060%20247H15C6.71573%20247%200%20240.284%200%20232V1C0%200.447715%200.447715%200%201%200H2292Z'%20fill='%23FFFFFF'/><g%20transform='translate(0,176)%20scale(0.99436,1)'><path%20d='M2306%2060.5H2260.14C2251.7%2060.5%202244.86%2053.7843%202244.86%2045.5V43.5C2244.86%2035.2157%202238.02%2028.5%202229.57%2028.5H2142.96C2135.64%2028.5%202129.71%2022.6797%202129.71%2015.5C2129.71%208.3203%202123.78%202.5%202116.47%202.5H2012.02C2008.36%202.5%202005.39%205.41015%202005.39%209C2005.39%2012.5899%202002.43%2015.5%201998.77%2015.5H1635.5C1627.62%2015.5%201621.23%2021.768%201621.23%2029.5C1621.23%2037.232%201614.85%2043.5%201606.97%2043.5H1584.55C1576.11%2043.5%201569.26%2050.2157%201569.26%2058.5V60.5C1569.26%2068.7843%201562.42%2075.5%201553.98%2075.5H1530.54C1524.92%2075.5%201520.35%2079.9772%201520.35%2085.5C1520.35%2091.0228%201515.79%2095.5%201510.16%2095.5H1194.27C1188.64%2095.5%201184.08%2091.0228%201184.08%2085.5C1184.08%2079.9772%201179.52%2075.5%201173.89%2075.5H1136.7C1129.66%2075.5%201123.96%2069.9036%201123.96%2063C1123.96%2056.0964%201118.26%2050.5%201111.22%2050.5H903.345C896.31%2050.5%20890.607%2056.0964%20890.607%2063C890.607%2069.9036%20884.904%2075.5%20877.87%2075.5H692.411C685.377%2075.5%20679.674%2069.9036%20679.674%2063C679.674%2056.0964%20673.971%2050.5%20666.936%2050.5H655.218C646.776%2050.5%20639.933%2043.7843%20639.933%2035.5V30.5C639.933%2022.2157%20633.089%2015.5%20624.648%2015.5H607.325C598.883%2015.5%20592.04%2022.2157%20592.04%2030.5V60.5C592.04%2068.7843%20585.196%2075.5%20576.755%2075.5H300.096C293.061%2075.5%20287.358%2069.9036%20287.358%2063C287.358%2056.0964%20281.655%2050.5%20274.621%2050.5H237.937C233.716%2050.5%20230.294%2047.1421%20230.294%2043C230.294%2038.8579%20226.872%2035.5%20222.652%2035.5H0'%20stroke='%2326456E'%20stroke-width='2'%20fill='none'%20vector-effect='non-scaling-stroke'/></g></svg>") !important;
}
.wave-sep.on-orange {
  background-image: url("data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%202293%20296'%20preserveAspectRatio='none'><path%20d='M2292%200C2292.55%202.14209e-05%202293%200.447728%202293%201V207C2293%20215.284%202286.28%20222%202278%20222H2059.5C2055.36%20222%202052%20225.358%202052%20229.5C2052%20233.642%202048.64%20237%202044.5%20237H1967C1958.72%20237%201952%20230.284%201952%20222V204C1952%20195.716%201945.28%20189%201937%20189H1759.5C1755.91%20189%201753%20191.91%201753%20195.5C1753%20199.09%201750.09%20202%201746.5%20202H1665C1656.72%20202%201650%20208.716%201650%20217V222C1650%20230.284%201643.28%20237%201635%20237H1623.5C1616.6%20237%201611%20242.596%201611%20249.5C1611%20256.404%201605.4%20262%201598.5%20262H1416.5C1409.6%20262%201404%20256.404%201404%20249.5C1404%20242.596%201398.4%20237%201391.5%20237H1187.5C1180.6%20237%201175%20242.596%201175%20249.5C1175%20256.404%201169.4%20262%201162.5%20262H603C594.716%20262%20588%20255.284%20588%20247V245C588%20236.716%20581.284%20230%20573%20230H551C543.268%20230%20537%20223.732%20537%20216C537%20208.268%20530.732%20202%20523%20202H316.5C312.91%20202%20310%20199.09%20310%20195.5C310%20191.91%20307.09%20189%20303.5%20189H201C193.82%20189%20188%20194.82%20188%20202C188%20209.18%20182.18%20215%20175%20215H90C81.7157%20215%2075%20221.716%2075%20230V232C75%20240.284%2068.2843%20247%2060%20247H15C6.71573%20247%200%20240.284%200%20232V1C0%200.447715%200.447715%200%201%200H2292Z'%20fill='%23E8A33D'/><g%20transform='translate(0,176)%20scale(0.99436,1)'><path%20d='M2306%2060.5H2260.14C2251.7%2060.5%202244.86%2053.7843%202244.86%2045.5V43.5C2244.86%2035.2157%202238.02%2028.5%202229.57%2028.5H2142.96C2135.64%2028.5%202129.71%2022.6797%202129.71%2015.5C2129.71%208.3203%202123.78%202.5%202116.47%202.5H2012.02C2008.36%202.5%202005.39%205.41015%202005.39%209C2005.39%2012.5899%202002.43%2015.5%201998.77%2015.5H1635.5C1627.62%2015.5%201621.23%2021.768%201621.23%2029.5C1621.23%2037.232%201614.85%2043.5%201606.97%2043.5H1584.55C1576.11%2043.5%201569.26%2050.2157%201569.26%2058.5V60.5C1569.26%2068.7843%201562.42%2075.5%201553.98%2075.5H1530.54C1524.92%2075.5%201520.35%2079.9772%201520.35%2085.5C1520.35%2091.0228%201515.79%2095.5%201510.16%2095.5H1194.27C1188.64%2095.5%201184.08%2091.0228%201184.08%2085.5C1184.08%2079.9772%201179.52%2075.5%201173.89%2075.5H1136.7C1129.66%2075.5%201123.96%2069.9036%201123.96%2063C1123.96%2056.0964%201118.26%2050.5%201111.22%2050.5H903.345C896.31%2050.5%20890.607%2056.0964%20890.607%2063C890.607%2069.9036%20884.904%2075.5%20877.87%2075.5H692.411C685.377%2075.5%20679.674%2069.9036%20679.674%2063C679.674%2056.0964%20673.971%2050.5%20666.936%2050.5H655.218C646.776%2050.5%20639.933%2043.7843%20639.933%2035.5V30.5C639.933%2022.2157%20633.089%2015.5%20624.648%2015.5H607.325C598.883%2015.5%20592.04%2022.2157%20592.04%2030.5V60.5C592.04%2068.7843%20585.196%2075.5%20576.755%2075.5H300.096C293.061%2075.5%20287.358%2069.9036%20287.358%2063C287.358%2056.0964%20281.655%2050.5%20274.621%2050.5H237.937C233.716%2050.5%20230.294%2047.1421%20230.294%2043C230.294%2038.8579%20226.872%2035.5%20222.652%2035.5H0'%20stroke='%2326456E'%20stroke-width='2'%20fill='none'%20vector-effect='non-scaling-stroke'/></g></svg>") !important;
}
.wave-sep.on-yellow {
  background-image: url("data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%202293%20296'%20preserveAspectRatio='none'><path%20d='M2292%200C2292.55%202.14209e-05%202293%200.447728%202293%201V207C2293%20215.284%202286.28%20222%202278%20222H2059.5C2055.36%20222%202052%20225.358%202052%20229.5C2052%20233.642%202048.64%20237%202044.5%20237H1967C1958.72%20237%201952%20230.284%201952%20222V204C1952%20195.716%201945.28%20189%201937%20189H1759.5C1755.91%20189%201753%20191.91%201753%20195.5C1753%20199.09%201750.09%20202%201746.5%20202H1665C1656.72%20202%201650%20208.716%201650%20217V222C1650%20230.284%201643.28%20237%201635%20237H1623.5C1616.6%20237%201611%20242.596%201611%20249.5C1611%20256.404%201605.4%20262%201598.5%20262H1416.5C1409.6%20262%201404%20256.404%201404%20249.5C1404%20242.596%201398.4%20237%201391.5%20237H1187.5C1180.6%20237%201175%20242.596%201175%20249.5C1175%20256.404%201169.4%20262%201162.5%20262H603C594.716%20262%20588%20255.284%20588%20247V245C588%20236.716%20581.284%20230%20573%20230H551C543.268%20230%20537%20223.732%20537%20216C537%20208.268%20530.732%20202%20523%20202H316.5C312.91%20202%20310%20199.09%20310%20195.5C310%20191.91%20307.09%20189%20303.5%20189H201C193.82%20189%20188%20194.82%20188%20202C188%20209.18%20182.18%20215%20175%20215H90C81.7157%20215%2075%20221.716%2075%20230V232C75%20240.284%2068.2843%20247%2060%20247H15C6.71573%20247%200%20240.284%200%20232V1C0%200.447715%200.447715%200%201%200H2292Z'%20fill='%23F3EAD8'/><g%20transform='translate(0,176)%20scale(0.99436,1)'><path%20d='M2306%2060.5H2260.14C2251.7%2060.5%202244.86%2053.7843%202244.86%2045.5V43.5C2244.86%2035.2157%202238.02%2028.5%202229.57%2028.5H2142.96C2135.64%2028.5%202129.71%2022.6797%202129.71%2015.5C2129.71%208.3203%202123.78%202.5%202116.47%202.5H2012.02C2008.36%202.5%202005.39%205.41015%202005.39%209C2005.39%2012.5899%202002.43%2015.5%201998.77%2015.5H1635.5C1627.62%2015.5%201621.23%2021.768%201621.23%2029.5C1621.23%2037.232%201614.85%2043.5%201606.97%2043.5H1584.55C1576.11%2043.5%201569.26%2050.2157%201569.26%2058.5V60.5C1569.26%2068.7843%201562.42%2075.5%201553.98%2075.5H1530.54C1524.92%2075.5%201520.35%2079.9772%201520.35%2085.5C1520.35%2091.0228%201515.79%2095.5%201510.16%2095.5H1194.27C1188.64%2095.5%201184.08%2091.0228%201184.08%2085.5C1184.08%2079.9772%201179.52%2075.5%201173.89%2075.5H1136.7C1129.66%2075.5%201123.96%2069.9036%201123.96%2063C1123.96%2056.0964%201118.26%2050.5%201111.22%2050.5H903.345C896.31%2050.5%20890.607%2056.0964%20890.607%2063C890.607%2069.9036%20884.904%2075.5%20877.87%2075.5H692.411C685.377%2075.5%20679.674%2069.9036%20679.674%2063C679.674%2056.0964%20673.971%2050.5%20666.936%2050.5H655.218C646.776%2050.5%20639.933%2043.7843%20639.933%2035.5V30.5C639.933%2022.2157%20633.089%2015.5%20624.648%2015.5H607.325C598.883%2015.5%20592.04%2022.2157%20592.04%2030.5V60.5C592.04%2068.7843%20585.196%2075.5%20576.755%2075.5H300.096C293.061%2075.5%20287.358%2069.9036%20287.358%2063C287.358%2056.0964%20281.655%2050.5%20274.621%2050.5H237.937C233.716%2050.5%20230.294%2047.1421%20230.294%2043C230.294%2038.8579%20226.872%2035.5%20222.652%2035.5H0'%20stroke='%2326456E'%20stroke-width='2'%20fill='none'%20vector-effect='non-scaling-stroke'/></g></svg>") !important;
}
/* the strip carries flat colour only — the page texture must not tile inside it */
.wave-sep { background-repeat: no-repeat; background-size: 100% 100%; }

/* ---- 33. Kids + groups carousels round on ALL four corners (2026-08-14).
        Rule 18 gave them top-only radius on the assumption they sat flush at the top of a
        card, the way the package card's photo strip does. They don't — both are free-
        standing panels, so the square bottom just looked broken. */
.groups-media {
  border-radius: var(--r-lg) !important;
  overflow: hidden;
}
.groups-media img,
.groups-media .slot-photo,
.groups-media .carousel-slide {
  border-radius: inherit !important;
}

/* ---- 34. The kids carousel's real class is .carousel.kids-carousel — `.kids-media`
        (rule 18/33) never existed, so those rules did nothing. Targeting the shared
        .carousel container covers kids and any future one. */
.carousel, .kids-carousel, .groups-media {
  border-radius: var(--r-lg) !important;
  overflow: hidden;
}
.carousel img, .carousel .slot-photo, .carousel .carousel-slide { border-radius: inherit !important; }
/* the package card's strip stays top-only — it IS flush to the card's top edge */
.lbw-pkg-card .scrub { border-radius: var(--r-lg) var(--r-lg) 0 0 !important; }

/* ---- 35. Fainter pattern on the white ground (2026-08-14). #E6E6E3 read as visible
        texture rather than a whisper; lightened and thinned. The coloured blocks keep
        their own strength — they are unaffected by this. */
:root {
  --batik-faint: url("data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20width='320'%20height='320'%20viewBox='0%200%20320%20320'><g%20fill='none'%20stroke='%23F2F2F0'%20stroke-width='1.2'><path%20d='M52,20%20Q60,32%2072,40%20Q60,48%2052,60%20Q44,48%2032,40%20Q44,32%2052,20%20Z'/><circle%20cx='52'%20cy='40'%20r='1.7'%20fill='%23F2F2F0'%20stroke='none'/><ellipse%20cx='196'%20cy='72'%20rx='9'%20ry='15'%20transform='rotate(24%20196%2072)'/><ellipse%20cx='214'%20cy='72'%20rx='9'%20ry='15'%20transform='rotate(-24%20214%2072)'/><circle%20cx='128'%20cy='128'%20r='2.4'%20fill='%23F2F2F0'%20stroke='none'/><path%20d='M268,150%20Q276,162%20288,170%20Q276,178%20268,190%20Q260,178%20248,170%20Q260,162%20268,150%20Z'/><ellipse%20cx='70'%20cy='226'%20rx='13'%20ry='21'%20transform='rotate(-14%2070%20226)'/><circle%20cx='70'%20cy='226'%20r='2'%20fill='%23F2F2F0'%20stroke='none'/><path%20d='M172,244%20Q178,253%20187,259%20Q178,265%20172,274%20Q166,265%20157,259%20Q166,253%20172,244%20Z'/><circle%20cx='286'%20cy='40'%20r='1.8'%20fill='%23F2F2F0'%20stroke='none'/><circle%20cx='24'%20cy='140'%20r='1.8'%20fill='%23F2F2F0'%20stroke='none'/><circle%20cx='240'%20cy='290'%20r='2'%20fill='%23F2F2F0'%20stroke='none'/><circle%20cx='118'%20cy='60'%20r='1.5'%20fill='%23F2F2F0'%20stroke='none'/><ellipse%20cx='300'%20cy='232'%20rx='8'%20ry='13'%20transform='rotate(38%20300%20232)'/></g></svg>");
}

/* ---- 36. Knewave emphasis in the hero too (2026-08-14).
        The hero accent already carries the brand orange, so the marker there INHERITS the
        colour instead of forcing pink — otherwise two accent colours fight inside one
        headline. Only the typeface changes. */
.hero-content .marker, .hero-panel .marker, h1 .marker { color: inherit; }

/* ---- 37. The Google rating pill matches the button beside it (2026-08-14).
        Rule 18 made it a full 999px pill; sitting directly under a 14px-radius button that
        reads as two different systems. Same radius as the buttons now. */
.rev-summary { border-radius: var(--r-md) !important; }

/* ---- 38. White Lukman Batik logo in the footer (2026-08-14).
        `brightness(0) invert(1)` forces any artwork to pure white regardless of its own
        colours — no second white-only asset to keep in sync. The wordmark below it is
        kept but demoted to a caption, so the footer still says the name in text (search
        engines and screen readers read that, not the image). */
.foot-logo {
  display: block; width: 200px; max-width: 60vw; height: auto; margin-bottom: 12px;
  filter: brightness(0) invert(1);
}
.site-footer .brand {
  font-size: 13px !important; font-weight: 400 !important;
  letter-spacing: .14em; text-transform: uppercase;
  color: rgba(255,255,255,.55) !important;
}
@media (max-width: 560px) { .foot-logo { width: 168px; } }

/* ---- 39. The logo artwork already reads "Lukman. BATIK WORKSHOP", so the caption under
        it repeated the name twice. Hidden — the name is not lost: it lives in the img's
        alt text, which is what screen readers announce and what search engines index. */
.site-footer .foot-brand .brand { display: none !important; }
.foot-logo { margin-bottom: 10px; }

/* ---- 40. Emphasis colour follows its ground (2026-08-14).
        Default is now ORANGE, which is what the white sections (kids, FAQ, reviews) get.
        The exceptions exist because orange would disappear or clash:
          · inside the orange block  -> pink   (orange on orange is invisible)
          · on the dark CTA card / maroon / groups card -> yellow (pink dulls on those)
          · in the hero -> inherit, so it keeps the headline's own accent colour  */
.marker { color: var(--if-orange); }
.plan-sec .marker { color: var(--if-pink) !important; }
.cta-card .marker, .loc-section .marker, .groups-card .marker { color: var(--if-yellow) !important; }
.hero-content .marker, .hero-panel .marker, h1 .marker { color: inherit !important; }

/* ---- 41. Hover backgrounds were still square (2026-08-14).
        Rule 1 zeroed radius globally and rule 17 re-granted it only to a named list —
        anything painting a background ONLY on :hover was never in that list, so the
        hover fill appeared as a hard-cornered box against otherwise rounded chrome.
        Covers the nav links, the bottom bar, the language toggle and the card menus. */
.topnav-link, .if-bottombar a, .lang-btn, .menu-item, .faq-q, .dot, .cal-cell {
  border-radius: var(--r-sm) !important;
}
.topnav-link:hover, .if-bottombar a:hover, .lang-btn:hover, .lang-btn.active,
.menu-item:hover, .faq-q:hover {
  border-radius: var(--r-sm) !important;
}
/* the bottom-bar links need a little breathing room or the hover fill hugs the text */
.if-bottombar a { padding: 4px 8px; }
/* dots stay circular — they are dots, not chips */
.dot, .scrub-dot, .hero-rail .dot { border-radius: 999px !important; }

/* ---- 42. No rule under the top bar (2026-08-15). The 2px ink border rule 7 added was
        the IdeaFest convention, but with the hero panel sitting right beneath it the page
        gained two hard lines a few pixels apart. */
.topbar { border-bottom: 0 !important; }

/* ---- 43. Bottom bar: Book Now sits with the links, copyright takes the right corner
        (2026-08-15). The copyright moved OUT of the footer block entirely — it lived in
        .foot-bottom, which is now gone, so the line is not duplicated. */
.if-bottombar .if-book { color: var(--if-orange); font-weight: 700; }
.if-bottombar .if-book:hover { background: var(--rollover) !important; color: #000 !important; }
.if-bottombar .if-copy {
  margin-left: auto; color: rgba(255,255,255,.5); font-weight: 400;
  letter-spacing: .08em; text-transform: none; white-space: nowrap;
}
/* On a phone the bar scrolls horizontally; the copyright would force a long scroll for
   something nobody scrolls to read, so it drops off there. */
@media (max-width: 700px) { .if-bottombar .if-copy { display: none; } }

/* ---- 44. Footer halves aligned on their centres (2026-08-15).
        .foot-top was align-items: flex-start, so the two columns hung from a shared top
        edge — and because the left column is taller (logo + wordmark + location) than the
        right (three contact lines), the left read as sitting low and the right as riding
        high. Centring makes their mid-points line up instead of their tops. */
.foot-top { align-items: center !important; }
/* stacked on a phone, centring a column would look odd — go back to left-aligned there */
@media (max-width: 700px) { .foot-top { align-items: flex-start !important; } }

/* ---- 45. No horizontal rules in the footer (2026-08-15). Two of them: a border-top on
        .site-footer itself and the divider under .foot-top. With the maroon block ending
        on a wave directly above, both read as stray lines. */
.site-footer { border-top: 0 !important; }
.foot-top { border-bottom: 0 !important; padding-bottom: 0 !important; }

/* ---- 46. "Super subtle" pass on the batik pattern (2026-08-15).
        Strength is now set by stroke-opacity INSIDE each tile instead of by blend modes.
        soft-light/overlay behave differently against every base colour — which is exactly
        how the pattern ended up invisible on white and heavy on maroon. Explicit opacity
        gives the same whisper on all five grounds. All three tiles are generated from one
        geometry, so they cannot drift apart. */
:root {
  --batik-faint: url("data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20width='320'%20height='320'%20viewBox='0%200%20320%20320'><g%20fill='none'%20stroke='%230A0C1B'%20stroke-width='1.2'%20stroke-opacity='.045'><path%20d='M52,20%20Q60,32%2072,40%20Q60,48%2052,60%20Q44,48%2032,40%20Q44,32%2052,20%20Z'/><circle%20cx='52'%20cy='40'%20r='2'%20fill='%230A0C1B'%20stroke='none'%20fill-opacity='.045'/><ellipse%20cx='196'%20cy='72'%20rx='9'%20ry='15'%20transform='rotate(24%20196%2072)'/><ellipse%20cx='214'%20cy='72'%20rx='9'%20ry='15'%20transform='rotate(-24%20214%2072)'/><circle%20cx='128'%20cy='128'%20r='2.4'%20fill='%230A0C1B'%20stroke='none'%20fill-opacity='.045'/><path%20d='M268,150%20Q276,162%20288,170%20Q276,178%20268,190%20Q260,178%20248,170%20Q260,162%20268,150%20Z'/><ellipse%20cx='70'%20cy='226'%20rx='13'%20ry='21'%20transform='rotate(-14%2070%20226)'/><circle%20cx='70'%20cy='226'%20r='2'%20fill='%230A0C1B'%20stroke='none'%20fill-opacity='.045'/><path%20d='M172,244%20Q178,253%20187,259%20Q178,265%20172,274%20Q166,265%20157,259%20Q166,253%20172,244%20Z'/><circle%20cx='286'%20cy='40'%20r='1.8'%20fill='%230A0C1B'%20stroke='none'%20fill-opacity='.045'/><circle%20cx='24'%20cy='140'%20r='1.8'%20fill='%230A0C1B'%20stroke='none'%20fill-opacity='.045'/><circle%20cx='240'%20cy='290'%20r='2'%20fill='%230A0C1B'%20stroke='none'%20fill-opacity='.045'/><circle%20cx='118'%20cy='60'%20r='1.5'%20fill='%230A0C1B'%20stroke='none'%20fill-opacity='.045'/><ellipse%20cx='300'%20cy='232'%20rx='8'%20ry='13'%20transform='rotate(38%20300%20232)'/></g></svg>");   /* white / pale grounds */
  --batik-dark:  url("data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20width='320'%20height='320'%20viewBox='0%200%20320%20320'><g%20fill='none'%20stroke='%230A0C1B'%20stroke-width='1.2'%20stroke-opacity='.07'><path%20d='M52,20%20Q60,32%2072,40%20Q60,48%2052,60%20Q44,48%2032,40%20Q44,32%2052,20%20Z'/><circle%20cx='52'%20cy='40'%20r='2'%20fill='%230A0C1B'%20stroke='none'%20fill-opacity='.07'/><ellipse%20cx='196'%20cy='72'%20rx='9'%20ry='15'%20transform='rotate(24%20196%2072)'/><ellipse%20cx='214'%20cy='72'%20rx='9'%20ry='15'%20transform='rotate(-24%20214%2072)'/><circle%20cx='128'%20cy='128'%20r='2.4'%20fill='%230A0C1B'%20stroke='none'%20fill-opacity='.07'/><path%20d='M268,150%20Q276,162%20288,170%20Q276,178%20268,190%20Q260,178%20248,170%20Q260,162%20268,150%20Z'/><ellipse%20cx='70'%20cy='226'%20rx='13'%20ry='21'%20transform='rotate(-14%2070%20226)'/><circle%20cx='70'%20cy='226'%20r='2'%20fill='%230A0C1B'%20stroke='none'%20fill-opacity='.07'/><path%20d='M172,244%20Q178,253%20187,259%20Q178,265%20172,274%20Q166,265%20157,259%20Q166,253%20172,244%20Z'/><circle%20cx='286'%20cy='40'%20r='1.8'%20fill='%230A0C1B'%20stroke='none'%20fill-opacity='.07'/><circle%20cx='24'%20cy='140'%20r='1.8'%20fill='%230A0C1B'%20stroke='none'%20fill-opacity='.07'/><circle%20cx='240'%20cy='290'%20r='2'%20fill='%230A0C1B'%20stroke='none'%20fill-opacity='.07'/><circle%20cx='118'%20cy='60'%20r='1.5'%20fill='%230A0C1B'%20stroke='none'%20fill-opacity='.07'/><ellipse%20cx='300'%20cy='232'%20rx='8'%20ry='13'%20transform='rotate(38%20300%20232)'/></g></svg>");   /* orange + yellow blocks */
  --batik-light: url("data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20width='320'%20height='320'%20viewBox='0%200%20320%20320'><g%20fill='none'%20stroke='%23FFFFFF'%20stroke-width='1.2'%20stroke-opacity='.05'><path%20d='M52,20%20Q60,32%2072,40%20Q60,48%2052,60%20Q44,48%2032,40%20Q44,32%2052,20%20Z'/><circle%20cx='52'%20cy='40'%20r='2'%20fill='%23FFFFFF'%20stroke='none'%20fill-opacity='.05'/><ellipse%20cx='196'%20cy='72'%20rx='9'%20ry='15'%20transform='rotate(24%20196%2072)'/><ellipse%20cx='214'%20cy='72'%20rx='9'%20ry='15'%20transform='rotate(-24%20214%2072)'/><circle%20cx='128'%20cy='128'%20r='2.4'%20fill='%23FFFFFF'%20stroke='none'%20fill-opacity='.05'/><path%20d='M268,150%20Q276,162%20288,170%20Q276,178%20268,190%20Q260,178%20248,170%20Q260,162%20268,150%20Z'/><ellipse%20cx='70'%20cy='226'%20rx='13'%20ry='21'%20transform='rotate(-14%2070%20226)'/><circle%20cx='70'%20cy='226'%20r='2'%20fill='%23FFFFFF'%20stroke='none'%20fill-opacity='.05'/><path%20d='M172,244%20Q178,253%20187,259%20Q178,265%20172,274%20Q166,265%20157,259%20Q166,253%20172,244%20Z'/><circle%20cx='286'%20cy='40'%20r='1.8'%20fill='%23FFFFFF'%20stroke='none'%20fill-opacity='.05'/><circle%20cx='24'%20cy='140'%20r='1.8'%20fill='%23FFFFFF'%20stroke='none'%20fill-opacity='.05'/><circle%20cx='240'%20cy='290'%20r='2'%20fill='%23FFFFFF'%20stroke='none'%20fill-opacity='.05'/><circle%20cx='118'%20cy='60'%20r='1.5'%20fill='%23FFFFFF'%20stroke='none'%20fill-opacity='.05'/><ellipse%20cx='300'%20cy='232'%20rx='8'%20ry='13'%20transform='rotate(38%20300%20232)'/></g></svg>");   /* maroon + footer */
}
body, .plan-sec, .cta-band, .loc-section, .site-footer {
  background-blend-mode: normal !important;
  background-size: 320px 320px !important;
}
body { background-image: var(--batik-faint) !important; }
.plan-sec, .cta-band { background-image: var(--batik-dark) !important; }
.loc-section, .site-footer { background-image: var(--batik-light) !important; }

/* ---- 47. Pattern scale on phones (2026-08-15).
        The tile is 320px. On a 1280px desktop it repeats four times and reads as fine
        texture; on a 375px phone ONE tile nearly fills the width, so each motif is huge
        and the "pattern" becomes a handful of big shapes floating over the copy — exactly
        what Agung saw. Density has to stay roughly constant, so the tile shrinks with the
        viewport. This is independent of opacity: even a faint motif looks wrong at that
        size. */
@media (max-width: 900px) {
  body, .plan-sec, .cta-band, .loc-section, .site-footer { background-size: 190px 190px !important; }
}
@media (max-width: 560px) {
  body, .plan-sec, .cta-band, .loc-section, .site-footer { background-size: 150px 150px !important; }
}

/* ---- 48. No pink TEXT in "Choose Your Time & Package" (2026-08-16, Agung).
        Rule 16 had promoted pink to an accent and rule 40 gave the orange block pink
        emphasis words; on this section he wants every word black. Only text colour is
        reset — the pink chrome that is a SHAPE rather than a word (the numbered step
        box, the MOST POPULAR flag) keeps its fill, and both already print black on it.
        The groups card is exempt: it is the dark panel nested inside this section, so
        black would vanish on it — same trap rules 10/12 hit. */
.plan-sec .marker { color: #0A0C1B !important; }
.plan-sec .session-time { color: #0A0C1B !important; }
.plan-sec .pkg-price .amount { color: #0A0C1B !important; }
.plan-sec .section-eyebrow { color: #0A0C1B !important; opacity: 1; }
.plan-sec .kawung { color: #0A0C1B !important; }
/* …but not inside the dark groups card, which keeps rule 40's yellow. */
.plan-sec .groups-card .marker { color: var(--if-yellow) !important; }
.plan-sec .groups-card .section-eyebrow { color: #D9922E !important; }

/* ---- 49. GALLERY — a continuously scrolling photo strip on a purple block (2026-08-16).
        Sits between the kids section and the FAQ, so it needs both wave variants: an
        `on-white to-purple` above it and an `on-purple to-white` below. Per rule 32 the
        strip's background-COLOUR is the section below and the SVG paints the section
        above, so `to-purple` sets the colour and `on-purple` sets the image. */
:root { --if-purple: #D9D2BE; }   /* SOGA — undyed/greige cloth (2026-08-17) */

.wave-sep.to-purple { background-color: var(--if-purple) !important; }
.wave-sep.on-purple {
  background-image: url("data:image/svg+xml,<svg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%202293%20296'%20preserveAspectRatio='none'><path%20d='M2292%200C2292.55%202.14209e-05%202293%200.447728%202293%201V207C2293%20215.284%202286.28%20222%202278%20222H2059.5C2055.36%20222%202052%20225.358%202052%20229.5C2052%20233.642%202048.64%20237%202044.5%20237H1967C1958.72%20237%201952%20230.284%201952%20222V204C1952%20195.716%201945.28%20189%201937%20189H1759.5C1755.91%20189%201753%20191.91%201753%20195.5C1753%20199.09%201750.09%20202%201746.5%20202H1665C1656.72%20202%201650%20208.716%201650%20217V222C1650%20230.284%201643.28%20237%201635%20237H1623.5C1616.6%20237%201611%20242.596%201611%20249.5C1611%20256.404%201605.4%20262%201598.5%20262H1416.5C1409.6%20262%201404%20256.404%201404%20249.5C1404%20242.596%201398.4%20237%201391.5%20237H1187.5C1180.6%20237%201175%20242.596%201175%20249.5C1175%20256.404%201169.4%20262%201162.5%20262H603C594.716%20262%20588%20255.284%20588%20247V245C588%20236.716%20581.284%20230%20573%20230H551C543.268%20230%20537%20223.732%20537%20216C537%20208.268%20530.732%20202%20523%20202H316.5C312.91%20202%20310%20199.09%20310%20195.5C310%20191.91%20307.09%20189%20303.5%20189H201C193.82%20189%20188%20194.82%20188%20202C188%20209.18%20182.18%20215%20175%20215H90C81.7157%20215%2075%20221.716%2075%20230V232C75%20240.284%2068.2843%20247%2060%20247H15C6.71573%20247%200%20240.284%200%20232V1C0%200.447715%200.447715%200%201%200H2292Z'%20fill='%23D9D2BE'/><g%20transform='translate(0,176)%20scale(0.99436,1)'><path%20d='M2306%2060.5H2260.14C2251.7%2060.5%202244.86%2053.7843%202244.86%2045.5V43.5C2244.86%2035.2157%202238.02%2028.5%202229.57%2028.5H2142.96C2135.64%2028.5%202129.71%2022.6797%202129.71%2015.5C2129.71%208.3203%202123.78%202.5%202116.47%202.5H2012.02C2008.36%202.5%202005.39%205.41015%202005.39%209C2005.39%2012.5899%202002.43%2015.5%201998.77%2015.5H1635.5C1627.62%2015.5%201621.23%2021.768%201621.23%2029.5C1621.23%2037.232%201614.85%2043.5%201606.97%2043.5H1584.55C1576.11%2043.5%201569.26%2050.2157%201569.26%2058.5V60.5C1569.26%2068.7843%201562.42%2075.5%201553.98%2075.5H1530.54C1524.92%2075.5%201520.35%2079.9772%201520.35%2085.5C1520.35%2091.0228%201515.79%2095.5%201510.16%2095.5H1194.27C1188.64%2095.5%201184.08%2091.0228%201184.08%2085.5C1184.08%2079.9772%201179.52%2075.5%201173.89%2075.5H1136.7C1129.66%2075.5%201123.96%2069.9036%201123.96%2063C1123.96%2056.0964%201118.26%2050.5%201111.22%2050.5H903.345C896.31%2050.5%20890.607%2056.0964%20890.607%2063C890.607%2069.9036%20884.904%2075.5%20877.87%2075.5H692.411C685.377%2075.5%20679.674%2069.9036%20679.674%2063C679.674%2056.0964%20673.971%2050.5%20666.936%2050.5H655.218C646.776%2050.5%20639.933%2043.7843%20639.933%2035.5V30.5C639.933%2022.2157%20633.089%2015.5%20624.648%2015.5H607.325C598.883%2015.5%20592.04%2022.2157%20592.04%2030.5V60.5C592.04%2068.7843%20585.196%2075.5%20576.755%2075.5H300.096C293.061%2075.5%20287.358%2069.9036%20287.358%2063C287.358%2056.0964%20281.655%2050.5%20274.621%2050.5H237.937C233.716%2050.5%20230.294%2047.1421%20230.294%2043C230.294%2038.8579%20226.872%2035.5%20222.652%2035.5H0'%20stroke='%2326456E'%20stroke-width='2'%20fill='none'%20vector-effect='non-scaling-stroke'/></g></svg>") !important;
}

.gallery-sec {
  background-color: var(--if-purple);
  padding: clamp(58px, 7vw, 96px) 0 clamp(64px, 7.5vw, 104px);
  overflow: hidden;                      /* the track is wider than the page by design */
}
/* The batik tile. Lavender is a PALE ground, so it takes the faint tile (rule 46's
   dark-on-white strength). The stronger --batik-dark is for orange/yellow — on this
   colour it reads as visible motifs rather than texture. */
.gallery-sec {
  background-image: var(--batik-faint) !important;
  background-blend-mode: normal !important;
  background-size: 320px 320px !important;
}
@media (max-width: 900px) { .gallery-sec { background-size: 190px 190px !important; } }
@media (max-width: 560px) { .gallery-sec { background-size: 150px 150px !important; } }

.gallery-sec .gal-head { max-width: 720px; margin: 0 auto clamp(30px, 4vw, 48px); text-align: center; padding: 0 18px; }
/* rule 16 hands pink eyebrows to a named list of sections; this one is not on it, so it
   would fall back to the muted grey that vanishes on lavender */
.gallery-sec .section-eyebrow { color: #0A0C1B !important; opacity: 1; }
.gallery-sec h2 { color: #0A0C1B; margin: 0 0 12px; }
.gallery-sec .marker { color: var(--if-pink) !important; }   /* pink reads on lavender; orange does not */
.gal-sub { color: #0A0C1B; opacity: .72; font-size: clamp(15px, 1.5vw, 17px); line-height: 1.55; margin: 0; }

/* The strip. The track holds the photo set TWICE and slides exactly -50%, so the second
   copy arrives as the first leaves. The spacing is a per-item margin, NOT `gap`: with a
   gap the track is 2N items + (2N-1) gaps wide, so -50% lands half a gap off the period
   and the loop visibly jumps every pass. */
.gal-marquee {
  width: 100%;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 5%, #000 95%, transparent 100%);
          mask-image: linear-gradient(90deg, transparent 0, #000 5%, #000 95%, transparent 100%);
}
.gal-track {
  display: flex;
  width: max-content;
  animation: gal-scroll 72s linear infinite;
  will-change: transform;
}
.gal-marquee:hover .gal-track { animation-play-state: paused; }
@keyframes gal-scroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }

.gal-item {
  flex: 0 0 auto;
  width: clamp(210px, 23vw, 320px);
  height: clamp(180px, 20vw, 260px);
  margin-right: clamp(12px, 1.4vw, 20px);
  border-radius: var(--r-lg) !important;   /* rule 1 zeroes radius globally with !important */
  overflow: hidden;
  background: rgba(10, 12, 27, .06);     /* holds the frame while the photo loads */
}
.gal-item img, .gal-item .slot-photo {
  width: 100%; height: 100%;
  object-fit: cover; display: block;
  border-radius: var(--r-lg) !important; /* same re-grant for the photo inside the frame */
}

/* No animation for anyone who asked for none — the strip becomes a normal swipeable row. */
@media (prefers-reduced-motion: reduce) {
  .gal-track { animation: none; }
  .gal-marquee { overflow-x: auto; -webkit-overflow-scrolling: touch; }
}

/* ---- 50. Every headline prints in ONE colour — black (2026-08-16, Agung: "all black
        color headline"). This retires the accent-word palette rule 40 built up: orange on
        white sections, pink in the orange block (rule 48 had already blacked that one) and
        pink in the gallery (rule 49). The Knewave face STAYS — the emphasis is now carried
        by the letterforms alone, not by colour.

        Four surfaces are excluded, and they are excluded to HONOUR the instruction rather
        than to dodge it: the hero photo, the dark CTA card, the maroon location block and
        the navy groups card all carry light headlines, so black there would not read as a
        black headline — it would be an invisible word. On those the accent takes
        `inherit`, i.e. exactly the colour of the headline around it, which is the same
        result the instruction asks for: a headline in one single colour. */
.marker,
.plan-sec .marker,
.gallery-sec .marker { color: #0A0C1B !important; }

/* Light headlines on dark grounds — one colour still, just not black. */
.cta-card .marker, .loc-section .marker, .groups-card .marker,
.hero-content .marker, .hero-panel .marker, h1 .marker { color: inherit !important; }

/* ---- 51. Accessibility pass + visible form fields (2026-08-16).
        Everything here was MEASURED against the real rendered background, not guessed —
        the ratios in the comments are what the audit computed. */

/* ---- 51a. Eyebrows. Rule 16 tried to make these pink through .reviews-sec / .faq-sec /
        .kids-section — three class names that do not exist, so FAMILY FRIENDLY and FAQ had
        been rendering --muted-2 grey at 4.12:1 (fails AA). The selectors are corrected
        above; the colour is now INK rather than pink, for two reasons: pink #EC008C on
        white is only 4.25:1 and also fails, and PLAN YOUR VISIT + GALLERY are already ink,
        so this is the majority. Ink scores 19.4:1 on white, 9.3 on orange, 10.7 on purple. */
.section-eyebrow { color: #0A0C1B !important; opacity: 1; }
/* The maroon block is the one dark ground an eyebrow sits on — ink would vanish, and the
   design's pink is 3.61:1 there. A lighter tint of the same pink gets 7.28:1. */
.loc-section .section-eyebrow { color: #FF8FC4 !important; }
/* the groups card is dark too and keeps its amber (set inline in index.html) */

/* ---- 51b. The floating WhatsApp button was the worst contrast on the site: a white label
        on brand green #25D366 is 1.98:1. Keeping the green matters — it is what makes the
        button recognisable — so the LABEL goes ink instead, which measures 9.79:1. The
        glyph stays white: it sits on the dark green circle, not on the bubble. */
.wa-bubble .wa-bubble-label { color: #0A0C1B !important; }

/* ---- 51c. Two greys that missed AA by a hair (4.12:1). --muted is the same family and
        clears it at 7.12:1. */
.review-when, .pkg-price .per { color: var(--muted) !important; }

/* ---- 51d. The BLOG nav link is pink on white = 4.25:1. A darker pink keeps it reading as
        the odd-one-out link without failing: 5.87:1. */
.topnav-link.topnav-blog { color: #C4006F !important; }

/* ---- 51e. FORM FIELDS WERE INVISIBLE (Agung, 2026-08-16: "field dikasih stroke outline
        biar keliatan field yang perlu diisi").
        `.form-input` is `background: #fff; border: none` — which worked on the v1 design
        where the modal card was cream, and became a white box on a white card the moment
        the IdeaFest restyle made `.modal-card` white. Only `.choice-card` looked like a
        control, because it is the one that carries its own 2px border.
        So: the same 2px ink outline every other control on this site uses. */
.form-input,
.phone-wrap,
textarea.form-input,
select.form-input,
input[type="date"].form-input {
  border: 2px solid var(--ink) !important;
  background: #fff;
}
/* .phone-wrap is a composite (select + input welded together) — the wrapper draws the box,
   so the inner controls must NOT draw their own or the row gets a double line. */
.phone-wrap .phone-num,
.phone-wrap .phone-dial { border: none !important; }
/* the inner divider that separated the two halves was a shadow tuned for a borderless
   field; against a real border it reads as a smudge */
.phone-wrap .phone-num { box-shadow: inset 2px 0 0 var(--ink) !important; }

/* Focus: thicken rather than recolour, so the field does not change meaning on focus. */
.form-input:focus, .form-input:focus-visible,
.phone-wrap:focus-within { outline: none !important; box-shadow: 0 0 0 2px var(--ink) !important; }

/* The one genuinely required field (email carries `required`) says so on the field itself,
   not only in the tiny red asterisk on its label. `:user-invalid` fires only AFTER the
   guest has typed and left it wrong — never on an untouched form — and browsers without it
   simply keep the plain border. */
.form-input[required]:user-invalid { border-color: #C0392B !important; }
.form-input[required]:user-invalid + .field-hint { color: #C0392B; }

/* Rule 1 zeroed radius globally and rule 17 re-granted it to a named list that missed the
   notice box, so it was the one hard-cornered panel inside a rounded modal. */
.notice { border-radius: var(--r-md) !important; }

/* ---- 52. Stepper: round again, and re-coloured by progress (2026-08-16, Agung —
        "bullet step ganti ke hitam untuk sudah terlampaui, belum abu2, active orange"
        + "dan bentuknya bulat, termasuk tombol close bulat").

        BOTH halves have the same root cause as rule 41: `.step-dot`, `.step-bar` and
        `.modal-close` all declare their own `border-radius: 50%` / `999px` further up the
        file, and rule 1's global `border-radius: 0 !important` has been overriding them —
        that is why the steps rendered as squares. They only need re-granting.

        The colours had drifted for a subtler reason: the "not yet" state was painted with
        `var(--line)`, and the restyle changed --line from a light grey to INK. So unvisited
        steps were coming out nearly black — darker than the active one. */
.step-dot, .modal-close { border-radius: 999px !important; }
.step-bar { border-radius: 999px !important; }

/* not yet reached — grey, and quiet */
.step-dot { background: var(--if-grey) !important; color: var(--muted) !important; }
.step-bar { background: var(--if-grey) !important; }

/* the step you are on — orange. Text is INK, not white: white on #FE9D00 is 2.1:1 and
   unreadable, ink is 9.3:1. */
.step-dot.active { background: var(--if-orange) !important; color: #0A0C1B !important; }

/* already completed — black */
.step-dot.done { background: var(--ink) !important; color: #fff !important; }
.step-bar.done { background: var(--ink) !important; }

/* labels follow the same three states */
.step-label { color: var(--muted) !important; }
.step-label.active { color: #0A0C1B !important; }

/* ---- 53. Step labels wrap to two lines instead of truncating (2026-08-16, Agung:
        "buat step indicator text title 2 baris").
        They were `white-space: nowrap` + `text-overflow: ellipsis`, so "Your Details" and
        "Package & Time" rendered as "Your Det…" / "Package …" — the ellipsis hid exactly
        the word that distinguishes the steps. Now they wrap and are clamped at 2 lines, so
        a longer translation still cannot push the stepper to three rows.
        (The ID strings are longer than the EN ones: "Detail Anda", "Paket & Waktu".) */
.step-label {
  white-space: normal !important;
  text-overflow: clip !important;
  overflow: hidden;
  line-height: 1.2;
  min-width: 4.5em;          /* or a long word wraps one character per line */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
/* the row now has two-line labels in it, so centre everything on the label block */
.stepper { align-items: center; }

/* Two-line labels need room that a 375px phone does not have: 4 labels + 4 dots + 3 bars
   wanted 426px inside a 291px modal, so the last step fell off the end. On phones the
   labels are dropped entirely and the dots carry the progress — nothing is lost, because
   `.modal-sub` directly above already spells it out ("Step 1 of 4 — tell us about you"),
   and it is translated where these labels are not. */
@media (max-width: 620px) {
  .step-label { display: none !important; }
  .stepper { gap: 8px; }
}

/* ---- 54. Dark navy footer + thinner field borders (2026-08-17, Agung).

        FOOTER. It was painting `var(--ink)` = #0A0C1B, which the token block calls
        "near-black with a blue cast" — in practice it reads as flat black, and it sits
        directly under the maroon block, so the page ended on two heavy dark slabs with no
        distinction between them. #16203C is a real navy: still dark enough to carry white
        text at 14:1, but the blue is now visible rather than implied.

        Only `.site-footer` is repointed — NOT the --ink token, which also paints the CTA
        card, the groups card, buttons, dots and the whole dashboard. Needs `!important`
        because rule 46 sets the footer's background-color with !important to attach the
        batik tile. */
.site-footer { background-color: #1A1930 !important; }   /* darker than the location block above it */

/* THINNER FIELD BORDERS. Rule 51e gave inputs the same 2px ink outline as every button and
   card, which made them visible but flattened the hierarchy — the Continue button no longer
   won its own form. 1.5px keeps the field clearly bounded while letting 2px go back to
   meaning "this is a control you press". */
.form-input,
.phone-wrap,
textarea.form-input,
select.form-input,
input[type="date"].form-input { border-width: 1.5px !important; }
/* the welded divider inside the phone field has to match, or it reads heavier than the box
   that contains it */
.phone-wrap .phone-num { box-shadow: inset 1.5px 0 0 var(--ink) !important; }
/* focus ring stays 2px — on focus the field SHOULD outrank the button */
.form-input:focus, .form-input:focus-visible,
.phone-wrap:focus-within { box-shadow: 0 0 0 2px var(--ink) !important; }

/* Two more `--muted-2` greys at 4.12:1, both inside the booking modal — they were missed by
   the first contrast sweep only because the modal was closed while it ran. Same fix as
   rule 51c: --muted clears AA at 7.12:1. Sweep with the modal OPEN next time. */
.field-label .opt, .field-hint { color: var(--muted) !important; }

/* ---- 55. "Why can't I continue?" (2026-08-17, Agung: sorot ke field yang dimaksud +
        border orange, tombolnya tetap greyed out).

        The two requests pull against each other: a real `disabled` button cannot be
        clicked, so it can never TELL you what is missing — you just press it and nothing
        happens. Resolved by splitting look from behaviour: `.is-blocked` paints the greyed
        state, the button stays clickable, and goNext() answers the press by scrolling to
        the first gap. `aria-disabled` carries the same meaning to screen readers. */
.nav-next.is-blocked {
  background: var(--if-grey) !important;
  border-color: var(--if-grey) !important;
  color: var(--muted) !important;
  cursor: not-allowed;
}
.nav-next.is-blocked svg { stroke: var(--muted) !important; }
/* no lift, no rollover fill — it must not look like it is ready to go */
.nav-next.is-blocked:hover { background: var(--if-grey) !important; color: var(--muted) !important; transform: none !important; }
.nav-next.is-blocked:hover::before { display: none !important; }

/* The gap itself: orange ring. Orange (not red) because nothing is wrong yet — the guest
   simply has not got there. Red is for a value that IS incorrect. */
.needs-fill,
.needs-fill.form-input,
.needs-fill.phone-wrap {
  border-color: var(--if-orange) !important;
  box-shadow: 0 0 0 3px rgba(254, 157, 0, .28) !important;
  border-radius: var(--r-md);
}
/* blocks (session cards, package grid, sizes, payment) have no border of their own, so the
   ring is drawn with outline instead — it follows the box without changing its layout */
.needs-fill:not(.form-input):not(.phone-wrap) {
  outline: 2px solid var(--if-orange) !important;
  outline-offset: 6px;
  border-radius: var(--r-md);
}
@keyframes lbw-nudge { 0%,100% { transform: translateX(0) } 25% { transform: translateX(-4px) } 75% { transform: translateX(4px) } }
.needs-fill { animation: lbw-nudge .28s ease both; }
@media (prefers-reduced-motion: reduce) { .needs-fill { animation: none; } }

.block-msg {
  margin: 0 0 10px; color: #A65200; font-size: 13.5px; font-weight: 700; line-height: 1.4;
}
.block-msg.hidden { display: none; }

/* rule 51e coloured a badly-typed required field red; orange is the form's one "needs your
   attention" colour now, so the two states stop competing */
.form-input[required]:user-invalid { border-color: var(--if-orange) !important; }

/* ---- 56. Stepped separators replace the wave (2026-08-17, Agung supplied separator.svg:
        "jadikan ini svg buat transisi tiap section, instead of wavy, merge the color tho").

        The shape drops straight into rule 32's scheme, which is why no HTML changed: the
        SVG fills from the top down to an irregular edge and leaves the rest transparent, so
        `on-<above>` still paints the section above and `to-<below>` still supplies the
        colour underneath. "Merge the color" is exactly that pairing — the supplied #D9D9D9
        is replaced per variant by the real section colour, baked in because a data-URI
        cannot read a CSS variable.

        Three earlier generations of wave geometry (rules 20/21/22 + the to-maroon image)
        were deleted in the same pass — 10 declarations that had not drawn a pixel since
        rule 32 inverted the scheme. There is now exactly ONE separator geometry in the file.

        HEIGHT MATTERS HERE in a way it did not for the wave. The artwork is 2293×282, an
        8:1 ratio, and `background-size: 100% 100%` stretches it to whatever the strip is.
        At the old wave height (82px) the steps flattened into shallow nicks. ~118px keeps
        them legible while staying well under the artwork's natural 157px at this width.
        The class is still called `.wave-sep` — it is load-bearing in ~20 rules for the
        seam/margin handling, so renaming it would be churn for no behaviour change. */
.wave-sep { height: clamp(58px, 8vw, 118px); }

/* One artwork used six times means six identical silhouettes. Mirroring every other one
   breaks that up at no cost — most worthwhile on the purple block, where both of its edges
   are in view at the same time. Pure paint: the strip is an empty div, so flipping it moves
   nothing and affects no layout. */
.wave-sep.flip { transform: scaleX(-1); }

/* ---- 57. SOGAN palette (2026-08-17, Agung picked option A from the three-way preview).
        Natural batik dyes replace the borrowed IdeaFest palette:
          kunyit #E8A33D (was orange)   indigo #26456E (was pink, the accent)
          cream  #F3EAD8 (was yellow)   soga   #D9D2BE (was lavender)
          deep indigo #2B2A4A (was maroon)   footer #1A1930
        Most of it lands through the :root tokens. What follows is only the places where the
        new colour means something DIFFERENT from the old one, each measured, not guessed.

        THE ONE STRUCTURAL CHANGE: pink was a LIGHT accent, indigo is a DARK one. Anything
        that used the accent as a filled box with black text on it now needs white text —
        black on #26456E is 2.0:1. */
/* rule 10 forces `.plan-sec .plan-num` to an ink box with orange digits — written when the
   block was #FE9D00 and the badge had to survive on it. On kunyit the accent works, and an
   indigo badge belongs to the palette in a way a plain ink dot does not. The box is a
   SHAPE so 4.51:1 is fine; the digit on it is white at 9.73:1. */
.plan-num, .plan-sec .plan-num {
  background: var(--if-pink) !important;
  color: #fff !important;
}
.pkg-badge { color: #fff !important; }

/* (A `.hero-eyebrow` rule used to sit here. That class does not exist — the markup is
   <div class="eyebrow"> — so rule 16's pink never applied and the hero eyebrow has always
   been white at 72% on the photo. Nothing to port; the dead selector was dropped.) */

/* Same problem on the dark location block: the eyebrow was a light pink, and indigo would
   vanish on #2B2A4A. Kunyit gets 6.35:1 there and keeps the accent family. */
.loc-section .section-eyebrow { color: var(--if-orange) !important; }

/* The required-field asterisk had been sharing the pink accent. An accent that is now a calm
   indigo reads as decoration, not a warning, so it becomes madder red — 6.02:1 on white and
   the only genuinely alarming colour in the palette. */
.field-label .req { color: #B5342B !important; }

/* BLOG kept a darkened pink for contrast; the accent clears AA on its own now (9.73:1). */
.topnav-link.topnav-blog { color: var(--if-pink) !important; }

/* Rollover was "pink fill, black text" (rule 15). With a dark accent the text must invert. */
/* Rule 57 had flipped hover text to WHITE because the fill was then the dark indigo accent.
   The fill is pink again, so it flips back to ink — on #EC008C white measures 4.25:1 and fails
   AA, ink measures 4.57:1 and passes. This is the same pairing rule 15 shipped originally. */
.btn:hover, .btn-primary:hover, .pkg-book:hover, .cta-big:hover, .nav-next:hover,
.loc-directions:hover, .choice-card:hover, .pay-card:hover, .size-pill:hover,
.groups-btn:hover, .inq-send:hover { color: #0A0C1B !important; }
.btn:hover svg, .btn-primary:hover svg, .pkg-book:hover svg, .cta-big:hover svg,
.nav-next:hover svg, .loc-directions:hover svg { stroke: #0A0C1B !important; }
/* …but the blocked Continue must not light up at all (rule 55 keeps it grey) */
.nav-next.is-blocked:hover { color: var(--muted) !important; }
.nav-next.is-blocked:hover svg { stroke: var(--muted) !important; }

/* ---- 59. WhatsApp bubble: the Jitter "Animated Emoji Button" behaviour, in CSS
        (2026-08-20, Agung sent the Jitter file and asked whether it could be used).

        WHY NOT THE JITTER EXPORT. Its timeline is Expand → Button·Scale → Hand·Play →
        Collapse over 4s, which is four transforms — cheap to reproduce, expensive to ship:
          · Lottie needs lottie-web (~250KB) or the dotlottie player + wasm, for ONE button on
            a page we just spent real effort getting to 4.1MB;
          · a transparent video is worse — VP9-with-alpha is not in Safari and HEVC-with-alpha
            is Safari-only, so it means shipping two files and still guessing;
          · a GIF cannot do a soft shadow over the page without a matte edge.
        So the motion is rebuilt here at 0KB, and it can honour prefers-reduced-motion, which
        a baked video cannot.

        IT RUNS 3 TIMES, NOT FOREVER — deliberately. Agung asked to cut the page from five
        autonomous animations to two; a permanent 4s loop in the corner would be a third, in
        the most attention-grabbing spot on screen. Three cycles greet an arriving visitor and
        then stop nagging. For the Jitter file's endless loop, change the `3` below to
        `infinite`. Hover expands it any time regardless. */

/* collapsed: a circle showing only the glyph. This is already the mobile presentation, so
   the two now match instead of diverging. */
.wa-bubble { padding: 14px; gap: 0; }
.wa-bubble .wa-bubble-label {
  max-width: 0; opacity: 0; overflow: hidden; white-space: nowrap;
  transition: max-width .34s cubic-bezier(.2,0,0,1), opacity .2s ease, margin-left .34s cubic-bezier(.2,0,0,1);
  margin-left: 0;
}
.wa-bubble:hover .wa-bubble-label,
.wa-bubble:focus-visible .wa-bubble-label { max-width: 9rem; opacity: 1; margin-left: 10px; }

/* the greeting: expand, wave, hold, collapse — the Jitter timeline, 4s */
@keyframes wa-greet {
  0%, 8%    { max-width: 0;     opacity: 0; margin-left: 0;    }
  16%, 78%  { max-width: 9rem;  opacity: 1; margin-left: 10px; }
  90%, 100% { max-width: 0;     opacity: 0; margin-left: 0;    }
}
@keyframes wa-wave {
  0%, 14%, 62%, 100% { transform: rotate(0deg); }
  24%, 44%           { transform: rotate(-16deg); }
  34%, 54%           { transform: rotate(16deg); }
}
/* the little scale pop as it opens */
@keyframes wa-pop { 0%,10%{transform:scale(1)} 18%{transform:scale(1.06)} 30%,100%{transform:scale(1)} }

.wa-bubble .wa-bubble-label { animation: wa-greet 4s cubic-bezier(.2,0,0,1) 1.6s 3 both; }
.wa-bubble svg { animation: wa-wave 4s ease-in-out 1.6s 3 both; transform-origin: 68% 72%; }
.wa-bubble    { animation: wa-pop 4s ease-in-out 1.6s 3 both; }
/* hovering must win over the animation, or the label snaps shut mid-hover */
.wa-bubble:hover .wa-bubble-label,
.wa-bubble:focus-visible .wa-bubble-label { animation: none; }
.wa-bubble:hover { animation: none; }

/* Reduced motion: no greeting, no wave — the label simply stays out, so nothing is lost.
   This is the part a Lottie or a video export could not have done. */
@media (prefers-reduced-motion: reduce) {
  .wa-bubble, .wa-bubble svg, .wa-bubble .wa-bubble-label { animation: none !important; }
  .wa-bubble { padding: 13px 20px 13px 16px; gap: 10px; }
  .wa-bubble .wa-bubble-label { max-width: 9rem; opacity: 1; margin-left: 0; transition: none; }
}
/* Phones keep it a circle throughout — rule 558 already hides the label there, and an
   expanding pill would sit over the bottom bar. */
@media (max-width: 560px) {
  .wa-bubble .wa-bubble-label, .wa-bubble svg { animation: none; }
}

/* ---- 60. Animate UI motion, ported (2026-08-20, Agung sent
        animate-ui.com/docs/components/radix/accordion and asked for it plus "all relevant
        button and interaction").

        THE COMPONENTS THEMSELVES CANNOT BE INSTALLED HERE, and it is worth writing down why
        so nobody tries again: Animate UI is React + Radix primitives + Motion + Tailwind,
        installed with `npx shadcn@latest add @animate-ui/...`. This site has no package.json,
        no node_modules, no bundler and no React — it is index.html + styles.css + app.js,
        deployed by uploading a zip. Adopting the library literally means rebuilding the site
        as a React app, which would take the i18n system, the 4-step booking wizard, the Apps
        Script backend, the Xendit work and 59 rules of design decisions with it.

        What IS portable is the motion, exactly. Animate UI declares the accordion content
        transition as `{ type: 'spring', stiffness: 150, damping: 22 }`. Solving that spring
        (mass 1 → ω₀ 12.247 rad/s, ζ 0.898, settling at 689ms) and sampling its step response
        gives a CSS `linear()` easing that IS the same curve — not a cubic-bezier guess.
        Note ζ 0.898 is nearly critically damped: it overshoots 0.16%, so it reads as a soft
        settle, NOT a bounce. A `--spring-bounce` is provided below if real bounce is wanted.

        These are all INTERACTION animations — they fire when the guest acts. They do not add
        to the autonomous-motion budget that was cut from five to two. */
:root {
  /* fallbacks first: a browser without linear() keeps these and still animates sensibly */
  --spring: cubic-bezier(.22,1,.36,1);
  --spring-snap: cubic-bezier(.34,1.56,.64,1);
  --spring-dur: 689ms;
  --spring-snap-dur: 387ms;
}
@supports (transition-timing-function: linear(0, 1)) {
  :root {
    /* stiffness 150, damping 22 — Animate UI's AccordionContent, solved */
    --spring: linear(0.0000, 0.0145, 0.0520, 0.1053, 0.1685, 0.2371, 0.3077, 0.3779, 0.4459, 0.5103, 0.5705, 0.6258, 0.6762, 0.7216, 0.7621, 0.7980, 0.8296, 0.8571, 0.8810, 0.9015, 0.9191, 0.9341, 0.9468, 0.9574, 0.9663, 0.9736, 0.9797, 0.9846, 0.9886, 0.9918, 0.9944, 0.9964, 0.9979, 0.9991, 1.0000, 1.0006, 1.0011, 1.0014, 1.0015, 1.0016, 1.0016, 1.0016, 1.0015, 1.0014, 1.0013, 1.0012, 1.0011, 1.0000);
    /* stiffness 400, damping 30 — Motion's usual tap spring: 2.8% overshoot, settles in 387ms */
    --spring-snap: linear(0.0000, 0.0275, 0.0967, 0.1910, 0.2977, 0.4074, 0.5135, 0.6116, 0.6991, 0.7749, 0.8386, 0.8908, 0.9323, 0.9644, 0.9883, 1.0053, 1.0167, 1.0237, 1.0273, 1.0284, 1.0276, 1.0257, 1.0230, 1.0200, 1.0169, 1.0139, 1.0111, 1.0086, 1.0064, 1.0046, 1.0031, 1.0000);
    /* unused by default — stiffness 180, damping 12, a real 21% bounce. Swap into --spring-snap
       if Agung wants the springiness to be obvious rather than felt. */
    --spring-bounce: linear(0.0000, 0.0695, 0.2401, 0.4594, 0.6840, 0.8828, 1.0375, 1.1411, 1.1952, 1.2075, 1.1889, 1.1511, 1.1048, 1.0586, 1.0186, 0.9880, 0.9682, 0.9585, 0.9571, 0.9618, 0.9701, 0.9798, 0.9892, 0.9973, 1.0033, 1.0071, 1.0088, 1.0088, 1.0077, 1.0059, 1.0039, 1.0000);
  }
}

/* ---- 60a. The accordion — the component Agung actually linked. */
.faq-a-wrap { transition: height var(--spring-dur) var(--spring) !important; }
.faq-a { transition: opacity 240ms ease, transform var(--spring-dur) var(--spring) !important; }
.faq-q .icon { transition: transform var(--spring-dur) var(--spring), color 200ms ease !important; }

/* ---- 60b. Buttons: press feedback on the snap spring. Animate UI's Button uses
        `whileTap={{ scale: 0.95 }}`; this is the same idea in CSS, and it applies to the
        classes this site actually uses (see rule 13 — several buttons have their own names). */
.btn, .btn-primary, .pkg-book, .cta-big, .nav-next, .nav-back, .loc-directions,
.groups-btn, .inq-send, .choice-card, .pay-card, .size-pill, .lang-btn,
.carousel-arrow, .faq-q, .rev-summary, .code-btn, .wpkg-card .qty button {
  transition: transform var(--spring-snap-dur) var(--spring-snap),
              background-color 200ms ease, color 200ms ease, border-color 200ms ease,
              box-shadow 240ms ease !important;
}
.btn:active, .btn-primary:active, .pkg-book:active, .cta-big:active, .nav-next:active,
.nav-back:active, .loc-directions:active, .groups-btn:active, .inq-send:active,
.choice-card:active, .pay-card:active, .size-pill:active, .lang-btn:active,
.carousel-arrow:active, .rev-summary:active, .wpkg-card .qty button:active {
  transform: scale(.96);
}
/* a blocked Continue must not feel pressable — rule 55 keeps it visually inert */
.nav-next.is-blocked:active { transform: none; }

/* ---- 60c. Selection states settle on the same spring as the accordion, so choosing a
        session, a package size or a payment method feels like one system. */
.choice-card.active, .pay-card.active, .size-pill.active, .wpkg-card.active {
  transition: transform var(--spring-dur) var(--spring),
              background-color 200ms ease, border-color 200ms ease, color 200ms ease !important;
}

/* ---- 60d. The modal enters on the spring instead of its old cubic-bezier. */
.modal-card { animation: lbw-rise var(--spring-dur) var(--spring) both !important; }

/* ---- 60e. Ripple on press — Animate UI ships a Ripple Button; this is the CSS equivalent,
        driven by two custom properties that app.js sets to the click point. No library. */
.btn, .btn-primary, .pkg-book, .cta-big, .nav-next, .loc-directions, .groups-btn, .inq-send {
  position: relative; overflow: hidden;
}
.lbw-ripple {
  position: absolute; border-radius: 999px; pointer-events: none;
  left: var(--rx); top: var(--ry); width: var(--rd); height: var(--rd);
  margin-left: calc(var(--rd) / -2); margin-top: calc(var(--rd) / -2);
  background: currentColor; opacity: .18;
  animation: lbw-ripple 560ms var(--spring) forwards;
}
@keyframes lbw-ripple {
  from { transform: scale(0);   opacity: .18; }
  to   { transform: scale(1);   opacity: 0;   }
}

@media (prefers-reduced-motion: reduce) {
  .faq-a-wrap, .faq-a, .faq-q .icon, .modal-card { transition: none !important; animation: none !important; }
  .btn:active, .btn-primary:active, .pkg-book:active, .cta-big:active, .nav-next:active,
  .choice-card:active, .pay-card:active, .size-pill:active { transform: none; }
  .lbw-ripple { display: none; }
}

/* ---- 61. Dialog, toggle group and hover — the rest of what Agung asked for
        (2026-08-20: "open dialog box too, accordion, hover, tabs, toggle. modal alert dialog same").

        ON TABS: there are none on this page. `grep role="tab"|data-tab index.html` → 0. The
        dashboard has tabs but it loads its own class/styles.css and is out of scope, so a tabs
        component here would be dead CSS. Nothing was invented for it. */

/* ---- 61a. Dialog: enter AND exit, both directions on the spring, plus the overlay fade
        Animate UI's Dialog does. The exit needed a JS change too — closeDialog() in app.js —
        because `.hidden` is `display:none !important` and killed the element before it could
        animate. */
@keyframes lbw-overlay-in  { from { opacity: 0 } to { opacity: 1 } }
@keyframes lbw-overlay-out { from { opacity: 1 } to { opacity: 0 } }
@keyframes lbw-dialog-out  { from { opacity: 1; transform: translateY(0)    scale(1);    }
                             to   { opacity: 0; transform: translateY(10px) scale(.975); } }

.modal-overlay:not(.hidden) { animation: lbw-overlay-in 220ms ease both; }
.modal-overlay.closing      { animation: lbw-overlay-out 200ms ease both; }
/* the card leaves faster than it arrives — a slow exit reads as lag, not polish */
.modal-overlay.closing .modal-card {
  animation: lbw-dialog-out 200ms cubic-bezier(.4,0,1,1) both !important;
}

/* ---- 61b. Toggle group: the active pill slides between EN and ID instead of just
        recolouring, which is the Animate UI Toggle Group signature.
        No JS was needed — applyI18n already writes `lang` onto <html>, so the indicator can
        be positioned from `html[lang="id"]` alone. */
.lang-toggle { position: relative; isolation: isolate; }
.lang-toggle::before {
  content: ''; position: absolute; z-index: -1;
  top: 3px; bottom: 3px; left: 3px; width: calc(50% - 3px);
  background: var(--ink); border-radius: var(--r-sm);
  transition: transform var(--spring-snap-dur) var(--spring-snap);
}
html[lang="id"] .lang-toggle::before { transform: translateX(100%); }
/* the buttons no longer paint their own fill — the sliding pill is the indicator now */
.lang-toggle .lang-btn.active { background: transparent !important; }
.lang-toggle .lang-btn { position: relative; z-index: 1; transition: color 200ms ease; }

/* ---- 61c. Hover, on one curve everywhere. The site had a global hover-lift with `.no-lift`
        opt-outs; the lift now uses the snap spring so hover, press and release feel like the
        same physical object. Cards lift less than buttons — they are bigger, so an equal
        translate reads as more movement. */
.btn:hover, .btn-primary:hover, .pkg-book:hover, .cta-big:hover, .nav-next:hover,
.loc-directions:hover, .groups-btn:hover, .inq-send:hover, .rev-summary:hover {
  transform: translateY(-2px);
}
.lbw-pkg-card:hover, .session-card:hover, .faq-item:hover {
  transform: translateY(-1px);
  transition: transform var(--spring-snap-dur) var(--spring-snap), box-shadow 240ms ease;
}
/* press must always win over hover, or a click on a hovered button does nothing visible */
.btn:active, .btn-primary:active, .pkg-book:active, .cta-big:active, .nav-next:active,
.loc-directions:active, .groups-btn:active, .inq-send:active, .rev-summary:active {
  transform: translateY(0) scale(.96);
}
.nav-next.is-blocked:hover, .nav-next.is-blocked:active { transform: none; }

@media (prefers-reduced-motion: reduce) {
  .modal-overlay:not(.hidden), .modal-overlay.closing,
  .modal-overlay.closing .modal-card { animation: none !important; }
  .lang-toggle::before { transition: none; }
  .btn:hover, .btn-primary:hover, .pkg-book:hover, .cta-big:hover, .nav-next:hover,
  .lbw-pkg-card:hover, .session-card:hover, .faq-item:hover { transform: none; }
}

/* ---- 62. GET DIRECTIONS never went pink on hover (2026-08-20, Agung spotted it).
        Rule 25 gives it `background: transparent !important` scoped as
        `.loc-section .loc-directions` — specificity (0,2,0), the SAME as rule 15's
        `.loc-directions:hover`, but further down the file, so it won every time. Scoping the
        hover to the section too takes it to (0,3,0) and settles it for good.

        Checked the other buttons the same way rather than patching this one blind. Three more
        do not go pink on hover, and all three are correct as they stand:
          · `.nav-next.is-blocked:hover` stays grey — a blocked Continue must not look ready;
          · `.choice-card.active` keeps its ink fill — a chosen card must still read as chosen;
          · `.rev-summary:hover` stays translucent white — it sits on the hero photo, where a
            pink pill would fight the headline.
        Pink fill + BLACK text is 4.57:1, the same pairing rule 58 measured. */
.loc-section .loc-directions:hover,
.loc-section .loc-directions:focus-visible {
  background: var(--rollover) !important;
  border-color: var(--rollover) !important;
  color: #0A0C1B !important;
}
.loc-section .loc-directions:hover svg,
.loc-section .loc-directions:focus-visible svg { stroke: #0A0C1B !important; }

/* ---- 63. The CTA button was the odd one out (2026-08-20, Agung: "cta on cta section is
        too big"). Measured before changing anything: it rendered at 24px / 76px tall, while
        EVERY other primary button on the page is 15–16px / 50–56px. So it was ~1.6x the type
        size and ~1.4x the height of its own siblings — not "large", inconsistent.

        It should still be the biggest button on the page, because it is the page's final ask;
        it just should not be in a different league. Now ~17px / ~60px: still the largest, by
        a margin you read as emphasis rather than as a different component.
        The mono face carries wide tracking, so the letter-spacing comes down with the size or
        the label ends up wider than the shrink saved. */
.cta-big {
  font-size: clamp(15px, 1.4vw, 17px) !important;
  letter-spacing: .12em !important;
  padding: 18px 34px !important;
  gap: 10px !important;
}
.cta-big svg { width: 18px; height: 18px; }
@media (max-width: 560px) {
  /* On a 375px phone the label wrapped to two lines with the arrow stranded beside it. Mono
     tracking is what costs the room, so it comes in first — the type size stays 15px because
     this is the page's primary action. `text-wrap: balance` is the safety net if a longer
     translation still wraps: two even lines read better than one long and one orphan. */
  .cta-big {
    font-size: 15px !important; padding: 16px 18px !important;
    letter-spacing: .05em !important; gap: 8px !important;
    text-wrap: balance;
  }
  .cta-big svg { width: 16px; height: 16px; }
}

/* ---- 64. Gallery photos 130% of their previous size (2026-08-20, Agung: "130% bigger image
        on gallery"). Read as ×1.3, the way "hero 15% taller" was read in rule 17 — ×2.3 would
        put each frame at 736px, which is not a strip any more.
        Every value scaled by the same factor so the proportions hold: 320→416 wide,
        260→338 tall, and the gap goes with them or the strip looks tighter than it did. */
.gal-item {
  width: clamp(273px, 30vw, 416px);
  height: clamp(234px, 26vw, 338px);
  margin-right: clamp(16px, 1.8vw, 26px);
}

/* ---- 65. Two gallery rows, opposite directions (2026-08-20, Agung asked "2 baris gallery
        yang atas ke kiri, yang bawah ke kanan? distracting gak?").

        HIS INSTINCT WAS RIGHT TO ASK. Counter-scrolling IS more attention-grabbing than one
        row — the eye cannot settle on either, because whatever it tracks is being crossed by
        something moving the other way. And he had just asked to cut the page from five
        autonomous animations to two, so a second moving row pushes against that.

        Built anyway, because it now earns its keep: the gallery is 30 photos, and one row
        meant a very long loop where most of them were never seen. Two rows halve the loop and
        show twice as much at once.

        The distraction is paid for with SPEED, not by refusing: 72s → 130s per row, i.e.
        ~92px/s down to ~51px/s. Slow enough to read as drift rather than traffic. If it still
        feels busy, raise the duration again before removing the second row — the counter-
        direction is what makes it interesting, the speed is what made it noisy. */
/* 130s -> 200s (2026-08-20). Agung read the two rows as running at DIFFERENT speeds; measured
   over a 3s window they were identical — 41.7px/s each, same duration, same `linear` timing, same
   track width, 0% apart. What he was seeing is the counter-motion illusion: two adjacent rows
   moving opposite ways have a RELATIVE velocity of twice either one, so whichever row the eye is
   not tracking reads as faster. That cannot be fixed by equalising speed — it already is equal.
   Slowing both is what actually helps, because it cuts the relative velocity too.
   200s puts each row at ~27px/s, i.e. ~54px/s relative — calmer than the ORIGINAL single row
   ever was at 92px/s. */
.gal-track   { animation-duration: 200s !important; }
/* Row B runs the same keyframe backwards, so it drifts right. `reverse` is used rather than a
   second keyframe so both rows can never drift out of sync with each other's timing. */
.gal-track-b { animation-direction: reverse !important; }
/* the two rows sit closer than a normal block gap — they read as one wall, not two strips */
.gal-marquee-b { margin-top: clamp(12px, 1.4vw, 20px); }

@media (prefers-reduced-motion: reduce) {
  /* rule 49 already stops the animation and makes each row swipeable; nothing to add beyond
     keeping the second row from inheriting a direction it no longer uses */
  .gal-track-b { animation-direction: normal !important; }
}

/* ---- 66. Trim the dead space under the gallery (2026-08-20, Agung: "make this area space
        shorter"). Measured before cutting: the beige below the last photo row was 143px —
        85px of the section's own bottom padding PLUS ~58px contributed by the separator
        itself, because rule 56's artwork fills from its top down to the stepped edge with the
        ABOVE colour, so the shallowest step (y=189 of 296) means ~64% of the 91px strip is
        soga no matter what.

        So the separator was already supplying the breathing room, and the padding was paying
        for it twice. Only the BOTTOM padding is cut — the top still has to separate the
        heading from the separator above it, and asymmetry is correct here precisely because
        nothing follows the photos. Strip height itself is left alone: it is shared by all six
        separators, and shrinking it to fix one section would change the whole page. */
/* Cut again on a second pass. The separator below already contributes ~58px of soga (its
   artwork fills down to the stepped edge), so the section's own bottom padding can go to
   almost nothing without the photos touching the step. */
.gallery-sec { padding-bottom: clamp(2px, 0.5vw, 8px); }

/* ---- 67. Every section's bottom padding matched to the gallery's (2026-08-20, Agung:
        "buat semua section padding bawahnya sama seperti gallery").

        This works for the same reason rule 66 worked: each of these sections is followed by a
        stepped separator, whose artwork fills from its top down to the step with the section's
        OWN colour — ~58px of the 91px strip, every time. So the separator already supplies the
        breathing room and the padding was paying for it twice, five times over.

        ONE EXCEPTION, deliberate: `.loc-section` keeps its padding. It is the only block with
        NO separator after it — the footer begins immediately (the hard edge noted back in rule
        56). Dropping it to 6px would jam Get Directions and the map into the footer. Verified
        from the DOM rather than assumed: walking `#view-landing`'s children shows a `.wave-sep`
        after reviews / plan / kids / gallery / faq / cta, and nothing after location.

        Top padding is untouched — it still has to separate a heading from the separator above. */
.section, .cta-band, .reviews-section { padding-bottom: clamp(2px, 0.5vw, 8px) !important; }
/* the one block the reasoning does not apply to */
.loc-section { padding-bottom: clamp(66px, 8vw, 104px) !important; }

/* ---- 68. The language toggle's sliding indicator was a hard rectangle inside a pill
        (2026-08-20, Agung: "fix the rounded corner").

        Rule 1's global `border-radius: 0 !important` again — but with a blind spot worth
        recording: the re-grant at rule 41 is `.lang-toggle *`, and **`*` does not match a
        `::before` pseudo-element**. So the indicator added in rule 61b kept radius 0 while its
        container is a 999px pill. Measured: container 999px, indicator 0px.

        A pill inside a pill nests concentrically (both clamp to height/2), which is why 999px
        is right here rather than trying to compute outer-minus-padding.
        The buttons get it too: rule 41 had left them at --r-sm (10px), invisible while they are
        transparent, but their HOVER fill is a rounded rect inside a pill. */
.lang-toggle::before { border-radius: 999px !important; }
.lang-toggle .lang-btn,
.lang-toggle .lang-btn:hover,
.lang-toggle .lang-btn.active { border-radius: 999px !important; }

/* ---- 69. Glass on the sticky top bar (2026-08-20, Agung: "waktu jadi sticky menu diatas,
        bisa terapkan blur gak? jadi seperti glass di hero").

        THE BLUR WAS ALREADY THERE AND HAS BEEN DEAD SINCE THE RESTYLE. The base `.topbar`
        (line ~136) ships `backdrop-filter: blur(10px)` with `rgba(255,255,255,.92)` — then
        rule 6 overrode the background with a fully opaque `#fff`, so there was nothing
        translucent left to blur through. Same pattern as the ghost selectors: a later rule
        quietly killed an earlier feature. This restores the translucency.

        HOW TRANSPARENT IT CAN GO IS MEASURED, NOT CHOSEN. Worst case is a black region of a
        hero photo behind the bar; every label in the bar has to clear 4.5:1 against that.

        I got this wrong once and it is worth recording why: I first computed the limit from
        `#C4006F`, the darkened pink rule 51d gave the BLOG link — but **rule 57 later repointed
        that link to the indigo accent `#26456E`**, which is dark and behaves like ink. Reading
        the colours out of the DOM instead of the stylesheet corrected it:
          Packages / FAQ / Visit  ink      12.5:1
          BLOG                    indigo    6.2:1
          "ID" (inactive)         muted     4.6:1   <- the real constraint
        The muted toggle label, not the BLOG link, is what caps this. It reaches the 4.5 floor
        at **0.81**, so 0.82 is used — 18% transparency rather than the 12% my wrong assumption
        allowed. Blur and saturation are pushed on top, the standard frosted recipe. */
.topbar {
  background: rgba(255, 255, 255, .82) !important;
  -webkit-backdrop-filter: blur(18px) saturate(160%);
          backdrop-filter: blur(18px) saturate(160%);
}
/* No backdrop-filter (older Firefox with it disabled) → go opaque rather than leave the nav
   sitting semi-transparent over a photo with no blur to separate it. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .topbar { background: #fff !important; }
}
/* Someone who has asked the OS for less transparency should not get a see-through nav. */
@media (prefers-reduced-transparency: reduce) {
  .topbar { background: #fff !important; -webkit-backdrop-filter: none; backdrop-filter: none; }
}

/* ---- 70. Contact bubble: emoji instead of the glyph, kunyit instead of green
        (2026-08-20, Agung: "ganti icon contact us dengan emoji, dan warna jadi orange coba").

        👋 is the emoji the Jitter reference used, and rule 59 already animates a wave on this
        element — so the icon and the motion now agree instead of a phone glyph pretending to
        wave. The wave selector is retargeted from `svg` to `.wa-emoji`.

        THE COLOUR WAS TRIED ORANGE AND REVERTED, on Agung's call the same day: kunyit was
        rendered, he looked at it and said "revert ke ijo aja, tapi tetep emoji". So the pill
        keeps WhatsApp green and only the icon changed. That is the better outcome anyway —
        dropping the glyph already costs some recognition, and dropping the green too would
        have left nothing saying "WhatsApp" except the aria-label. Green + 👋 still reads as
        the WhatsApp button; kunyit + 👋 read as a generic greeting.

        Ink label on #25D366 measures 9.79:1, so rule 51b's fix survives the icon swap — that
        label was the worst contrast on the entire site (1.98:1 white) before it was corrected,
        so it is the one thing here not to disturb. */
.wa-bubble { background: #25D366 !important; }
.wa-bubble:hover { background: #1EBE5B !important; }
.wa-emoji {
  flex-shrink: 0; font-size: 24px; line-height: 1;
  /* emoji sit high in their box; nudge so the pill looks optically centred */
  display: block; transform-origin: 68% 72%;
  /* Agung chose Apple's drawing (2026-08-20, "apple aja") — which needs NO webfont: system
     emoji already gives each visitor their own platform's version, and Apple devices get
     Apple's. Naming the stack explicitly rather than leaving it to fallback: the span
     inherits `--sans` (Roboto Mono), which has no glyph for U+1F44B, so the emoji was being
     picked by the browser walking an unpredictable monospace fallback chain. Asking for the
     emoji fonts by name pins Apple first where it exists and keeps sizing stable elsewhere.
     Apple Color Emoji is deliberately NOT shipped as an asset — it is proprietary and licensed
     for use ON Apple platforms only, so serving the artwork from this domain would breach that.
     Noto was measured as an alternative (1.6 KB subset, OFL, identical on every device) and
     not taken. */
  font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
}
/* retarget rule 59's wave from the old <svg> to the emoji */
.wa-bubble svg { animation: none !important; }
.wa-bubble .wa-emoji { animation: wa-wave 4s ease-in-out 1.6s 3 both; }
.wa-bubble:hover .wa-emoji { animation: none; }
@media (prefers-reduced-motion: reduce) { .wa-bubble .wa-emoji { animation: none !important; } }
@media (max-width: 560px) { .wa-bubble .wa-emoji { animation: none; } }
