@charset "UTF-8";
/* ==========================================================================
   Motion, preloader, scroll behaviour and the responsive pass.
   Loaded after app.css.

   Rules this file holds itself to:
     · transform and opacity only — never width/height/top/left, which force
       layout on every frame
     · nothing animates before it is on screen
     · every effect is inside a prefers-reduced-motion guard
     · the preloader must never delay first paint (see the note by .preloader)
   ========================================================================== */

/* ── Scroll ─────────────────────────────────────────────────────────────── */

html {
  /* overscroll-behavior stops a scroll at the end of the page from pulling
     the whole document / triggering pull-to-refresh mid-read. */
  overscroll-behavior-y: none;
}

body {
  /* Reserve the scrollbar gutter so the layout does not shift by ~15px when a
     short page (a policy page) is followed by a long one (notices). */
  scrollbar-gutter: stable;
}

/* Deliberately NOT using a JS smooth-scroll library (Lenis and friends).
   Hijacking the wheel makes a government site feel laggy on low-end Android,
   breaks find-in-page, and fights screen readers. Native smooth scrolling with
   correct scroll-padding gives the same feel for free. */
:target {
  scroll-margin-top: calc(var(--header-h) + var(--sp-6));
}

/*
 * Reading-progress bar.
 *
 * Lives inside .site-header and sits exactly on its gold bottom rule, so the
 * rule appears to fill as you read. Originally it was position:fixed at top:0,
 * which overlapped the header and read as a stray yellow fragment; positioning
 * it with JS then left it a few pixels adrift. As a child of the header, CSS
 * places it perfectly with no measurement and no layout reads at all.
 */
.scroll-progress {
  position: absolute; inset-inline: 0; bottom: -3px;
  height: 3px;
  background: var(--navy-500);
  transform: scaleX(0);
  transform-origin: 0 50%;
  will-change: transform;
  pointer-events: none;
}
html[data-contrast="high"] .scroll-progress { background: #ffff00; }

/* ── Preloader ──────────────────────────────────────────────────────────── */

/*
 * A preloader is a perceived-performance trick, and done wrong it is slower
 * than none at all — a splash that waits for window.load makes the site feel
 * worse on exactly the slow connections it is meant to help.
 *
 * So this one:
 *   · is painted by CSS immediately, with no JS needed to appear
 *   · hides on DOMContentLoaded, not window.load — it never waits for images
 *   · hides itself after 1.4s regardless, via CSS animation, so a JS error or
 *     a blocked script can never leave a visitor staring at a blank screen
 *   · is skipped entirely for repeat visits within the session
 *   · is skipped entirely under reduced motion
 */
.preloader {
  position: fixed; inset: 0;
  z-index: var(--z-toast);
  display: flex; align-items: center; justify-content: center;
  background: var(--navy-800);
  /* The safety net: fades out on its own even if app.js never runs. */
  animation: preloader-autohide 1.4s var(--ease-out) forwards;
}
.preloader[hidden] { display: none; }
.preloader.is-done { animation: preloader-out 380ms var(--ease-out) forwards; }

/* Set by the inline head script before first paint, so a visitor moving to a
   second page never sees the splash flash up and disappear again. */
html[data-seen="1"] .preloader { display: none; }

.preloader-inner { text-align: center; padding: var(--sp-6); }

.preloader-emblem {
  width: 84px; height: 84px; margin: 0 auto var(--sp-5);
  background: #fff; border-radius: 50%; padding: 6px;
  object-fit: contain;
  animation: preloader-pulse 1.6s var(--ease-out) infinite;
}

.preloader-name {
  font-family: var(--font-display);
  font-size: clamp(1rem, .9rem + .6vw, 1.25rem);
  font-weight: 700; color: #fff;
  margin: 0 0 var(--sp-2);
  letter-spacing: .01em;
}
.preloader-gov {
  font-size: var(--fs-xs); font-weight: 600; letter-spacing: .12em;
  text-transform: uppercase; color: var(--gold-500);
  margin: 0 0 var(--sp-5);
}
html[lang^="bn"] .preloader-gov { letter-spacing: .02em; text-transform: none; }

/* A determinate-looking bar. It is not tied to real progress — pretending to
   measure it would be a lie — it simply fills once, at a steady rate. */
.preloader-bar {
  width: 172px; height: 2px; margin: 0 auto;
  background: rgba(255, 255, 255, .18);
  overflow: hidden;
}
.preloader-bar span {
  display: block; height: 100%;
  background: var(--gold-500);
  transform: scaleX(0); transform-origin: 0 50%;
  animation: preloader-fill 1.2s var(--ease-out) forwards;
}

@keyframes preloader-fill {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}
@keyframes preloader-pulse {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.06); }
}
@keyframes preloader-out {
  to { opacity: 0; visibility: hidden; }
}
@keyframes preloader-autohide {
  0%, 70%  { opacity: 1; visibility: visible; }
  100%     { opacity: 0; visibility: hidden; }
}

/* ── Header behaviour on scroll ─────────────────────────────────────────── */

/* Condense the header once the visitor starts reading, to give back screen
   height on a phone.
   Only box-shadow is transitioned. The height and logo changes are applied
   instantly and deliberately: animating min-height/width would reflow the
   whole document below the sticky header for the duration of the transition,
   which is exactly the jank this file is meant to avoid. Snapping once at the
   threshold is imperceptible and free. */
.site-header { transition: box-shadow var(--dur) var(--ease-out); }

.site-header.is-condensed { box-shadow: 0 2px 10px rgba(0, 12, 30, .25); }
.site-header.is-condensed .brand-logo { width: 42px; height: 42px; }
.site-header.is-condensed .brand-block { display: none; }

/* The reduced header height applies from tablet up only. On a phone the
   panchayat name already wraps to two lines, so forcing a shorter header there
   just crowds it — dropping the block/district line and shrinking the logo is
   enough to reclaim the space. */
@media (min-width: 768px) {
  .site-header.is-condensed .header-inner { min-height: 58px; }
}
@media (min-width: 1024px) {
  .site-header.is-condensed .brand-name { font-size: 1.05rem; }
}

/* ── Reveal on scroll ───────────────────────────────────────────────────── */

/* Directional variants, so motion carries meaning: content rises into place,
   a side-by-side pair slides in from its own edge. */
[data-reveal="up"]    { opacity: 0; transform: translate3d(0, 22px, 0); }
[data-reveal="left"]  { opacity: 0; transform: translate3d(0, 22px, 0); }
[data-reveal="right"] { opacity: 0; transform: translate3d(0, 22px, 0); }
[data-reveal="scale"] { opacity: 0; transform: scale(.97); }

/*
 * Horizontal reveals only from tablet up.
 *
 * On a phone the container padding is ~16px, so translating a full-width card
 * sideways by more than that pushes it past the viewport edge — which is real
 * horizontal overflow, not a visual nicety: the page becomes draggable
 * sideways until the reveal fires. Below 768px these fall back to rising,
 * which reads the same and cannot overflow. The 16px offset above 768px stays
 * inside the larger padding at every width.
 */
@media (min-width: 768px) {
  [data-reveal="left"]  { transform: translate3d(-16px, 0, 0); }
  [data-reveal="right"] { transform: translate3d(16px, 0, 0); }
}

[data-reveal].is-visible {
  opacity: 1;
  transform: none;
  transition:
    opacity 520ms var(--ease-out) calc(var(--reveal-i, 0) * 70ms),
    transform 620ms var(--ease-out) calc(var(--reveal-i, 0) * 70ms);
}

/* ── Timeline draw-in ───────────────────────────────────────────────────── */

/* The spine grows downward as the visitor reads, which is the one place on
   this site where motion genuinely explains the content: it is a chronology. */
.timeline { --spine: 0; }
.timeline::before {
  transform: scaleY(var(--spine));
  transform-origin: 0 0;
  transition: transform 900ms var(--ease-out);
}
.timeline-dot {
  transform: scale(0);
  transition: transform 420ms var(--ease-out);
}
.timeline > li.is-visible .timeline-dot { transform: scale(1); }
.timeline > li.is-visible .timeline-year {
  animation: chip-in 420ms var(--ease-out) 120ms backwards;
}
@keyframes chip-in {
  from { opacity: 0; transform: translate3d(0, -6px, 0); }
}

/* ── Link and button micro-interactions ─────────────────────────────────── */

/* Underline sweeps in from the left rather than simply appearing. */
.link-more { position: relative; text-decoration: none; }
.link-more::after {
  content: ''; position: absolute; left: 0; right: 22px; bottom: -2px;
  height: 1.5px; background: currentColor;
  transform: scaleX(0); transform-origin: 0 50%;
  transition: transform var(--dur) var(--ease-out);
}
.link-more:hover { text-decoration: none; }
.link-more:hover::after, .link-more:focus-visible::after { transform: scaleX(1); }

.btn { position: relative; overflow: hidden; }
/* A sheen that crosses the button once on hover. Purely a hover affordance,
   and it costs one transform. */
.btn::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(100deg, transparent 30%, rgba(255, 255, 255, .28) 50%, transparent 70%);
  transform: translateX(-120%);
  transition: transform 620ms var(--ease-out);
  pointer-events: none;
}
.btn:hover::after { transform: translateX(120%); }
.btn-outline::after { background: linear-gradient(100deg, transparent 30%, rgba(0, 51, 102, .12) 50%, transparent 70%); }

/* Cards lift on a spring-ish curve. */
.card-hover {
  transition: transform 260ms cubic-bezier(.2, .8, .2, 1),
              box-shadow 260ms var(--ease-out),
              border-color 260ms var(--ease-out);
}
.card-hover:hover { transform: translateY(-4px); }
.card-hover:active { transform: translateY(-1px); }

/* Notice rows: a gold marker wipes down the leading edge and the content
   nudges across. The nudge is a transform on the inner block, not a padding
   change on the row — animating padding would relayout the whole list. */
.notice-list li { position: relative; }
.notice-item { transition: background var(--dur-fast) var(--ease-out); }
.notice-item::before {
  content: ''; position: absolute; inset-block: 0; inset-inline-start: 0;
  width: 3px; background: var(--gold-500);
  transform: scaleY(0); transform-origin: 50% 0;
  transition: transform var(--dur) var(--ease-out);
}
.notice-item:hover::before, .notice-item:focus-visible::before { transform: scaleY(1); }

.notice-item .notice-body { transition: transform var(--dur) var(--ease-out); }
.notice-item:hover .notice-body,
.notice-item:focus-visible .notice-body { transform: translateX(4px); }

/* Service icons rotate very slightly — enough to feel alive, not cartoonish. */
.service-icon { transition: transform 320ms cubic-bezier(.2, .8, .2, 1), background 260ms var(--ease-out); }
.card-hover:hover .service-icon { transform: translateY(-2px) rotate(-3deg); }

/* Chips */
.chip { transition: transform 220ms cubic-bezier(.2, .8, .2, 1), box-shadow 220ms var(--ease-out), border-color 220ms; }
.chip:hover { transform: translateY(-2px); border-inline-start-color: var(--navy-600); }

/* Staggered entrance for the header nav on first paint. */
@media (min-width: 1024px) {
  .nav-list > li {
    animation: nav-in 420ms var(--ease-out) backwards;
    animation-delay: calc(var(--nav-i, 0) * 45ms + 120ms);
  }
}
@keyframes nav-in {
  from { opacity: 0; transform: translate3d(0, -8px, 0); }
}

/* Drawer items cascade in when it opens. */
.drawer.is-open .drawer-nav li {
  animation: drawer-in 340ms var(--ease-out) backwards;
  animation-delay: calc(var(--nav-i, 0) * 35ms + 60ms);
}
@keyframes drawer-in {
  from { opacity: 0; transform: translate3d(18px, 0, 0); }
}

/* Hero copy arrives in sequence after the image. */
.hero-slide.is-active .hero-eyebrow { animation: hero-in 520ms var(--ease-out) 80ms backwards; }
.hero-slide.is-active .hero-title   { animation: hero-in 620ms var(--ease-out) 180ms backwards; }
.hero-slide.is-active .hero-sub     { animation: hero-in 620ms var(--ease-out) 280ms backwards; }
.hero-slide.is-active .hero-actions { animation: hero-in 620ms var(--ease-out) 380ms backwards; }
@keyframes hero-in {
  from { opacity: 0; transform: translate3d(0, 18px, 0); }
}

/* Counters tick up; the tile itself stays still. */
.stat-card .stat-value { font-variant-numeric: tabular-nums; }

/* ── Focus polish ──────────────────────────────────────────────────────── */

:focus-visible { transition: outline-offset 120ms var(--ease-out); }

/* ── Responsive pass ───────────────────────────────────────────────────── */

/*
 * Phones up to 599px: the menu button drops its "Menu" label and becomes
 * icon-only. That returns roughly 60px to the panchayat name, which is what
 * stops it wrapping onto a third line and crowding the header. The button keeps
 * its 48px target and its accessible name via aria-label plus the
 * visually-hidden text in the markup.
 */
@media (max-width: 599px) {
  .nav-toggle span:not(.visually-hidden) { display: none; }
  .nav-toggle { min-width: 48px; padding: 0; }
}

/* 320–359px — the smallest phones still in real use in rural West Bengal.
   Nothing may overflow horizontally at this width. */
@media (max-width: 359px) {
  :root { --sp-4: .875rem; --sp-6: 1.125rem; }
  .container { padding-inline: var(--sp-3); }
  .brand-logo { width: 42px; height: 42px; }
  .brand-name { font-size: .875rem; }
  .brand-gov { font-size: 10px; }
  .brand-block { display: none; }
  .util-bar .container { justify-content: center; }
  .util-left { display: none; }                /* keep the a11y controls */
  .hero-body { padding-block: var(--sp-8); }
  .btn { width: 100%; }                        /* stacked, full-bleed CTAs */
  .hero-actions { flex-direction: column; align-items: stretch; }
  .notice-date { width: 52px; }
  .stat-value { font-size: 1.6rem; }
  .footer-logo { width: 62px; height: 62px; }
  .gallery-grid { grid-template-columns: repeat(2, 1fr); gap: var(--sp-2); }
}

/* 360–479px */
@media (max-width: 479px) {
  .section { padding-block: var(--sp-10); }
  .card-pad { padding: var(--sp-4); }
  .table-scroll { margin-inline: calc(var(--sp-4) * -1); padding-inline: var(--sp-4); }
  .breadcrumb { font-size: var(--fs-xs); }
  .timeline > li { padding-left: 38px; }
  .timeline::before { left: 11px; }
  .timeline-dot { width: 16px; height: 16px; left: 4px; border-width: 3px; }
  /* A ticker on a narrow screen is more noise than help. */
  .ticker-label span { display: none; }
}

/* Landscape phone — vertical space is the scarce resource. */
@media (max-height: 480px) and (orientation: landscape) {
  .hero-media, .hero-body { min-height: 300px; }
  .site-header { position: static; }   /* do not eat 30% of the viewport */
  .util-bar { display: none; }
  .preloader-emblem { width: 54px; height: 54px; margin-bottom: var(--sp-3); }
}

/* 480–767px */
@media (min-width: 480px) and (max-width: 767px) {
  .grid-4 { grid-template-columns: repeat(2, 1fr); }
}

/* Tablet portrait — the awkward width where a desktop nav does not fit but
   the phone layout looks sparse. */
@media (min-width: 768px) and (max-width: 1023px) {
  .grid-4 { grid-template-columns: repeat(2, 1fr); }
  .grid-3 { grid-template-columns: repeat(2, 1fr); }
  .footer-grid { grid-template-columns: repeat(2, 1fr); }
  .hero-title { font-size: 2.1rem; }
  .util-bar .container { justify-content: space-between; }
}

/* Wide desktop — stop the measure growing past comfortable reading. */
@media (min-width: 1536px) {
  :root { --container: 1380px; }
  .hero-media, .hero-body { min-height: 560px; }
}

/* Very wide / 4K — cap it rather than letting text stretch. */
@media (min-width: 1920px) {
  :root { --container: 1480px; --fs-base: 1.0625rem; }
}

/* Coarse pointers (touch) get larger hit areas and no hover-only affordances. */
@media (hover: none) and (pointer: coarse) {
  .card-hover:hover { transform: none; box-shadow: var(--shadow-1); }
  .btn:hover::after { transform: translateX(-120%); }
  .gallery-caption { opacity: 1; transform: none; }   /* no hover to reveal it */
  .notice-item:hover .notice-body { transform: none; }
  .a11y-btn, .icon-btn { min-width: 44px; min-height: 44px; }
}

/* Fine pointers get the full hover vocabulary. */
@media (hover: hover) and (pointer: fine) {
  .gallery-item:hover img { transform: scale(1.06); }
}

/* Printing a notice or a form is a real use case at a panchayat office. */
@media print {
  .preloader, .scroll-progress { display: none !important; }
}

/* ── Reduced motion ────────────────────────────────────────────────────── */

/*
 * Everything above degrades to "already in its final state". Note the
 * preloader is removed outright rather than faded — someone who has asked for
 * less motion should not be shown a splash screen at all.
 */
@media (prefers-reduced-motion: reduce) {
  .preloader { display: none !important; }
  .scroll-progress { display: none; }

  [data-reveal],
  [data-reveal="up"], [data-reveal="left"],
  [data-reveal="right"], [data-reveal="scale"] {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }

  .timeline::before { transform: none; }
  .timeline-dot { transform: scale(1); }
  .timeline > li.is-visible .timeline-year { animation: none; }

  .btn::after { display: none; }
  .card-hover:hover, .chip:hover { transform: none; }
  .card-hover:hover .service-icon { transform: none; }
  .notice-item:hover .notice-body { transform: none; }
  .link-more::after { transform: scaleX(1); }
  .nav-list > li, .drawer.is-open .drawer-nav li { animation: none; }
  .hero-slide.is-active .hero-eyebrow,
  .hero-slide.is-active .hero-title,
  .hero-slide.is-active .hero-sub,
  .hero-slide.is-active .hero-actions { animation: none; }
  .site-header.is-condensed .header-inner,
  .site-header.is-condensed .brand-logo { transition: none; }
}
