/* Shared site styles — keyframes, pseudo-elements, and effects that Tailwind utilities can't express directly. */

html { scroll-behavior: smooth; }

/* Prevent decorative absolute-positioned elements (blobs, sparkles, oversized SVGs)
   from causing horizontal scroll on small viewports. */
html, body { overflow-x: hidden; }
body { max-width: 100vw; }

/* ...but `hidden` turns <body> into a scroll container, which stops
   `position: sticky` working for anything inside it - the site header among
   them. `clip` trims the overflow the same way without becoming a scroll
   container, so where it is supported the header sticks on every page.
   Older browsers keep `hidden` (and a header that scrolls away). */
@supports (overflow-x: clip) {
  html, body { overflow-x: clip; }
}

/* Pages could also opt out entirely with class="allow-sticky" on <html> and
   take responsibility for clipping their own decoration (see the blob
   wrapper in volunteer-with-us.html). */
html.allow-sticky, html.allow-sticky body { overflow-x: visible; }
html.allow-sticky body { max-width: none; }

/* Long unbreakable strings (URLs, emails, addresses) shouldn't overflow on mobile */
.break-anywhere { overflow-wrap: anywhere; word-break: break-word; }

/* ---------- Active nav link ---------- */
/* Keep the same font-weight as other nav links so JS-added active class
   doesn't reflow the menu on page load (visible "jerk"). */
.nav-active { color: #1f6891; position: relative; }
.nav-active::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: -22px;
  height: 3px;
  background: #f5a623;
  border-radius: 3px;
}
#mobileMenu .nav-active::after { display: none; }
/* Use a border-left that doesn't change layout: apply a transparent 3px border
   to every mobile nav item by default; active items switch only the color. */
#mobileMenu nav a { border-left: 3px solid transparent; padding-left: 10px; }
#mobileMenu .nav-active { border-left-color: #f5a623; }

/* ---------- Compact header on scroll ----------
   main.js adds .is-scrolled to the sticky header once the page moves. The
   bar tightens (64/80px -> 56/64px), the logo shrinks with it and a soft
   shadow lifts the bar off the content. Every page shares the same header
   markup, so these child selectors hold everywhere. */
header.sticky { transition: box-shadow .3s ease, background-color .3s ease; }
header.sticky > div:first-child { transition: height .3s ease; }
header.sticky > div:first-child > a:first-child img { transition: height .3s ease; }

header.sticky.is-scrolled {
  background-color: rgba(255, 255, 255, .96);
  box-shadow: 0 8px 24px -16px rgba(25, 59, 82, .35);
}
header.sticky.is-scrolled > div:first-child { height: 3.5rem; }
header.sticky.is-scrolled > div:first-child > a:first-child img { height: 2.5rem; }
@media (min-width: 768px) {
  header.sticky.is-scrolled > div:first-child { height: 4rem; }
  header.sticky.is-scrolled > div:first-child > a:first-child img { height: 2.75rem; }
}
/* Keep the active-link underline the same 8px above the bar's bottom edge. */
header.sticky.is-scrolled .nav-active::after { bottom: -14px; }

/* ---------- Scroll reveal ---------- */
.reveal { opacity: 0; transform: translateY(24px); transition: all .7s ease-out; }
.reveal.visible { opacity: 1; transform: none; }

/* ---------- Hero background ---------- */
.hero-bg {
  background:
    radial-gradient(1200px 500px at 10% 0%, rgba(74, 157, 200, .22), transparent 60%),
    radial-gradient(900px 600px at 100% 100%, rgba(20, 163, 163, .18), transparent 55%),
    linear-gradient(180deg, #f6fbfd 0%, #ffffff 70%);
}

/* Ambient blobs */
.blob {
  position: absolute;
  border-radius: 50%;
  filter: blur(60px);
  opacity: .55;
  animation: float 14s ease-in-out infinite;
  pointer-events: none;
}
.blob.b1 { width: 360px; height: 360px; background: #aed8ea; top: -60px; left: -80px; }
.blob.b2 { width: 280px; height: 280px; background: #f5a623; opacity: .28; bottom: -60px; right: 10%; animation-delay: -5s; }
.blob.b3 { width: 220px; height: 220px; background: #14a3a3; opacity: .22; top: 40%; left: 45%; animation-delay: -9s; }
@keyframes float {
  0%, 100% { transform: translate(0,0) scale(1); }
  50% { transform: translate(30px, -25px) scale(1.08); }
}

/* ---------- Hand-drawn doodle texture ----------
   Lays the care motifs in assets/images/hero-graffiti.svg across the hero as a
   single scaled image - see the note in the rule about why it is not tiled.

   `opacity` is the one dial for how faint this reads. .07 is a whisper; go to
   about .12 to make it plainly visible, and no higher or it starts competing
   with the headline. */
.hero-graffiti {
  /* One wide canvas, laid once and scaled to cover - deliberately NOT tiled.
     Any repeating background shows its grid here: with a sparse set of marks
     the eye picks out the same heart or pulse recurring at the same height
     across the width. Every mark now exists exactly once.
     To change how many or where, edit hero-graffiti.svg itself. */
  background-image: url('../images/hero-graffiti.svg');
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  opacity: .08;
}

/* The stencil mural behind the hero. Its own dial, separate from the doodles:
   at .06 it is barely there — a suggestion of a wall behind the line-art
   portrait rather than a second image competing with it.

   The background-image is declared only inside the media query, so narrow
   screens never fetch the file at all. */
.hero-stencil { display: none; }

@media (min-width: 1024px) {
  .hero-stencil {
    display: block;
    position: absolute;
    top: 0;
    bottom: 0;
    opacity: .06;
    pointer-events: none;
  }
  .hero-stencil-r {
    right: -70px;
    width: 360px;
    background: url('../images/hero-stencil-right.webp') right center / auto 100% no-repeat;
  }
}

/* Dotted texture */
.grid-dots {
  background-image: radial-gradient(rgba(31,104,145,.18) 1px, transparent 1px);
  background-size: 22px 22px;
}

/* ---------- Rotating word ---------- */
.word-rotator { display: inline-grid; vertical-align: bottom; }
.word-rotator > span {
  grid-area: 1 / 1;
  opacity: 0;
  transform: translateY(40%);
  animation: wordCycle 8s infinite;
  color: #0f8686;
}
.word-rotator > span:nth-child(1) { animation-delay: 0s; }
.word-rotator > span:nth-child(2) { animation-delay: 2s; }
.word-rotator > span:nth-child(3) { animation-delay: 4s; }
.word-rotator > span:nth-child(4) { animation-delay: 6s; }
@keyframes wordCycle {
  0%, 20% { opacity: 1; transform: translateY(0); }
  25%, 100% { opacity: 0; transform: translateY(-40%); }
}

/* ---------- Photo mosaic tiles ---------- */
.tile { overflow: hidden; border-radius: 1.25rem; box-shadow: 0 20px 50px -20px rgba(25,59,82,.35); }
.tile img { transition: transform .9s ease; }
.tile:hover img { transform: scale(1.06); }
.tile-1 { transform: rotate(-1.5deg); }
.tile-2 { transform: rotate(2deg); }
.tile-3 { transform: rotate(-1deg); }

/* Floating badge bob */
.float-badge { animation: bob 5s ease-in-out infinite; }
@keyframes bob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-8px); }
}

/* ---------- Marquee ticker ---------- */
.marquee { display: flex; gap: 3rem; animation: marquee 30s linear infinite; }
@keyframes marquee {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

/* Hero fits viewport on lg+ screens */
@media (min-width: 1024px) {
  .hero-section {
    height: calc(100vh - 5rem);
    min-height: 620px;
  }
}

/* ---------- Hero entrance animations ---------- */
.fade-up { opacity: 0; transform: translateY(28px); animation: fadeUp .9s cubic-bezier(.22,.61,.36,1) forwards; }
.fade-up.d1 { animation-delay: .10s; }
.fade-up.d2 { animation-delay: .25s; }
.fade-up.d3 { animation-delay: .40s; }
.fade-up.d4 { animation-delay: .55s; }
.fade-up.d5 { animation-delay: .70s; }
@keyframes fadeUp { to { opacity: 1; transform: translateY(0); } }

/* ---------- Hero portrait, below lg ----------
   On phones and tablets the portrait leads the page alone - no stencils, no
   badge - and a still drawing there read as flat. So it inks itself in from
   the bottom up, then sways very slightly; the glow behind it breathes; and
   a few hand-drawn care marks (heart, pulse, plus signs) draw in around it
   once the portrait has landed. Desktop keeps its plain fade-up. */
.hero-doodles .doodle { position: absolute; height: auto; overflow: visible; opacity: 0; }

@media (max-width: 1023px) {
  .hero-portrait.fade-up {
    opacity: 1;
    transform: none;
    -webkit-mask-image: linear-gradient(to top, #000 45%, transparent 55%);
            mask-image: linear-gradient(to top, #000 45%, transparent 55%);
    -webkit-mask-size: 100% 250%;
            mask-size: 100% 250%;
    -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;
    transform-origin: 50% 100%;
    animation: portraitInk 1.6s cubic-bezier(.45,.05,.35,1) .3s both,
               portraitSway 7s ease-in-out 2.2s infinite;
  }

  .hero-glow { animation: heroGlow 5s ease-in-out infinite; }

  .hero-doodles .doodle-heart {
    animation: doodlePop .5s cubic-bezier(.34,1.56,.64,1) 1.7s forwards,
               heartbeat 2.4s ease-in-out 2.4s infinite;
  }
  .hero-doodles .doodle-plus {
    animation: doodlePop .5s cubic-bezier(.34,1.56,.64,1) var(--d, 2s) forwards,
               doodleTwinkle 3.2s ease-in-out calc(var(--d, 2s) + .5s) infinite;
  }
  .hero-doodles .doodle-pulse { opacity: 1; }
  .hero-doodles .doodle-pulse path {
    stroke-dasharray: 100;
    stroke-dashoffset: 100;
    animation: pulseTrace 3.4s ease-in-out 1.9s infinite;
  }
}

/* The mask is 250% tall: at 0% only its transparent top covers the art; at
   100% only its solid bottom does - so the ink rises up the figure. */
@keyframes portraitInk {
  from { -webkit-mask-position: 0 0%;   mask-position: 0 0%;   opacity: .3; }
  to   { -webkit-mask-position: 0 100%; mask-position: 0 100%; opacity: 1; }
}
@keyframes portraitSway {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50%      { transform: translateY(-6px) rotate(.6deg); }
}
@keyframes heroGlow {
  0%, 100% { transform: translateX(-50%) scale(.92); opacity: .75; }
  50%      { transform: translateX(-50%) scale(1.08); opacity: 1; }
}
@keyframes doodlePop {
  from { opacity: 0; transform: scale(.4) rotate(-20deg); }
  to   { opacity: 1; transform: scale(1) rotate(0deg); }
}
@keyframes doodleTwinkle {
  0%, 100% { transform: scale(1) rotate(0deg); opacity: 1; }
  50%      { transform: scale(.7) rotate(45deg); opacity: .55; }
}
/* Draws the trace, holds it, then lets it run off the far end. */
@keyframes pulseTrace {
  0%   { stroke-dashoffset: 100; }
  45%  { stroke-dashoffset: 0; }
  70%  { stroke-dashoffset: 0; }
  100% { stroke-dashoffset: -100; }
}

@media (prefers-reduced-motion: reduce) {
  .hero-portrait.fade-up {
    -webkit-mask-image: none; mask-image: none;
    animation: none !important; opacity: 1 !important; transform: none !important;
  }
  .hero-glow { animation: none !important; }
  .hero-doodles .doodle { animation: none !important; opacity: 1 !important; transform: none !important; }
  .hero-doodles .doodle path { animation: none !important; stroke-dashoffset: 0 !important; }
}

.tile-in { opacity: 0; animation: tileIn 1s cubic-bezier(.22,.61,.36,1) forwards; }
.tile-1.tile-in { transform: translate(40px, 20px) rotate(-1.5deg); animation-delay: .35s; --tilt: -1.5deg; }
.tile-2.tile-in { transform: translate(40px, -20px) rotate(2deg); animation-delay: .55s; --tilt: 2deg; }
.tile-3.tile-in { transform: translate(40px, 20px) rotate(-1deg); animation-delay: .75s; --tilt: -1deg; }
@keyframes tileIn { to { opacity: 1; transform: translate(0,0) rotate(var(--tilt, 0deg)); } }

/* SVG underline draw-in */
.underline-draw { stroke-dasharray: 400; stroke-dashoffset: 400; animation: drawUnderline 1.2s ease-out .9s forwards; }
@keyframes drawUnderline { to { stroke-dashoffset: 0; } }

/* Shine sweep on main tile */
.tile-1::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(120deg, transparent 30%, rgba(255,255,255,.35) 50%, transparent 70%);
  transform: translateX(-120%);
  animation: shine 3.5s ease-out 1.4s;
  z-index: 2;
  pointer-events: none;
}
@keyframes shine { to { transform: translateX(120%); } }

/* Pop-in */
.pop-in { opacity: 0; transform: scale(.85); animation: popIn .6s cubic-bezier(.34,1.56,.64,1) 1.2s forwards; }
@keyframes popIn { to { opacity: 1; transform: scale(1); } }

/* CTA glow */
.cta-glow { position: relative; }
.cta-glow::after {
  content: '';
  position: absolute;
  inset: -2px;
  border-radius: 9999px;
  background: radial-gradient(closest-side, rgba(245,166,35,.6), transparent);
  opacity: 0;
  animation: ctaGlow 3s ease-in-out 2s infinite;
  z-index: -1;
}
@keyframes ctaGlow {
  0%, 100% { opacity: 0; transform: scale(.9); }
  50% { opacity: .6; transform: scale(1.05); }
}

/* ---------- Donate button heart ----------
   A double thump then a long rest, which is how a heartbeat actually reads —
   an even pulse just looks like a wobble. Applied to the <svg> itself, so
   transform-origin behaves without needing transform-box. */
.heartbeat {
  transform-origin: center;
  animation: heartbeat 2.4s ease-in-out infinite;
}
@keyframes heartbeat {
  0%  { transform: scale(1); }
  7%  { transform: scale(1.22); }
  14% { transform: scale(1); }
  21% { transform: scale(1.15); }
  28% { transform: scale(1); }
  /* the remaining ~70% of the cycle is the rest between beats */
  100% { transform: scale(1); }
}

/* Lean in when someone is about to click. */
a:hover > .heartbeat { animation-duration: 1.1s; }

/* ---------- Request a camp button icon ----------
   Sits next to the beating heart, so it deliberately does NOT pulse: two
   scale animations side by side read as clutter. This one rests for most of
   the cycle then gives a single small nudge. The delay keeps it from firing
   in lockstep with the heart. */
.icon-nudge {
  transform-origin: center;
  animation: iconNudge 4s ease-in-out .8s infinite;
}
@keyframes iconNudge {
  0%, 70%, 100% { transform: rotate(0deg) scale(1); }
  76%           { transform: rotate(-12deg) scale(1.14); }
  82%           { transform: rotate(9deg) scale(1.1); }
  88%           { transform: rotate(-4deg) scale(1.06); }
  94%           { transform: rotate(0deg) scale(1); }
}

a:hover > .icon-nudge { animation-duration: 1.6s; }

/* "Do" cards */
.do-card { transition: transform .4s ease, box-shadow .4s ease; }
.do-card:hover { transform: translateY(-6px); box-shadow: 0 30px 60px -25px rgba(25,59,82,.35); }
.do-card .cover { transition: transform .7s ease; }
.do-card:hover .cover { transform: scale(1.08); }

/* ---------- Hero second figure — CURRENTLY HIDDEN ----------
   A stencil figure that ran the full height of the hero's left edge, opposite
   the right-hand mural.

   It is switched off: the media query below is commented out, so the element
   stays display:none at every width and the browser never fetches the image.
   Uncomment that block to bring it back exactly as it was. */
.hero-figure-2 { display: none; }

/*
@media (min-width: 1024px) {
  .hero-figure-2 {
    display: block;
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    // Proportional, not a fixed width: the background is sized `auto 100%`, so
    // the figure grows with the hero's height. A fixed box clipped its right
    // edge on tall viewports. 45% is always wider than the figure can get, and
    // the surplus is empty and click-through.
    width: 45%;
    background: url('../images/hero-figure-left.webp') left bottom / auto 100% no-repeat;
    opacity: .08;
    pointer-events: none;
  }
}
*/

/* ---------- Our Supporters cards ----------
   Deliberately unlike the rounded, bordered cards used everywhere else on the
   site: a hand-drawn frame in the same hand as the hero doodles, each card
   pinned at a slight angle as if placed by hand. Straightens and lifts on
   hover, so the wall feels touchable rather than static.

   The logos keep their own colours - no grayscale. */
.supporter-card {
  background-image: url('../images/sketch-frame.svg');
  background-size: 100% 100%;
  background-repeat: no-repeat;
  /* These cards also carry .reveal, and this rule is later in the file, so it
     replaces .reveal's `transition: all .7s`. Opacity is listed here at that
     same duration or the scroll-in would snap rather than fade. The rotation
     below likewise overrides .reveal's translateY, so the cards fade in on the
     spot instead of sliding - which is what the pinned look wants anyway. */
  transition: transform .35s cubic-bezier(.22,.61,.36,1),
              box-shadow .35s ease,
              opacity .7s ease-out;
}
.supporter-card:nth-child(odd)  { transform: rotate(-0.9deg); }
.supporter-card:nth-child(even) { transform: rotate(0.9deg); }

/* Only the linked cards react - myMD has no site yet and should not look
   clickable. */
a.supporter-card:hover,
a.supporter-card:focus-visible {
  transform: rotate(0deg) translateY(-5px);
  box-shadow: 0 18px 35px -18px rgba(25, 59, 82, .4);
}

@media (prefers-reduced-motion: reduce) {
  .supporter-card,
  a.supporter-card:hover,
  a.supporter-card:focus-visible { transform: none; }
}

/* Partner logo treatment.
   The .group rule lets a whole tile drive the effect, so the logo comes to
   colour when the cursor is anywhere on its card - not only on the mark
   itself. The bare :hover keeps working for logos that stand on their own
   (the partner strip on the archive page). */
.partner { filter: grayscale(1); opacity: .75; transition: all .3s ease; }
.partner:hover,
.group:hover .partner { filter: none; opacity: 1; transform: translateY(-3px); }

/* ---------- The `hidden` attribute must always win ----------
   Tailwind's preflight puts `[hidden] { display: none }` in the base layer,
   so any display utility on the same element (flex, grid, block …) overrides
   it and the element stays visible. Several elements here are toggled via the
   `hidden` property in JS *and* carry a display utility for their open state
   (the gallery lightbox is `flex`, the camp results are `grid`), so without
   this the lightbox covers the whole page on load. */
[hidden] { display: none !important; }

/* ---------- Archive timeline (archive.html) ---------- */
/* The vertical rail the year markers sit on. The markers are 32px (40px from
   sm up) and pinned to left:0, so the rail is centred under them. */
.archive-rail { position: relative; }
.archive-rail::before {
  content: '';
  position: absolute;
  left: 15px;
  top: 10px;
  bottom: 10px;
  width: 2px;
  background: #e2e8f0;
}
@media (min-width: 640px) {
  .archive-rail::before { left: 19px; }
}

/* Hide the native disclosure triangle — each year has its own chevron. */
.archive-year summary { list-style: none; }
.archive-year summary::-webkit-details-marker { display: none; }
.archive-year summary::marker { content: ''; }

/* An opening year unfolds downward from its heading. */
.archive-year[open] > div { animation: archiveOpen .4s cubic-bezier(.22,.61,.36,1); }
@keyframes archiveOpen {
  from { opacity: 0; transform: translateY(-12px); }
  to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .archive-year[open] > div { animation: none; }
}

/* ---------- Puja camp finder (durga-puja-camps.html) ---------- */
/* Cards are injected by camps.js after the IntersectionObserver has already
   run, so they use the CSS-only .fade-up entrance instead of .reveal. */
.camp-card { animation-duration: .55s; }

/* Respect reduced-motion for entrance animations. */
@media (prefers-reduced-motion: reduce) {
  .fade-up, .camp-card, .tile-in, .pop-in, .heartbeat, .icon-nudge {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}
