/* ============================================================
   Pabrik Batik — "Woven Clarity"
   Component/styling layer. All values come from css/tokens.css.
   ------------------------------------------------------------
   NOTE ON SPECIFICITY
   The Tailwind Play CDN injects its utilities into <head> *after*
   this stylesheet, so a plain `.glass-card` would lose to `bg-white`
   at equal specificity. Selectors here are therefore doubled
   (`.glass-card.glass-card`) or element-qualified (`h1.font-heading`)
   to win cleanly — no !important needed.
   ============================================================ */

/* Only a flat colour lives on the root. It backs the rubber-band overscroll
   area on iOS (where a fixed layer cannot reach) so the page never flashes
   white when you pull past the top or bottom. */
html {
  scroll-behavior: smooth;
  background-color: var(--color-canvas);
}

/* ---------- Ambient colour mesh ----------
   This used to ride on `html` with `background-attachment: fixed`, which iOS
   Safari does not honour — it silently falls back to `scroll`. With `scroll`
   the painting area becomes the whole document (14000px+ here) instead of the
   viewport, and since the layers are `no-repeat` the mesh only ever painted in
   the top screenful; the remaining ~94% of the page was flat cream.

   A `position: fixed` ELEMENT is fully supported on iOS, unlike a fixed
   background, so the mesh moves onto its own layer. It also self-sizes to the
   visual viewport as Safari's URL bar collapses, which is why no `100vh` is
   involved — `100vh` on iOS resolves to the *largest* viewport height and
   would leave the mesh mis-scaled while the toolbars are shown. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -2;
  pointer-events: none;
  background-color: var(--color-canvas);
  background-image: var(--canvas-lighting), var(--gradient-canvas);
  background-repeat: no-repeat;
  background-size: cover;
}

/* Let the html canvas gradient show through the body. */
body.antialiased {
  background-color: transparent;
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--color-primary);
}

/* ---------- Linen / canvas grain ---------- */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: var(--texture-opacity);
  background-image:
    repeating-linear-gradient(0deg,  rgba(26,31,58,0.5) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(90deg, rgba(26,31,58,0.5) 0 1px, transparent 1px 3px),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: auto, auto, 180px 180px;
}

/* ============================================================
   VERTICAL RHYTHM
   Replaces the uniform py-20/py-24 that every section carried. Applied per
   section by weight — see css/tokens.css for the scale and the reasoning.
   `-b-` variants pad the bottom only, for sections whose content deliberately
   overlaps the band aboveit (the contact info cards pull up with -mt).
   ============================================================ */

.rhythm-xl { padding-block: var(--rhythm-xl); }
.rhythm-lg { padding-block: var(--rhythm-lg); }
.rhythm-md { padding-block: var(--rhythm-md); }
.rhythm-sm { padding-block: var(--rhythm-sm); }

.rhythm-b-xl { padding-bottom: var(--rhythm-xl); }
.rhythm-b-lg { padding-bottom: var(--rhythm-lg); }
.rhythm-b-md { padding-bottom: var(--rhythm-md); }

/* ============================================================
   TYPOGRAPHY
   Poppins 700 headings / Lato 400 body / Playfair Display italic quotes.
   Element+class selectors override the Tailwind text-* utilities.
   ============================================================ */

h1.font-heading,
h2.font-heading,
h3.font-heading {
  font-family: var(--font-heading);
  font-weight: 700;
}

h1.font-heading {
  font-size: var(--fs-hero);
  line-height: var(--lh-hero);
  letter-spacing: -0.02em;
}

h2.font-heading {
  font-size: var(--fs-h2);
  line-height: var(--lh-h2);
  letter-spacing: -0.015em;
}

h3.font-heading {
  font-size: var(--fs-h3);
  line-height: var(--lh-h3);
  letter-spacing: -0.005em;
}

/* ---------- Gradient headline fill ----------
   Scoped by the colour utility already on each heading, so the light-section
   and dark-section treatments never collide. Inline children that set their
   own colour (the terracotta highlight span in the hero) are unaffected,
   because background-clip only ever applies to the element's own background. */
h1.font-heading[class~="text-maroon"],
h2.font-heading[class~="text-maroon"],
h1.font-heading[class~="text-white"],
h2.font-heading[class~="text-white"] {
  -webkit-background-clip: text;
          background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}

/* Every stop stays dark enough to read as solid ink — the gradient shows as a
   blue shimmer travelling through the word, not as the text fading out. */
h1.font-heading[class~="text-maroon"],
h2.font-heading[class~="text-maroon"] {
  background-image: linear-gradient(112deg,
                      rgb(var(--primary-rgb)) 0%,
                      rgb(31 45 96) 32%,
                      rgb(44 62 122) 52%,
                      rgb(28 38 78) 72%,
                      rgb(var(--primary-rgb)) 100%);
}

/* Holds near-solid white and only eases at the very end, so the headline keeps
   its weight instead of dissolving. */
h1.font-heading[class~="text-white"],
h2.font-heading[class~="text-white"] {
  background-image: linear-gradient(168deg,
                      #ffffff 0%,
                      #ffffff 52%,
                      rgb(226 232 252) 78%,
                      rgba(255, 255, 255, 0.92) 100%);
}

/* `-webkit-text-fill-color` INHERITS and outranks a descendant's own `color`,
   so an inline highlight inside a gradient headline would render invisible.
   Hand painting back to each child's own colour. */
h1.font-heading[class~="text-maroon"] > *,
h2.font-heading[class~="text-maroon"] > *,
h1.font-heading[class~="text-white"] > *,
h2.font-heading[class~="text-white"] > * {
  -webkit-text-fill-color: currentColor;
  color: inherit;
}

/* Matches both the solid CTA button pattern (bg-accent) and the translucent
   hero highlight (bg-accent/80) — attribute ~= only matches whole tokens, so
   an opacity-modified class needs its own entry rather than falling through. */
h1.font-heading > [class~="bg-accent"],
h2.font-heading > [class~="bg-accent"],
h1.font-heading > [class~="bg-accent/80"],
h2.font-heading > [class~="bg-accent/80"] {
  color: #ffffff;
  -webkit-text-fill-color: #ffffff;
}

p, li, span, button, input, textarea, select, label {
  font-family: var(--font-body);
}

p {
  line-height: var(--lh-body);
}

/* Quote / testimonial accent */
p.italic,
blockquote,
.font-accent {
  font-family: var(--font-accent);
  font-style: italic;
  font-weight: 400;
  line-height: 1.65;
  letter-spacing: 0.005em;
}

p.italic {
  font-size: 1.0625rem;   /* Playfair runs small — nudge up from text-sm */
}

/* Caption / eyebrow pills */
.tracking-widest {
  letter-spacing: var(--tracking-caption);
}

/* ============================================================
   GLASSMORPHISM
   ============================================================ */

/* `position: relative` is only here to anchor the ::before rim and ::after
   batik corner. Setting it unconditionally at this specificity silently beat
   Tailwind's own `absolute`/`fixed` utilities (0,1,0), so a glass card meant to
   float over something was quietly demoted to in-flow. Exempt those — an
   absolutely positioned box anchors the pseudo-elements just as well. */
.glass-card.glass-card:not([class~="absolute"]):not([class~="fixed"]) {
  position: relative;
}

.glass-card.glass-card {
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-border);
  border-radius: var(--glass-radius);
  box-shadow: var(--glass-shadow);
  transition: var(--transition-glass);
}

/* Interactive cards only — static panels keep the default state. */
.glass-card.glass-card.card-hover:hover,
.glass-card.glass-card.is-interactive:hover {
  background: var(--glass-bg-hover);
  -webkit-backdrop-filter: var(--glass-blur-hover);
  backdrop-filter: var(--glass-blur-hover);
  border-color: var(--glass-border-hover);
  box-shadow: var(--glass-shadow-hover);
  transform: translateY(-4px);
}

/* Glass sitting on the deep-indigo hero / CTA bands */
.glass-card.glass-card.glass-card--dark {
  background: var(--glass-bg-dark);
  border-color: var(--glass-border-dark);
  box-shadow: var(--glass-shadow-dark);
}

.glass-card.glass-card.glass-card--dark.card-hover:hover {
  background: var(--glass-bg-dark-hover);
  border-color: rgba(255, 255, 255, 0.3);
}

/* ---------- Specular rim ----------
   A 1px gradient ring drawn with the two-layer mask trick: one mask covers the
   content box, one covers the whole box, and `exclude` leaves only the 1px
   padding band. That gives a border whose brightness varies around the edge —
   impossible with a plain `border`, which can only be one flat colour.
   pointer-events:none keeps it clear of clicks; the mask hides everything but
   the band, so it never tints the content underneath. */
.glass-card.glass-card::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: var(--glass-rim);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
}

.glass-card.glass-card.glass-card--dark::before {
  background: var(--glass-rim-dark);
}

/* Without mask-composite the cut-out never happens and the ring would paint as
   a solid gradient slab over the whole card. Withdraw it rather than risk that;
   the flat border underneath still gives those browsers an edge. */
@supports not ((mask-composite: exclude) or (-webkit-mask-composite: xor)) {
  .glass-card.glass-card::before,
  body > section.batik-overlay:first-of-type > div.grid > div:first-child::before,
  body > section.batik-overlay:first-of-type > div:not(.grid)::before {
    display: none;
  }
}

/* Micro-batik reinforcement in the lower-right corner of selected cards. */
.glass-card.glass-card--woven::after {
  content: "";
  position: absolute;
  right: 0;
  bottom: 0;
  width: 96px;
  height: 96px;
  border-radius: 0 0 var(--glass-radius) 0;
  background-image: url('../assets/batik-pattern.svg');
  background-size: 64px 64px;
  background-repeat: repeat;
  opacity: var(--batik-opacity);
  pointer-events: none;
  -webkit-mask-image: radial-gradient(circle at 100% 100%, #000 0%, transparent 72%);
  mask-image: radial-gradient(circle at 100% 100%, #000 0%, transparent 72%);
}

/* ============================================================
   HERO — real batik cloth behind a deep-indigo scrim
   ------------------------------------------------------------
   Selectors are structural (`:first-of-type` / `:last-of-type` among the
   section children of body) so no markup changes: the first section on every
   page is the hero, the last is the CTA banner.
   ============================================================ */

body > section.batik-overlay:first-of-type {
  background-image: var(--hero-scrim), var(--hero-cloth);
  background-size: cover, cover;
  background-position: center, center 35%;
  background-repeat: no-repeat, no-repeat;
}

/* The cloth already supplies the texture — dial the SVG pattern right back
   so the two do not fight each other. */
body > section.batik-overlay:first-of-type::before {
  opacity: 0.05;
}

/* CTA banner gets the same treatment with a different cloth, so the page
   opens and closes on the same material without repeating one image. */
body > section.batik-overlay:last-of-type {
  background-image: var(--hero-scrim), url('../assets/foto/proses-pewarnaan.webp');
  background-size: cover, cover;
  background-position: center, center;
  background-repeat: no-repeat, no-repeat;
}

body > section.batik-overlay:last-of-type::before {
  opacity: 0.05;
}

/* ---------- Stats band ----------
   A batik-overlay section that is neither the hero nor the closing CTA — site
   wide that is only the statistics band on tentang-kami. Unlike the hero and
   CTA this is a tileable *pattern*, not a photograph, so it repeats at a fixed
   size instead of stretching: the diagonal keeps its angle and the motif stays
   the same size on a phone and on a 2560px monitor. */
body > section.batik-overlay:not(:first-of-type):not(:last-of-type) {
  background-image: var(--band-scrim), url('../assets/foto/pola-batik-parang.jpg');
  background-size: cover, 420px auto;
  background-repeat: no-repeat, repeat;
  background-position: center, center;
}

body > section.batik-overlay:not(:first-of-type):not(:last-of-type)::before {
  opacity: 0.04;
}

/* ---------- Hero content sits on a dark glass pane ---------- */

/* index: two-column hero — only the text column becomes glass. */
body > section.batik-overlay:first-of-type > div.grid > div:first-child,
/* inner pages: single centred block. */
body > section.batik-overlay:first-of-type > div:not(.grid) {
  position: relative;
  /* Solid indigo base guarantees a contrast floor for the copy no matter
     which part of the cloth ends up behind the pane; the gradient on top
     supplies the glass sheen. */
  background-color: rgba(26, 31, 58, 0.44);
  background-image: var(--glass-bg-dark);
  -webkit-backdrop-filter: blur(16px) saturate(140%);
  backdrop-filter: blur(16px) saturate(140%);
  border: 1px solid var(--glass-border-dark);
  border-radius: 16px;
  box-shadow: var(--glass-shadow-dark);
  padding: 2rem 1.5rem;
}

@media (min-width: 640px) {
  body > section.batik-overlay:first-of-type > div.grid > div:first-child,
  body > section.batik-overlay:first-of-type > div:not(.grid) {
    padding: 2.75rem 2.5rem;
  }
}

/* Inner-page hero pane: inset from the viewport edge on small screens without
   losing the centring. Setting a fixed left/right margin here would override
   Tailwind's `mx-auto` and pin the pane to the left on wide screens, so the
   gutter comes from the width instead and the margins stay auto. */
body > section.batik-overlay:first-of-type > div:not(.grid) {
  width: calc(100% - 2rem);
  margin-left: auto;
  margin-right: auto;
}

/* Same specular rim on the hero pane. */
body > section.batik-overlay:first-of-type > div.grid > div:first-child::before,
body > section.batik-overlay:first-of-type > div:not(.grid)::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: var(--glass-rim-dark);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
}

/* Eyebrow pills on the dark bands read as small frosted chips. */
.batik-overlay [class~="bg-white/10"] {
  -webkit-backdrop-filter: blur(8px) saturate(140%);
  backdrop-filter: blur(8px) saturate(140%);
  border: 1px solid rgba(255, 255, 255, 0.22);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
}

/* ---------- Hero stat badge ----------
   It used to be light glass sitting beside the dark hero pane, which read as a
   foreign white blob rather than part of the composition. Recast as dark glass
   in the same language as the pane, with the number carrying the colour. */
body > section.batik-overlay:first-of-type .glass-card.glass-card {
  background-color: rgba(20, 25, 48, 0.60);
  background-image: linear-gradient(150deg,
                      rgba(255, 255, 255, 0.22) 0%,
                      rgba(255, 255, 255, 0.06) 55%,
                      rgba(255, 255, 255, 0.13) 100%);
  -webkit-backdrop-filter: blur(20px) saturate(165%);
          backdrop-filter: blur(20px) saturate(165%);
  border-color: rgba(255, 255, 255, 0.30);
  border-radius: 16px;
  padding: 1.15rem 1.35rem;
  box-shadow: 0 22px 50px rgba(0, 0, 0, 0.45),
              inset 0 1px 0 rgba(255, 255, 255, 0.38);
}

body > section.batik-overlay:first-of-type .glass-card.glass-card::before {
  background: var(--glass-rim-dark);
}

/* The larger chip and roomier padding squeezed the caption onto four lines;
   a little more width puts it back to three. */
body > section.batik-overlay:first-of-type .glass-card.glass-card {
  max-width: 19.5rem;
}

/* The -left-6 overhang only reads as deliberate once the hero splits into two
   columns. Below that the badge lands flush against the viewport edge, so pull
   it back in line with the photo. */
@media (max-width: 1023px) {
  body > section.batik-overlay:first-of-type .glass-card.glass-card {
    left: 0.75rem;
  }
}

/* On phones the badge sits *inside* the photo's lower edge rather than
   overhanging it, so the same overlap idea as desktop survives on a 375px
   screen. The photo is only 16rem tall here, so the badge is slimmed down —
   at its desktop size it would have buried nearly half the image. */
@media (max-width: 639px) {
  body > section.batik-overlay:first-of-type .hero-media > .glass-card.glass-card {
    left: 0.75rem;
    right: 0.75rem;
    bottom: 0.75rem;
    max-width: none;
    margin-left: 0;
    padding: 0.8rem 0.95rem;
    gap: 0.7rem;
    border-radius: 14px;
  }

  body > section.batik-overlay:first-of-type .hero-media > .glass-card [class~="rounded-full"] {
    width: 2.6rem;
    height: 2.6rem;
    font-size: 1rem;
  }

  body > section.batik-overlay:first-of-type .hero-media > .glass-card p {
    font-size: 0.8125rem;
    line-height: 1.35;
  }
}

body > section.batik-overlay:first-of-type .glass-card p {
  color: rgb(var(--canvas-rgb));
  letter-spacing: 0.005em;
  line-height: 1.45;
}

/* The chip had w-12/h-12 but no shrink-0, so flexbox squashed it into an oval
   as soon as the caption pushed against it. Pinning the basis fixes the shape;
   the gradient makes it the focal point instead of dust grey. */
body > section.batik-overlay:first-of-type .glass-card [class~="rounded-full"] {
  flex: 0 0 auto;
  width: 3.25rem;
  height: 3.25rem;
  background-color: transparent;
  background-image: linear-gradient(145deg,
                      rgb(var(--accent-rgb)) 0%,
                      rgb(212 140 84) 50%,
                      rgb(var(--gold-rgb)) 100%);
  color: #ffffff;
  font-size: 1.3125rem;
  line-height: 1;
  letter-spacing: -0.03em;
  box-shadow: 0 10px 26px rgb(var(--accent-rgb) / 0.50),
              0 2px 6px rgb(var(--accent-rgb) / 0.30),
              inset 0 1px 0 rgba(255, 255, 255, 0.55),
              inset 0 -1px 0 rgba(0, 0, 0, 0.10);
}

/* Hero photo frame — brighter rim so it reads as glass-edged, not a flat box. */
body > section.batik-overlay:first-of-type [class~="rounded-3xl"] {
  border-color: rgba(255, 255, 255, 0.28);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.42),
              inset 0 1px 0 rgba(255, 255, 255, 0.35);
}

/* ---------- Hero: layered composition ----------
   From `lg` up the copy pane and the photo share one grid row and overlap by a
   column, so the pane's backdrop-filter frosts the photo edge behind it rather
   than the two sitting side by side as separate blocks. Below `lg` they stack
   normally and none of this applies. */

/* A warm bloom behind the photo, so it sits in light instead of on a flat band. */
.hero-media::before {
  content: "";
  position: absolute;
  inset: -12% -8% -18% -6%;
  border-radius: 50%;
  background: radial-gradient(closest-side,
                rgb(var(--accent-rgb) / 0.42) 0%,
                rgb(var(--gold-rgb) / 0.22) 48%,
                transparent 78%);
  filter: blur(48px);
  pointer-events: none;
  z-index: -1;
}

@media (min-width: 1024px) {
  /* The pane now sits over a bright photograph for part of its width, so the
     indigo floor is raised — white copy keeps its contrast across the overlap
     rather than only over the dark cloth. */
  body > section.batik-overlay:first-of-type > div.grid > div:first-child {
    background-color: rgba(20, 25, 48, 0.72);
    padding-right: 3.25rem;
  }

  /* Nudge the photo down so the pane crosses it off-centre — a square overlap
     reads as a mistake, an offset one reads as composition. */
  .hero-media {
    margin-top: 2.5rem;
  }

  /* The badge's default -left-6 anchor falls inside the overlap zone, where the
     copy pane (z-20) covers it; pinning it to the far right instead ran it into
     the fixed WhatsApp button. Centre it on the photo's lower edge — clear of
     both, and it tracks the photo at any width. Centred with a negative margin
     rather than translateX, because the scroll-reveal rules own `transform` on
     .glass-card and would cancel it. */
  .hero-media > .glass-card {
    left: 50%;
    right: auto;
    margin-left: -9.75rem;   /* half of the 19.5rem max-width */
  }
}

/* ============================================================
   STICKY HEADER — frosted over whatever scrolls beneath it
   ============================================================ */

/* #site-header is an empty mount `<div>` in every page's markup that JS fills
   with the `<header>` via innerHTML — so the div's own box is exactly as tall
   as the header and nothing else. Per the sticky-positioning spec, a sticky
   element can only stay "stuck" while its containing block is still within
   the scrollport; once that block's own height has scrolled past, its sticky
   child scrolls away with it. With a same-height wrapper, that happens the
   instant the header's own height is scrolled — so it never stayed pinned
   past the first ~80px. `display: contents` removes the wrapper's box
   entirely (its child `<header>` becomes a direct flow child of `<body>`,
   which is thousands of pixels tall), giving the sticky header its real
   effective range across the whole page. The DOM node stays put, so
   `document.getElementById("site-header").innerHTML = ...` is untouched. */
#site-header {
  display: contents;
}

#site-header header {
  background-color: rgba(26, 31, 58, 0.72);
  -webkit-backdrop-filter: blur(16px) saturate(150%);
  backdrop-filter: blur(16px) saturate(150%);
  border-bottom: 1px solid rgba(255, 255, 255, 0.10);
  transform: translateY(0);
  transition: transform var(--duration-base) var(--ease-out);
  will-change: transform;
}

/* Toggled by js/main.js while scrolling down past the header's own height;
   scrolling up, being near the top, or opening the mobile menu all clear it. */
#site-header header.header-hidden {
  transform: translateY(-100%);
}

@supports not ((-webkit-backdrop-filter: blur(10px)) or (backdrop-filter: blur(10px))) {
  #site-header header {
    background-color: rgb(26, 31, 58);
  }
  body > section.batik-overlay:first-of-type > div.grid > div:first-child,
  body > section.batik-overlay:first-of-type > div:not(.grid) {
    background: rgba(26, 31, 58, 0.72);
  }
}

/* ============================================================
   PHOTO CROPPING
   Every slot uses object-cover, so a portrait source dropped into a
   landscape slot gets centre-cropped and loses its subject. These rules
   re-anchor the crop per photo. Keyed on the filename so no markup
   changes and the intent stays readable.
   ============================================================ */

/* Portrait sources in landscape slots — bias upward to keep faces/hands. */
img[src$="workshop-produksi.webp"]          { object-position: center 30%; }
img[src$="perusahaan-bpr-dharma-naga.jpg"]  { object-position: center 52%; }
img[src$="produksi-cetak-kain.webp"]        { object-position: center 42%; }

/* Square studio shots — subjects sit low in frame. */
img[src$="seragam-sekolah.jpg"]             { object-position: center 38%; }
img[src$="seragam-sekolah-2.jpg"]           { object-position: center 34%; }
img[src$="seragam-kantor-couple.webp"]      { object-position: center 40%; }

/* Carries a device watermark + timestamp along the bottom edge;
   crop upward so it stays out of frame. */
img[src$="komunitas-organisasi.jpg"]        { object-position: center 40%; }

/* Gallery / portfolio image tiles carry no glass (an opaque photo hides it),
   but their radius is normalised to match the glass surfaces. */
[class~="rounded-2xl"][class~="overflow-hidden"][class~="group"] {
  border-radius: var(--glass-radius);
  box-shadow: var(--glass-shadow);
  transition: var(--transition-glass);
}

[class~="rounded-2xl"][class~="overflow-hidden"][class~="group"]:hover {
  box-shadow: var(--glass-shadow-hover);
}

/* ============================================================
   FEATURE CARDS — the strongest glass on the site
   ------------------------------------------------------------
   `.glass-card--woven` marks exactly the "why choose us" style feature cards.
   Stronger glass means MORE refraction, not more paint: the fill stays fairly
   transparent while blur and saturation climb and the rim brightens. Pushing
   opacity instead would just make them look like plain white boxes.
   ============================================================ */

.glass-card.glass-card.glass-card--woven {
  border-radius: 18px;
  background: linear-gradient(150deg,
                rgba(255, 255, 255, 0.66) 0%,
                rgba(255, 255, 255, 0.30) 46%,
                rgba(255, 255, 255, 0.48) 100%);
  -webkit-backdrop-filter: blur(24px) saturate(190%);
          backdrop-filter: blur(24px) saturate(190%);
  border-color: rgba(255, 255, 255, 0.88);
  box-shadow: 0 20px 52px rgba(26, 31, 58, 0.14),
              0 2px 8px rgba(26, 31, 58, 0.06),
              inset 0 1px 0 rgba(255, 255, 255, 0.98);
}

.glass-card.glass-card.glass-card--woven::before {
  background: linear-gradient(135deg,
                rgba(255, 255, 255, 1) 0%,
                rgba(255, 255, 255, 0.70) 16%,
                rgba(255, 255, 255, 0.14) 46%,
                rgba(255, 255, 255, 0.34) 70%,
                rgba(255, 255, 255, 0.95) 100%);
}

.glass-card.glass-card.glass-card--woven.card-hover:hover {
  background: linear-gradient(150deg,
                rgba(255, 255, 255, 0.78) 0%,
                rgba(255, 255, 255, 0.40) 46%,
                rgba(255, 255, 255, 0.58) 100%);
  -webkit-backdrop-filter: blur(30px) saturate(210%);
          backdrop-filter: blur(30px) saturate(210%);
  box-shadow: 0 28px 64px rgba(26, 31, 58, 0.18),
              0 0 0 1px rgba(255, 255, 255, 0.5),
              inset 0 1px 0 rgba(255, 255, 255, 1);
}

/* Let more of the ambient bloom through the band these cards sit on, so the
   panes have richer colour to refract. */
section.bg-white:has(.glass-card--woven) {
  background-color: rgba(255, 255, 255, 0.18);
}

/* ---------- Feature icons ---------- */

[class~="w-12"][class~="h-12"][class~="rounded-xl"][class~="bg-accent/10"] {
  border-radius: 15px;
  background-color: transparent;
  background-image: linear-gradient(145deg,
                      rgb(var(--accent-rgb)) 0%,
                      rgb(212 140 84) 52%,
                      rgb(var(--gold-rgb)) 100%);
  color: #ffffff;
  box-shadow: 0 10px 22px rgb(var(--accent-rgb) / 0.34),
              0 2px 6px rgb(var(--accent-rgb) / 0.20),
              inset 0 1px 0 rgba(255, 255, 255, 0.60),
              inset 0 -1px 0 rgba(0, 0, 0, 0.06);
  transition: transform var(--duration-fast) var(--ease-out),
              box-shadow var(--duration-fast) var(--ease-out);
}

/* Slightly heavier stroke reads better reversed out on a saturated fill. */
[class~="w-12"][class~="h-12"][class~="rounded-xl"][class~="bg-accent/10"] svg {
  stroke-width: 1.75;
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.18));
}

.card-hover:hover [class~="w-12"][class~="h-12"][class~="rounded-xl"][class~="bg-accent/10"] {
  transform: scale(1.08) translateY(-2px);
  box-shadow: 0 16px 30px rgb(var(--accent-rgb) / 0.42),
              0 3px 8px rgb(var(--accent-rgb) / 0.26),
              inset 0 1px 0 rgba(255, 255, 255, 0.75);
}

@media (hover: none) {
  .card-hover:hover [class~="w-12"][class~="h-12"][class~="rounded-xl"][class~="bg-accent/10"] {
    transform: none;
  }
}

/* Wide translucent bands replacing the old opaque bg-white sections. */
section.bg-white {
  background-color: rgba(255, 255, 255, 0.35);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  border-top: 1px solid rgba(255, 255, 255, 0.5);
  border-bottom: 1px solid rgba(255, 255, 255, 0.5);
}

/* Browsers without backdrop-filter: fall back to opaque-enough surfaces
   so body copy never loses contrast. */
@supports not ((-webkit-backdrop-filter: blur(10px)) or (backdrop-filter: blur(10px))) {
  .glass-card.glass-card {
    background: rgba(255, 255, 255, 0.86);
  }
  .glass-card.glass-card.card-hover:hover,
  .glass-card.glass-card.is-interactive:hover {
    background: rgba(255, 255, 255, 0.94);
  }
  .glass-card.glass-card.glass-card--dark {
    background: rgba(255, 255, 255, 0.14);
  }
  section.bg-white {
    background-color: rgba(255, 255, 255, 0.72);
  }
}

/* ============================================================
   BUTTONS
   ============================================================ */

a[class~="bg-accent"],
button[class~="bg-accent"],
a[class~="border-2"],
button[class~="border-2"] {
  transition: transform var(--duration-fast) var(--ease-out),
              background-color var(--duration-fast) ease,
              border-color var(--duration-fast) ease,
              color var(--duration-fast) ease,
              box-shadow var(--duration-fast) ease;
}

a[class~="bg-accent"]:hover,
button[class~="bg-accent"]:hover {
  transform: scale(1.02);
  box-shadow: 0 6px 22px rgb(var(--accent-rgb) / 0.35),
              0 0 0 1px rgb(var(--accent-rgb) / 0.25);
}

a[class~="border-2"]:hover,
button[class~="border-2"]:hover {
  transform: scale(1.02);
  box-shadow: 0 6px 18px rgb(var(--primary-rgb) / 0.12);
}

a[class~="bg-accent"]:active,
button[class~="bg-accent"]:active,
a[class~="border-2"]:active,
button[class~="border-2"]:active {
  transform: scale(0.99);
}

/* ============================================================
   THREAD DIVIDER  (injected between sections by js/main.js)
   ============================================================ */

.thread-divider {
  display: block;
  width: 100%;
  height: 28px;
  line-height: 0;
  pointer-events: none;
  overflow: hidden;
}

.thread-divider svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* `non-scaling-stroke` keeps the line exactly 1.25px at every width, but it
   also makes stroke-dasharray resolve in SCREEN pixels — which defeats the
   pathLength="1200" normalisation on the path. A fixed dash would therefore
   cover less and less of the line as the viewport grows (only ~47% at
   2560px). js/main.js measures the path in screen space and publishes it as
   --thread-len, remeasuring on resize; the fallback covers a very wide
   screen should the script not run. */
.thread-divider path {
  fill: none;
  stroke: var(--color-dust);
  stroke-width: 1.25;
  stroke-opacity: var(--thread-opacity);
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
  stroke-dasharray: var(--thread-len, 4000);
  stroke-dashoffset: var(--thread-len, 4000);
  animation: thread-draw 1.8s var(--ease-out) 0.15s forwards;
}

@keyframes thread-draw {
  to { stroke-dashoffset: 0; }
}

/* Hold the draw-in until the divider actually scrolls into view, otherwise
   every line below the fold finishes drawing while still invisible. */
.js-reveal .thread-divider path {
  animation-play-state: paused;
}

.js-reveal .thread-divider.is-visible path {
  animation-play-state: running;
}

/* ============================================================
   SCROLL REVEAL
   Gated behind .js-reveal on <html> so content stays visible
   if JavaScript never runs.
   ============================================================ */

.js-reveal .reveal {
  opacity: 0;
  transform: translateY(18px);
  filter: blur(6px);
  transition: opacity var(--duration-base) var(--ease-out),
              transform var(--duration-base) var(--ease-out),
              filter var(--duration-base) var(--ease-out);
}

.js-reveal .reveal.is-visible {
  opacity: 1;
  transform: none;
  filter: blur(0);
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  .js-reveal .reveal {
    opacity: 1;
    transform: none;
    filter: none;
    transition: none;
  }

  .thread-divider path {
    animation: none;
    stroke-dashoffset: 0;
  }

  a[class~="bg-accent"]:hover,
  button[class~="bg-accent"]:hover,
  a[class~="border-2"]:hover,
  button[class~="border-2"]:hover,
  .glass-card.glass-card.card-hover:hover {
    transform: none;
  }
}

/* ============================================================
   EXISTING COMPONENTS  (behaviour unchanged, retokenised)
   ============================================================ */

.font-heading {
  font-family: var(--font-heading);
}

/* Decorative low-opacity batik pattern on the deep-indigo hero / CTA bands */
.batik-overlay {
  position: relative;
  isolation: isolate;
}

.batik-overlay::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: url('../assets/batik-pattern.svg');
  background-repeat: repeat;
  background-size: 120px 120px;
  opacity: 0.14;
  z-index: 0;
  pointer-events: none;
}

.batik-overlay > * {
  position: relative;
  z-index: 1;
}

/* Accordion content collapse animation */
.accordion-panel {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.35s var(--ease-out), padding 0.35s var(--ease-out);
}

.accordion-item.active .accordion-panel {
  max-height: 500px;
}

.accordion-item .accordion-icon {
  transition: transform var(--duration-fast) var(--ease-out);
}

.accordion-item.active .accordion-icon {
  transform: rotate(45deg);
}

/* Mobile nav panel */
#mobile-menu {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.35s var(--ease-out);
}

#mobile-menu.open {
  max-height: 600px;
}

/* Card lift for non-glass cards */
.card-hover {
  transition: transform var(--duration-fast) var(--ease-out),
              box-shadow var(--duration-fast) var(--ease-out),
              background-color var(--duration-fast) ease,
              border-color var(--duration-fast) ease;
}

.card-hover:hover {
  transform: translateY(-4px);
  box-shadow: var(--glass-shadow-hover);
}

/* Line clamp fallback */
.line-clamp-2 {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.line-clamp-3 {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Active nav link indicator */
.nav-link.active {
  color: var(--color-canvas);
  font-weight: 700;
}

.nav-link.active::after {
  content: "";
  display: block;
  height: 2px;
  background-color: var(--color-accent);
  margin-top: 4px;
  border-radius: 9999px;
}

.nav-link-mobile.active {
  background-color: rgba(255, 255, 255, 0.12);
  color: #ffffff;
}

.filter-pill {
  transition: var(--transition-glass);
}

.filter-pill.active {
  background-color: var(--color-accent);
  color: #ffffff;
  border-color: transparent;
}

/* ============================================================
   RESPONSIVE
   Blur is expensive on low-power mobile GPUs and the fixed grain
   layer can tear on scroll — dial both back below the sm breakpoint.
   ============================================================ */

@media (max-width: 639px) {
  :root {
    --glass-blur: blur(10px) saturate(140%);
    --glass-blur-hover: blur(12px) saturate(150%);
  }

  /* The mesh is a fixed layer now, so it needs no mobile fallback — the old
     `background-attachment: scroll` + `100vh` override here was what killed
     the gradient below 640px in the first place. */

  .thread-divider {
    height: 20px;
  }

  .glass-card.glass-card--woven::after {
    width: 72px;
    height: 72px;
  }
}

/* Touch devices get no hover lift — it sticks after tap. */
@media (hover: none) {
  .glass-card.glass-card.card-hover:hover,
  .card-hover:hover,
  a[class~="bg-accent"]:hover,
  a[class~="border-2"]:hover {
    transform: none;
  }
}
