/* ---- tokens.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  tokens.css — pulls in the two already-built, already-approved token
  sources rather than duplicating them. Signal Emerald colors/glass
  tokens and the self-hosted type system both already live in branding/
  (built and reviewed before Phase 4) — this file is the ONE place the
  live site declares "these are the tokens," so a future palette/font
  change happens in branding/ and is inherited everywhere, never
  copy-pasted into a second location that can drift out of sync.
*/

/* These two used to be @import'd from here (real perf bug, fixed
   2026-08-22, Phase 16: a CSS @import forces the browser to fetch and
   parse THIS file first before it can even discover the imported
   ones, serializing what should be parallel requests — directly
   delays font/color-token discovery, which is exactly what the font
   preload item below exists to prevent). Now loaded as real sibling
   <link rel="stylesheet"> tags in head-meta.php, alongside every
   other stylesheet, same as the rest of the site's ~27 CSS files
   already do. Nothing else changes — same two files, same cascade
   order (both still load before base.css, which is the first file to
   actually USE these custom properties), just discovered in parallel
   instead of serially. */

:root {
  /* Type scale — fluid clamp(), not fixed px per breakpoint. Values
     read as (min, viewport-relative, max); tuned for the reading-first
     nature of this site (long-form posts), not decorative headline sizes. */
  --step-caption:  clamp(0.78rem, 0.75rem + 0.15vw, 0.85rem);
  --step-body:     clamp(1rem,    0.96rem + 0.2vw,  1.125rem);
  --step-lead:     clamp(1.125rem, 1.05rem + 0.4vw, 1.35rem);
  --step-h3:       clamp(1.25rem, 1.1rem + 0.75vw,  1.6rem);
  --step-h2:       clamp(1.55rem, 1.3rem + 1.2vw,   2.1rem);
  --step-h1:       clamp(1.9rem,  1.5rem + 2vw,      2.75rem);
  --step-display:  clamp(2.3rem, 1.7rem + 3vw,       3.75rem);

  /* Spacing scale — same fluid approach, used for section rhythm. */
  --space-xs:  clamp(0.5rem, 0.46rem + 0.2vw, 0.65rem);
  --space-sm:  clamp(0.75rem, 0.68rem + 0.35vw, 1rem);
  --space-md:  clamp(1.25rem, 1.1rem + 0.75vw, 1.75rem);
  --space-lg:  clamp(2rem, 1.6rem + 2vw, 3rem);
  --space-xl:  clamp(3rem, 2.2rem + 4vw, 5rem);

  /* Font role assignments — see branding/fonts/fonts.css for the
     @font-face declarations these names resolve to. */
  --font-display: "Fraunces", Georgia, serif;
  --font-accent:  "Bodoni Moda", Georgia, serif;
  --font-body:    "Hanken Grotesk", system-ui, -apple-system, sans-serif;
  --font-utility: "Questrial", system-ui, -apple-system, sans-serif;

  --touch-target-min: 44px; /* PERFORMANCE.md / ACCESSIBILITY.md minimum */
}


/* ---- glass.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  glass.css — Clear Glass surfaces (DECISION: replaces the earlier
  Liquid Glass system 100%, sitewide). A real glass surface, kept
  simple: backdrop blur (actual optical blur of whatever sits behind
  it — that's what reads as "glass"), a translucent tint, a thin bright
  border, a soft shadow, a faint top specular line. No bent/distorted
  rim, no feTurbulence/feDisplacementMap, no SVG filter dependency —
  see [[feedback_liquid_glass_technique]] in project memory for why the
  bend filter was dropped. One layer per element, not a rim+inner-pane
  stack.

  Structure every .glass-primary/.glass-secondary element needs:
    <div class="glass-primary">
      <div class="glass-content"> ...real content... </div>
    </div>
  The ::before pseudo-element carries the glass effect; .glass-content
  sits above it via z-index so text/icons stay crisp.
*/

.glass-primary,
.glass-secondary {
  position: relative;
}

.glass-content {
  position: relative;
  z-index: 1;
}

.glass-primary::before,
.glass-secondary::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
}

/* ---- Primary tier: more opaque/blurred — for a few higher-emphasis
   surfaces (homepage hero card, prominent cards). ---- */
.glass-primary::before {
  background: var(--glass-primary-bg);
  border: 1px solid var(--glass-primary-border);
  backdrop-filter: blur(var(--glass-primary-blur)) saturate(150%);
  -webkit-backdrop-filter: blur(var(--glass-primary-blur)) saturate(150%);
  box-shadow: var(--glass-shadow), inset 0 1px 0 rgba(255, 255, 255, 0.3);
}

/* ---- Secondary tier: thinner, more transparent — nav pill, bottom
   nav, icon buttons, dropdown menus. Never put body-length text on
   this tier; large headline text or icon-only UI only, per
   ACCESSIBILITY.md's contrast rule (secondary glass is only WCAG-safe
   at the large-text threshold, not body-text contrast). ---- */
.glass-secondary::before {
  background: var(--glass-secondary-bg);
  border: 1px solid var(--glass-secondary-border);
  backdrop-filter: blur(var(--glass-secondary-blur)) saturate(150%);
  -webkit-backdrop-filter: blur(var(--glass-secondary-blur)) saturate(150%);
  box-shadow: var(--glass-shadow), inset 0 1px 0 rgba(255, 255, 255, 0.25);
}

@media (prefers-reduced-motion: reduce) {
  .glass-primary,
  .glass-secondary {
    transition: none !important;
  }
}


/* ---- base.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  base.css — the device-optimization shell every page inherits, built
  once here in Phase 4 so Phase 5+ pages don't each reinvent fluid type,
  grid, and touch-target rules. See PERFORMANCE.md for the full list
  this implements: clamp() typography/spacing, CSS Grid minmax()/
  auto-fit, container queries, dvh units, 44x44 touch targets.
*/

*,
*::before,
*::after {
  box-sizing: border-box;
}

html,
body {
  overflow-x: hidden !important;
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100dvh; /* dvh, not vh — correct on mobile browsers whose
                          chrome shows/hides and changes the viewport. */
  background: var(--color-bg);
  color: var(--color-ink);
  font-family: var(--font-body);
  font-size: var(--step-body);
  line-height: 1.6;
}

img,
svg,
video {
  max-width: 100%;
  height: auto;
  display: block;
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  line-height: 1.2;
  margin: 0 0 var(--space-sm);
  text-wrap: balance;
}
h1 { font-size: var(--step-h1); margin-top: var(--space-lg); }
h2 { font-size: var(--step-h2); }
h3 { font-size: var(--step-h3); }

p {
  margin: 0 0 var(--space-md);
  max-width: 65ch; /* CONTENT-GUIDE.md's reading-first principle — long
                       lines hurt readability regardless of screen width. */
}

a {
  color: var(--color-link);
}

/* ---- Layout primitives ---- */

/* <main> gets the same treatment as .container directly, rather than
   layout.php wrapping every page's $bodyContent in an extra div — it
   never had ANY width/padding rule before this (header/footer opted
   into .container explicitly in their own markup; <main> just never
   did), which was invisible on the old single-paragraph stub homepage
   but would have put a full bento grid flush against the browser
   edge at every viewport size the moment real content landed here. */
.container,
main {
  width: 100%;
  max-width: 1120px;
  margin-inline: auto;
  padding-inline: var(--space-md);
  container-type: inline-size; /* enables container queries on children */
}

.grid-auto {
  display: grid;
  gap: var(--space-md);
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
}

/* ---- Touch targets ---- */

button,
a.button,
.touch-target {
  min-height: var(--touch-target-min);
  min-width: var(--touch-target-min);
}

/* ---- Section head — shared eyebrow/title pattern used by any
   homepage (or future page) section that needs one, so it's defined
   once instead of duplicated per component stylesheet. ---- */
.section-head {
  max-width: 60ch;
  margin-bottom: var(--space-lg);
}
.section-head__eyebrow {
  margin: 0;
  font-family: var(--font-utility);
  font-size: var(--step-caption);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-brand-green);
}
.section-head__title {
  margin: 6px 0 0;
  font-family: var(--font-display);
  font-weight: 500;
}

/* ---- Buttons — structural base only (box model, type). Color,
   glass, and the molten-glass hover treatment for .btn--primary/
   --ghost/--link live in their own file, assets/css/buttons.css,
   loaded after this one. ---- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 12px 22px;
  border-radius: 999px;
  font-family: var(--font-utility);
  font-size: var(--step-caption);
  font-weight: 500;
  letter-spacing: 0.01em;
  text-decoration: none;
  white-space: nowrap;
}

/* ---- Page-to-page navigation (Phase 16, PERFORMANCE.md's "page-to-
   page navigation speed" section) — cross-document View Transitions.
   One rule, progressive enhancement: browsers that support it get an
   automatic cross-fade between any two same-origin pages on this site;
   unsupported browsers just navigate normally, no fallback code
   needed. Shared-element morphing DONE 2026-08-23 (direct instruction)
   — a real per-slug `view-transition-name: post-hero-{slug}` pairing
   between an archive/homepage card's thumbnail (archive-listing.php)
   and that same post's own featured image (post.php): the card visibly
   morphs into the hero across the navigation instead of getting the
   plain cross-fade every other element gets. Every OTHER element on
   the page (nav, footer, body text) still just gets the ordinary
   cross-fade from this one sitewide rule — shared-element pairing is
   deliberately scoped to just that one card↔hero relationship, not
   applied everywhere. */
@view-transition {
  navigation: auto;
}

/* ---- Motion ---- */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }

  /* The universal selector above does NOT reach the browser-generated
     ::view-transition-* pseudo-elements (they sit in their own top
     layer, outside the normal DOM tree) — needs its own explicit kill
     switch so a reduced-motion visitor gets an instant page swap, not
     a suppressed-everywhere-except-this cross-fade. */
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}


/* ---- buttons.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  buttons.css — molten-glass treatment for every button/link-button on
  the site: .btn--primary (filled), .btn--ghost (secondary/outlined),
  and .btn--link (a new variant for inline "arrow" CTAs — archive row
  links, the terminal's "Browse the archive", footer's "Work with
  me"). Structural box model lives in base.css's plain .btn rule;
  everything here is color, glass, and hover motion.

  "Molten glass": a real glossy surface — layered gradients standing
  in for refracted light (a bright specular highlight, a warmer
  under-glow), a slow ambient shimmer even at rest, and on hover a
  bright sheen sweeps across like light catching liquid glass, plus a
  soft color-matched glow bloom. Uses ::before/::after with empty
  content:"" for the decorative layers — this project's established
  rule is that pseudo-element GENERATED TEXT doesn't reliably render
  here, not that pseudo-elements can't carry background/gradient
  layers (glass.css's own ::before proves that works fine).

  This is a deliberate, scoped exception to the sitewide Clear Glass
  simplification (see [[feedback_liquid_glass_technique]] in project
  memory, and glass.css's own comment) — that decision retired the
  rim+bend-filter treatment for GLASS PANELS (header, cards). Buttons
  are a different, much smaller surface where the user explicitly
  asked for "impressive molten-glass" specifically, so this doesn't
  reopen that panel decision.
*/

.btn {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  border: 1px solid transparent;
  transition: transform 0.2s ease, box-shadow 0.3s ease, border-color 0.3s ease, color 0.2s ease, background-color 0.2s ease;
}
.btn:hover,
.btn:focus-visible {
  transform: translateY(-2px);
}
.btn:active {
  transform: translateY(-1px) scale(0.98);
}

/* Sheen — a bright diagonal bar that sweeps left-to-right on hover.
   Sits above the gradient background, below the label text. */
.btn--primary::after,
.btn--ghost::after {
  content: "";
  position: absolute;
  top: -20%;
  left: -60%;
  width: 35%;
  height: 140%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.55), transparent);
  transform: skewX(-18deg);
  transition: left 0.65s cubic-bezier(0.2, 0.8, 0.2, 1);
  pointer-events: none;
  z-index: 1;
}
.btn--primary:hover::after,
.btn--primary:focus-visible::after,
.btn--ghost:hover::after,
.btn--ghost:focus-visible::after {
  left: 130%;
}

/* ---- Primary — filled molten emerald glass ---- */
.btn--primary {
  color: #fff;
  background:
    radial-gradient(120% 160% at 20% -20%, rgba(255, 255, 255, 0.5), transparent 55%),
    linear-gradient(155deg, #17C989 0%, #0E8F5E 45%, #0A6B45 100%);
  background-size: 100% 100%, 220% 220%;
  border-color: rgba(255, 255, 255, 0.35);
  box-shadow:
    0 4px 18px rgba(14, 143, 94, 0.35),
    inset 0 1px 0 rgba(255, 255, 255, 0.45),
    inset 0 -8px 14px rgba(0, 0, 0, 0.18);
  animation: molten-drift 7s ease-in-out infinite;
}
.btn--primary span,
.btn--primary {
  position: relative;
}
.btn--primary:hover,
.btn--primary:focus-visible {
  box-shadow:
    0 8px 28px rgba(14, 143, 94, 0.55),
    inset 0 1px 0 rgba(255, 255, 255, 0.55),
    inset 0 -8px 14px rgba(0, 0, 0, 0.16);
}
@keyframes molten-drift {
  0%, 100% { background-position: 0% 0%, 0% 50%; }
  50%      { background-position: 0% 0%, 100% 50%; }
}

/* ---- Ghost/secondary — translucent molten glass, colored rim glow
   on hover rather than a flat border-color swap. ---- */
.btn--ghost {
  color: var(--color-ink);
  background:
    radial-gradient(120% 160% at 25% -10%, rgba(255, 255, 255, 0.14), transparent 55%),
    linear-gradient(160deg, rgba(255, 255, 255, 0.06), rgba(14, 143, 94, 0.07));
  border-color: var(--color-border);
  backdrop-filter: blur(8px) saturate(140%);
  -webkit-backdrop-filter: blur(8px) saturate(140%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12);
}
.btn--ghost:hover,
.btn--ghost:focus-visible {
  color: var(--color-brand-green);
  border-color: rgba(14, 143, 94, 0.6);
  box-shadow:
    0 0 0 1px rgba(14, 143, 94, 0.25),
    0 8px 24px rgba(14, 143, 94, 0.28),
    inset 0 1px 0 rgba(255, 255, 255, 0.18);
}

/* ---- Link — inline "arrow" CTAs (archive row links, the terminal's
   browse-archive link, footer emphasis link). Resets .btn's pill
   chrome: no fill, no border, no padding — just text with a molten
   underline that fills in and an arrow that nudges forward. ---- */
.btn--link {
  padding: 0;
  border: none;
  border-radius: 0;
  background: none;
  box-shadow: none;
  color: var(--color-brand-green);
  background-image: linear-gradient(currentColor, currentColor);
  background-size: 0% 1.5px;
  background-position: 0 100%;
  background-repeat: no-repeat;
  transition: background-size 0.3s ease, color 0.2s ease, text-shadow 0.3s ease, transform 0.2s ease;
}
.btn--link:hover,
.btn--link:focus-visible {
  background-size: 100% 1.5px;
  text-shadow: 0 0 12px rgba(14, 143, 94, 0.55);
  transform: none;
}
.btn--link .btn--link__arrow {
  display: inline-block;
  transition: transform 0.25s ease;
}
.btn--link:hover .btn--link__arrow,
.btn--link:focus-visible .btn--link__arrow {
  transform: translateX(4px);
}

@media (prefers-reduced-motion: reduce) {
  .btn {
    transition: none !important;
    animation: none !important;
  }
  .btn:hover,
  .btn:focus-visible,
  .btn:active {
    transform: none !important;
  }
  .btn--primary::after,
  .btn--ghost::after {
    display: none !important;
  }
  .btn--link .btn--link__arrow {
    transition: none !important;
  }
}


/* ---- header.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  header.css — the site header: a plain, usual centered nav pill.
  Clear Glass secondary tier (see glass.css) — thin, light, no bend
  filter. NEVER stretches full width and NEVER sticky, at any viewport
  size (that's the whole point of this shape, not a mobile-only rule) —
  it's a compact pill that scrolls away with the page like any other
  element.

  Full nav links (Categories/About/Contact) hide below 900px in
  portrait — a hamburger button takes their place instead, opening a
  vertical Clear Glass panel with the same links (the fixed bottom
  nav's icons are shortcuts, not full nav parity). This 900px/portrait
  breakpoint MUST stay in sync with bottom-nav.css/back-to-top.css's
  own breakpoints.
*/

.site-header {
  position: relative;
  z-index: 200; /* glass.css's .glass-content sets position:relative +
    z-index:1 on every glass wrapper, which creates its own stacking
    context — that silently trapped the mobile drawer's z-index:200
    inside a context that itself only counted as "1" against the root
    (bottom-nav/back-to-top compare directly in the root context, so
    they were winning at 46/45 against an effectively-1 header). Real
    bug, not a guess — verified via elementFromPoint showing bottom-nav
    painting over an "open" drawer. Promoting the header itself fixes
    it for the whole subtree without fighting glass.css's own rule. */
  display: flex;
  justify-content: center;
  padding-block: var(--space-sm);
}

.site-header__pill {
  --glass-radius: 999px;
  display: inline-flex;
  max-width: calc(100% - var(--space-md) * 2); /* never edge-to-edge, even on narrow screens */
  border-radius: 999px;
}
/* Direct-child combinator, not a bare descendant selector — the pill
   also CONTAINS other .glass-content wrappers (the dropdown menu, the
   mobile hamburger panel), nested several levels deep. A descendant
   selector here would silently match those too and stomp their own
   vertical/left-aligned layout with this row-flex one — that was a
   real bug (dropdown items rendering centered instead of left-aligned
   and losing their column stacking). `>` scopes this to the pill's own
   immediate content wrapper only. */
.site-header__pill > .glass-content {
  display: flex;
  align-items: center;
  gap: calc(var(--space-md) + 4px); /* a touch wider now the logo reads bigger */
  padding-inline: calc(var(--space-md) + 2px);
  padding-block: 10px;
}

.site-header__logo {
  display: flex;
  align-items: center;
  min-height: var(--touch-target-min);
  text-decoration: none;
  /* Real bug, confirmed via getBoundingClientRect on a live mobile
     viewport: without this, the anchor was a default flex-shrink:1
     item inside .site-header__pill > .glass-content's row — on
     narrow screens where the pill got tight (logo + nav-replacement
     icons + mute toggle all competing for space), it shrank well
     below the logo image's natural width, and base.css's global
     `img { max-width: 100% }` then capped the <img> to that shrunk
     box — rendering it at ~79px wide against a 44px fixed height
     (should have been ~206px), badly squished and reading as "tiny."
     flex-shrink:0 here, matching the img's own flex:none, is the
     actual fix — the logo now keeps its true proportions no matter
     how tight the pill gets. */
  flex-shrink: 0;
}
.site-header__logo-img {
  /* The wordmark's own text is drawn at font-size 22 inside a 64-tall
     SVG viewBox (see branding/logo/primary-*.svg) — at 44px rendered
     height that scales to ~15px, which is what actually lines up with
     the adjacent nav text's ~16-18px size. It was 28px before (wordmark
     text ~9.5px, visibly smaller/thinner than the nav) then 36px
     (~12.4px, still visibly off) — 44px is the value that stops
     looking asymmetric next to real nav text at this type scale. */
  height: 44px;
  width: auto;
  flex: none;
}
.site-header__logo-img--light {
  display: none;
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .site-header__logo-img--dark { display: none; }
  :root:not([data-theme="dark"]) .site-header__logo-img--light { display: block; }
}
:root[data-theme="light"] .site-header__logo-img--dark { display: none; }
:root[data-theme="light"] .site-header__logo-img--light { display: block; }

.site-header__nav {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}
.site-header__nav a,
.site-header__nav summary {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target-min);
  padding-inline: 12px;
  color: var(--color-ink);
  text-decoration: none;
  text-underline-offset: 6px;
  text-decoration-thickness: 2px;
  font-family: var(--font-utility);
  font-size: var(--step-body);
  letter-spacing: 0.01em;
  border-radius: var(--radius, 8px);
  cursor: pointer;
  transition: color 0.2s ease;
  /* Same iOS Safari long-press-preview issue as bottom-nav.css's
     .bottom-nav__item — a plain <a> can show a callout menu instead
     of navigating on a slightly-too-long tap. */
  -webkit-touch-callout: none;
}
/* Real `text-decoration` + `color`, not a ::after/::before pseudo-
   element sweep — this environment (confirmed in both the preview
   pane AND the user's actual Edge browser) does not reliably paint
   generated pseudo-element content/transforms on these elements even
   though getComputedStyle reports the values as applied; a plain DOM
   span with the same styling rendered fine in the same spot, so real
   elements/properties are what's actually reliable here. The active
   page's own link/summary gets the SAME underline as hover, just
   permanently on. */
.site-header__nav a:hover,
.site-header__nav a:focus-visible,
.site-header__nav a.is-active,
.site-header__nav summary:hover,
.site-header__nav summary:focus-visible,
.nav-dropdown.is-active summary,
.nav-dropdown[open] summary {
  color: var(--color-brand-green);
}
.site-header__nav a:hover,
.site-header__nav a:focus-visible,
.site-header__nav a.is-active {
  text-decoration-line: underline;
  text-decoration-color: var(--color-brand-green);
}
.nav-dropdown:hover .nav-dropdown__label,
.nav-dropdown.is-active .nav-dropdown__label,
.nav-dropdown[open] .nav-dropdown__label {
  text-decoration-line: underline;
  text-decoration-color: var(--color-brand-green);
}

/* Flat category dropdown — <details>/<summary>, no JS required, no
   sub-category nesting per the taxonomy override. Secondary-tier Clear
   Glass menu (this is a bounded card, exactly what glass.css's
   secondary tier is for). The indicator is a real monospace [+]/[–]
   toggle (two real <span>s, one shown at a time via the native [open]
   attribute — NOT ::after generated content, per the reliability note
   above) — ties into the same ui-monospace/"techy console" motif as
   the mobile drawer's "/menu" tag and numbered link index. */
.nav-dropdown {
  position: relative;
}
.nav-dropdown summary {
  list-style: none;
}
.nav-dropdown summary::-webkit-details-marker {
  display: none;
}
.nav-dropdown__indicator {
  margin-left: 6px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 12px;
  letter-spacing: -0.02em;
  color: var(--color-ink-muted);
}
.nav-dropdown__indicator-open {
  display: none;
}
.nav-dropdown[open] .nav-dropdown__indicator-closed {
  display: none;
}
.nav-dropdown[open] .nav-dropdown__indicator-open {
  display: inline;
  color: var(--color-brand-green);
}
.nav-dropdown__menu {
  --glass-radius: 14px;
  position: absolute;
  top: calc(100% + 10px);
  left: 0;
  min-width: 200px;
  /* Grows to fit the longest category label instead of wrapping it —
     real bug, confirmed live: "Workflow Optimization" (nav_label,
     config/site.config.php) wrapped to two lines inside the old fixed
     200px min-width. width: max-content plus nowrap on the links
     below means the menu's own width always matches whatever the
     longest current category label needs, not a number tuned for
     today's labels that breaks again the next time one gets longer. */
  width: max-content;
  max-width: calc(100vw - var(--space-md) * 2);
  border-radius: var(--glass-radius);
}
/* Items stack vertically — a plain <ul>, one per line, not a row. */
.nav-dropdown__list {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 8px;
  list-style: none;
}
.nav-dropdown__list li + li {
  margin-top: 2px;
}
.nav-dropdown__list a {
  position: relative;
  display: flex;
  align-items: center;
  min-height: var(--touch-target-min);
  padding-inline: 10px;
  border-radius: 8px;
  color: var(--color-ink);
  text-decoration: none;
  font-family: var(--font-body);
  white-space: nowrap; /* pairs with .nav-dropdown__menu's width: max-content above — every label stays on one line */
}
.nav-dropdown__list a:hover,
.nav-dropdown__list a.is-active {
  background: rgba(14, 143, 94, 0.12);
  color: var(--color-brand-green);
}
.nav-dropdown__list a.is-active {
  font-weight: 600;
}

/* ---- Mobile menu (<900px portrait only) — a real full-screen drawer,
   not a small anchored card, replacing the full nav there. Pure
   <details>/[open] CSS, zero extra JS: the panel goes from `display:
   none` to a fixed full-viewport primary-tier Clear Glass sheet purely
   off the native [open] attribute. The hamburger icon (three bars)
   morphs into an × via CSS transforms and pins itself to the corner
   while open, so the same control opens and closes it. ---- */
.site-header__mobile-menu {
  display: none;
  position: relative;
}
.site-header__hamburger {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  width: 40px;
  height: 40px;
  border-radius: 999px;
  color: var(--color-ink);
  cursor: pointer;
  list-style: none;
  transition: color 0.2s ease, background-color 0.2s ease;
}
.site-header__hamburger::-webkit-details-marker {
  display: none;
}

/* ---- Sound mute toggle (assets/js/ui-sound.js) — same 40px circular
   footprint/hover language as the hamburger button above, so it reads
   as part of the same control family rather than a bolted-on extra.
   Two real SVGs crossfade instead of redrawing a path on toggle (this
   project's established rule: pseudo-element/generated content is
   unreliable here, and swapping opacity on two already-rendered real
   elements sidesteps that entirely). The on-state's sound-wave arcs
   pulse gently and continuously — the one place on the header this
   animates without a hover, since it's the visual confirmation that
   sound is actually live, not just a static icon. ---- */
.site-header__sound-toggle {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: none;
  border-radius: 999px;
  background: transparent;
  color: var(--color-ink);
  cursor: pointer;
  transition: color 0.2s ease, background-color 0.2s ease;
}
.site-header__sound-toggle:hover,
.site-header__sound-toggle:focus-visible {
  color: var(--color-brand-green);
  background: rgba(14, 143, 94, 0.14);
}
.site-header__sound-icon {
  position: absolute;
  width: 19px;
  height: 19px;
  transition: opacity 0.25s ease, transform 0.25s ease;
}
.site-header__sound-icon--on {
  opacity: 1;
  transform: scale(1) rotate(0deg);
}
.site-header__sound-icon--off {
  opacity: 0;
  transform: scale(0.7) rotate(-12deg);
}
.site-header__sound-toggle.is-muted .site-header__sound-icon--on {
  opacity: 0;
  transform: scale(0.7) rotate(12deg);
}
.site-header__sound-toggle.is-muted .site-header__sound-icon--off {
  opacity: 1;
  transform: scale(1) rotate(0deg);
}
/* The two outer sound-wave arcs (2nd/3rd <path>) in the "on" icon —
   a slow, continuous, alternating pulse so an unmuted state visibly
   reads as "live" rather than a dead icon. */
.site-header__sound-icon--on path:nth-child(2) {
  animation: sound-wave-pulse 2.4s ease-in-out infinite;
}
.site-header__sound-icon--on path:nth-child(3) {
  animation: sound-wave-pulse 2.4s ease-in-out infinite 0.3s;
}
@keyframes sound-wave-pulse {
  0%, 100% { opacity: 0.45; }
  50%      { opacity: 1; }
}
.site-header__hamburger-bar {
  width: 19px;
  height: 2px;
  border-radius: 1px;
  background: currentColor;
  transition: transform 0.25s ease, opacity 0.2s ease, background-color 0.2s ease;
}
/* Three equal bars stay unmistakably "hamburger menu" at a glance —
   the middle one is always brand green (not just on hover), a small
   on-brand/techy signature rather than three flat identical lines. */
.site-header__hamburger-bar:nth-child(2) {
  background: var(--color-brand-green);
}
.site-header__hamburger:hover,
.site-header__hamburger:focus-visible {
  color: var(--color-brand-green);
  background: rgba(14, 143, 94, 0.14);
}

.site-header__mobile-panel {
  display: none;
}
.site-header__mobile-menu[open] .site-header__hamburger {
  position: fixed;
  top: max(16px, env(safe-area-inset-top, 0px));
  right: max(16px, env(safe-area-inset-right, 0px));
  z-index: 201;
  color: var(--color-brand-green);
  background: var(--color-surface);
  box-shadow: var(--glass-shadow);
}
.site-header__mobile-menu[open] .site-header__hamburger-bar:nth-child(1) {
  transform: translateY(7px) rotate(45deg);
}
.site-header__mobile-menu[open] .site-header__hamburger-bar:nth-child(2) {
  opacity: 0;
}
.site-header__mobile-menu[open] .site-header__hamburger-bar:nth-child(3) {
  transform: translateY(-7px) rotate(-45deg);
}
.site-header__mobile-menu[open] .site-header__mobile-panel {
  display: block;
  position: fixed;
  inset: 0;
  z-index: 200;
  border-radius: 0;
}
.site-header__mobile-panel-inner {
  height: 100dvh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  /* Top padding cleared to 112px (was 64px) — real overlap, confirmed
     via screenshot: the theme-cord (theme-toggle.css, z-index 210,
     ABOVE this panel's 200) rests its knob at y 34-78px, which sat
     directly on top of this panel's own "/menu"/"Categories" text
     when padding-top only cleared 64px. The cord is a persistent
     top-left control at every breakpoint (by design), so this panel's
     own content has to start below it, not the other way around. */
  padding: calc(112px + env(safe-area-inset-top, 0px)) var(--space-lg) calc(var(--space-lg) + env(safe-area-inset-bottom, 0px));
}
.site-header__mobile-tag {
  margin: 0 0 var(--space-md);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 12px;
  letter-spacing: 0.02em;
  color: var(--color-brand-green);
}
.site-header__mobile-eyebrow {
  margin: 0 0 var(--space-xs);
  font-family: var(--font-utility);
  font-size: var(--step-caption);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-ink-muted);
}
.site-header__mobile-list {
  display: flex;
  flex-direction: column;
}
.site-header__mobile-link {
  display: flex;
  align-items: baseline;
  gap: 16px;
  min-height: 56px;
  padding-block: 12px;
  border-bottom: 1px solid var(--color-border);
  color: var(--color-ink);
  text-decoration: none;
  font-family: var(--font-display);
  font-size: var(--step-h3);
  font-weight: 500;
  transition: color 0.2s ease, padding-left 0.2s ease;
  /* Same iOS Safari long-press-preview issue noted in bottom-nav.css. */
  -webkit-touch-callout: none;
}
.site-header__mobile-link-index {
  flex: none;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 13px;
  color: var(--color-ink-muted);
}
.site-header__mobile-link--plain {
  font-family: var(--font-utility);
  font-size: var(--step-lead);
}
.site-header__mobile-link:hover,
.site-header__mobile-link:focus-visible,
.site-header__mobile-link.is-active {
  color: var(--color-brand-green);
  padding-left: 8px;
}
.site-header__mobile-link.is-active .site-header__mobile-link-index {
  color: var(--color-brand-green);
}
.site-header__mobile-divider {
  height: 1px;
  margin-block: var(--space-md);
  background: var(--color-border);
}

@media (max-width: 900px) and (orientation: portrait) {
  .site-header__nav {
    display: none;
  }
  .site-header__mobile-menu {
    display: block;
  }
  /* Real overlap bug, confirmed via screenshot + measured rects: at
     narrow phone widths a CENTERED 320px pill leaves only ~27px clear
     on either side, but the theme-cord (theme-toggle.css, persistent
     top-left at every breakpoint by design) needs ~78px of clearance
     for its 44px knob. Reserving that as padding-left — rather than
     centering — means this pill no longer sits pixel-aligned with
     bottom-nav.css's independently-centered capsule at the very
     narrowest widths, which is a real but minor cosmetic tradeoff
     against an actual icon-on-top-of-nav-text bug. */
  .site-header {
    padding-left: 78px;
  }
  /* Collapsed to logo + hamburger here, the pill would naturally size
     to whatever those two need (~315px once the logo is legible-sized)
     while the bottom nav's own capsule sizes to its 4 icons (~250px) —
     two "floating pills" on the same screen, different widths, reading
     as unrelated. Force an identical explicit width (the SAME formula
     as bottom-nav.css's own portrait rule, so they resolve to the
     literal same pixel value at any given viewport width) and let the
     pill's content spread to fill it edge-to-edge instead of bunching
     left with dead space on the right. */
  .site-header__pill {
    width: min(320px, 100% - 32px);
  }
  /* Real bug, confirmed via getBoundingClientRect: .glass-content has
     no width of its own anywhere (glass.css), so as the pill's only
     flex child it was shrinking to its OWN content's size (~260px)
     instead of filling the pill's explicit 320px, and sitting
     flush-left inside the leftover space — a visible dead gap between
     the hamburger and the pill's right edge. `width: 100%` here is
     safe (no circular-sizing risk) because the pill's width is a
     concrete pixel value in this branch, not auto/content-based like
     the desktop rule. With this, `justify-content: space-between`
     below finally has real room to push logo/hamburger to the two
     edges, which also means the hamburger can no longer get pushed
     off-screen by a wide logo (the earlier bug that .site-header__
     logo-img's 30px mobile height was a workaround for) — the box
     itself now always matches the pill, so content just spreads
     within it instead of overflowing past it. */
  .site-header__pill > .glass-content {
    width: 100%;
    justify-content: space-between;
    /* Tighter than the desktop pill's own padding/gap (which was sized
       for logo + full nav + sound toggle) — with only logo+hamburger
       left here, that generous spacing was wasted width the logo
       could use instead. Freeing it is what makes a real size bump
       possible below, instead of trimming the logo down further. */
    padding-inline: 14px;
    gap: 12px;
  }
  /* Moved to the bottom app-nav at this breakpoint (bottom-nav.php) —
     see header.php's comment. Freeing this ~40px + gap is also what
     lets the logo render at a sane size instead of getting squeezed. */
  .site-header__sound-toggle--pill {
    display: none;
  }
  /* 32px — bigger than the previous 26px, made possible by trimming
     .glass-content's own padding/gap above rather than shrinking the
     logo further. The `min(320px, 100% - 32px)` pill-width formula's
     "100%" resolves against .site-header's CONTENT box — which the
     cord clearance above already shrank by 78px — so the pill is NOT
     a flat 320px on an actual phone the way it is at a wider "portrait
     tablet" test width: at a real 375px-wide phone it measures ~256px
     (confirmed live via getBoundingClientRect). Checked against the
     narrower ~355px case too (not just 375px) since that's the
     tighter constraint: still real safety margin there. */
  .site-header__logo-img {
    height: 32px;
  }
}
/* Extreme-narrow tier — checked down to 320px CSS width (iPhone SE 1st
   gen and similar) since "verified at 375px" isn't the same claim as
   "verified at the actual narrowest realistic width," a mistake this
   exact spot has made before. 32px overflows there (confirmed live:
   -14px clearance), so this steps back down just for ≤340px, while
   the more common 355px+ range keeps the bigger 32px above. */
@media (max-width: 340px) and (orientation: portrait) {
  .site-header__logo-img {
    height: 24px;
  }
}

/* Local safety net — see glass-shards.css's identical comment for why
   this is redundant-but-cheap alongside base.css's global rule. */
@media (prefers-reduced-motion: reduce) {
  .site-header__nav a,
  .site-header__nav summary,
  .site-header__hamburger-bar,
  .site-header__mobile-link,
  .site-header__sound-toggle,
  .site-header__sound-icon {
    transition: none !important;
  }
  .site-header__sound-icon--on path {
    animation: none !important;
    opacity: 1 !important;
  }
}


/* ---- footer.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  footer.css — Phase 5 site footer. Solid base but NOT flat: a radial
  brand-glow sits under the surface color. A plain, usual top border —
  the earlier angled clip-path divider was reverted per direction. All
  four nav columns (Explore/Legal/Connect) are plain links, same
  weight — a footer is wayfinding, not a second CTA bank; "Work with
  me" gets a color/weight bump via .site-footer__link--emphasis, not a
  button treatment.
*/

.site-footer {
  position: relative;
  isolation: isolate;
  margin-top: var(--space-xl);
  overflow: clip;
  color: var(--color-ink);
  background: var(--color-bg);
  border-top: 1px solid var(--color-border);
  /* Reserves the fixed back-to-top button's own footprint (see
     back-to-top.css's top comment) — kept INSIDE the footer's own
     padding box, not as body padding, so .site-footer::before's tinted
     glow extends through it instead of exposing the plain page
     background as a visibly different shade at the very bottom. */
  padding-bottom: calc(48px + 20px + 16px + env(safe-area-inset-bottom, 0px));
}
@media (max-width: 900px) and (orientation: portrait) {
  .site-footer {
    /* Back-to-top is display:none here (back-to-top.css) and
       bottom-nav.css's own body padding-bottom reserves space for the
       bottom nav bar instead — this footer doesn't need its own. */
    padding-bottom: 0;
  }
}
.site-footer::before {
  /* Brand glow — ties the footer to the palette without being a
     decorative card blur; it's a background tint, not a UI surface. */
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    radial-gradient(60% 140% at 12% 0%, rgba(14, 143, 94, 0.16), transparent 60%),
    var(--color-surface);
}

.site-footer__grid {
  display: grid;
  gap: var(--space-lg);
  grid-template-columns: 1.3fr 1fr 1fr 1.2fr;
  padding-block: var(--space-xl) var(--space-lg);
}
@media (max-width: 860px) {
  .site-footer__grid {
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  }
}

.site-footer__col--brand {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
}
.site-footer__mark {
  display: block;
  margin-bottom: var(--space-xs);
}
.site-footer__mark img {
  width: 44px;
  height: 44px;
}
.site-footer__brand-name {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--step-lead);
  font-weight: 500;
  color: var(--color-ink);
}
.site-footer__brand-role {
  margin: 0;
  max-width: 30ch;
  font-size: var(--step-caption);
  color: var(--color-ink-muted);
}

.site-footer__heading {
  margin: 0 0 var(--space-xs);
  font-family: var(--font-utility);
  font-size: var(--step-caption);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-ink-muted);
}

.site-footer__col nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.site-footer__col nav a {
  display: flex;
  align-items: center;
  min-height: var(--touch-target-min);
  color: var(--color-ink);
  text-decoration: none;
  font-size: var(--step-body);
  transition: color 0.15s ease;
  /* Same iOS Safari long-press-preview issue as bottom-nav.css's
     .bottom-nav__item — a plain <a> can show a callout menu instead
     of navigating on a slightly-too-long tap. */
  -webkit-touch-callout: none;
}
.site-footer__col nav a:hover {
  color: var(--color-brand-green);
}
/* Active marker — same underline language as the header's active nav
   items, not just a hover-only color change. */
.site-footer__col nav a.is-active {
  color: var(--color-brand-green);
  font-weight: 600;
  text-decoration: underline;
  text-decoration-color: var(--color-brand-green);
  text-underline-offset: 3px;
}

/* The molten underline/glow itself is .btn--link (buttons.css); this
   just keeps the deliberate green→ink color-flip on hover (not a
   shade-of-green tweak) — the underline follows via currentColor
   since it's drawn from the same property. */
.site-footer__link--emphasis {
  align-self: flex-start; /* .site-footer__col nav is a column flexbox (align-items: stretch by
    default) — without this, the anchor stretches to the column's full
    width, and .btn--link's background-size:100% underline then spans
    that whole width instead of just the text, reading as a stray line
    disconnected from the label. */
  color: var(--color-brand-green) !important;
  font-weight: 600;
}
.site-footer__link--emphasis:hover,
.site-footer__link--emphasis:focus-visible {
  color: var(--color-ink) !important;
}

.site-footer__bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding-block: var(--space-sm);
  border-top: 1px solid var(--color-border);
}

.site-footer__credit {
  margin: 0;
  font-size: var(--step-caption);
  color: var(--color-ink-muted);
}
.site-footer__credit a {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--color-border);
  text-underline-offset: 2px;
}
.site-footer__credit a:hover {
  color: var(--color-brand-green);
  text-decoration-color: currentColor;
}
/* Deploy build badge — deliberately quiet (smaller than the credit
   text it sits next to, muted, no border/pill/background) since it's
   a diagnostic for the operator, not something a visitor needs to
   notice. The title attribute (footer.php) carries the explanation;
   nothing here calls attention to it visually. */
.site-footer__version {
  font-size: 11px;
  color: var(--color-ink-muted);
  opacity: 0.6;
  cursor: help;
}

.site-footer__socials {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* CLEAR glass, not the dark-tinted --glass-secondary-bg token (that
   tint reads as "shadow" on a dark page, not "glass"). Same translucent
   white/brand-green recipe as glass-shards.css's clear-pane look —
   bright specular blob + thin bright rim — scaled to icon-button size,
   with a soft brand-green glow that blooms in on hover. */
.site-footer__social {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 999px;
  color: var(--color-ink);
  text-decoration: none;
  background:
    radial-gradient(120% 120% at 30% 22%, rgba(255, 255, 255, 0.55), transparent 55%),
    linear-gradient(135deg, rgba(255, 255, 255, 0.16), rgba(14, 143, 94, 0.12) 60%, rgba(255, 255, 255, 0.04));
  border: 1px solid rgba(255, 255, 255, 0.38);
  backdrop-filter: blur(6px) saturate(160%);
  -webkit-backdrop-filter: blur(6px) saturate(160%);
  box-shadow: 0 0 0 0 rgba(14, 143, 94, 0);
  transition: color 0.2s ease, box-shadow 0.25s ease, transform 0.2s ease, border-color 0.2s ease;
}
.site-footer__socials svg {
  width: 17px;
  height: 17px;
  position: relative;
  z-index: 1;
}
.site-footer__social:hover {
  color: var(--color-brand-green);
  border-color: rgba(14, 143, 94, 0.6);
  transform: translateY(-2px);
  box-shadow:
    0 0 16px 4px rgba(14, 143, 94, 0.45),
    0 6px 18px rgba(0, 0, 0, 0.25);
}
.site-footer__social-pending {
  opacity: 0.4;
  cursor: default;
  color: var(--color-ink-muted);
}

/* Dark mode specifically: the light-mode tint/border values above read
   as near-invisible against the dark page background (too little
   contrast between the button's own translucent fill and what's
   behind it) — brighten both for dark without touching light, which
   already has plenty of contrast against a white/near-white page. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .site-footer__social {
    color: #FFFFFF;
    border-color: rgba(255, 255, 255, 0.5);
    background:
      radial-gradient(120% 120% at 30% 22%, rgba(255, 255, 255, 0.4), transparent 55%),
      linear-gradient(135deg, rgba(255, 255, 255, 0.14), rgba(14, 143, 94, 0.22) 60%, rgba(255, 255, 255, 0.06));
  }
  /* The icons currently ON the page are in this pending state (no
     FOOTER_SOCIAL_LINKS URL yet) — that's what was actually "hard to
     see," not the (not-yet-reachable) linked-state color above. Keep
     it visibly distinct from an active icon (still dimmer, still
     cursor:default) but make the glyph itself legible against dark. */
  :root:not([data-theme="light"]) .site-footer__social-pending {
    color: rgba(255, 255, 255, 0.75);
    opacity: 0.65;
  }
}
:root[data-theme="dark"] .site-footer__social {
  color: #FFFFFF;
  border-color: rgba(255, 255, 255, 0.5);
  background:
    radial-gradient(120% 120% at 30% 22%, rgba(255, 255, 255, 0.4), transparent 55%),
    linear-gradient(135deg, rgba(255, 255, 255, 0.14), rgba(14, 143, 94, 0.22) 60%, rgba(255, 255, 255, 0.06));
}
:root[data-theme="dark"] .site-footer__social-pending {
  color: rgba(255, 255, 255, 0.75);
  opacity: 0.65;
}

/* Local safety net — see glass-shards.css's identical comment for why
   this is redundant-but-cheap alongside base.css's global rule. */
@media (prefers-reduced-motion: reduce) {
  .site-footer__col nav a,
  .site-footer__social {
    transition: none !important;
  }
}


/* ---- back-to-top.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  back-to-top.css — floating action button, bottom-left, primary-tier
  Clear Glass (see glass.css). Only shown on non-portrait-mobile/tablet
  devices; the fixed bottom nav's own "Top" item covers portrait
  mobile/tablet instead. This 900px/portrait breakpoint MUST stay in
  sync with header.css's nav-hide breakpoint — both encode the same
  "bottom nav is in charge below here" rule.

  Non-obstruction: footer.css's .site-footer gets bottom padding
  reserving this button's own footprint (48px + its 20px offset + a
  little breathing room) so scrolling to the true bottom of a long
  page never puts the footer's own copyright row underneath the fixed
  button. This used to be an UNSCOPED `body { padding-bottom }` rule
  instead — moved onto the footer itself (2026-08-14) because a plain
  body background doesn't carry the footer's own tinted
  background/glow (.site-footer::before), so that reserved strip
  showed as a visibly different, flatter shade right at the bottom of
  the page in dark mode. Putting the padding INSIDE the footer's own
  padding box means its ::before (inset: 0) naturally extends through
  it instead. Scoped to the same breakpoint as the button itself
  (see footer.css) — on portrait mobile/tablet, the button is hidden
  and bottom-nav.css's own body padding handles that case separately.
*/

.back-to-top {
  --glass-radius: 999px;

  position: fixed;
  left: max(20px, env(safe-area-inset-left, 0px));
  bottom: max(20px, env(safe-area-inset-bottom, 0px));
  z-index: 45;

  width: 48px;
  height: 48px;
  border-radius: 999px;
  border: none;
  padding: 0;
  background: transparent;
  cursor: pointer;
  color: var(--color-ink);
}

.back-to-top .glass-content {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
}
.back-to-top svg {
  width: 20px;
  height: 20px;
}

.back-to-top:hover {
  color: var(--color-brand-green); /* dark is the default/primary theme */
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .back-to-top:hover {
    color: var(--color-brand-green-safe);
  }
}
:root[data-theme="light"] .back-to-top:hover {
  color: var(--color-brand-green-safe);
}

@media (max-width: 900px) and (orientation: portrait) {
  .back-to-top {
    display: none !important;
  }
}


/* ---- glass-shards.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  glass-shards.css — sitewide floating Clear Glass shards. Small,
  clear, unevenly-shaped panes drifting slowly and widely over every
  page, fixed to the viewport. Non-interactive by design (drag was
  tried and removed — floating alone is the feature). Built from
  translucent white/brand-green tints, not new colors.

  Layering: fixed + z-index -1. A NEGATIVE z-index is the whole trick —
  by the CSS stacking spec, negative-z children paint below their
  parent's own in-flow content (text, links, cards), but above that
  parent's plain background. Since this partial is included as a direct
  child of <body> before header/main/footer, that means shards render
  behind literally every real element on the page (any text they'd
  otherwise sit under wins automatically, no per-position tuning
  needed) while still showing up in the page's empty margins/gutters —
  "not obstructive" enforced structurally.
*/

.glass-shards {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  overflow: hidden;
}

.shard {
  position: fixed;
  display: block;
  background:
    radial-gradient(120% 120% at 22% 18%, rgba(255, 255, 255, 0.6), transparent 50%),
    linear-gradient(135deg, rgba(255, 255, 255, 0.18), rgba(14, 143, 94, 0.14) 55%, rgba(255, 255, 255, 0.05));
  border: 1px solid rgba(255, 255, 255, 0.4);
  backdrop-filter: blur(3px) saturate(150%);
  -webkit-backdrop-filter: blur(3px) saturate(150%);
  filter: drop-shadow(0 8px 16px rgba(0, 0, 0, 0.14));
  will-change: transform;
  animation-name: shard-float;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}

/* Each shard wanders a real fraction of the viewport (vw/vh, not fixed
   px) across several waypoints rather than jittering ±10px in place —
   "moving more freely" means an actual walk, not a shiver. */
@keyframes shard-float {
  0%   { transform: translate(0, 0) rotate(var(--shard-rot, 0deg)); }
  25%  { transform: translate(var(--shard-x1, 8vw), var(--shard-y1, -6vh)) rotate(calc(var(--shard-rot, 0deg) + 10deg)); }
  50%  { transform: translate(var(--shard-x2, -6vw), var(--shard-y2, 9vh)) rotate(calc(var(--shard-rot, 0deg) - 8deg)); }
  75%  { transform: translate(var(--shard-x3, 10vw), var(--shard-y3, 7vh)) rotate(calc(var(--shard-rot, 0deg) + 4deg)); }
  100% { transform: translate(0, 0) rotate(var(--shard-rot, 0deg)); }
}

/* Sizes are one third of the original set. */
.shard-1 {
  top: 9%; left: 7%;
  width: 16px; height: 16px;
  clip-path: polygon(20% 0%, 100% 15%, 85% 100%, 0% 75%);
  --shard-rot: -12deg;
  --shard-x1: 10vw; --shard-y1: -8vh;
  --shard-x2: 4vw;  --shard-y2: 14vh;
  --shard-x3: 16vw; --shard-y3: 4vh;
  animation-duration: 34s;
  animation-delay: -2s;
}
.shard-2 {
  top: 16%; right: 12%;
  width: 21px; height: 21px;
  clip-path: polygon(0% 20%, 70% 0%, 100% 60%, 40% 100%, 0% 80%);
  --shard-rot: 20deg;
  --shard-x1: -12vw; --shard-y1: 8vh;
  --shard-x2: -20vw; --shard-y2: -5vh;
  --shard-x3: -8vw;  --shard-y3: 12vh;
  animation-duration: 42s;
  animation-delay: -9s;
}
.shard-3 {
  top: 46%; left: 15%;
  width: 11px; height: 11px;
  clip-path: polygon(10% 0%, 100% 25%, 75% 100%, 0% 90%);
  --shard-rot: 8deg;
  --shard-x1: 14vw; --shard-y1: 10vh;
  --shard-x2: 22vw; --shard-y2: -6vh;
  --shard-x3: 9vw;  --shard-y3: -14vh;
  animation-duration: 27s;
  animation-delay: -5s;
}
.shard-4 {
  top: 58%; right: 22%;
  width: 15px; height: 15px;
  clip-path: polygon(0% 0%, 100% 10%, 90% 100%, 15% 85%);
  --shard-rot: -18deg;
  --shard-x1: -16vw; --shard-y1: -10vh;
  --shard-x2: -6vw;  --shard-y2: -18vh;
  --shard-x3: -20vw; --shard-y3: 6vh;
  animation-duration: 38s;
  animation-delay: -12s;
}
.shard-5 {
  bottom: 22%; left: 9%;
  width: 13px; height: 13px;
  clip-path: polygon(15% 0%, 100% 30%, 80% 100%, 0% 60%);
  --shard-rot: 15deg;
  --shard-x1: 12vw; --shard-y1: -12vh;
  --shard-x2: 18vw; --shard-y2: -20vh;
  --shard-x3: 6vw;  --shard-y3: -8vh;
  animation-duration: 30s;
  animation-delay: -7s;
}
.shard-6 {
  bottom: 8%; right: 6%;
  width: 19px; height: 19px;
  clip-path: polygon(0% 15%, 60% 0%, 100% 70%, 45% 100%, 0% 55%);
  --shard-rot: -6deg;
  --shard-x1: -10vw; --shard-y1: -14vh;
  --shard-x2: -18vw; --shard-y2: -24vh;
  --shard-x3: -7vw;  --shard-y3: -10vh;
  animation-duration: 46s;
  animation-delay: -14s;
}
.shard-7 {
  top: 33%; right: 38%;
  width: 7px; height: 7px;
  clip-path: polygon(25% 0%, 100% 40%, 60% 100%, 0% 70%);
  --shard-rot: 30deg;
  --shard-x1: 9vw;  --shard-y1: 11vh;
  --shard-x2: -11vw; --shard-y2: 16vh;
  --shard-x3: 5vw;  --shard-y3: 22vh;
  animation-duration: 25s;
  animation-delay: -3s;
  opacity: 0.55;
}

/* Small viewports: fewer, smaller shards — still safe (negative
   z-index) at any size, this is purely about not over-decorating a
   screen that's mostly text edge-to-edge. */
@media (max-width: 640px) {
  .shard-2, .shard-4, .shard-7 {
    display: none;
  }
  .shard-1, .shard-3, .shard-5, .shard-6 {
    width: 10px;
    height: 10px;
  }
}

/* Local safety net — base.css's global `*` rule already zeroes
   animation-duration/iteration-count sitewide, so this is redundant in
   practice, but an explicit local guard means the float stops even if
   that global rule is ever scoped down later. */
@media (prefers-reduced-motion: reduce) {
  .shard {
    animation: none;
  }
}


/* ---- cursor-fx.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  cursor-fx.css — the reticle cursor-follower. Fixed, 26px box, moved
  via translate3d in cursor-fx.js (transform only — GPU-cheap, no
  layout thrash). Hidden by default; JS adds .is-active once it has a
  real mouse position, and .is-hover while over an interactive element.

  Themed per mode (not just recolored):
  - Dark (default/primary): a soft glowing filled center dot + a thin
    outer ring — reads well against a dark page.
  - Light (opt-in): a crisp plain outline only, no glow — a glow blooms
    into a muddy halo on a white background, so light mode drops it
    rather than fight that.
*/

.cursor-fx {
  position: fixed;
  top: 0;
  left: 0;
  width: 26px;
  height: 26px;
  margin-left: -13px;
  margin-top: -13px;
  pointer-events: none;
  z-index: 60; /* above page content, below nothing critical — purely decorative */
  opacity: 0;
  transition: opacity 0.2s ease;
  will-change: transform;
}
.cursor-fx.is-active {
  opacity: 1;
}
/* Scale-on-hover is applied by cursor-fx.js as part of the same
   translate3d() string it already sets every frame — an inline
   style always wins over a class rule here, so a separate
   `.is-hover { transform: ... }` rule would silently never apply. */

.cursor-fx__corner {
  position: absolute;
  width: 7px;
  height: 7px;
  border: 1.5px solid var(--color-brand-green);
  opacity: 0.85;
}
.cursor-fx__corner--tl { top: 0; left: 0; border-right: none; border-bottom: none; }
.cursor-fx__corner--tr { top: 0; right: 0; border-left: none; border-bottom: none; }
.cursor-fx__corner--bl { bottom: 0; left: 0; border-right: none; border-top: none; }
.cursor-fx__corner--br { bottom: 0; right: 0; border-left: none; border-top: none; }

.cursor-fx__dot {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 4px;
  height: 4px;
  margin: -2px 0 0 -2px;
  border-radius: 999px;
  background: var(--color-brand-green);
}

/* ---- Dark mode: glow ---- */
.cursor-fx__dot {
  box-shadow: 0 0 8px 2px rgba(14, 143, 94, 0.6);
}
.cursor-fx.is-hover .cursor-fx__dot {
  box-shadow: 0 0 12px 3px rgba(14, 143, 94, 0.8);
}

/* ---- Light mode: crisp, no glow ---- */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .cursor-fx__dot {
    box-shadow: none;
  }
  :root:not([data-theme="dark"]) .cursor-fx__corner {
    border-color: var(--color-brand-green-safe);
    opacity: 1;
  }
}
:root[data-theme="light"] .cursor-fx__dot {
  box-shadow: none;
}
:root[data-theme="light"] .cursor-fx__corner {
  border-color: var(--color-brand-green-safe);
  opacity: 1;
}

/* Reduced motion / touch: cursor-fx.js never adds .is-active in these
   cases, but belt-and-suspenders — force it invisible regardless. */
@media (prefers-reduced-motion: reduce) {
  .cursor-fx {
    display: none !important;
  }
}


/* ---- bottom-nav.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  bottom-nav.css — the "Floating Capsule" bottom nav. Portrait
  mobile/tablet ONLY — hidden everywhere else, same 900px/portrait
  breakpoint as header.css/back-to-top.css (all three encode the same
  "who's in charge of navigation here" rule; keep them in sync).
  Secondary-tier Clear Glass (see glass.css) — plain thin border, real
  blur, no chromatic rim (that technique was retired sitewide, see
  [[feedback_liquid_glass_technique]] in project memory).

  Non-obstruction: <body> gets extra bottom padding, scoped to the
  same breakpoint this nav shows in, sized to the nav's own reserved
  footprint — so scrolling all the way down on a portrait device
  always clears the footer's own content above the fixed bar, never
  underneath it. If the item height/padding/bottom-offset numbers
  below ever change, the body padding-bottom sum at the end of this
  file MUST change with them.
*/

.bottom-nav {
  --glass-radius: 999px;
  display: none; /* shown only inside the breakpoint below */
  position: fixed;
  left: 50%;
  bottom: max(16px, env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  z-index: 46;
  max-width: calc(100% - 32px); /* floats clear of both screen edges, never flush */
  border-radius: 999px; /* the actual fix — --glass-radius alone did nothing;
    glass.css's ::before reads the element's own border-radius via
    `inherit`, not the custom property, so the outer capsule was
    rendering square while each item stayed pill-shaped individually.
    Same treatment as .site-header__pill now, genuinely synced. */
}
.bottom-nav .glass-content {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 6px;
}

.bottom-nav__item {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 44px;
  min-height: 44px;
  padding-inline: 11px;
  border: none;
  border-radius: 999px;
  background: transparent;
  color: var(--color-ink-muted);
  text-decoration: none;
  cursor: pointer;
  overflow: hidden;
  transition: color 0.2s ease, background-color 0.2s ease;
  /* Real bug, found post-launch (2026-08-24) — reported as the bottom
     nav's label text getting cut off on a real mobile device (not
     reproducible in this project's own test tooling). At the time
     each item still had a hover/active label-reveal span (since
     removed — see the comment where .bottom-nav__label used to be
     defined, below), and nothing declared flex-shrink, so the flex
     row was free to silently squeeze an item below its content width
     the moment a label expanded. The label itself is gone now
     (icon-only, permanently), but flex-shrink: 0 stays as a correct
     defensive default regardless. */
  flex-shrink: 0;
  /* Explicit, not assumed — without this, some mobile browsers hold a
     tap briefly to disambiguate it from the start of a double-tap-to-
     zoom gesture before firing the real click, which can read as "the
     first tap did nothing." Belt-and-suspenders alongside the
     width=device-width viewport meta tag (head-meta.php), which
     already disables that gesture site-wide in evergreen browsers. */
  touch-action: manipulation;
  /* iOS Safari specifically: an <a href> (unlike a <button>) shows a
     long-press preview/context menu (Open, Copy Link, etc.) by
     default — if a tap lands even slightly like a long-press, the
     menu appears instead of navigating, and the real navigation only
     happens on a second, cleaner tap. Home/Archive here are real <a>
     tags while Categories/Sound/Top are <button>s, which never get
     this treatment — a plausible explanation for taps on the first
     two needing to be repeated. Harmless on the button items too. */
  -webkit-touch-callout: none;
}
.bottom-nav__item svg {
  width: 20px;
  height: 20px;
  flex: none;
}

/* The sound-toggle item reuses header.css's crossfading on/off icon
   pair (.site-header__sound-icon) instead of a single static svg —
   same visual language as the header's own mute button, just sized
   to match this row's 20px icons. */
.bottom-nav__sound-icons {
  position: relative;
  width: 20px;
  height: 20px;
  flex: none;
}
.bottom-nav__sound-icons .site-header__sound-icon {
  width: 20px;
  height: 20px;
}

/* .bottom-nav__label (the hover/active label-reveal span) REMOVED
   2026-08-24 — real bug reported post-launch (text clipping on a real
   mobile device, not reproducible in this project's own test tooling)
   led first to a defensive fix (flex-shrink: 0 above, a bigger max-
   width, an ellipsis fallback), then a simpler direct instruction:
   drop the label entirely, icon-only is sufficient. The markup no
   longer emits the span at all (templates/partials/bottom-nav.php) —
   every item still carries a real `aria-label` for the accessible
   name. Only the color/background hover-and-active state remains.

   No text-clipping class of bug can recur here — the "flex-shrink:0
   found missing while investigating an unreproducible max-width
   squeeze" lesson stays live above though, since that's a correct
   defensive pattern independent of this specific label. */
.bottom-nav__item.is-active,
.bottom-nav__item:hover,
.bottom-nav__item:focus-visible {
  color: var(--color-brand-green);
  background: rgba(14, 143, 94, 0.14);
}

@media (max-width: 900px) and (orientation: portrait) {
  .bottom-nav {
    display: block;
    /* Same explicit width, same formula, as header.css's portrait rule
       for .site-header__pill — the two floating pills on this screen
       are meant to read as one system, not two coincidentally-similar
       capsules. */
    width: min(320px, 100% - 32px);
  }
  .bottom-nav .glass-content {
    justify-content: space-between;
  }
  body {
    /* item min-height(44) + glass-content padding(6+6) + nav bottom
       offset(16) + a little breathing room, plus the safe-area inset. */
    padding-bottom: calc(44px + 12px + 16px + 12px + env(safe-area-inset-bottom, 0px));
  }
}

/* ---- Category sheet — opens from the bottom nav's Categories
   button (bottom-nav.php/bottom-nav.js). A real bottom sheet, not a
   dead link: dedicated per-category pages don't exist as content yet,
   so every card here jumps into the real /blog-archive/ page's matching
   section instead (#slug anchors — see archive-listing.php's row
   ids). Solid tinted surfaces throughout, no backdrop-filter — same
   page-wide reasoning as the Breakout game modal and the FAQ items. */
.category-sheet {
  position: fixed;
  inset: 0;
  z-index: 250; /* above the bottom nav (46) and the theme-cord (210), below the game modal (300) */
  display: flex;
  align-items: flex-end;
  justify-content: center;
}
.category-sheet[hidden] {
  display: none;
}
.category-sheet__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(4, 8, 9, 0.7);
  opacity: 0;
  transition: opacity 0.25s ease;
}
.category-sheet__panel {
  position: relative;
  width: 100%;
  max-width: 480px;
  max-height: min(78vh, 640px);
  overflow-y: auto;
  padding: var(--space-sm) var(--space-lg) calc(var(--space-lg) + env(safe-area-inset-bottom, 0px));
  border: 1px solid var(--color-border);
  border-bottom: none;
  border-radius: 22px 22px 0 0;
  background: var(--color-surface);
  box-shadow: 0 -12px 40px rgba(0, 0, 0, 0.35);
  transform: translateY(100%);
  transition: transform 0.32s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.category-sheet.is-open .category-sheet__backdrop {
  opacity: 1;
}
.category-sheet.is-open .category-sheet__panel {
  transform: translateY(0);
}

.category-sheet__handle {
  width: 36px;
  height: 4px;
  margin: 0 auto var(--space-sm);
  border-radius: 999px;
  background: var(--color-border);
}
.category-sheet__head {
  position: relative;
  margin-bottom: var(--space-md);
  padding-right: 40px;
}
.category-sheet__title {
  margin: 4px 0 0;
  font-family: var(--font-display);
  font-size: var(--step-h3);
  font-weight: 500;
}
.category-sheet__close {
  position: absolute;
  top: 0;
  right: 0;
  width: 32px;
  height: 32px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: transparent;
  color: var(--color-ink-muted);
  cursor: pointer;
}
.category-sheet__close svg {
  width: 16px;
  height: 16px;
}
.category-sheet__close:hover,
.category-sheet__close:focus-visible {
  color: var(--color-brand-green);
  border-color: rgba(14, 143, 94, 0.55);
}

/* Single column, not the earlier 2-column grid — real bug, confirmed
   live: at 2 columns each card was only ~220px wide (480px panel /
   2), which wrapped "Workflow Optimization" (nav_label,
   config/site.config.php) onto two lines. One column gives every
   card the panel's near-full ~430px width, comfortably fitting any
   category name on one line, current or future, without needing a
   number re-tuned per label. */
.category-sheet__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-sm);
}
.category-sheet__card-name {
  white-space: nowrap;
}
.category-sheet__card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: var(--space-sm) var(--space-md);
  border-radius: 16px;
  border: 1px solid var(--color-border);
  background: color-mix(in srgb, var(--color-surface) 70%, var(--color-brand-green) 6%);
  color: inherit;
  text-decoration: none;
  opacity: 0;
  transform: translateY(10px);
  transition: border-color 0.2s ease, transform 0.2s ease;
}
.category-sheet.is-open .category-sheet__card {
  animation: category-card-in 0.35s ease-out forwards;
  animation-delay: calc(var(--i, 0) * 0.06s);
}
.category-sheet__card:hover,
.category-sheet__card:focus-visible {
  border-color: rgba(14, 143, 94, 0.55);
  transform: translateY(-2px);
}
.category-sheet__card-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 10px;
  background: rgba(14, 143, 94, 0.14);
  color: var(--color-brand-green);
}
.category-sheet__card-icon svg {
  width: 18px;
  height: 18px;
}
.category-sheet__card-name {
  font-family: var(--font-display);
  font-size: var(--step-body);
  font-weight: 500;
}
.category-sheet__card-desc {
  font-size: 12px;
  line-height: 1.4;
  color: var(--color-ink-muted);
}
@keyframes category-card-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .bottom-nav__item {
    transition: none !important;
  }
  .category-sheet__backdrop,
  .category-sheet__panel,
  .category-sheet__card {
    transition: none !important;
    animation: none !important;
  }
  .category-sheet.is-open .category-sheet__card {
    opacity: 1 !important;
    transform: none !important;
  }
}


/* ---- theme-toggle.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  theme-toggle.css — the pull-cord light/dark toggle. Fixed to the
  viewport top, present at every screen size (unlike the bottom nav/
  back-to-top, this isn't breakpoint-gated — "on all kinds of devices/
  screen sizes"). z-index 210, above the header's own 200 (see
  header.css's comment on why the header needed that) and everything
  else fixed on the page — it's a persistent global control.

  The knob starts at its CSS rest position (translate(0,34px), matching
  REST_Y in theme-toggle.js) so it's in the right place even before JS
  runs; the script takes over from there every frame.
*/

.theme-cord {
  position: fixed;
  top: 0;
  /* Left, not right — the mobile drawer's × close button pins itself
     to top-right (see header.css), and both are persistent/high
     z-index fixed elements, so sharing a corner meant they overlapped
     whenever the drawer was open. Top-left is empty at every
     breakpoint (the header pill is centered). */
  left: max(28px, env(safe-area-inset-left, 0px));
  width: 40px;
  height: 150px; /* tall enough for REST_Y(56) + the drag pull range */
  z-index: 210;
  pointer-events: none;
}
.theme-cord__rope {
  position: absolute;
  inset: 0;
  overflow: visible;
  pointer-events: none;
}
.theme-cord__rope path {
  stroke: var(--color-border);
}

.theme-cord__knob {
  --glass-radius: 999px;
  position: absolute;
  left: 50%;
  top: 0;
  width: 44px;
  height: 44px;
  margin-left: -22px;
  /* Real size is 44px (matches the touch-target-min used sitewide —
     back-to-top is 48px, footer socials are 40px — 44 fits right in),
     NOT the 26px this was first written with: base.css's generic
     `button { min-width/min-height: 44px }` accessibility rule was
     silently overriding an explicit 26px width/height, so the actual
     rendered knob was already 44px while the JS math assumed a 13px
     radius — that mismatch was the real source of the visible gap
     between the rope and the bead, not just a rope-endpoint bug.
     34 = REST_Y(56) - KNOB_R(22): this is the static fallback shown
     before JS's own render() call overrides it, so it must use the
     same center-alignment math or there'd be a 1-frame jump. REST_Y
     itself was dropped from 90 to 56 separately — see theme-toggle.js —
     to stop the knob sitting on top of page H1s on some layouts. */
  transform: translate(0, 34px);
  border: none;
  border-radius: 999px;
  padding: 0;
  cursor: grab;
  pointer-events: auto;
  color: var(--color-brand-green);
  touch-action: none; /* dragging owns vertical touch gestures on the knob itself */
  will-change: transform;
}
.theme-cord__knob:active {
  cursor: grabbing;
}
.theme-cord__knob:hover,
.theme-cord__knob:focus-visible {
  color: var(--color-ink);
}
.theme-cord__knob .glass-content {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
}
.theme-cord__icon {
  width: 18px;
  height: 18px;
}

/* Icon shows the CURRENT theme (sun when light is active, moon when
   dark is) — same three-way dark-default/light-opt-in pattern as the
   header logo swap. */
.theme-cord__icon--sun {
  display: none;
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .theme-cord__icon--moon { display: none; }
  :root:not([data-theme="dark"]) .theme-cord__icon--sun { display: block; }
}
:root[data-theme="light"] .theme-cord__icon--moon { display: none; }
:root[data-theme="light"] .theme-cord__icon--sun { display: block; }

/* The theme cross-fade itself (View Transitions API, triggered from
   theme-init.js). Custom duration/easing instead of the browser
   default so it reads as a deliberate "scene change," not a flicker. */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.5s;
  animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

@media (prefers-reduced-motion: reduce) {
  .theme-cord__knob {
    transition: none !important;
  }
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation: none !important;
  }
}


/* ---- homepage-intro.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  homepage-intro.css — the single identity card that opens the
  homepage (see homepage-intro.php). Single-column text now — the
  earlier side-by-side text+portrait layout is gone along with the
  Game of Life; the wireframe globe (assets/js/hero-globe-bg.js) runs
  as a full-bleed background layer behind the text instead of sharing
  a row with it.
*/

.intro {
  --glass-radius: 24px;
  position: relative;
  border-radius: 24px;
  /* No overflow: hidden — hero-globe-bg.js fits the sphere fully
     inside the card on both axes (see its own comment), so nothing
     should ever overflow. Deliberately not relying on clipping to
     enforce that: overflow:hidden + a WebGL canvas + border-radius is
     a known WebKit/mobile combo that fails to re-clip correctly
     during scroll — confirmed live as a white "ghost" box appearing
     below the card while scrolling on mobile/tablet. */
  margin-block: var(--space-lg) var(--space-xl);
}
.intro__layout {
  padding: var(--space-lg);
}
@media (max-width: 640px) {
  .intro__layout {
    padding: var(--space-md);
  }
}

/* Wireframe scan-reveal globe, full-bleed behind the text. Sits above
   the card's own glass-primary::before tint (glass.css) in DOM order
   but below .glass-content (z-index: 1 there) — low opacity so the
   green wireframe/solid reads as background texture, not something
   competing with the headline for attention. */
.intro__globe-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}
/* Desktop/laptop/widescreen only: anchor the globe's mount to the
   card's right side instead of stretching full-width behind the text
   — per direct feedback, centering it across the whole (very wide)
   card read as unnecessarily centered rather than intentionally
   placed. hero-globe-bg.js reads this element's own width/height for
   its camera fit, so shrinking it here is enough on its own — no JS
   change needed, and the fit-both-axes math (see hero-globe-bg.js)
   still guarantees nothing clips inside this smaller box. Left full-
   width on tablet/mobile, where there's no spare horizontal room to
   separate it from the text anyway. */
@media (min-width: 900px) {
  .intro__globe-bg {
    left: auto;
    width: min(48%, 460px);
  }
}
.intro__globe-canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 0.6s ease;
}
.intro__globe-bg--live .intro__globe-canvas {
  opacity: 0.4;
}
@media (prefers-reduced-motion: reduce) {
  .intro__globe-canvas {
    transition: none !important;
  }
}

.intro__text {
  position: relative;
  max-width: 62ch;
}
.intro__eyebrow {
  margin: 0;
  font-family: var(--font-utility);
  font-size: var(--step-caption);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-brand-green);
}
.intro__title {
  margin: 6px 0 var(--space-sm);
  font-family: var(--font-display);
  font-size: var(--step-h2);
  font-weight: 500;
  line-height: 1.15;
}
.intro__bio {
  margin: 0 0 var(--space-xs);
  max-width: 58ch;
  color: var(--color-ink-muted);
}
@media (max-width: 640px) {
  .intro__bio {
    max-width: none;
  }
}
.intro__stat {
  margin: 0 0 var(--space-sm);
  font-size: var(--step-caption);
  color: var(--color-ink-muted);
}
.intro__stat strong {
  color: var(--color-brand-green);
  font-family: var(--font-display);
}

.intro__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
}



/* ---- homepage-archive-preview.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  homepage-archive-preview.css — shared by the homepage's flat "Latest
  posts" 2x2 grid (homepage-archive-preview.php) and the full
  per-category rows on /blog-archive/ (archive-page.php) — both
  include archive-listing.php for the actual card markup. Uses the
  shared .section-head component (base.css) for its own heading.
*/

.archive-preview {
  margin-block: var(--space-xl);
  scroll-margin-top: var(--space-lg); /* so #latest-posts doesn't land flush under the fixed header on anchor-scroll */
}

/* The homepage's own "Browse the full archive" link, replacing the
   per-category "View all X posts" links that don't apply to a flat
   grid — .section-head (base.css) is a plain block shared by other
   homepage sections with no link of their own, so this stays scoped
   here rather than changing that shared component. */
.archive-preview .section-head__link {
  display: inline-flex;
  margin-top: var(--space-sm);
  font-family: var(--font-utility);
  font-size: var(--step-caption);
}

.archive-preview__row {
  margin-bottom: var(--space-lg);
  scroll-margin-top: calc(var(--space-lg) + 60px); /* clears the fixed header/bottom-nav on #slug anchor-scroll from the category sheet */
}
.archive-preview__row-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-sm);
  margin-bottom: var(--space-sm);
  flex-wrap: wrap;
}
.archive-preview__row-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--step-h3);
  font-weight: 500;
}
/* Per direct feedback (2026-08-20): each category row heading on
   /blog-archive/ gets its own color, not the same brand green
   repeated six times — real visual distinction scanning down a long
   page of category rows, not just the icon/name changing. Signal
   Emerald itself is deliberately monochrome (see
   branding/colors/signal-emerald.md — "the site moved 100% to
   Clear Glass... single brand hue"), so this is a small, scoped
   extension for large-text headings only (WCAG's 3:1 large-text
   threshold, not the stricter 4.5:1 body-text rule these row titles
   don't need to clear) — every other heading/link sitewide stays
   plain brand green, unaffected. Order matches CATEGORIES' own
   insertion order (config/site.config.php), so a row's color stays
   stable as long as that order doesn't change; nth-of-type is safe
   here since .archive-preview__row-title only ever appears in this
   one grouped context (the homepage's own "Latest posts" grid is
   flat now, no row titles at all). */
.archive-page__body ~ .archive-preview .archive-preview__row:nth-of-type(1) .archive-preview__row-title { color: #13A876; } /* AI Blogs — emerald */
.archive-page__body ~ .archive-preview .archive-preview__row:nth-of-type(2) .archive-preview__row-title { color: #C79A3E; } /* Workflow Optimization Blogs — amber */
.archive-page__body ~ .archive-preview .archive-preview__row:nth-of-type(3) .archive-preview__row-title { color: #4E9BD1; } /* Windows Blogs — sky blue */
.archive-page__body ~ .archive-preview .archive-preview__row:nth-of-type(4) .archive-preview__row-title { color: #D1724E; } /* Linux Blogs — burnt orange */
.archive-page__body ~ .archive-preview .archive-preview__row:nth-of-type(5) .archive-preview__row-title { color: #8F7AD9; } /* Self-Hosting Blogs — violet */
.archive-page__body ~ .archive-preview .archive-preview__row:nth-of-type(6) .archive-preview__row-title { color: #D1608F; } /* Tech Journey Blogs — rose */
/* Color/underline hover now comes from .btn--link (buttons.css) — this
   just fits it into the row-head's layout. */
.archive-preview__row-link {
  font-family: var(--font-utility);
  font-size: var(--step-caption);
  white-space: nowrap;
}

.archive-preview__cards {
  display: grid;
  gap: var(--space-sm);
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
}
/* The homepage's flat "Latest posts" grid — exactly 2 columns (a
   real 2x2 for 4 cards), not the per-category rows' auto-fit flow
   above. Comes after that rule so it wins at equal specificity.
   Single column under 560px, same breakpoint reasoning as this
   card's own min-width elsewhere on the site. */
.archive-preview__cards--flat {
  grid-template-columns: repeat(2, 1fr);
}
@media (max-width: 560px) {
  .archive-preview__cards--flat {
    grid-template-columns: 1fr;
  }
}

/* Hover: a real Clear Glass response (the tint's own border/blur
   tokens brighten — not a fake color swap) plus a "targeting
   reticle" of four corner brackets and a one-shot scanline, echoing
   the scan/reveal language used elsewhere on the homepage. These
   cards aren't links yet (no real posts to point at), so the effect
   is polish, not a click affordance — hence aria-hidden on every
   decorative element. */
.archive-card {
  --glass-radius: 14px;
  position: relative;
  display: block;
  border-radius: 14px;
  overflow: hidden;
  transition: transform 0.25s ease;
  /* A real card is an <a> now (archive-listing.php), an <article> for
     pure placeholders — reset link styling for the former so both
     render identically; nothing here should look like default browser
     link text since the whole card is the click target. */
  color: inherit;
  text-decoration: none;
}
.archive-card:hover {
  transform: translateY(-4px);
  --glass-secondary-border: rgba(14, 143, 94, 0.6);
  --glass-secondary-blur: 11px;
}
/* ---- Featured image (dummy for now — see archive-listing.php's
   iitc_render_card_thumb()) — a "circuit board" panel instead of a
   stock photo: PCB-trace lines, the category's own icon glowing at
   the center like a chip, a monospace HUD tag. Real inline SVG, not
   a raster placeholder, so it inherits currentColor/theme tokens like
   any other on-brand graphic and costs zero image requests. ---- */
.archive-card__thumb {
  display: block;
  aspect-ratio: 16 / 9;
  color: var(--color-brand-green);
}
/* A real <img> (once real posts exist) shares this same box — base.css's
   global `img { height: auto }` would otherwise break the fixed 16:9
   crop, so height/object-fit are pinned explicitly here rather than
   relying on that global rule. */
.archive-card__thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.archive-card__thumb-svg,
.archive-card__thumb img {
  display: block;
  width: 100%;
  height: 100%;
  transition: transform 0.35s ease;
}
.archive-card:hover .archive-card__thumb-svg,
.archive-card:hover .archive-card__thumb img {
  transform: scale(1.045);
}
/* Fixed dark fill regardless of site theme — like a terminal/code
   screenshot, this "chip" imagery is meant to read as its own dark
   console panel, not a tinted version of the page surface. Tested
   against --color-surface in light mode: mixing a light surface into
   a low-opacity green motif produced a washed-out, low-contrast
   result (both the circuit lines and the chip nearly disappeared). */
.thumb-bg {
  fill: #0B1215;
}
.thumb-circuit {
  color: var(--color-brand-green);
  opacity: 0.32;
  stroke: currentColor;
  /* A fixed dasharray longer than any path in the three variants —
     partially drawn at rest (dashoffset 90, not 0: fully hidden read
     as an empty panel rather than a faint circuit) and draws the rest
     of the way in on hover, reading as the trace "powering on" rather
     than a plain fade. */
  stroke-dasharray: 260;
  stroke-dashoffset: 90;
  transition: opacity 0.3s ease, stroke-dashoffset 0.9s ease;
}
.archive-card:hover .thumb-circuit {
  opacity: 0.85;
  stroke-dashoffset: 0;
}
.thumb-glow {
  fill: var(--color-brand-green);
  opacity: 0;
  transition: opacity 0.35s ease;
}
.archive-card:hover .thumb-glow {
  opacity: 0.22;
}
.thumb-chip {
  color: var(--color-brand-green);
  transition: filter 0.3s ease;
}
.thumb-chip rect {
  stroke: currentColor;
  opacity: 0.8;
}
.archive-card:hover .thumb-chip {
  filter: drop-shadow(0 0 7px rgba(14, 143, 94, 0.65));
}
.thumb-tag {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 11px;
  letter-spacing: 0.02em;
  fill: var(--color-brand-green);
  opacity: 0.55;
  transition: opacity 0.25s ease;
}
.archive-card:hover .thumb-tag {
  opacity: 0.9;
}
/* No light-mode override needed: .thumb-bg is a fixed dark fill in
   both themes (see its own comment above), so the green motif already
   has the same dark backdrop — and therefore the same contrast — it
   was designed against, regardless of site theme. */

.archive-card > .glass-content {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: var(--space-sm) var(--space-md);
  min-height: 110px;
}
.archive-card__meta {
  font-family: var(--font-utility);
  font-size: 11px;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--color-ink-muted);
}
.archive-card__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--step-body);
  font-weight: 500;
  line-height: 1.25;
  transition: color 0.2s ease;
}
.archive-card:hover .archive-card__title {
  color: var(--color-brand-green);
}
.archive-card__excerpt {
  display: block;
  font-size: var(--step-caption);
  color: var(--color-ink-muted);
}

.archive-card__corner {
  position: absolute;
  width: 16px;
  height: 16px;
  border: 0 solid var(--color-brand-green);
  opacity: 0;
  pointer-events: none;
  z-index: 3;
  transition: opacity 0.25s ease, transform 0.25s ease;
}
.archive-card__corner--tl { top: 7px; left: 7px; border-width: 2px 0 0 2px; transform: translate(6px, 6px); }
.archive-card__corner--tr { top: 7px; right: 7px; border-width: 2px 2px 0 0; transform: translate(-6px, 6px); }
.archive-card__corner--bl { bottom: 7px; left: 7px; border-width: 0 0 2px 2px; transform: translate(6px, -6px); }
.archive-card__corner--br { bottom: 7px; right: 7px; border-width: 0 2px 2px 0; transform: translate(-6px, -6px); }
.archive-card:hover .archive-card__corner {
  opacity: 1;
  transform: translate(0, 0);
}

.archive-card__scan {
  position: absolute;
  left: 0;
  right: 0;
  top: -30%;
  height: 30%;
  background: linear-gradient(180deg, transparent, rgba(14, 143, 94, 0.45), transparent);
  opacity: 0;
  pointer-events: none;
  z-index: 1;
}
.archive-card:hover .archive-card__scan {
  opacity: 1;
  animation: archive-card-scan 0.8s ease-out;
}
@keyframes archive-card-scan {
  from { top: -30%; }
  to   { top: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  .archive-card,
  .archive-card__title,
  .archive-card__corner {
    transition: none !important;
  }
  .archive-card:hover {
    transform: none !important;
  }
  .archive-card__scan {
    animation: none !important;
    opacity: 0 !important;
  }
}

/* .archive-empty — the real "nothing published yet" state
   (templates/partials/archive-listing.php's iitc_render_archive_empty_state()),
   replacing what used to be a grid of fake placeholder cards. One
   real Clear Glass block, real copy, a real newsletter link — no
   fabricated content pretending to be a post. */
.archive-empty {
  border-radius: 14px;
}
.archive-empty__inner {
  padding: var(--space-lg) var(--space-md);
  text-align: center;
}
.archive-empty__text {
  margin: 0;
  color: var(--color-ink-muted);
  font-size: 0.95rem;
  line-height: 1.6;
}
.archive-empty__text a {
  color: var(--color-brand-green);
  font-weight: 600;
}


/* ---- homepage-about.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  homepage-about.css — the compact "what this blog is, what I do,
  how to reach me" section. Plain flowing layout: a lede paragraph,
  then a two-column row (service links / CTA buttons) that stacks on
  narrow screens. No card grid, no scroll-reveal animation.
*/

.about-strip {
  margin-block: var(--space-xl);
  padding-top: var(--space-lg);
  border-top: 1px solid var(--color-border);
}

.about-strip__layout {
  display: flex;
  gap: var(--space-xl);
  align-items: stretch;
}
@media (max-width: 780px) {
  .about-strip__layout {
    flex-direction: column;
  }
}

.about-strip__main {
  flex: 1.4;
  min-width: 0;
}

.about-strip__lede {
  max-width: 68ch;
  color: var(--color-ink-muted);
}

.about-strip__services {
  margin-top: var(--space-md);
}

.about-strip__services h3 {
  margin: 0 0 var(--space-sm);
  font-family: var(--font-display);
  font-size: var(--step-h3);
  font-weight: 500;
}
.about-strip__services ul {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.about-strip__services a {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--color-ink);
  text-decoration: none;
  font-size: var(--step-body);
  transition: color 0.15s ease;
}
.about-strip__services a svg {
  width: 18px;
  height: 18px;
  flex: none;
  color: var(--color-brand-green);
}
.about-strip__services a:hover,
.about-strip__services a:focus-visible {
  color: var(--color-brand-green);
  text-decoration: underline;
}

.about-strip__cta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin-top: var(--space-lg);
}

/* Decorative glass block — fills the space beside the text on wide
   viewports. Real Clear Glass (backdrop blur + tint + border, same
   tokens as every other glass surface), a faint terminal-line
   texture, and a soft green scanline that sweeps top-to-bottom on a
   continuous loop. Purely decorative (aria-hidden), so it's fine for
   the motion to run indefinitely — it never carries content a user
   needs to read. */
.about-strip__glass {
  position: relative;
  flex: 1;
  min-height: 220px;
  border-radius: 22px;
  border: 1px solid var(--glass-primary-border);
  background-image:
    var(--glass-primary-bg),
    repeating-linear-gradient(0deg, transparent 0 27px, rgba(255, 255, 255, 0.035) 27px 28px);
  backdrop-filter: blur(var(--glass-primary-blur));
  -webkit-backdrop-filter: blur(var(--glass-primary-blur));
  box-shadow: var(--glass-shadow);
  overflow: hidden;
}
@media (max-width: 780px) {
  .about-strip__glass {
    min-height: 140px;
  }
}
.about-strip__scan {
  position: absolute;
  left: -15%;
  right: -15%;
  top: -45%;
  height: 45%;
  background: linear-gradient(180deg,
    transparent,
    rgba(14, 143, 94, 0.28) 40%,
    rgba(14, 143, 94, 0.5) 50%,
    rgba(14, 143, 94, 0.28) 60%,
    transparent);
  filter: blur(3px);
  animation: about-scan-sweep 7s ease-in-out infinite;
}
@keyframes about-scan-sweep {
  0%   { top: -45%; }
  50%  { top: 100%; }
  100% { top: -45%; }
}

/* Name label — invisible almost the whole cycle, briefly readable
   right as the scanline crosses it (same 7s timing as
   about-scan-sweep, so the two always stay in sync). Reads like a
   terminal scan revealing a hidden line, not a persistent caption. */
.about-strip__glass-label {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: var(--step-lead);
  letter-spacing: 0.04em;
  color: var(--color-ink);
  text-shadow: 0 0 16px rgba(14, 143, 94, 0.6);
  opacity: 0;
  animation: about-scan-label 7s ease-in-out infinite;
}
@keyframes about-scan-label {
  0%, 18%   { opacity: 0; filter: blur(4px); }
  25%       { opacity: 1; filter: blur(0); }
  32%, 100% { opacity: 0; filter: blur(4px); }
}

@media (prefers-reduced-motion: reduce) {
  .about-strip__services a {
    transition: none !important;
  }
  .about-strip__scan {
    animation: none !important;
    top: 30%;
  }
  .about-strip__glass-label {
    animation: none !important;
    opacity: 1;
    filter: none !important;
  }
}


/* ---- shatter-glass.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  shatter-glass.css — the "broken clear glass" decorative motif shared
  by homepage-journey.php and homepage-craft.php. Solid tinted panel
  (NOT backdrop-filter — this page is already documented as sitting
  past the point where more real Clear Glass layers silently stop
  painting), but genuinely EVENTFUL, not a static crack drawing:
    - each fracture line flickers on its own staggered timer, reading
      as current still arcing through the break, not a dead diagram
    - a bright flash pulses at the impact point in sync with the
      cracks brightening, like the moment of impact keeps replaying
    - a diagonal light sheen sweeps across the whole panel on a slow
      loop, so the surface itself reads as reflective glass, not flat
      paint
  All three layered together is what makes this feel like something
  actually happening, not a still image with a name.
*/

.shatter-bg {
  fill: color-mix(in srgb, var(--color-surface) 85%, var(--color-brand-green) 8%);
}

.shatter-glow {
  fill: var(--color-brand-green);
  opacity: 0.16;
  transform-origin: 190px 90px;
}
@media (prefers-reduced-motion: no-preference) {
  .shatter-glow {
    animation: shatter-glow-breathe 3.2s ease-in-out infinite;
  }
}
@keyframes shatter-glow-breathe {
  0%, 100% { opacity: 0.14; transform: scale(1); }
  50%      { opacity: 0.3;  transform: scale(1.12); }
}

.shatter-cracks {
  stroke: var(--color-brand-green);
  stroke-width: 1.4;
  opacity: 0.55;
}
/* Each fracture segment flickers on its own offset timer — an
   "arcing current" read, not a single uniform pulse. nth-child covers
   the main 6 radiating spokes plus their shorter branch segments;
   cycling through 7 distinct delays keeps it from ever looking like
   the whole graphic blinks in sync. */
@media (prefers-reduced-motion: no-preference) {
  .shatter-cracks path {
    animation: shatter-crackle 2.6s ease-in-out infinite;
  }
  .shatter-cracks path:nth-child(1) { animation-delay: 0s; }
  .shatter-cracks path:nth-child(2) { animation-delay: 0.3s; }
  .shatter-cracks path:nth-child(3) { animation-delay: 0.65s; }
  .shatter-cracks path:nth-child(4) { animation-delay: 1s; }
  .shatter-cracks path:nth-child(5) { animation-delay: 1.35s; }
  .shatter-cracks path:nth-child(6) { animation-delay: 1.7s; }
  .shatter-cracks path:nth-child(7) { animation-delay: 2.05s; }
}
@keyframes shatter-crackle {
  0%, 100% { opacity: 0.35; stroke-width: 1.2; filter: none; }
  50%      { opacity: 1;    stroke-width: 2;   filter: drop-shadow(0 0 4px rgba(14, 143, 94, 0.85)); }
}

/* The impact flash — a small bright core at the fracture's origin
   point, pulsing brightest right as the nearest cracks flare, so it
   reads as the source the current is arcing FROM. */
.shatter-flash {
  fill: var(--color-brand-green);
  transform-origin: 190px 90px;
  opacity: 0.5;
}
@media (prefers-reduced-motion: no-preference) {
  .shatter-flash {
    animation: shatter-flash-pulse 2.6s ease-in-out infinite;
  }
}
@keyframes shatter-flash-pulse {
  0%, 100% { opacity: 0.35; transform: scale(1); }
  15%      { opacity: 1;    transform: scale(1.8); }
  35%      { opacity: 0.4;  transform: scale(1); }
}

/* A slow diagonal sheen sweeping across the whole panel — same
   "light catching a surface" language as buttons.css's molten sheen,
   scaled to a much slower, ambient pace since this isn't a hover
   effect, it's a background surface that should never stop moving. */
.shatter-sheen {
  /* fill is set inline in the markup (a per-instance gradient id) —
     journey and craft sections both render on the homepage at once,
     so a shared CSS-referenced id would collide between the two SVGs.
     The skew is folded into this transform rather than left as the
     markup's own `transform="skewX(...)"` attribute — a CSS
     `transform` declaration replaces an SVG presentation attribute
     entirely rather than combining with it, so the skew would
     otherwise silently vanish the moment this animation applies. */
  transform: translateX(-140px) skewX(-18deg);
}
@media (prefers-reduced-motion: no-preference) {
  .shatter-sheen {
    /* linear, not ease-in-out, and no hold keyframe (a real bug the
       first version had: a "60%, 100%" stop meant the sheen reached
       its end position at 60% of the duration and then sat frozen,
       off-canvas, for the remaining 40% before snapping back to the
       start — reading as "comes, stops, vanishes, comes again"
       instead of continuous motion). This sweeps for the ENTIRE
       duration at constant speed; the only jump is the loop reset
       from 340px back to -140px, which is invisible since both
       positions sit outside the SVG's own 0–300 viewBox. */
    animation: shatter-sweep 3s linear infinite;
  }
}
@keyframes shatter-sweep {
  /* Explicit opacity fade layered onto the sweep — without this, the
     sheen (a hard-edged rect, its own gradient only softens ITS OWN
     left/right edges, not the sweep's start/end) just cuts to nothing
     the instant it clips against the SVG's viewBox, which read as an
     abrupt vanish rather than a graceful exit. Fading to 0 before the
     loop resets, and back in as it re-enters, is what actually makes
     the loop point invisible — not just relying on off-canvas timing. */
  0%   { transform: translateX(-140px) skewX(-18deg); opacity: 0; }
  10%  { opacity: 1; }
  85%  { opacity: 1; }
  100% { transform: translateX(340px) skewX(-18deg); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .shatter-glow,
  .shatter-cracks path,
  .shatter-flash,
  .shatter-sheen {
    animation: none !important;
  }
  .shatter-cracks path {
    opacity: 0.6 !important;
  }
}


/* ---- homepage-journey.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  homepage-journey.css — "The long way here" section layout. Same
  two-column shape as homepage-about.css's .about-strip__layout
  (text + a decorative panel beside it, stacking on narrow screens),
  but the panel here is the shared "broken glass" motif
  (shatter-glass.css) instead of a real Clear Glass surface.
*/

.journey {
  margin-block: var(--space-xl);
}
.journey__layout {
  display: flex;
  gap: var(--space-xl);
  align-items: stretch;
}
@media (max-width: 780px) {
  .journey__layout {
    flex-direction: column-reverse; /* text stays first in reading order; panel is decorative-only */
  }
}

.journey__shatter {
  position: relative;
  flex: 1;
  min-height: 220px;
  border-radius: 22px;
  overflow: hidden;
  border: 1px solid var(--color-border);
}
@media (max-width: 780px) {
  .journey__shatter {
    min-height: 140px;
  }
}
.journey__shatter-svg {
  display: block;
  width: 100%;
  height: 100%;
}

.journey__text {
  flex: 1.4;
  min-width: 0;
}
.journey__body {
  max-width: 68ch;
  color: var(--color-ink-muted);
}
.journey__body strong {
  color: var(--color-brand-green);
  font-family: var(--font-display);
}


/* ---- homepage-explore.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  homepage-explore.css — "Who this is for" (mock terminal, full-bleed
  band) and "How to work with me" (connected-steps pipeline diagram).

  IMPORTANT — backdrop-filter budget: this page already stacks a LOT
  of Clear Glass surfaces (header, hero card, up to 15 archive cards,
  the about-strip glass block). Adding more backdrop-filter layers
  past that point silently fails to paint in real testing — both this
  project's automated screenshot tool AND the user's own real Edge
  browser showed the pipeline nodes and FAQ items as fully blank even
  though the DOM/computed styles were correct, i.e. a real GPU
  compositing-layer ceiling, not a tooling artifact. Fix: neither
  .pipeline__node nor .faq-item (homepage-faq.css) uses
  backdrop-filter — both use a solid/near-solid tint instead, visually
  close to secondary glass without the extra blur layer. Don't add
  backdrop-filter back to either without re-testing the whole page at
  full scroll depth first.
*/

/* Full-bleed bands — break out of <main>'s 1120px container so these
   sections read as distinct panel strips, per direct feedback ("make
   this entire section full width"). NO margin at all — every bit of
   breathing room is the band's OWN padding-block, so a boundary
   between two adjacent bands is always bounded by a visible border on
   both sides, never a floating unbordered gap. (Two earlier attempts
   — a decorative divider between sections, then a margin-bottom-only
   approach — both still left "unnecessary big gaps" per direct
   feedback; padding-block was xl, up to 80px, stacking with the next
   band's own xl top padding for up to ~160px of dead-feeling space.
   Turned down to space-lg here for exactly that reason.) Content
   inside is re-centered via .container (base.css). */
.explore-services {
  width: 100vw;
  position: relative;
  left: 50%;
  transform: translateX(-50%);
  padding-block: var(--space-lg);
  background:
    radial-gradient(ellipse 60% 100% at 85% 0%, rgba(14, 143, 94, 0.07), transparent 70%),
    var(--color-surface);
  border-block: 1px solid var(--color-border);
}

.explore-audience {
  width: 100vw;
  position: relative;
  left: 50%;
  transform: translateX(-50%);
  padding-block: var(--space-lg);
  background:
    radial-gradient(ellipse 60% 100% at 15% 0%, rgba(14, 143, 94, 0.08), transparent 70%),
    var(--color-surface);
  border-block: 1px solid var(--color-border);
}

/* ---- Terminal block ----
   Deliberately theme-INDEPENDENT — always dark, like a real terminal,
   regardless of the site's light/dark mode. Fix for a real contrast
   bug: this block used to mix --color-surface (which flips to white
   in light mode) with a fixed-dark COLOR_SUPPORT tint, while its text
   used --color-ink/--color-ink-muted (which ALSO flip, to near-black,
   assuming a light background) — in light mode that produced dark
   text on a background that was no longer reliably dark, sometimes
   unreadable. Redeclaring the ink/border tokens locally, scoped to
   .terminal, means every descendant that reads var(--color-ink) etc.
   automatically gets the fixed dark-mode values instead, with zero
   changes needed to the child rules themselves. */
.terminal {
  --color-ink: #D8DEDA;
  --color-ink-muted: #8FA69A;
  --color-border: #26312B;
  width: 100%;
  border-radius: 16px;
  border: 1px solid var(--color-border);
  background: #0F1713;
  box-shadow: var(--glass-shadow);
}
.terminal__body {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: var(--space-lg);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: var(--step-body);
}
.terminal__titlebar {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 4px;
  padding-bottom: var(--space-sm);
  border-bottom: 1px solid var(--color-border);
}
.terminal__dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--color-border);
}
.terminal__dot--a { background: #E4664A; }
.terminal__dot--b { background: #E4B14A; }
.terminal__dot--c { background: var(--color-brand-green); }
.terminal__filename {
  margin-left: 8px;
  color: var(--color-ink-muted);
  font-size: 11px;
  letter-spacing: 0.03em;
}
.terminal__line {
  position: relative;
  color: var(--color-ink-muted);
  line-height: 1.6;
  opacity: 0;
  transform: translateX(-8px);
  animation: terminal-boot-in 0.4s ease-out forwards;
  animation-delay: calc(var(--i, 0) * 0.35s);
}
.terminal__line--cmd {
  color: var(--color-ink);
}
.terminal__cursor {
  display: inline-block;
  width: 8px;
  height: 1em;
  margin-left: 4px;
  vertical-align: text-bottom;
  background: var(--color-brand-green);
  animation: terminal-blink 1s step-end infinite;
}
.terminal__link {
  align-self: flex-start;
  margin-top: 4px;
  color: var(--color-brand-green);
  font-family: var(--font-utility);
  text-decoration: none;
  opacity: 0;
  transform: translateX(-8px);
  animation: terminal-boot-in 0.4s ease-out forwards;
  animation-delay: calc(var(--i, 0) * 0.35s);
}
/* Hover styling (underline/glow) now comes from .btn--link (buttons.css). */
@keyframes terminal-blink {
  0%, 50% { opacity: 1; }
  50.01%, 100% { opacity: 0; }
}
@keyframes terminal-boot-in {
  from { opacity: 0; transform: translateX(-8px); }
  to   { opacity: 1; transform: translateX(0); }
}

/* Slow ambient scanline across the whole terminal — plays
   continuously, low-opacity, GPU-cheap (transform + opacity only, no
   extra blur layer — see the backdrop-filter budget note above). */
.terminal__scan {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 40%;
  background: linear-gradient(180deg, transparent, rgba(14, 143, 94, 0.08), transparent);
  transform: translateY(-100%);
  animation: terminal-scan-sweep 8s ease-in-out infinite;
  pointer-events: none;
}
@keyframes terminal-scan-sweep {
  0%   { transform: translateY(-100%); }
  50%  { transform: translateY(250%); }
  100% { transform: translateY(-100%); }
}

/* ---- Services pipeline diagram ---- */
.explore-services__lede {
  max-width: 68ch;
  color: var(--color-ink-muted);
}
.pipeline {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin: var(--space-lg) 0;
}
@media (max-width: 700px) {
  .pipeline {
    flex-direction: column;
    align-items: stretch;
  }
}
.pipeline__node {
  flex: 1;
  position: relative;
  padding: var(--space-md);
  border-radius: 16px;
  border: 1px solid var(--color-border);
  background: color-mix(in srgb, var(--color-surface) 70%, var(--color-brand-green) 6%);
}
.pipeline__node--end {
  border-color: rgba(14, 143, 94, 0.5);
}
.pipeline__node-label {
  display: block;
  font-family: var(--font-display);
  font-size: var(--step-lead);
  font-weight: 500;
  margin-bottom: 4px;
}
.pipeline__node-desc {
  display: block;
  font-size: var(--step-caption);
  color: var(--color-ink-muted);
}
.pipeline__connector {
  flex: none;
  width: 48px;
  height: 2px;
  background-image: repeating-linear-gradient(90deg, var(--color-brand-green) 0 6px, transparent 6px 12px);
  background-size: 24px 2px;
  animation: pipeline-flow 1.2s linear infinite;
}
@media (max-width: 700px) {
  .pipeline__connector {
    width: 2px;
    height: 24px;
    margin-inline: auto;
    background-image: repeating-linear-gradient(180deg, var(--color-brand-green) 0 6px, transparent 6px 12px);
    background-size: 2px 24px;
    animation: pipeline-flow-vertical 1.2s linear infinite;
  }
}
@keyframes pipeline-flow {
  to { background-position: 24px 0; }
}
@keyframes pipeline-flow-vertical {
  to { background-position: 0 24px; }
}
.explore-services__cta {
  margin-top: var(--space-md);
}

@media (prefers-reduced-motion: reduce) {
  .terminal__cursor {
    animation: none !important;
    opacity: 1;
  }
  .terminal__line,
  .terminal__link {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
  .terminal__scan {
    animation: none !important;
    display: none;
  }
  .pipeline__connector {
    animation: none !important;
  }
}


/* ---- homepage-craft.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  homepage-craft.css — "How this actually gets written" section
  layout. Single-column text (like homepage-explore.css's
  .explore-services), not a second two-column glass+text block — the
  shared "broken glass" panel (shatter-glass.css) sits as a small
  corner accent behind the heading instead, mirrored horizontally via
  `transform: scaleX(-1)` so it doesn't read as a copy-pasted repeat
  of homepage-journey.css's own panel.
*/

.craft {
  margin-block: var(--space-xl);
}
.craft__inner {
  position: relative;
}
.craft__shatter {
  position: absolute;
  top: -12px;
  right: 0;
  width: min(200px, 40vw);
  aspect-ratio: 1;
  border-radius: 18px;
  overflow: hidden;
  border: 1px solid var(--color-border);
  transform: scaleX(-1);
  opacity: 0.9;
  z-index: 0;
}
.craft__shatter-svg {
  display: block;
  width: 100%;
  height: 100%;
}
/* On narrow screens the corner panel has no room beside the heading
   text without crowding it — drop it rather than let it collide with
   the read, per the explicit "not obstructing the read" requirement. */
@media (max-width: 640px) {
  .craft__shatter {
    display: none;
  }
}

/* Explicit reserved space matching the panel's own footprint, not a
   ch-based max-width guess — at some in-between viewport widths a
   character-count estimate and the panel's actual min(200px, 40vw)
   box can disagree enough to overlap by a few pixels. This can't.
   Real bug, confirmed live via getBoundingClientRect at 700px width:
   an earlier version only reserved this space on .section-head, not
   .craft__body — a short one-line heading left the panel's full
   ~200px height still overlapping the body paragraph directly below
   it. Both need the reservation, not just the heading. */
.craft .section-head {
  position: relative;
  z-index: 1;
  padding-right: min(220px, 42vw);
}
.craft__body {
  position: relative;
  z-index: 1;
  max-width: 72ch;
  padding-right: min(220px, 42vw);
  color: var(--color-ink-muted);
}
@media (max-width: 640px) {
  .craft .section-head,
  .craft__body {
    padding-right: 0;
  }
}


/* ---- homepage-faq.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  homepage-faq.css — the FAQ accordion. Same details/summary +
  real-DOM [+]/[–] indicator pattern as header.css's category
  dropdown.

  NO backdrop-filter here on purpose — see homepage-explore.css's top
  comment on the page's backdrop-filter budget. Each faq-item uses a
  solid tinted background + an animated left accent bar instead of
  glass, which is what keeps it actually painting after 20+ other
  Clear Glass layers earlier on this same page.
*/

/* Plain contained section — NOT a full-bleed tinted band. That
   treatment (100vw + padding-block + its own background/border) was
   tried and is what kept producing a reported "unnecessary big gap"
   right before the footer, however much the padding got turned down:
   a boxed section with visible top/bottom borders and a tinted
   background inherently reads as "space", even when the numbers are
   modest. Matches homepage-about.css's .about-strip pattern instead —
   a plain top border for the "new section" cue, everything else is
   just normal document flow straight into the footer.

   margin-TOP only, no margin-bottom — confirmed via DevTools that
   .site-footer (footer.css) already carries its own sitewide
   margin-top: var(--space-xl). This is the LAST homepage section, so
   adding a matching bottom margin here just doubled that same gap
   again, the identical stacking bug as the earlier full-bleed bands,
   just from a sitewide rule this time instead of another homepage
   partial. Not touching footer.css itself since other pages (About,
   Contact, posts) rely on that margin as their only pre-footer
   spacing. */
.homepage-faq {
  margin-top: var(--space-xl);
  padding-top: var(--space-lg);
  border-top: 1px solid var(--color-border);
}

.homepage-faq__layout {
  display: flex;
  align-items: center;
  gap: var(--space-xl);
}
@media (max-width: 860px) {
  .homepage-faq__layout {
    flex-direction: column;
    align-items: stretch;
  }
}

.homepage-faq__main {
  flex: 1;
  min-width: 0;
}

.faq-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  max-width: 820px;
}

/* ---- Second "game-like thing" — a self-playing brick-breaker,
   vertically centered against the FAQ list via the layout's own
   align-items: center. No caption, no score display, purely ambient —
   see the top-of-file comment. The expand button opens a real,
   player-controlled version in .game-modal (below). ---- */
.homepage-faq__game {
  position: relative;
  flex: none;
  display: flex;
  justify-content: center;
}
@media (max-width: 860px) {
  .homepage-faq__game {
    margin-top: var(--space-lg);
  }
}
.game-breakout-frame {
  width: 200px;
  height: 240px;
  border-radius: 16px;
  border: 1px solid var(--color-border);
  background: color-mix(in srgb, var(--color-surface) 60%, var(--color-brand-green) 8%);
  box-shadow: var(--glass-shadow);
  overflow: hidden;
}
.game-breakout-canvas {
  display: block;
  width: 100%;
  height: 100%;
}
.game-breakout-expand {
  position: absolute;
  top: 8px;
  right: calc(50% - 100px + 8px); /* frame is centered + fixed 200px wide */
  width: 32px;
  height: 32px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  border: 1px solid var(--color-border);
  background: color-mix(in srgb, var(--color-surface) 70%, transparent);
  color: var(--color-ink-muted);
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}
.game-breakout-expand svg {
  width: 15px;
  height: 15px;
}
.game-breakout-expand:hover,
.game-breakout-expand:focus-visible {
  color: var(--color-brand-green);
  border-color: rgba(14, 143, 94, 0.55);
  transform: scale(1.06);
}

/* ---- Full playable game modal — a real, player-controlled Breakout,
   separate from the ambient board above (assets/js/breakout-game.js).
   Fixed dark backdrop + a centered "boxed" panel (not the native
   Fullscreen API — that needs a permission gesture and has patchy
   mobile support; a full-viewport overlay reads as "full screen" to a
   visitor without either problem). Solid backgrounds throughout, no
   backdrop-filter — same page-wide reasoning as everywhere else here. */
.game-modal {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-md);
  background: rgba(4, 8, 9, 0.78);
}
.game-modal[hidden] {
  display: none;
}
.game-modal__box {
  width: 100%;
  max-width: 420px;
  border-radius: 18px;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.45);
  overflow: hidden;
}
.game-modal__hud {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-sm) var(--space-md);
  border-bottom: 1px solid var(--color-border);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: var(--step-caption);
  color: var(--color-ink-muted);
}
.game-modal__stat strong {
  color: var(--color-brand-green);
  font-weight: 600;
}
.game-modal__close {
  margin-left: auto;
  width: 32px;
  height: 32px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  border: 1px solid var(--color-border);
  background: transparent;
  color: var(--color-ink-muted);
  cursor: pointer;
}
.game-modal__close svg {
  width: 16px;
  height: 16px;
}
.game-modal__close:hover,
.game-modal__close:focus-visible {
  color: var(--color-brand-green);
  border-color: rgba(14, 143, 94, 0.55);
}

.game-modal__stage {
  position: relative;
  width: 100%;
  aspect-ratio: 5 / 6;
  touch-action: none; /* the stage owns touch-drag for the paddle — don't let the page scroll under a finger dragging here */
}
#bkCanvas {
  display: block;
  width: 100%;
  height: 100%;
}
.game-modal__overlay {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  padding: var(--space-lg);
  text-align: center;
  background: rgba(11, 18, 21, 0.85);
}
.game-modal__overlay[hidden] {
  display: none;
}
/* Fixed light text, NOT var(--color-ink)/--color-ink-muted — this
   overlay's scrim (rgba(11,18,21,0.85)) is deliberately dark
   regardless of site theme, but those tokens flip to near-black in
   light mode (they assume a light page background), which produced
   unreadable near-black-on-near-black text there. Same bug, same fix
   as the terminal panel in homepage-explore.css: text tied to a
   fixed-dark surface has to be fixed-light too, not theme-reactive. */
.game-modal__overlay-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--step-h3);
  font-weight: 500;
  color: #F2F5F3;
}
.game-modal__overlay-sub {
  margin: 0 0 var(--space-xs);
  max-width: 32ch;
  font-size: var(--step-caption);
  color: #AFC0B8;
}

@media (prefers-reduced-motion: reduce) {
  .game-breakout-expand {
    transition: none !important;
  }
}

.faq-item {
  position: relative;
  border-radius: 14px;
  overflow: hidden;
  background: color-mix(in srgb, var(--color-surface) 65%, var(--color-brand-green) 5%);
  border: 1px solid var(--color-border);
  opacity: 0;
  transform: translateY(10px);
  animation: faq-boot-in 0.4s ease-out forwards;
  animation-delay: calc(var(--i, 0) * 0.08s);
  transition: border-color 0.2s ease;
}
.faq-item:hover {
  border-color: rgba(14, 143, 94, 0.45);
}

/* Animated left accent — a thin bar that's dim while closed, and
   pulses brand-green while the item is open. Real element, not a
   pseudo (this project's convention), and cheap (background-color +
   box-shadow only, no blur). */
.faq-item__accent {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--color-border);
  transition: background 0.2s ease;
}
.faq-item[open] .faq-item__accent {
  background: var(--color-brand-green);
  animation: faq-accent-pulse 2s ease-in-out infinite;
}

.faq-item__summary {
  cursor: pointer;
  list-style: none;
}
.faq-item__summary::-webkit-details-marker {
  display: none;
}
.faq-item__summary-inner {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md) var(--space-sm) calc(var(--space-md) + 6px);
}
.faq-item__index {
  flex: none;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: var(--step-caption);
  color: var(--color-brand-green);
}
.faq-item__question {
  flex: 1;
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--step-body);
  font-weight: 500;
}
.faq-item__indicator-closed,
.faq-item__indicator-open {
  flex: none;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: var(--step-caption);
  color: var(--color-ink-muted);
}
.faq-item__indicator-open {
  display: none;
}
.faq-item[open] .faq-item__indicator-closed {
  display: none;
}
.faq-item[open] .faq-item__indicator-open {
  display: inline;
  color: var(--color-brand-green);
}

.faq-item__answer {
  margin: 0;
  padding: 0 var(--space-md) var(--space-md) calc(var(--space-md) + 34px + 6px);
  color: var(--color-ink-muted);
  max-width: 62ch;
}

@keyframes faq-boot-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes faq-accent-pulse {
  0%, 100% { box-shadow: 0 0 0 rgba(14, 143, 94, 0); }
  50%      { box-shadow: 0 0 10px rgba(14, 143, 94, 0.7); }
}

@media (prefers-reduced-motion: reduce) {
  .faq-item {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  .faq-item[open] .faq-item__accent {
    animation: none !important;
  }
}


/* ---- error-404.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  error-404.css — Phase 5a. The 404 page: a live drifting three.js
  particle field (assets/js/error-particles.js) instead of a static
  graphic — replaced the earlier Game of Life per direct feedback, to
  keep this page's interactive visual distinct from both the homepage
  hero and the About page orbit rather than repeating either. Framed
  with the site's own corner-bracket "targeting reticle" motif (see
  homepage-archive-preview.css), a monospace "/404" tag (matching
  header.php's "/menu" drawer tag), and a CONTINUOUS looping
  chromatic-glitch flicker on the heading (a recurring burst every 4s,
  not a nonstop shake — see the animation's own comment below) —
  gated behind prefers-reduced-motion like every other animation on
  the site. A second section below used to continue the homepage's
  "broken glass" motif (shatter-glass.css) as this page's own
  explanation of why 404s happen here; that's now a second live Game
  of Life instance instead, per direct feedback.

  Layout mirrors homepage-intro.css's intro card on purpose (text
  first in the DOM/reading order on every screen size, portrait
  second — same reasoning: decorative content can't come before the
  real heading/text, not just visually) but this is its own full
  section, not a glass card — the 404 page IS the page, not a card on
  a page with other things above/below it.
*/

.error404 {
  display: flex;
  align-items: center;
  /* Capped at 620px — plain calc(100dvh - 320px) was fine on typical
     desktop heights but on tall tablet/portrait viewports (e.g. iPad
     Pro at 1366px tall) it demanded ~1000px+ of vertical centering
     room the actual content never filled, confirmed live as huge
     equal blank space above and below the card. The cap keeps the
     "reads as one deliberate screen" intent on normal heights without
     that runaway growth on unusually tall viewports. */
  min-height: min(calc(100dvh - 320px), 620px);
  /* Top matches every other page's nav-to-hero gap (var(--space-lg) —
     about/contact/archive/category pages all use it); bottom stays
     the wider var(--space-xl) for this page's own vertical-centering
     feel before the footer. Was padding-block: var(--space-xl) on
     both sides, which made 404's nav-to-hero gap visibly bigger than
     every other page's — confirmed live, ~81px vs ~65px at the same
     viewport — direct feedback (2026-08-20). */
  padding-block: var(--space-lg) var(--space-xl);
}
.error404__layout {
  display: flex;
  align-items: center;
  gap: var(--space-xl);
  width: 100%;
}
@media (max-width: 720px) {
  .error404__layout {
    flex-direction: column;
    align-items: flex-start;
    text-align: left;
    gap: var(--space-lg);
  }
}

.error404__text {
  flex: 1;
  min-width: 0;
}
.error404__tag {
  margin: 0 0 var(--space-sm);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--color-brand-green);
}
.error404__title {
  margin: 0 0 var(--space-sm);
  font-size: var(--step-display, var(--step-h1));
  line-height: 1.05;
}
@media (prefers-reduced-motion: no-preference) {
  .error404__title {
    /* Continuous, not one-shot — but a recurring BURST every 4s, not a
       constant nonstop jitter: "Lost signal" reads as a signal that
       periodically drops out and recovers, and holding still for most
       of each cycle keeps the heading actually readable rather than
       permanently shaking. */
    animation: error404-glitch 4s steps(1, end) infinite;
  }
}
@keyframes error404-glitch {
  0%   { text-shadow: -3px 0 rgba(230, 90, 90, 0.7), 3px 0 rgba(14, 143, 94, 0.7); }
  3%   { text-shadow: 3px 0 rgba(230, 90, 90, 0.7), -3px 0 rgba(14, 143, 94, 0.7); }
  6%   { text-shadow: -2px 0 rgba(230, 90, 90, 0.6), 2px 0 rgba(14, 143, 94, 0.6); }
  9%   { text-shadow: 2px 0 rgba(230, 90, 90, 0.55), -2px 0 rgba(14, 143, 94, 0.55); }
  12%  { text-shadow: -1px 0 rgba(230, 90, 90, 0.4), 1px 0 rgba(14, 143, 94, 0.4); }
  16%, 100% { text-shadow: none; }
}
.error404__body {
  max-width: 52ch;
  color: var(--color-ink-muted);
}
.error404__stat {
  margin: 0 0 var(--space-md);
  font-size: var(--step-caption);
  color: var(--color-ink-muted);
}
.error404__stat strong {
  color: var(--color-brand-green);
  font-family: var(--font-display);
}
.error404__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
}

/* ---- Portrait: the live particle field, bigger than the homepage's
   148px hero slot since this IS the page's main visual, not a small
   accent next to a lot of text. Hand-rolled Clear Glass surface
   (matches homepage-intro.css's old .intro__life-frame recipe exactly
   — not the .glass-primary/.glass-content wrapper pattern, since this
   is a single canvas-holding button, not a multi-child card). ---- */
.error404__portrait {
  position: relative;
  flex: none;
  width: min(320px, 80vw);
  aspect-ratio: 1;
}
@media (max-width: 720px) {
  /* .error404__layout switches to flex-direction: column at this same
     breakpoint, and `aspect-ratio` on a flex item whose own axis is
     now the flex container's cross axis is a known unreliable
     combination on some mobile browsers — confirmed live as the box
     stretching tall instead of staying square. An explicit height
     (identical formula to the width above) sidesteps aspect-ratio
     entirely rather than fighting the flex interaction. */
  .error404__portrait {
    height: min(320px, 80vw);
  }
}
.error404__particles-frame {
  width: 100%;
  height: 100%;
  padding: 0;
  border: 1px solid var(--glass-primary-border);
  border-radius: 20px;
  /* No overflow: hidden — error-particles.js fits its camera to both
     axes so nothing should ever bleed past this frame; see
     hero-globe-bg.js's own comment for why relying on clipping a
     WebGL canvas inside a border-radius container is a real
     mobile/WebKit bug, confirmed live on the homepage hero. */
  cursor: pointer;
  background: linear-gradient(150deg, rgba(255, 255, 255, 0.08), rgba(14, 143, 94, 0.18) 60%, rgba(11, 18, 21, 0.32));
  backdrop-filter: blur(var(--glass-primary-blur));
  -webkit-backdrop-filter: blur(var(--glass-primary-blur));
  box-shadow: var(--glass-shadow);
  transition: transform 0.15s ease, border-color 0.15s ease;
}
.error404__particles-frame:hover,
.error404__particles-frame:focus-visible {
  transform: scale(1.015);
  border-color: rgba(14, 143, 94, 0.55);
}
.error404__particles-mount {
  /* No overflow: hidden here either (see .error404__particles-frame's
     own comment) — the canvas is transparent (renderer alpha:true)
     and error-particles.js fits its scene fully inside this box, so
     the rounded frame corners never need to clip anything; they'd
     just be transparent canvas pixels showing the frame's own
     background gradient through, same visual result without the
     WebKit clipping-bug risk. */
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
}
.error404__particles-canvas {
  display: block;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 0.5s ease;
}
.error404__particles-mount--live .error404__particles-canvas {
  opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
  .error404__particles-canvas {
    transition: none !important;
  }
}

/* Targeting-reticle corners — same shape/motion language as
   .archive-card__corner (homepage-archive-preview.css), scaled up and
   ALWAYS visible here (not hover-only — this page has no hover state
   to reveal them from, the frame IS the whole point of the page). */
.error404__corner {
  position: absolute;
  width: 22px;
  height: 22px;
  border: 0 solid var(--color-brand-green);
  pointer-events: none;
}
.error404__corner--tl { top: -8px; left: -8px; border-width: 2px 0 0 2px; }
.error404__corner--tr { top: -8px; right: -8px; border-width: 2px 2px 0 0; }
.error404__corner--bl { bottom: -8px; left: -8px; border-width: 0 0 2px 2px; }
.error404__corner--br { bottom: -8px; right: -8px; border-width: 0 2px 2px 0; }

@media (prefers-reduced-motion: reduce) {
  .error404__title {
    animation: none !important;
    text-shadow: none !important;
  }
  .error404__particles-frame {
    transition: none !important;
  }
}

/* ---- "Why this actually happens" — a second live Game of Life sits
   as a corner accent behind the heading (replaced the earlier
   shatter-glass panel per direct feedback), same reserved-space
   technique as homepage-craft.css's .craft__shatter (explicit
   padding-right matching the panel's own footprint, not a ch-based
   guess that can disagree with the panel's real box at in-between
   widths), and drops entirely on narrow screens rather than crowd the
   read. Same box footprint/position the shatter panel had — just the
   content inside changed. ---- */
.error404-why {
  position: relative;
  margin-block: var(--space-xl);
  padding-block: var(--space-lg);
  border-top: 1px solid var(--color-border);
}
.error404-why__life {
  position: absolute;
  top: var(--space-lg);
  right: 0;
  width: min(200px, 40vw);
  aspect-ratio: 1;
  z-index: 0;
}
.error404-why__life-frame {
  width: 100%;
  height: 100%;
  padding: 0;
  border: 1px solid var(--glass-primary-border);
  border-radius: 18px;
  overflow: hidden;
  cursor: pointer;
  background: linear-gradient(150deg, rgba(255, 255, 255, 0.08), rgba(14, 143, 94, 0.18) 60%, rgba(11, 18, 21, 0.32));
  backdrop-filter: blur(var(--glass-primary-blur));
  -webkit-backdrop-filter: blur(var(--glass-primary-blur));
  box-shadow: var(--glass-shadow);
  transition: transform 0.15s ease, border-color 0.15s ease;
}
.error404-why__life-frame:hover,
.error404-why__life-frame:focus-visible {
  transform: scale(1.02);
  border-color: rgba(14, 143, 94, 0.55);
}
.error404-why__life-canvas {
  display: block;
  width: 100%;
  height: 100%;
}
@media (prefers-reduced-motion: reduce) {
  .error404-why__life-frame {
    transition: none !important;
  }
}
@media (max-width: 640px) {
  .error404-why__life {
    display: none;
  }
}
.error404-why__text {
  position: relative;
  z-index: 1;
  padding-right: min(220px, 42vw);
}
@media (max-width: 640px) {
  .error404-why__text {
    padding-right: 0;
  }
}
.error404-why__title {
  margin: 0 0 var(--space-sm);
  font-size: var(--step-h2);
}
.error404-why__body {
  max-width: 68ch;
  color: var(--color-ink-muted);
}


/* ---- about-page.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  about-page.css — Phase 8. Layout for templates/partials/about.php.
  Journey section mirrors homepage-journey.css's two-column text +
  shatter-glass-panel shape (text first in DOM/reading order on every
  screen size, same reasoning as everywhere else that pattern is
  used). Services render as two link-list tiers rather than a 10-card
  grid — this site already avoids stacking card grids (see
  homepage-explore.css's own comment on that), and a plain list of
  name+description rows reads faster for 10 items than 10 cards would.
*/

/* Real bug, confirmed live: this had its own max-width:900px +
   margin-inline:auto on top of the sitewide <main> container's own
   1120px cap (base.css) — making this page narrower than the
   homepage's own sections, which rely on that single sitewide cap
   with no second one layered on. Removed per direct feedback ("this
   page and everything in it should be properly full width like
   homepage") — no extra constraint here now, same as
   homepage-intro.php's .intro section. */
.about-page {
  /* Top was --space-xl (up to 5rem), now --space-lg — fine as top
     breathing room below the header. Bottom went further per direct
     feedback ("reduce bottom gap more") — it stacks with the
     newsletter banner's own bottom margin below (nothing collapses
     through this padding), so it needed to be smaller than the top. */
  padding-top: var(--space-lg);
  padding-bottom: var(--space-sm);
}

/* ---- Hero: text + the orbit mascot (assets/js/about-mascot.js) ---- */
.about-page__hero {
  display: flex;
  /* flex-start, NOT center — real bug, confirmed live via screenshot
     (2026-08-20): the mascot mount's aspect-ratio is 4/5 (up to 525px
     tall), noticeably taller than Contact's equivalent 1:1 beacon
     frame (max 380px), so centering the shorter text column against
     it pushed the tag/H1 well below the header, reading as a much
     bigger nav-to-hero gap than every other page. Top-aligning both
     columns removes that push regardless of how tall the mascot ever
     renders. */
  align-items: flex-start;
  gap: var(--space-xl);
}
@media (max-width: 720px) {
  .about-page__hero {
    /* Real bug, confirmed live: DOM order is already text-then-photo,
       so `column-reverse` was flipping that to show the photo FIRST —
       the opposite of the intended "text stays first" reasoning this
       comment used to have. Plain `column` keeps DOM order (text,
       then photo), per direct feedback. */
    flex-direction: column;
    gap: var(--space-lg);
  }
}
.about-page__hero-text {
  /* Narrowed from flex: 1.3, per direct feedback ("shorten the width
     of hero textual section to give the orbit more room to shine") —
     the orbit's pointer-tracking effect needs real on-screen area to
     read, so the text column gives up space rather than the two
     staying evenly split. */
  flex: 0.85;
  min-width: 0;
}

/* Bare mount for the orbit mascot — no glass frame, no corner
   brackets, no card background, per direct feedback ("only place the
   orbit with all its animated glory and not any background
   div/section or text"). The <img> inside is the real fallback (no-JS,
   no-WebGL, reduced-motion-static-frame) — about-mascot.js hides it
   once the canvas mounts; until then this is just the photo, sized
   exactly as it always was. Widened (260px -> 420px, and given real
   flex-grow instead of flex: none) alongside the text column's
   shrink above, for the same reason. */
.about-page__photo {
  position: relative;
  flex: 1.15;
  width: min(420px, 80vw);
  aspect-ratio: 4 / 5;
  border-radius: 16px;
  overflow: hidden;
}
.about-mascot__canvas {
  position: absolute;
  inset: 0;
  display: block;
  /* Fades in once about-mascot.js's IntersectionObserver fires and the
     canvas has actually painted a frame, rather than popping in the
     instant the element exists — see .about-page__photo--ready. */
  opacity: 0;
  transition: opacity 0.35s ease;
}
.about-page__photo--ready .about-mascot__canvas {
  opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
  .about-mascot__canvas {
    transition: none;
  }
}

.about-page__tag {
  margin: 0 0 var(--space-sm);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--color-brand-green);
}
.about-page__title {
  margin: 0 0 var(--space-sm);
}
.about-page__lede {
  max-width: 68ch;
  color: var(--color-ink-muted);
}
.about-page__stat {
  margin: 0 0 var(--space-lg);
  font-size: var(--step-caption);
  color: var(--color-ink-muted);
}
.about-page__stat strong {
  color: var(--color-brand-green);
  font-family: var(--font-display);
}

/* ---- Journey ---- */
.about-page__journey {
  display: flex;
  align-items: center;
  gap: var(--space-xl);
  margin-block: var(--space-xl);
  padding-block: var(--space-lg);
  border-top: 1px solid var(--color-border);
}
@media (max-width: 780px) {
  .about-page__journey {
    /* Same bug as the hero photo had (see .about-page__hero's own
       fix comment): DOM order is text-then-game, so column-reverse
       was flipping that to show the game FIRST on mobile. Plain
       `column` keeps DOM order. */
    flex-direction: column;
    gap: var(--space-lg);
  }
}
.about-page__journey-text {
  flex: 1.4;
  min-width: 0;
}
.about-page__journey-text h2 {
  margin: 0 0 var(--space-sm);
}
.about-page__journey-text p {
  max-width: 68ch;
  color: var(--color-ink-muted);
}
.about-page__journey-hint {
  font-size: var(--step-caption);
  font-style: italic;
}

/* ---- Journey Snake teaser — the small inline card replacing the
   earlier shatter-glass panel; clicking Play opens the real game in a
   full-screen .game-modal (homepage-faq.css, already loaded sitewide)
   instead of playing inline, per direct feedback matching the
   homepage FAQ's Breakout game behavior. Solid tinted card, not
   backdrop-filter — same restrained choice as every other decorative
   surface on this page. ---- */
.journey-snake-teaser {
  flex: 1;
  min-width: 0;
  width: 100%;
  max-width: 320px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-sm);
  padding: var(--space-lg) var(--space-md);
  border-radius: 20px;
  border: 1px solid var(--color-border);
  background: color-mix(in srgb, var(--color-surface) 88%, var(--color-brand-green) 6%);
}
.journey-snake-teaser__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: 999px;
  background: rgba(14, 143, 94, 0.14);
  color: var(--color-brand-green);
}
.journey-snake-teaser__icon svg {
  width: 28px;
  height: 28px;
}
.journey-snake-teaser__label {
  margin: 0;
  font-size: var(--step-caption);
  color: var(--color-ink-muted);
}

/* ---- The real game, inside the modal ---- */
/* Wider than the shared .game-modal__box default (420px, sized for
   Breakout's own board) — per direct feedback ("should take more
   width to make it more enjoyable"). Scoped to THIS modal only, not a
   change to the shared homepage-faq.css rule, so Breakout's own modal
   is untouched. */
#journeySnakeModal .game-modal__box {
  max-width: 560px;
}
.journey-snake__stage-wrap {
  position: relative;
  aspect-ratio: 1;
  width: min(100%, 460px); /* grows with the wider box above — this is the actual playable board, not just surrounding chrome */
  /* Top gap only — the HUD's bottom border and the stage sat flush
     against each other with zero breathing room. Bottom stays
     unchanged (no added margin there) since the D-pad spacing below
     was already reverted back to the version that read fine. */
  margin: var(--space-sm) auto 0;
  border-radius: 14px;
  overflow: hidden;
  background: color-mix(in srgb, var(--color-bg) 92%, var(--color-brand-green) 4%);
}
.journey-snake__stage-wrap canvas {
  display: block;
  width: 100%;
  height: 100%;
}
.journey-snake__overlay-sub {
  max-width: 32ch;
}
.journey-snake__pad {
  /* Real bug, confirmed live via the user's own screenshot: .game-modal__box
     (homepage-faq.css) has no padding of its own — Breakout's modal
     never needed any, since its .game-modal__stage fills the box
     edge to edge with nothing below it. This D-pad is new content
     Breakout's modal never had, so without its own padding here it
     sits flush against the box's bottom/side edges. */
  padding: 0 var(--space-md) var(--space-md);
  margin-top: var(--space-sm);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(2, 1fr);
  gap: 4px;
  justify-items: center;
}
.journey-snake__pad-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 10px;
  border: 1px solid var(--color-border);
  background: transparent;
  color: var(--color-ink-muted);
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
}
.journey-snake__pad-btn svg {
  width: 18px;
  height: 18px;
}
.journey-snake__pad-btn:hover,
.journey-snake__pad-btn:focus-visible {
  color: var(--color-brand-green);
  border-color: rgba(14, 143, 94, 0.55);
  background: rgba(14, 143, 94, 0.08);
}
.journey-snake__pad-btn--up { grid-column: 2; grid-row: 1; }
.journey-snake__pad-btn--left { grid-column: 1; grid-row: 2; }
.journey-snake__pad-btn--right { grid-column: 3; grid-row: 2; }
.journey-snake__pad-btn--down { grid-column: 2; grid-row: 2; }

/* ---- Services ---- */
.about-page__services {
  margin-block: var(--space-md); /* was --space-lg, before that --space-xl — still read as excess per direct feedback */
  padding-block: var(--space-md);
  border-top: 1px solid var(--color-border);
}
.about-page__services h2 {
  margin: 0 0 var(--space-sm);
}
.about-page__services-lede {
  /* No ch cap here, unlike this site's usual lede convention — those
     other paragraphs (homepage-journey.css etc.) sit beside a real
     visual (a photo, a panel) that fills the leftover width, so the
     cap doesn't read as dead space. This one had nothing beside it,
     so the cap just looked like an unfinished half-width section —
     per direct feedback ("make these sections full width too"). Needs
     an explicit override, not just removing the rule that used to be
     here — base.css's sitewide `p { max-width: 65ch; }` (a deliberate
     reading-width default for ordinary body copy) still applies
     otherwise and was the real reason this still looked narrow. */
  max-width: none;
  color: var(--color-ink-muted);
  margin-bottom: var(--space-lg);
}
.about-page__services h3 {
  margin: 0 0 var(--space-sm);
  font-family: var(--font-display);
  font-size: var(--step-h3);
  font-weight: 500;
}
.about-page__service-list {
  display: grid;
  gap: var(--space-sm);
  margin: 0 0 var(--space-lg);
  padding: 0;
  list-style: none;
}
.about-page__service-list--primary {
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
}
.about-page__service-list--specialized {
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
}
.about-page__service-list a {
  display: flex;
  flex-direction: column;
  gap: 4px;
  height: 100%;
  padding: var(--space-sm) var(--space-md);
  border-radius: 12px;
  /* var(--color-border) alone (#26312B on the dark-mode #0B1215 bg) is
     nearly invisible — confirmed live, especially on small screens
     where cards sit closer together with less surrounding page chrome
     to imply their edges. Direct feedback: "the services looks non-
     distinguishable... borders need to be more visible (nerdy
     effect)." Fixed color (not a theme token) + dashed style, same
     "spec sheet" idea as the corner brackets elsewhere on this page —
     reads clearly in both themes, not just a slightly-brighter
     version of the same near-invisible line. */
  border: 1px dashed rgba(143, 166, 154, 0.45);
  color: inherit;
  text-decoration: none;
  transition: border-color 0.2s ease, transform 0.2s ease, background-color 0.2s ease;
}
.about-page__service-list a:hover,
.about-page__service-list a:focus-visible {
  border-color: rgba(14, 143, 94, 0.55);
  background: rgba(14, 143, 94, 0.06);
  transform: translateY(-2px);
}
.about-page__service-name {
  font-family: var(--font-display);
  font-weight: 500;
  color: var(--color-ink);
}
.about-page__service-list a:hover .about-page__service-name,
.about-page__service-list a:focus-visible .about-page__service-name {
  color: var(--color-brand-green);
}
.about-page__service-desc {
  font-size: var(--step-caption);
  color: var(--color-ink-muted);
}
.about-page__gtm {
  /* Same reasoning as .about-page__services-lede above — nothing
     beside this paragraph to fill the leftover width, so drop the
     ch cap rather than leave visible dead space. Same explicit
     override needed too, for the same base.css `p` reason. */
  max-width: none;
  color: var(--color-ink-muted);
}

/* ---- Newsletter + Free Tools banner — one 2-column grid, per direct
   feedback ("include free tools discussion and link inside the
   newsletter banner. make the banner 2 grid"). Free Tools deliberately
   carries no tool-name list ("do not list the tools as they will
   change later") — just a short description and a link out to the
   live page, so this never goes stale as that page's own tool count
   changes. Both solid tinted panels, not backdrop-filter glass — same
   restrained choice as the rest of this page's decorative surfaces
   (see about.php's own comment on the shatter panel). ---- */
.about-page__banners {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-md);
  margin-top: var(--space-md); /* was margin-block --space-lg, before that --space-xl */
  margin-bottom: var(--space-xs); /* bottom trimmed further per direct feedback ("reduce bottom gap more") — pairs with .about-page's own smaller bottom padding above */
}
@media (max-width: 640px) {
  .about-page__banners {
    grid-template-columns: 1fr;
  }
}
.about-page__newsletter,
.about-page__free-tools {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--space-md);
  padding: var(--space-lg);
  border-radius: 16px;
  border: 1px solid var(--color-border);
}
.about-page__newsletter {
  background: color-mix(in srgb, var(--color-surface) 88%, var(--color-brand-green) 8%);
}
.about-page__free-tools {
  /* Distinct from the newsletter panel's green tint so the two don't
     read as one repeated block — quieter, plain surface. */
  background: var(--color-surface);
}
.about-page__newsletter-text h2,
.about-page__free-tools-text h2 {
  margin: 0 0 4px;
  font-size: var(--step-h3);
}
.about-page__newsletter-text p,
.about-page__free-tools-text p {
  margin: 0;
  max-width: 40ch;
  color: var(--color-ink-muted);
}
.about-page__newsletter .btn,
.about-page__free-tools .btn {
  align-self: flex-start;
}

@media (prefers-reduced-motion: reduce) {
  .about-page__service-list a {
    transition: none !important;
  }
}


/* ---- contact-page.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  contact-page.css — Phase 9. Layout for templates/partials/contact.php.
  Hero shape mirrors about-page.css's text + bare-3D-mount pattern
  (text first in DOM/reading order on every screen size, same
  reasoning as everywhere else that pattern is used) — deliberately
  reused rather than reinvented, since the two pages share the same
  "topic-matched 3D interactive next to real copy" structure.
*/
.contact-page {
  padding-top: var(--space-lg);
  padding-bottom: var(--space-sm);
}

/* ---- Hero: text + the beacon mount (assets/js/contact-beacon.js) ---- */
.contact-page__hero {
  display: flex;
  align-items: center;
  gap: var(--space-xl);
}
@media (max-width: 720px) {
  .contact-page__hero {
    flex-direction: column; /* DOM order stays text-then-beacon, see about-page.css's own note on why not column-reverse */
    gap: var(--space-lg);
  }
}
.contact-page__hero-text {
  flex: 1;
  min-width: 0;
}
.contact-page__tag {
  margin: 0 0 var(--space-sm);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--color-brand-green);
}
.contact-page__title {
  margin: 0 0 var(--space-md);
  font-family: var(--font-display);
  font-size: var(--step-h1);
  text-wrap: balance;
}
.contact-page__lede {
  max-width: none; /* base.css's sitewide p{max-width:65ch} would otherwise cap this — see about-page.css's own note on the same bug */
  color: var(--color-ink-muted);
  margin-bottom: var(--space-lg);
}
.contact-page__mailto {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
}

/* Clear Glass frame around the hacker-terminal mount — direct
   feedback specifically asked for glass here (unlike About's bare
   mascot mount). Square-ish, not the About photo's portrait 4:5,
   since a terminal monitor reads better centered than cropped tall. */
.contact-page__beacon-frame {
  flex: 1;
  width: min(380px, 80vw);
  aspect-ratio: 1;
  border-radius: 16px;
  overflow: hidden;
}
.contact-page__beacon-frame .glass-content {
  width: 100%;
  height: 100%;
  padding: 0;
}
.contact-page__beacon {
  position: relative;
  width: 100%;
  height: 100%;
}
.contact-beacon__canvas {
  position: absolute;
  inset: 0;
  display: block;
  cursor: pointer; /* click/tap fires a pulse — signal it's interactive */
  opacity: 0;
  transition: opacity 0.35s ease;
}
.contact-page__beacon--ready .contact-beacon__canvas {
  opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
  .contact-beacon__canvas {
    transition: none;
  }
}

/* ---- What's worth emailing about (scope-setting section) ---- */
.contact-page__scope {
  margin-top: var(--space-xl);
  padding-top: var(--space-lg);
  border-top: 1px solid var(--color-border);
}
.contact-page__scope h2 {
  margin: 0 0 var(--space-sm);
}
.contact-page__scope p {
  max-width: none; /* same base.css p{max-width:65ch} override as .contact-page__lede above */
  color: var(--color-ink-muted);
}

/* ---- Signal Catch teaser + modal — same shape as the About page's
   journey-snake-teaser (text + small inline card, Play opens the
   shared full-screen .game-modal), deliberately reused rather than
   reinvented since both pages follow the same "topic-matched text +
   playable teaser" pattern. ---- */
.contact-page__game {
  display: flex;
  align-items: center;
  gap: var(--space-xl);
  margin-top: var(--space-xl);
  padding-top: var(--space-lg);
  border-top: 1px solid var(--color-border);
}
@media (max-width: 720px) {
  .contact-page__game {
    flex-direction: column;
    gap: var(--space-lg);
  }
}
.contact-page__game-text {
  flex: 1;
  min-width: 0;
}
.contact-page__game-text h2 {
  margin: 0 0 var(--space-sm);
}
.contact-page__game-text p {
  max-width: none;
  color: var(--color-ink-muted);
}
.signal-catch-teaser {
  flex: 1;
  min-width: 0;
  width: 100%;
  max-width: 320px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-sm);
  padding: var(--space-lg) var(--space-md);
  border-radius: 20px;
  border: 1px solid var(--color-border);
  background: color-mix(in srgb, var(--color-surface) 88%, var(--color-brand-green) 6%);
}
.signal-catch-teaser__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: 999px;
  background: rgba(14, 143, 94, 0.14);
  color: var(--color-brand-green);
}
.signal-catch-teaser__icon svg {
  width: 28px;
  height: 28px;
}
.signal-catch-teaser__label {
  margin: 0;
  font-size: var(--step-caption);
  color: var(--color-ink-muted);
}
.signal-catch__stage-wrap {
  position: relative;
  aspect-ratio: 1;
  width: min(100%, 420px);
  margin: var(--space-sm) auto 0; /* top gap only, same reasoning as journey-snake's own fix */
  border-radius: 14px;
  overflow: hidden;
  background: color-mix(in srgb, var(--color-bg) 92%, var(--color-brand-green) 4%);
  cursor: crosshair;
}
.signal-catch__stage-wrap canvas {
  display: block;
  width: 100%;
  height: 100%;
}
.signal-catch__overlay-sub {
  max-width: 34ch;
}

/* ---- Socials — plain text pills, no icons, per direct feedback
   ("remove all icons, only keep Text name and all pills should be
   same size and center+middle text"). Fixed height via padding-block
   + a min-height floor so every pill matches regardless of label
   length ("X" vs "Pinterest"), text centered both axes. ---- */
.contact-page__socials {
  margin-top: var(--space-xl);
  padding-top: var(--space-lg);
  border-top: 1px solid var(--color-border);
}
.contact-page__socials h2 {
  margin: 0 0 var(--space-md);
}
.contact-page__social-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(160px, 100%), 1fr));
  gap: var(--space-sm);
  margin: 0;
  padding: 0;
  list-style: none;
}
.contact-page__social-list a {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 56px;
  padding: var(--space-sm) var(--space-md);
  border-radius: 12px;
  text-align: center;
  /* Same fix as .about-page__service-list a — var(--color-border) is
     nearly invisible in dark mode, confirmed live on that page. Fixed,
     more visible border from the start here instead of repeating that
     bug and fixing it later. */
  border: 1px dashed rgba(143, 166, 154, 0.45);
  color: inherit;
  text-decoration: none;
  font-family: var(--font-display);
  font-weight: 500;
  transition: border-color 0.2s ease, transform 0.2s ease, background-color 0.2s ease;
}
.contact-page__social-list a:hover,
.contact-page__social-list a:focus-visible {
  border-color: rgba(14, 143, 94, 0.55);
  background: rgba(14, 143, 94, 0.06);
  transform: translateY(-2px);
}

@media (prefers-reduced-motion: reduce) {
  .contact-page__social-list a {
    transition: none !important;
  }
}


/* ---- legal-page.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  legal-page.css — Phase 10. Shared layout for all four legal pages
  (Privacy Policy, Terms of Service, Cookie Policy, Disclaimer),
  rendered by iitc_serve_legal_page() in index.php from real markdown
  in content/pages/. Explicit design brief for this phase (direct
  instruction): no three.js, no 3D, no game — the one deliberate
  exception to design-guide.html §08's per-page 3D requirement — but
  still a "tech savvy" effect, and Clear Glass where it's used. The
  tech-savvy read comes from the monospace tag/updated badge and the
  table-of-contents rendered as a real directory tree (├─/└─), not
  from any animation — everything here is static CSS + real content.

  Body copy sits on the plain page background, no glass/card wrapper
  — same restrained choice as About/Contact's own prose sections
  (glass is for chrome, never for body-length text, per design-guide's
  own do/don't list).
*/
.legal-page {
  padding-top: var(--space-lg);
  padding-bottom: var(--space-lg);
}

.legal-page__tag {
  margin: 0 0 var(--space-sm);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--color-brand-green);
}

/* "Last updated" badge — a small bracketed chip, monospace, echoing
   the corner-bracket/tag motif used across the site (About's photo
   frame, the OG images' trace corners) without needing any glass or
   motion to read as "on-brand tech." */
.legal-page__updated {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 var(--space-lg);
  padding: 4px 10px;
  border-radius: 6px;
  border: 1px dashed rgba(143, 166, 154, 0.45);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: var(--step-caption);
  color: var(--color-ink-muted);
}
.legal-page__updated strong {
  color: var(--color-ink);
  font-weight: 500;
}

/* ---- Body ---- */
/* Full width, like the homepage — direct feedback ("all legal pages
   should be full width like homepage"). No extra max-width on the
   container, and base.css's sitewide `p { max-width: 65ch; }` needs
   an explicit override here too, same lesson learned on the About
   page: removing a component-level cap alone isn't enough when a
   more specific sitewide rule is still sitting underneath it. */
.legal-page__body p,
.legal-page__body li {
  max-width: none;
}
.legal-page__body h1 {
  margin-top: 0;
}
.legal-page__body h2 {
  margin-top: var(--space-xl);
  padding-top: var(--space-md);
  border-top: 1px solid var(--color-border);
  scroll-margin-top: var(--space-lg); /* headings still carry real ids (markdown.php) for direct #anchor links even without an on-page ToC — this keeps a jumped-to heading clear of the header pill */
}
.legal-page__body ul {
  padding-left: 1.3em;
}
.legal-page__body li {
  margin-bottom: 6px;
}
.legal-page__body strong {
  color: var(--color-ink);
}

/* ---- Cross-links to the other three legal pages ---- */
.legal-page__crosslinks {
  margin-top: var(--space-xl);
  padding-top: var(--space-md);
  border-top: 1px solid var(--color-border);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm) var(--space-md);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: var(--step-caption);
}
.legal-page__crosslinks a {
  color: var(--color-ink-muted);
}
.legal-page__crosslinks a:hover,
.legal-page__crosslinks a:focus-visible {
  color: var(--color-brand-green);
}


/* ---- archive-page.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  archive-page.css — Phase 12. Layout for the /blog-archive/ page's own
  hero + intro sections (templates/partials/archive-page.php). The
  actual per-category post grid below it is homepage-archive-preview.css's
  .archive-preview component, shared with the homepage's own preview —
  not duplicated here.
*/
.archive-page {
  padding-top: var(--space-lg);
  padding-bottom: var(--space-sm);
}

/* ---- Hero: text + the live Game of Life (assets/js/hero-life.js) ---- */
.archive-page__hero {
  display: flex;
  align-items: center;
  gap: var(--space-xl);
  margin-bottom: var(--space-xl);
}
@media (max-width: 720px) {
  .archive-page__hero {
    flex-direction: column; /* DOM order stays text-then-visual, same reasoning as About/Contact */
    gap: var(--space-lg);
  }
}
.archive-page__hero-text {
  flex: 1;
  min-width: 0;
}
.archive-page__tag {
  margin: 0 0 var(--space-sm);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--color-brand-green);
}
.archive-page__title {
  margin: 0 0 var(--space-md);
  font-family: var(--font-display);
  font-size: var(--step-h1);
  text-wrap: balance;
}
.archive-page__lede,
.archive-page__body p {
  max-width: none; /* base.css's sitewide p{max-width:65ch} would otherwise cap this — same override as every other full-width page */
  color: var(--color-ink-muted);
}
.archive-page__lede {
  margin-bottom: 0;
}

/* Clear Glass frame around the matrix-rain canvas — direct feedback
   asked specifically for glass here (About/Contact's mounts stayed
   bare by contrast). glass-primary's own ::before carries the real
   backdrop-filter blur/tint/border; this just sizes the box and
   clips the canvas to its rounded corners. Square, not portrait —
   reads better centered next to a couple of paragraphs than cropped
   tall. */
.archive-page__matrix {
  flex: 1;
  min-width: 0;
  width: min(320px, 80vw);
  aspect-ratio: 1;
  border-radius: 16px;
  overflow: hidden;
}
.archive-page__matrix-inner {
  width: 100%;
  height: 100%;
  padding: 0;
}
.archive-page__matrix-canvas {
  display: block;
  width: 100%;
  height: 100%;
}

/* ---- Body: real copy explaining how this archive is organized ---- */
.archive-page__body {
  margin-bottom: var(--space-xl);
}
.archive-page__body h2 {
  margin-top: var(--space-lg);
}

/* ---- Category quick-links — real "tech-savvy" cards, not the
   earlier plain dashed-border pills (direct feedback 2026-08-20:
   "the bordered sections looks pungent... make them nerdy with slick
   hover effect"). Same targeting-reticle language as archive-card's
   own hover treatment (homepage-archive-preview.css) — four real DOM
   corner brackets that snap in plus a one-shot scanline sweep —
   reused here instead of inventing a second hover language, plus a
   monospace "/slug" tag above the name so each card reads like a
   real route, not just a label. Fixed 2 columns (direct feedback,
   2026-08-20), not the earlier auto-fit flow. ---- */
.archive-page__cat-links {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-sm);
  margin: var(--space-md) 0 0;
  padding: 0;
  list-style: none;
}
@media (max-width: 480px) {
  .archive-page__cat-links {
    grid-template-columns: 1fr;
  }
}
.archive-page__cat-links a {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-height: 72px;
  padding: var(--space-sm) var(--space-md);
  border-radius: 12px;
  overflow: hidden;
  text-align: center;
  background: color-mix(in srgb, var(--color-bg) 92%, var(--color-brand-green) 4%);
  border: 1px solid var(--color-border);
  color: inherit;
  text-decoration: none;
  transition: border-color 0.2s ease, transform 0.2s ease, background-color 0.2s ease;
}
.archive-page__cat-link-tag {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 11px;
  letter-spacing: 0.02em;
  color: var(--color-brand-green);
  opacity: 0.75;
}
.archive-page__cat-link-name {
  font-family: var(--font-display);
  font-weight: 500;
}
.archive-page__cat-links a:hover,
.archive-page__cat-links a:focus-visible {
  border-color: rgba(14, 143, 94, 0.55);
  background: color-mix(in srgb, var(--color-bg) 86%, var(--color-brand-green) 8%);
  transform: translateY(-2px);
}

/* Corner brackets — identical recipe to .archive-card__corner
   (homepage-archive-preview.css), just scoped to this component
   since these links live in a different stacking context. */
.archive-page__cat-link-corner {
  position: absolute;
  width: 14px;
  height: 14px;
  border: 0 solid var(--color-brand-green);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease, transform 0.25s ease;
}
.archive-page__cat-link-corner--tl { top: 6px; left: 6px; border-width: 2px 0 0 2px; transform: translate(6px, 6px); }
.archive-page__cat-link-corner--tr { top: 6px; right: 6px; border-width: 2px 2px 0 0; transform: translate(-6px, 6px); }
.archive-page__cat-link-corner--bl { bottom: 6px; left: 6px; border-width: 0 0 2px 2px; transform: translate(6px, -6px); }
.archive-page__cat-link-corner--br { bottom: 6px; right: 6px; border-width: 0 2px 2px 0; transform: translate(-6px, -6px); }
.archive-page__cat-links a:hover .archive-page__cat-link-corner {
  opacity: 1;
  transform: translate(0, 0);
}

.archive-page__cat-link-scan {
  position: absolute;
  left: 0;
  right: 0;
  top: -30%;
  height: 30%;
  background: linear-gradient(180deg, transparent, rgba(14, 143, 94, 0.35), transparent);
  opacity: 0;
  pointer-events: none;
}
.archive-page__cat-links a:hover .archive-page__cat-link-scan {
  opacity: 1;
  animation: cat-link-scan 0.8s ease-out;
}
@keyframes cat-link-scan {
  from { top: -30%; }
  to   { top: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  .archive-page__cat-links a,
  .archive-page__cat-link-corner {
    transition: none !important;
  }
  .archive-page__cat-links a:hover {
    transform: none !important;
  }
  .archive-page__cat-link-scan {
    animation: none !important;
    opacity: 0 !important;
  }
}


/* ---- category-page.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  category-page.css — Phase 13. Shared layout for every category
  archive page (templates/partials/category-page.php). Same hero
  shape as About/Contact (text + a teaser card, side by side); the
  real game itself opens in the shared .game-modal (homepage-faq.css)
  via category-game-shell.js, not inline in the hero (see that file's
  own header comment for why). The actual per-category post grid
  below is homepage-archive-preview.css's .archive-preview component,
  shared sitewide, not duplicated here.
*/
.category-page {
  padding-top: var(--space-lg);
  padding-bottom: var(--space-sm);
}

/* ---- Hero: text + this category's own game teaser card ---- */
.category-page__hero {
  display: flex;
  align-items: center;
  gap: var(--space-xl);
  margin-bottom: var(--space-xl);
}
@media (max-width: 720px) {
  .category-page__hero {
    flex-direction: column; /* DOM order stays text-then-game, same reasoning as every other hero built this way */
    gap: var(--space-lg);
  }
}
.category-page__hero-text {
  flex: 1;
  min-width: 0;
}
.category-page__tag {
  margin: 0 0 var(--space-sm);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--color-brand-green);
}
.category-page__title {
  margin: 0 0 var(--space-md);
  font-family: var(--font-display);
  font-size: var(--step-h1);
  text-wrap: balance;
}
.category-page__lede {
  max-width: none; /* base.css's sitewide p{max-width:65ch} would otherwise cap this — same override as every other full-width page */
  color: var(--color-ink-muted);
}

/* ---- Teaser card in the hero — opens the real game in a full-screen
   modal, per direct feedback (2026-08-20): the earlier bare inline
   canvas "wasn't standard playable" and pressing Space/arrows to play
   it scrolled the page. Same teaser-card shape as About's Journey
   Snake (about-page.css's .journey-snake-teaser), reused here as a
   generic pattern instead of a second bespoke card design. ---- */
.category-game-teaser {
  flex: 1;
  min-width: 0;
  width: 100%;
  max-width: 320px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-sm);
  padding: var(--space-lg) var(--space-md);
  border-radius: 20px;
  border: 1px solid var(--color-border);
  background: color-mix(in srgb, var(--color-surface) 88%, var(--color-brand-green) 6%);
}
.category-game-teaser__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: 999px;
  background: rgba(14, 143, 94, 0.14);
  color: var(--color-brand-green);
}
.category-game-teaser__icon svg {
  width: 28px;
  height: 28px;
}
.category-game-teaser__label {
  margin: 0;
  font-size: var(--step-caption);
  color: var(--color-ink-muted);
}

/* ---- The real game, inside the modal (.game-modal chrome shared
   sitewide via homepage-faq.css) ----
   Wider than the shared .game-modal__box default (420px) — "spacious"
   per direct feedback — and wrapped in glass-primary (also direct
   feedback: "make the elements more glass"), scoped to this modal
   only via the box's own class, not a change to the shared rule
   Breakout/Journey Snake's modals use. */
.category-game-modal__box {
  max-width: 560px;
  /* .game-modal centers this via flex align-items/justify-content
     with only padding: var(--space-md) reserving edge space — fine
     for Breakout/Snake's shorter boxes, but this box's wider stage +
     pad row made it taller than the viewport on common screen sizes,
     so it overflowed flush against the top/bottom edges. Direct
     feedback (2026-08-20, twice) was explicit: no scrollbar, ever, on
     a real desktop window — an overflow-y:auto safety net still
     showed a visible scrollbar in practice, so this is now a hard
     overflow:hidden instead. The actual fix is
     .category-game-modal__stage sizing itself against 100dvh with a
     generous reserve (below) so the box's real content height never
     gets close to max-height in the first place; hidden overflow here
     is the guarantee, not the mechanism. */
  max-height: calc(100dvh - var(--space-lg) * 2);
  overflow: hidden;
  background: transparent; /* glass-primary's own ::before supplies the tinted blur backdrop */
}
/* Fixed dark background, NOT var(--color-bg) — every game's palette
   (bright emissive-green ship/ball/bolts against near-black) is
   designed to pop against a dark canvas. var(--color-bg) flips to
   near-white in light mode, which left bright objects (the AI game's
   near-white bolts especially) with almost no contrast against the
   stage — same "fixed-dark-surface needs fixed-light content" bug
   .game-modal__overlay's own comment already documents, same fix. */
/* Sized down from an earlier 540px, and capped against viewport
   height too (not just width) — per direct feedback, the popup must
   never need to scroll on a real desktop window. 540px made the whole
   box (HUD + stage + pad row) taller than plenty of ordinary desktop
   browser heights, forcing .category-game-modal__box's overflow-y
   scrollbar to kick in even outside small/short viewports. The
   calc(100dvh - 260px) term shrinks the stage on short windows before
   it ever gets a chance to overflow; that scrollbar stays only as a
   fallback for genuinely small tablet/phone heights. */
.category-game-modal__stage {
  position: relative;
  width: min(100%, 460px, calc(100dvh - 340px));
  aspect-ratio: 1;
  margin: var(--space-sm) auto 0;
  border-radius: 14px;
  overflow: hidden;
  background: #0B1512;
}
.category-game-modal__stage #categoryGameMount,
.category-game-modal__stage #categoryGameMount canvas {
  display: block;
  width: 100%;
  height: 100%;
}
.category-game-modal__overlay-sub {
  max-width: 34ch;
}

/* On-screen touch controls — a flat row rather than a fixed D-pad
   grid, since each game declares a different subset of
   up/down/left/right/fire (config/site.config.php's CATEGORY_GAMES
   'controls'), not always four opposing directions. */
.category-game-pad {
  padding: 0 var(--space-md) var(--space-md);
  margin-top: var(--space-sm);
  display: flex;
  justify-content: center;
  gap: var(--space-sm);
}
.category-game-pad__btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: 12px;
  border: 1px solid var(--color-border);
  background: transparent;
  color: var(--color-ink-muted);
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
}
.category-game-pad__btn svg {
  width: 20px;
  height: 20px;
}
.category-game-pad__btn:hover,
.category-game-pad__btn:focus-visible {
  color: var(--color-brand-green);
  border-color: rgba(14, 143, 94, 0.55);
  background: rgba(14, 143, 94, 0.08);
}
/* Fire is the action button, not a direction — filled/brand-green so
   it reads as distinct from the movement buttons next to it. */
.category-game-pad__btn--fire {
  border-color: rgba(14, 143, 94, 0.55);
  background: rgba(14, 143, 94, 0.16);
  color: var(--color-brand-green);
}

/* ---- Body: real per-category copy ---- */
.category-page__body {
  margin-bottom: var(--space-xl);
}
.category-page__body p {
  max-width: none;
  color: var(--color-ink-muted);
}
.category-page__body h2 {
  margin-top: var(--space-lg);
}

/* ---- Bottom banner: back to archive + newsletter, same shape on
   every category page (2026-08-20 direct instruction). Same 2-column
   pattern as About's own free-tools/newsletter banner
   (about-page.css's .about-page__banners) — reused rather than a
   third bespoke banner design. ---- */
.category-page__banners {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-md);
  margin-top: var(--space-md);
  margin-bottom: var(--space-xs);
}
@media (max-width: 640px) {
  .category-page__banners {
    grid-template-columns: 1fr;
  }
}
.category-page__back,
.category-page__newsletter {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--space-md);
  padding: var(--space-lg);
  border-radius: 16px;
  border: 1px solid var(--color-border);
}
.category-page__back {
  /* Quieter, plain surface — distinct from the newsletter panel's
     green tint so the two don't read as one repeated block, same
     reasoning as About's own pairing. */
  background: var(--color-surface);
}
.category-page__newsletter {
  background: color-mix(in srgb, var(--color-surface) 88%, var(--color-brand-green) 8%);
}
.category-page__back-text h2,
.category-page__newsletter-text h2 {
  margin: 0 0 4px;
  font-size: var(--step-h3);
}
.category-page__back-text p,
.category-page__newsletter-text p {
  margin: 0;
  max-width: 40ch;
  color: var(--color-ink-muted);
}
.category-page__back .btn,
.category-page__newsletter .btn {
  align-self: flex-start;
}


/* ---- pagination.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  pagination.css — Clear Glass, tech-savvy, animated pager (see
  lib/pagination.php for the render logic). Direct instruction
  (2026-08-20): every category archive page gets this once a listing
  passes 12 posts on one page, page-number buttons 4 per row on
  bigger screens. Secondary-tier Clear Glass (same thin/light treatment
  as the header pill and bottom nav — this is icon/number-scale UI,
  not body text, exactly what that tier is for), monospace numerals to
  read as "tech-savvy" rather than a generic pager, and a real
  animated pulse on the current page rather than a flat color swap.
*/
.pagination {
  --glass-radius: 16px;
  margin-top: var(--space-lg);
  border-radius: var(--glass-radius);
}
.pagination__inner {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--space-sm) var(--space-md);
  padding: var(--space-sm) var(--space-md);
}

.pagination__nav {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: var(--step-caption);
  color: var(--color-ink);
  text-decoration: none;
  white-space: nowrap;
  transition: color 0.15s ease, opacity 0.15s ease;
}
.pagination__nav:hover,
.pagination__nav:focus-visible {
  color: var(--color-brand-green);
}
.pagination__nav.is-disabled {
  color: var(--color-ink-muted);
  opacity: 0.4;
  pointer-events: none;
}

/* Exactly 4 page-number buttons per row on desktop — wraps to more
   rows on its own for large page counts, never squeezes narrower. */
.pagination__pages {
  display: grid;
  grid-template-columns: repeat(4, minmax(40px, 1fr));
  gap: 6px;
}
@media (max-width: 480px) {
  .pagination__pages {
    grid-template-columns: repeat(3, minmax(36px, 1fr));
  }
}

.pagination__page {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 40px;
  min-height: 40px;
  border-radius: 10px;
  border: 1px solid var(--color-border);
  background: transparent;
  color: var(--color-ink-muted);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: var(--step-caption);
  text-decoration: none;
  overflow: hidden;
  transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease, transform 0.15s ease;
}
.pagination__page:hover,
.pagination__page:focus-visible {
  color: var(--color-brand-green);
  border-color: rgba(14, 143, 94, 0.55);
  background: rgba(14, 143, 94, 0.08);
  transform: translateY(-1px);
}

/* Current page — a real filled/glowing state with a slow animated
   pulse, not just a bolded number, so it reads as "live system
   state" rather than a static label. */
.pagination__page.is-current {
  color: #d8fce9;
  border-color: rgba(14, 143, 94, 0.7);
  background: linear-gradient(160deg, rgba(14, 143, 94, 0.55), rgba(14, 143, 94, 0.28));
  font-weight: 600;
  animation: pagination-pulse 2.2s ease-in-out infinite;
}
.pagination__page.is-current::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 0%, rgba(255, 255, 255, 0.35) 45%, transparent 60%);
  transform: translateX(-120%);
  animation: pagination-scan 2.6s ease-in-out infinite;
}

@keyframes pagination-pulse {
  0%, 100% { box-shadow: 0 0 0 rgba(14, 143, 94, 0); }
  50% { box-shadow: 0 0 14px rgba(14, 143, 94, 0.55); }
}
@keyframes pagination-scan {
  0% { transform: translateX(-120%); }
  55%, 100% { transform: translateX(120%); }
}

@media (prefers-reduced-motion: reduce) {
  .pagination__page,
  .pagination__nav {
    transition: none !important;
  }
  .pagination__page.is-current {
    animation: none !important;
  }
  .pagination__page.is-current::after {
    display: none !important;
  }
}


/* ---- newsletter-page.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  newsletter-page.css — the dedicated Newsletter page (Phase 20-adjacent,
  built 2026-08-20 to replace the earlier content/pages/newsletter.md
  stub). Same hero shape as About/Contact (text + a topic-matched 3D
  artifact, side by side) and the same section rhythm as every other
  dedicated page built this way. The ConvertKit embed itself
  (.newsletter-form) keeps its original FormKit class names/data
  attributes so ck.5.js still finds its own hooks — only the visual
  layer is replaced here, nothing structural.
*/
.newsletter-page {
  padding-top: var(--space-lg);
  padding-bottom: var(--space-sm);
}

/* ---- Hero: text + the Signal Beacon artifact ---- */
.newsletter-page__hero {
  display: flex;
  align-items: center;
  gap: var(--space-xl);
  margin-bottom: var(--space-xl);
}
@media (max-width: 720px) {
  .newsletter-page__hero {
    flex-direction: column; /* DOM order stays text-then-artifact, same reasoning as every other hero built this way */
    gap: var(--space-lg);
  }
}
.newsletter-page__hero-text {
  flex: 1;
  min-width: 0;
}
.newsletter-page__tag {
  margin: 0 0 var(--space-sm);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--color-brand-green);
}
.newsletter-page__title {
  margin: 0 0 var(--space-md);
  font-family: var(--font-display);
  font-size: var(--step-h1);
  text-wrap: balance;
}
.newsletter-page__lede {
  max-width: none; /* base.css's sitewide p{max-width:65ch} would otherwise cap this — same override as every other full-width page */
  color: var(--color-ink-muted);
}

/* Clear Glass frame around the beacon — same tier as Archive's
   matrix-rain and Contact's hacker terminal (both explicitly asked
   for glass), not About's bare mascot mount. Square, reads better
   centered than cropped tall, same reasoning as every other 3D hero
   mount on this site. */
.newsletter-page__beacon-frame {
  flex: 1;
  min-width: 0;
  width: min(360px, 80vw);
  aspect-ratio: 1;
  border-radius: 16px;
  overflow: hidden;
}
.newsletter-page__beacon-frame .glass-content {
  width: 100%;
  height: 100%;
  padding: 0;
}
.newsletter-page__beacon {
  position: relative;
  width: 100%;
  height: 100%;
}
.newsletter-page__beacon canvas {
  display: block;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 0.35s ease;
}
.newsletter-page__beacon--ready canvas {
  opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
  .newsletter-page__beacon canvas {
    transition: none !important;
  }
}

/* ---- Body: What it is / Who it's for / Why get it ---- */
.newsletter-page__body {
  margin-bottom: var(--space-md);
}
.newsletter-page__body p {
  max-width: none;
  color: var(--color-ink-muted);
}
.newsletter-page__body h2 {
  margin-top: var(--space-lg);
}

/* ---- "How to get it" — the form section. Clear Glass (glass-primary
   — direct feedback, 2026-08-20), full width like the rest of this
   page (no card-width cap), the same glass-primary/glass-content
   structure every other glass surface on the site uses (glass.css):
   the ::before pseudo carries the actual blur/tint, .glass-content
   sits above it and owns the real padding, since glass.css's own
   pseudo doesn't reserve any. ---- */
.newsletter-page__signup {
  margin-bottom: var(--space-xl);
  border-radius: 20px;
  overflow: hidden;
}
.newsletter-page__signup-inner {
  padding: var(--space-lg);
}
.newsletter-page__signup h2 {
  margin-top: 0;
}
.newsletter-page__signup-lede {
  max-width: none;
  color: var(--color-ink-muted);
  margin-bottom: var(--space-md);
}

/* Benefit chips — real, independently-verifiable claims (no invented
   subscriber counts), replacing what used to be one more paragraph of
   prose here. Recognition over recall (NN/g heuristic #6): four short
   labels scan faster than a sentence making the same four points. */
.newsletter-page__chips {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-sm);
  margin: 0 0 var(--space-lg);
  padding: 0;
  list-style: none;
}
@media (max-width: 560px) {
  .newsletter-page__chips {
    grid-template-columns: 1fr;
  }
}
.newsletter-page__chip {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm);
  padding: var(--space-sm);
  border-radius: 12px;
  background: rgba(14, 143, 94, 0.08);
}
.newsletter-page__chip-icon {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 999px;
  background: rgba(14, 143, 94, 0.16);
  color: var(--color-brand-green);
}
.newsletter-page__chip-icon svg {
  width: 18px;
  height: 18px;
}
.newsletter-page__chip-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: var(--step-caption);
}
.newsletter-page__chip-text strong {
  font-family: var(--font-display);
  font-size: var(--step-body);
  font-weight: 500;
}
.newsletter-page__chip-text span {
  color: var(--color-ink-muted);
}

.newsletter-page__signup-note {
  max-width: none; /* base.css's sitewide p{max-width:65ch} would otherwise cap this — full width per direct feedback (2026-08-20) */
  margin: var(--space-sm) 0 0;
  font-size: var(--step-caption);
  color: var(--color-ink-muted);
}

/* ---- "Who it's for" — a real two-column comparison instead of one
   dense paragraph making the same point (direct feedback: "text to
   design item ratio is not good"). Faster self-qualification too —
   NN/g "recognition rather than recall": a visitor scans two short
   lists and knows in a few seconds whether this is for them, instead
   of parsing a sentence to extract the same signal. ---- */
.newsletter-page__fit {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-md);
  margin: var(--space-md) 0 var(--space-lg);
}
@media (max-width: 640px) {
  .newsletter-page__fit {
    grid-template-columns: 1fr;
  }
}
.newsletter-page__fit-col {
  padding: var(--space-md);
  border-radius: 14px;
  border: 1px solid var(--color-border);
}
.newsletter-page__fit-col--yes {
  background: rgba(14, 143, 94, 0.06);
  border-color: rgba(14, 143, 94, 0.3);
}
.newsletter-page__fit-col--no {
  background: color-mix(in srgb, var(--color-surface) 92%, transparent);
}
.newsletter-page__fit-head {
  margin: 0 0 var(--space-sm);
  font-family: var(--font-display);
  font-weight: 500;
}
.newsletter-page__fit-col--yes .newsletter-page__fit-head {
  color: var(--color-brand-green);
}
.newsletter-page__fit-col ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}
.newsletter-page__fit-col li {
  position: relative;
  padding-left: 26px;
  color: var(--color-ink-muted);
  max-width: none;
}
.newsletter-page__fit-col li::before {
  position: absolute;
  left: 0;
  top: 0;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-weight: 700;
}
.newsletter-page__fit-col--yes li::before {
  content: "✓";
  color: var(--color-brand-green);
}
.newsletter-page__fit-col--no li::before {
  content: "✕";
  color: var(--color-ink-muted);
}

/* ---- Bottom CTA — a second, real conversion point at the end of the
   page (classic long-page CRO pattern: never make a convinced reader
   scroll back up to act). Anchors to #newsletter-signup rather than
   duplicating the whole form again. ---- */
.newsletter-page__cta {
  margin-top: var(--space-sm);
}

/* ---- ConvertKit form re-skin ----
   The embed (newsletter.php) keeps FormKit's own class names/data-*
   attributes untouched (ck.5.js queries those directly for
   validation/submission/spinner/analytics) — this file replaces only
   the visual layer that used to come from the embed's own inline
   style="" attributes and trailing <style> block, both stripped from
   the markup entirely so nothing here has to fight embed-authored
   specificity.

   Full width, one line — per direct feedback (2026-08-20): email,
   first name, and the submit button all sit in a single row (not
   submit on its own row below), flexing to fill the glass panel's
   full width instead of capping out at a fixed max-width. Wraps only
   below 560px, where a single row genuinely can't fit three controls
   at a usable touch-target size. */
.newsletter-form {
  container-type: inline-size;
}
.newsletter-form .formkit-fields {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: var(--space-sm);
}
.newsletter-form .formkit-field {
  flex: 1 1 220px;
  margin: 0;
}
.newsletter-form .formkit-submit {
  flex: 0 0 auto;
  width: auto;
  white-space: nowrap;
}
@media (max-width: 560px) {
  .newsletter-form .formkit-field,
  .newsletter-form .formkit-submit {
    flex: 1 1 100%;
  }
}
.newsletter-form .formkit-input {
  width: 100%;
  height: 100%;
  padding: 12px var(--space-md);
  border-radius: 12px;
  border: 1px solid var(--color-border);
  background: var(--color-bg);
  color: var(--color-ink);
  font-family: inherit;
  font-size: var(--step-body);
  transition: border-color 0.2s ease;
}
.newsletter-form .formkit-input::placeholder {
  color: var(--color-ink-muted);
}
.newsletter-form .formkit-input:focus {
  outline: none;
  border-color: rgba(14, 143, 94, 0.6);
}
.newsletter-form .formkit-submit {
  position: relative;
  margin-top: 0;
  padding: 13px var(--space-lg);
  border: 0;
  border-radius: 999px;
  background: linear-gradient(160deg, var(--color-brand-green), #0a6f48);
  color: #fff;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: var(--step-body);
  cursor: pointer;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.newsletter-form .formkit-submit:hover,
.newsletter-form .formkit-submit:focus-visible {
  transform: translateY(-1px);
  box-shadow: 0 8px 20px rgba(14, 143, 94, 0.35);
}
.newsletter-form .formkit-submit span {
  display: block;
}
/* FormKit's own submit-spinner markup — three bouncing dots, kept
   functional (ck.5.js toggles [data-active] on the button while a
   request is in flight) but restyled to the brand's ink-on-dark. */
.newsletter-form .formkit-spinner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  height: 0;
  overflow: hidden;
  transition: height 0.2s ease;
}
.newsletter-form .formkit-submit[data-active] .formkit-spinner {
  height: 20px;
}
.newsletter-form .formkit-submit[data-active] .formkit-spinner ~ span {
  opacity: 0;
}
.newsletter-form .formkit-spinner div {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #fff;
  opacity: 0.5;
  animation: newsletter-spinner-bounce 1.4s infinite ease-in-out both;
}
.newsletter-form .formkit-spinner div:nth-child(1) { animation-delay: -0.32s; }
.newsletter-form .formkit-spinner div:nth-child(2) { animation-delay: -0.16s; }
@keyframes newsletter-spinner-bounce {
  0%, 80%, 100% { transform: scale(0); }
  40% { transform: scale(1); }
}
.newsletter-form .formkit-guarantee {
  margin: var(--space-sm) 0 0;
  font-size: var(--step-caption);
  color: var(--color-ink-muted);
}
.newsletter-form .formkit-alert {
  margin: 0 0 var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  border-radius: 10px;
  list-style: none;
  font-size: var(--step-caption);
}
.newsletter-form .formkit-alert:empty {
  display: none;
}
.newsletter-form .formkit-alert-error {
  background: rgba(178, 58, 58, 0.12);
  border: 1px solid rgba(178, 58, 58, 0.4);
  color: #d99;
}
.newsletter-form .formkit-alert-success {
  background: rgba(14, 143, 94, 0.12);
  border: 1px solid rgba(14, 143, 94, 0.4);
  color: var(--color-brand-green);
}
.newsletter-form .formkit-powered-by-convertkit-container {
  margin-top: var(--space-sm);
  opacity: 0.6;
}
.newsletter-form .formkit-powered-by-convertkit {
  font-size: 11px;
  color: var(--color-ink-muted);
  text-decoration: none;
}

@media (prefers-reduced-motion: reduce) {
  .newsletter-form .formkit-submit,
  .newsletter-form .formkit-spinner {
    transition: none !important;
  }
  .newsletter-form .formkit-spinner div {
    animation: none !important;
  }
}

/* Tablet-width case: the two fields still fit on one row, but the
   button no longer fits alongside them and wraps to its own line —
   direct feedback (2026-08-20): once it's alone on that line, it
   should span full width rather than sit at its auto content-width.
   A container query (not a viewport media query) is the right tool
   here, since the wrap point tracks the glass panel's actual width,
   not the viewport's. Placed after the base .formkit-submit rule
   above so it wins the cascade at equal specificity — @container
   doesn't add specificity, so source order is what decides here. */
@container (max-width: 640px) {
  .newsletter-form .formkit-submit {
    flex: 1 1 100%;
    width: 100%;
  }
}


/* ---- sitemap-page.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  sitemap-page.css — the dedicated /sitemap-for-istiqur-it-consultant/
  page (built 2026-08-20). Same hero shape as Archive/Newsletter (text
  + a Clear Glass-framed 3D artifact) and the same targeting-reticle
  link-card recipe as archive-page.css's category quick-links, just
  scoped to this page's own classes and extended to more groups/items.
*/
.sitemap-page {
  padding-top: var(--space-lg);
  padding-bottom: var(--space-sm);
}

/* ---- Hero: text + the Tron grid artifact ---- */
.sitemap-page__hero {
  display: flex;
  align-items: center;
  gap: var(--space-xl);
  margin-bottom: var(--space-xl);
}
@media (max-width: 720px) {
  .sitemap-page__hero {
    flex-direction: column;
    gap: var(--space-lg);
  }
}
.sitemap-page__hero-text {
  flex: 1;
  min-width: 0;
}
.sitemap-page__tag {
  margin: 0 0 var(--space-sm);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--color-brand-green);
}
.sitemap-page__title {
  margin: 0 0 var(--space-md);
  font-family: var(--font-display);
  font-size: var(--step-h1);
  text-wrap: balance;
}
.sitemap-page__lede {
  max-width: none; /* base.css's sitewide p{max-width:65ch} would otherwise cap this — same override as every other full-width page */
  color: var(--color-ink-muted);
}

/* Clear Glass frame around the Tron grid — same tier as Archive's
   matrix-rain and Newsletter's Signal Beacon (both explicitly asked
   for glass). Square, same reasoning as every other 3D hero mount on
   this site. */
.sitemap-page__grid-frame {
  flex: 1;
  min-width: 0;
  width: min(360px, 80vw);
  aspect-ratio: 1;
  border-radius: 16px;
  overflow: hidden;
}
.sitemap-page__grid-frame .glass-content {
  width: 100%;
  height: 100%;
  padding: 0;
}
.sitemap-page__grid {
  position: relative;
  width: 100%;
  height: 100%;
}
.sitemap-page__grid canvas {
  display: block;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 0.35s ease;
}
.sitemap-page__grid--ready canvas {
  opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
  .sitemap-page__grid canvas {
    transition: none !important;
  }
}

/* ---- Stat strip — real counts pulled from the same data the link
   map below loops over (never hand-typed, can't drift). Direct
   feedback (2026-08-20): the page read as too text-heavy — this and
   the tier cards below turn some of that prose into scannable design
   items instead of adding more paragraphs. ---- */
.sitemap-page__stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-sm);
  margin: 0 0 var(--space-xl);
  padding: 0;
  list-style: none;
}
@media (max-width: 640px) {
  .sitemap-page__stats {
    grid-template-columns: repeat(2, 1fr);
  }
}
.sitemap-page__stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: var(--space-md) var(--space-sm);
  border-radius: 12px;
  text-align: center;
  background: rgba(14, 143, 94, 0.08);
}
.sitemap-page__stat-n {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: var(--step-h2, 32px);
  font-weight: 700;
  color: var(--color-brand-green);
  line-height: 1;
}
.sitemap-page__stat-label {
  font-size: var(--step-caption);
  color: var(--color-ink-muted);
}

/* ---- Tier cards — the "How the site is organized" three-tier
   summary, replacing what used to be one dense paragraph carrying
   the same three facts. ---- */
.sitemap-page__tiers {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-sm);
  margin: var(--space-md) 0 var(--space-lg);
  padding: 0;
  list-style: none;
}
@media (max-width: 720px) {
  .sitemap-page__tiers {
    grid-template-columns: 1fr;
  }
}
.sitemap-page__tier {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding: var(--space-md);
  border-radius: 14px;
  border: 1px solid var(--color-border);
  background: color-mix(in srgb, var(--color-bg) 92%, var(--color-brand-green) 4%);
}
.sitemap-page__tier-badge {
  position: absolute;
  top: var(--space-sm);
  right: var(--space-sm);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 12px;
  color: var(--color-brand-green);
  opacity: 0.55;
}
.sitemap-page__tier-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 999px;
  background: rgba(14, 143, 94, 0.16);
  color: var(--color-brand-green);
}
.sitemap-page__tier-icon svg {
  width: 18px;
  height: 18px;
}
.sitemap-page__tier-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: var(--step-caption);
}
.sitemap-page__tier-text strong {
  font-family: var(--font-display);
  font-size: var(--step-body);
  font-weight: 500;
}
.sitemap-page__tier-text span {
  color: var(--color-ink-muted);
}

/* ---- Body: real copy + the grouped link map ---- */
.sitemap-page__body {
  margin-bottom: var(--space-md); /* not --space-xl — same fix as newsletter-page.css, direct feedback (2026-08-20): the gap before the footer read too large */
}
.sitemap-page__body > p {
  max-width: none;
  color: var(--color-ink-muted);
}
.sitemap-page__body h2 {
  margin-top: var(--space-lg);
}
.sitemap-page__group-label {
  margin: var(--space-md) 0 var(--space-sm);
  font-family: var(--font-display);
  font-size: var(--step-body);
  font-weight: 500;
  color: var(--color-brand-green);
}

/* Link cards — fixed 2 columns (direct feedback, 2026-08-20 — same
   instruction already applied to archive-page.css's own quick-links),
   not auto-fit, across all three groups regardless of item count.
   Left-aligned wrapping text (not vertically centered single-line) —
   each card shows a page's real meta <title> instead of a short nav
   label, so titles run two lines fairly often and need room to wrap
   cleanly. */
.sitemap-page__links {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-sm);
  margin: 0 0 var(--space-md);
  padding: 0;
  list-style: none;
}
@media (max-width: 480px) {
  .sitemap-page__links {
    grid-template-columns: 1fr;
  }
}
.sitemap-page__links a {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 64px;
  padding: var(--space-sm) var(--space-md);
  border-radius: 12px;
  overflow: hidden;
  background: color-mix(in srgb, var(--color-bg) 92%, var(--color-brand-green) 4%);
  border: 1px solid var(--color-border);
  color: inherit;
  text-decoration: none;
  transition: border-color 0.2s ease, transform 0.2s ease, background-color 0.2s ease;
}
.sitemap-page__links a.is-active {
  border-color: rgba(14, 143, 94, 0.55);
  background: color-mix(in srgb, var(--color-bg) 86%, var(--color-brand-green) 8%);
}
.sitemap-page__link-name {
  font-family: var(--font-display);
  font-size: var(--step-caption);
  font-weight: 500;
  line-height: 1.35;
}
/* Hover: a jittery glitch-slice sweep, not the smooth corner-bracket
   scan archive-page.css's own cards use — direct feedback (2026-08-20,
   "change their hover effect too, something irregular"). Two things
   happen together, both driven by steps() timing instead of ease so
   the motion reads stuttery/broken rather than smooth: the card itself
   jitters a few pixels off-axis, and a diagonal green slice (an
   asymmetric clip-path, not a straight full-width band) sweeps through
   at the same stepped cadence. Pure CSS on the anchor + its ::before —
   no extra decorative markup needed. */
.sitemap-page__links a::before {
  content: "";
  position: absolute;
  inset: -20% -10%;
  background: linear-gradient(115deg, transparent 42%, rgba(75, 232, 155, 0.4) 50%, transparent 58%);
  clip-path: polygon(0 15%, 100% 0%, 100% 85%, 0% 100%);
  transform: translateX(-130%);
  opacity: 0;
  pointer-events: none;
}
.sitemap-page__links a:hover,
.sitemap-page__links a:focus-visible {
  border-color: rgba(14, 143, 94, 0.55);
  background: color-mix(in srgb, var(--color-bg) 86%, var(--color-brand-green) 8%);
  animation: sitemap-link-jitter 0.32s steps(2, jump-none);
}
.sitemap-page__links a:hover::before,
.sitemap-page__links a:focus-visible::before {
  opacity: 1;
  animation: sitemap-link-slice 0.45s steps(4, jump-none);
}
@keyframes sitemap-link-jitter {
  0%   { transform: translate(0, 0); }
  25%  { transform: translate(-2px, 1px); }
  50%  { transform: translate(2px, 0); }
  75%  { transform: translate(-1px, -1px); }
  100% { transform: translate(0, 0); }
}
@keyframes sitemap-link-slice {
  from { transform: translateX(-130%); }
  to   { transform: translateX(130%); }
}

@media (prefers-reduced-motion: reduce) {
  .sitemap-page__links a {
    transition: none !important;
    animation: none !important;
  }
  .sitemap-page__links a::before {
    animation: none !important;
    opacity: 0 !important;
  }
}


/* ---- post-page.css ---- */
/*!
 * Author:     Md. Istiqur Rahman
 * Profession: Remote SEO Consultant & GTM Manager
 * Experience: 18+ Years
 * Website:    https://istiquritconsultant.com
 * Email:      contact@istiquritconsultant.com
 *
 * This stylesheet is authored and maintained by the above for Istiqur IT Consultant.
 */
/*
  post-page.css — Phase 14, the dedicated single-post template
  (templates/partials/post.php). Always full-width, no sidebar — a
  fixed rule, not a per-post choice (see post.php's own comment).

  UPDATE 2026-08-21 (direct feedback, "text blocks are still not full
  width"): base.css's sitewide `p { max-width: 65ch }` WAS deliberately
  left alone here for reading-first typography — that decision is
  overridden now. `.post-body p`/`li` get `max-width: none` explicitly
  below, same override every other full-width page on this site
  (Newsletter/Sitemap/Archive) already applies to its own hero/lede
  copy.
*/

/* ---- 1. Hero band — full-bleed, a distinct background from the rest
   of the page (spec requirement), same full-bleed treatment as
   homepage-faq.php's own band. A slow ambient gradient drift behind
   the text is the "optional decorative ambient motion" item — CSS-only,
   generic (not claiming to be a topic-matched 3D piece), gated by
   prefers-reduced-motion below. ---- */
.post-page__hero {
  /* True full-bleed breakout — same 100vw/left:50%/translateX(-50%)
     recipe as homepage-explore.css's own full-bleed bands
     (.explore-audience/.explore-services), since .post-page sits
     directly inside <main>, which is capped + padded (base.css's
     .container/main rule) like every other page. Direct feedback
     (2026-08-21): the hero specifically needs to touch both edges,
     not just match main's own 1120px width like the rest of the
     page's sections do. */
  position: relative;
  left: 50%;
  width: 100vw;
  transform: translateX(-50%);
  overflow: hidden;
  padding-block: var(--space-xl) var(--space-lg); /* horizontal padding lives on .post-page__hero-inner instead, so it lines up with main's own left/right edge rather than doubling up */
  margin-bottom: var(--space-lg);
  background: color-mix(in srgb, var(--color-surface) 92%, var(--color-brand-green) 8%);
  border-bottom: 1px solid var(--color-border);
}
.post-page__hero::before {
  content: "";
  position: absolute;
  inset: -30%;
  background: radial-gradient(circle at 30% 20%, rgba(14, 143, 94, 0.18), transparent 55%),
              radial-gradient(circle at 75% 70%, rgba(75, 232, 155, 0.14), transparent 50%);
  animation: post-hero-drift 22s ease-in-out infinite alternate;
  pointer-events: none;
}
@keyframes post-hero-drift {
  from { transform: translate(-3%, -2%) scale(1); }
  to   { transform: translate(3%, 2%) scale(1.08); }
}
@media (prefers-reduced-motion: reduce) {
  .post-page__hero::before {
    animation: none !important;
  }
}
.post-page__hero-inner {
  /* Re-establish the sitewide container width INSIDE the full-bleed
     band above — same max-width/padding-inline/margin-inline values
     as base.css's .container/main rule, so the hero's actual content
     lines up with every other section's left/right edge instead of
     sprawling the full 100vw unreadably. */
  position: relative;
  max-width: 1120px;
  margin-inline: auto;
  padding-inline: var(--space-md);
}

.post-page__category-pill {
  display: inline-block;
  margin-bottom: var(--space-sm);
  padding: 4px 12px;
  border-radius: 999px;
  background: rgba(14, 143, 94, 0.16);
  color: var(--color-brand-green);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 12px;
  letter-spacing: 0.03em;
  text-decoration: none;
}
.post-page__category-pill:hover,
.post-page__category-pill:focus-visible {
  background: rgba(14, 143, 94, 0.26);
}

.post-page__meta-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin: 0 0 var(--space-sm);
  color: var(--color-ink-muted);
  font-size: var(--step-caption);
}
.post-page__meta-row span:not(:last-child)::after {
  content: "·";
  margin-left: var(--space-sm);
  opacity: 0.6;
}

.post-page__title {
  position: relative;
  margin: 0 0 var(--space-md);
  font-family: var(--font-display);
  font-size: var(--step-h1);
  /* text-wrap: wrap — NOT balance. Real bug, confirmed live
     (2026-08-22): removing `text-wrap: balance` from this rule
     without replacing it with an explicit value left base.css's
     sitewide `h1,h2,h3{text-wrap:balance}` rule silently still in
     effect (that rule's element selector has lower specificity than
     this class, but with NO competing declaration here for that one
     property there was nothing to actually override — the earlier fix
     removed the wrong thing instead of overriding it). Explicit `wrap`
     here is what actually stops balance from shrinking every line's
     measure — see the fuller reasoning this replaces below. */
  text-wrap: wrap;
}

/* Wireframe scan-reveal globe background — assets/js/post-hero-globe.js,
   the exact same effect/recipe as the homepage hero's own
   .intro__globe-bg (homepage-intro.css). Full-bleed behind the hero's
   text content (z-index below .post-page__hero-inner, which is
   already position:relative so it naturally stacks above this),
   opacity kept low so the green wireframe reads as background texture
   rather than competing with the H1 for attention — same 0.4 opacity
   value the homepage version uses. */
.post-page__hero-globe-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}
/* Right-anchored, not centered — direct instruction (2026-08-22),
   same treatment as the homepage's own .intro__globe-bg
   (homepage-intro.css): shrinking this mount's own box is enough on
   its own, no JS change needed — post-hero-globe.js already reads
   this element's real width/height for its camera fit (fit-both-axes
   math, same file), so the sphere re-fits into whatever box it's
   given. Full-width on mobile/tablet, where there's no spare
   horizontal room to separate it from the text anyway. */
@media (min-width: 900px) {
  .post-page__hero-globe-bg {
    left: auto;
    width: min(48%, 460px);
  }
}
.post-page__hero-globe-canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 0.6s ease;
}
.post-page__hero-globe-bg--live .post-page__hero-globe-canvas {
  opacity: 0.4;
}
@media (prefers-reduced-motion: reduce) {
  .post-page__hero-globe-canvas {
    transition: none !important;
  }
}

/* ---- Share row ---- */
.post-page__share {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
}
.post-page__share-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 36px;
  height: 36px;
  min-width: 36px; /* real bug, confirmed live (2026-08-22): base.css's sitewide `button{min-width/min-height: 44px}` touch-target rule was silently bumping the copy-link/bookmark BUTTONS above this 36px, while the platform-link <a>s (unaffected by that rule, since it only targets button/a.button) stayed 36px — the exact "not the same size" mismatch reported. Overridden here so all seven controls in this row are identical regardless of tag. */
  min-height: 36px;
  padding: 0;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  background: var(--color-bg);
  color: var(--color-ink-muted);
  font: inherit;
  text-decoration: none;
  cursor: pointer;
  transition: border-color 0.2s ease, color 0.2s ease, background-color 0.2s ease, width 0.2s ease;
}
.post-page__share-btn svg {
  flex: none;
  width: 16px;
  height: 16px;
  margin: 0 auto;
}
.post-page__share-btn:hover,
.post-page__share-btn:focus-visible {
  border-color: rgba(14, 143, 94, 0.55);
  color: var(--color-brand-green);
  background: rgba(14, 143, 94, 0.08);
}
/* Icon-only — direct instruction (2026-08-21): same 36px circle as
   every other share button, no visible text label. The label span
   stays in the DOM but visually hidden (standard clip-rect pattern) —
   the real confirmation is #postCopyToast (Clear Glass, aria-live), so
   nothing meaningful is lost for a screen-reader user either. */
.post-page__share-copy-label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}
.post-page__share-btn--copied {
  border-color: rgba(14, 143, 94, 0.6);
  color: var(--color-brand-green);
  background: rgba(14, 143, 94, 0.12);
}

/* ---- Native share button — replaces the removed bookmark button
   (2026-08-22). Same 36px circle as every other control in this row;
   no special state needed (assets/js/post-native-share.js just opens
   the OS/browser's own native share sheet on click). */

/* ---- 2. Featured image + byline ---- */
.post-page__body-wrap {
  /* No extra max-width here — direct feedback (2026-08-21): this
     should match the homepage's own width, which <main> (base.css)
     already provides (1120px, padding-inline: --space-md) since
     .post-page sits directly inside it. Re-capping narrower here was
     the bug; every other full-width page on this site (About/Contact/
     Archive/Newsletter/Sitemap) leaves <main>'s own cap alone too. */
  padding-bottom: var(--space-md);
}
/* One shared corner radius for EVERY image/media surface in a post —
   featured image, plain body figures, gallery, carousel, 2-col images,
   chart/graph/table cards — direct instruction (2026-08-22, "I see
   some images with zero radius"). The plain body <figure> (a regular
   markdown image, no wrapper class of its own) was the actual gap:
   every OTHER image type already had ITS OWN radius value hand-typed
   (14px here, 10px on gallery/2-col) — inconsistent AND one had none
   at all. One variable now, used everywhere below. */
.post-page {
  --post-media-radius: 14px;
}
.post-page__featured {
  margin: 0 0 var(--space-lg);
}
.post-page__featured img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--post-media-radius);
}
/* The plain body figure (a regular ![]() image, no class of its own)
   had NO radius at all before this — the "zero radius" image. */
.post-body figure img {
  border-radius: var(--post-media-radius);
}
.post-page__byline {
  margin: var(--space-sm) 0 0;
  max-width: none;
  color: var(--color-ink-muted);
  font-style: italic;
  font-size: var(--step-caption);
}
.post-page__byline--no-image {
  margin-bottom: var(--space-lg);
}

/* ---- 3. TL;DR box — Clear Glass (glass-secondary), 2026-08-21 ---- */
.post-page__tldr {
  margin: 0 0 var(--space-lg);
  border-radius: 14px;
  overflow: hidden;
}
.post-page__tldr-inner {
  padding: var(--space-md);
}
/* Light-mode contrast fix (2026-08-22, "light mode tldr and TOC
   background box not showing") — the sitewide --glass-secondary-bg
   token is a near-invisible white-on-white tint in light mode
   (branding/colors/signal-emerald.css), designed for surfaces that
   float over a photo/hero behind them. TL;DR and TOC sit on the plain
   flat page background instead, so that token alone gives them no
   visible box at all here. A brand-tinted local override — scoped to
   just these two components, light mode only — fixes it without
   touching the shared token or any other glass-secondary surface
   sitewide. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .post-page__tldr,
  :root:not([data-theme="dark"]) .post-page__toc {
    --glass-secondary-bg: rgba(14, 143, 94, 0.07);
    --glass-secondary-border: rgba(14, 143, 94, 0.35);
  }
}
:root[data-theme="light"] .post-page__tldr,
:root[data-theme="light"] .post-page__toc {
  --glass-secondary-bg: rgba(14, 143, 94, 0.07);
  --glass-secondary-border: rgba(14, 143, 94, 0.35);
}
.post-page__tldr-label {
  margin: 0 0 var(--space-xs);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: var(--step-caption);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-brand-green);
}
.post-page__tldr ul {
  margin: 0;
  padding-left: 1.2em;
  color: var(--color-ink);
}
.post-page__tldr li + li {
  margin-top: 4px;
}

/* ---- 4. Table of contents — Clear Glass (glass-secondary), 2026-08-21 ---- */
.post-page__toc {
  margin: 0 0 var(--space-lg);
  border-radius: 14px;
  overflow: hidden;
}
.post-page__toc-inner {
  padding: var(--space-md);
}
.post-page__toc-label {
  margin: 0 0 var(--space-xs);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: var(--step-caption);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-ink-muted);
}
.post-page__toc-list,
.post-page__toc-sublist {
  margin: 0;
  padding-left: 1.2em;
}
.post-page__toc-sublist {
  margin-top: 4px;
  list-style-type: lower-alpha; /* a, b, c… for nested h3 entries — the outer list stays native 1, 2, 3…; direct instruction (2026-08-21), max two levels */
}
.post-page__toc-list > li {
  margin-top: 6px;
}
.post-page__toc a {
  color: var(--color-ink);
  text-decoration: none;
  border-bottom: 1px solid transparent;
}
.post-page__toc a:hover,
.post-page__toc a:focus-visible {
  color: var(--color-brand-green);
  border-bottom-color: currentColor;
}
/* Scrollspy — assets/js/post-toc-scrollspy.js toggles this class on
   whichever entry's own heading is currently in view (2026-08-22,
   "scrollspy highlight on the TOC"). Bold + brand-green, same hover
   color, so the active entry reads as "this is where you are" without
   a second color needing its own contrast check. */
.post-page__toc a.is-active {
  color: var(--color-brand-green);
  font-weight: 600;
  border-bottom-color: currentColor;
}

/* ---- "Finished reading" confetti — assets/js/post-finish-confetti.js
   (2026-08-22). One canvas, created and removed by the script itself;
   fixed/full-viewport, high z-index so it draws over everything, but
   never intercepts a click — this is a celebration, not a modal. ---- */
.post-finish-confetti {
  position: fixed;
  inset: 0;
  z-index: 400;
  pointer-events: none;
}
/* Anchor targets land under the sticky header, not flush against it —
   every h2/h3 markdown.php gives an id gets this, post pages included. */
.post-body h2[id],
.post-body h3[id] {
  scroll-margin-top: var(--space-lg);
}

/* ---- 5. Article body + its raw-HTML "component" convention ----
   .post-body's h2/h3/blockquote/code styling still comes straight from
   base.css's sitewide rules, same as every other rendered-markdown
   page on this site — only the rules below are new to this template. */

/* Section gaps — direct instruction (2026-08-21): "H2 or main sections
   should have 3rem worth of paragraph gap and 2rem for H3 and below."
   Overrides base.css's sitewide h2/h3 margin-top specifically inside a
   post's own body, since a single post's sections are the one place
   this site actually has H2-delimited "main sections" stacked one
   after another the way this rule describes. */
.post-body h2 {
  margin-top: 3rem;
}
.post-body h3,
.post-body h4,
.post-body h5,
.post-body h6 {
  margin-top: 2rem;
}

/* Full width, no character-count cap — direct instruction, repeated
   (2026-08-21/22): every text block on this template spans the full
   1120px column, same as its images/tables/other components, full
   stop. An earlier pass tried capping prose at 72ch (NN/g's own
   50-75-characters-per-line guidance) as a middle ground, but that
   was overridden again just as explicitly — "no text is full width
   YET" — so max-width is none here now, no exceptions. Line-height
   1.65 and this margin-bottom are kept: WCAG 1.4.8 requires paragraph
   spacing of at least 1.5x line spacing, and neither of those is what
   "not full width" was ever about. */
/* Real bug, confirmed live (2026-08-22, "some image and text start
   are not aligned"): a plain markdown-rendered body image
   (markdown.php's iitc_render_figure(), no class of its own) is
   wrapped in a bare <figure> — browsers' own default UA stylesheet
   gives <figure> a 1em 40px margin, never reset anywhere on this
   site before now, so it sat 40px further right than the paragraphs
   around it. Every OTHER figure-based component here already resets
   its own margin (post-page__featured, the chart/graph/gallery/2-col
   wrappers all set their own), so this was the one gap. */
.post-body figure {
  margin: 0 0 var(--space-lg);
}
.post-body p,
.post-body li {
  max-width: none;
  line-height: 1.65;
}
.post-body p {
  margin-bottom: 1.5em;
}
.post-body li + li {
  margin-top: 0.5em;
}

/* ---- Nerdy text-link hover — deliberately different from the
   sitewide plain-link hover (base.css), per direct instruction
   (2026-08-21: "3d is preferred, nerdy is needed"). REDESIGNED
   2026-08-22 — the original version used absolutely-positioned [ ]
   bracket pseudo-elements needing real margin clearance on both
   sides; direct feedback confirmed this was genuinely broken on
   mobile ("taking too much space... breaking text and paragraph").
   This version is fully self-contained — no overflow outside the
   link's own inline box, so it needs no extra margin and can't
   collide with neighboring words at any viewport width. "Nerdy"
   now comes from a layered green text-shadow "glow" (a flat, cheap
   stand-in for neon/CRT glow, not a geometric 3D transform) plus a
   background highlight that fills in on hover; the perspective/
   rotateX tilt is kept but gated to (hover:hover) and (pointer:fine)
   devices only, since a transform on tap-triggered "hover" is exactly
   what caused the mobile breakage. Scoped to the post's own textual
   links: body copy, Sources, the license line, AND the author's
   publication links (direct instruction: "author links (hover:
   yes)") — the author PHOTO right next to those links gets a real
   ambient three.js ring instead (assets/js/post-author-photo-3d.js),
   non-hover, per that same instruction. */
.post-body a,
.post-page__sources a,
.post-page__license a,
.post-page__author-links a {
  color: var(--color-link);
  text-decoration: none;
  border-bottom: 1px dashed rgba(14, 143, 94, 0.5);
  border-radius: 3px;
  padding: 0 2px;
  box-decoration-break: clone; /* so the background/border still look right if a link wraps across two lines */
  -webkit-box-decoration-break: clone;
  transition: color 0.15s ease, background-color 0.2s ease, border-color 0.2s ease, text-shadow 0.2s ease;
}
.post-body a:hover,
.post-page__sources a:hover,
.post-page__license a:hover,
.post-page__author-links a:hover,
.post-body a:focus-visible,
.post-page__sources a:focus-visible,
.post-page__license a:focus-visible,
.post-page__author-links a:focus-visible {
  color: var(--color-brand-green);
  background: rgba(14, 143, 94, 0.12);
  border-bottom-color: var(--color-brand-green);
  border-bottom-style: solid;
  text-shadow: 0 0 8px rgba(75, 232, 155, 0.6);
}
/* The 3D lift — real pointer devices only (a touch "hover" is really
   a tap, where a transform mid-scroll/mid-read is what broke mobile
   reading flow before). */
@media (hover: hover) and (pointer: fine) {
  .post-body a,
  .post-page__sources a,
  .post-page__license a,
  .post-page__author-links a {
    display: inline-block;
    transform-origin: bottom center;
    transition: color 0.15s ease, background-color 0.2s ease, border-color 0.2s ease, text-shadow 0.2s ease, transform 0.2s ease;
  }
  .post-body a:hover,
  .post-page__sources a:hover,
  .post-page__license a:hover,
  .post-page__author-links a:hover,
  .post-body a:focus-visible,
  .post-page__sources a:focus-visible,
  .post-page__license a:focus-visible,
  .post-page__author-links a:focus-visible {
    transform: perspective(340px) rotateX(10deg) translateY(-1px);
  }
}
@media (prefers-reduced-motion: reduce) {
  .post-body a,
  .post-page__sources a,
  .post-page__license a,
  .post-page__author-links a {
    transition: none !important;
    transform: none !important;
  }
}

/* External-link "aura" — a persistent (not hover-only) glow + a small
   ↗ mark, so an external link reads as external before a reader even
   touches it. Direct instruction (2026-08-21): "applicable for blog
   body links only" — scoped to .post-body specifically, NOT Sources/
   license/author-links. Detected via href^="http" — every internal
   link sitewide uses a root-relative href (never a fully-qualified
   istiquritconsultant.com URL, confirmed convention across every
   template on this site), so this is a reliable signal without
   needing PHP-side host parsing.
   REDESIGNED 2026-08-22 alongside the link-hover rework above — the
   ↗ mark is now a normal INLINE ::after (no absolute positioning, no
   overflow, no space needed on either side of the link) so it can't
   collide with neighboring text at any width. */
.post-body a[href^="http"] {
  text-shadow: 0 0 7px rgba(75, 232, 155, 0.4);
}
.post-body a[href^="http"]::after {
  content: " ↗";
  opacity: 0.7; /* visible at rest — this IS the "easily detectable" part, not just a hover reveal */
}
.post-body a[href^="http"]:hover,
.post-body a[href^="http"]:focus-visible {
  text-shadow: 0 0 12px rgba(75, 232, 155, 0.7);
}
.post-body a[href^="http"]:hover::after,
.post-body a[href^="http"]:focus-visible::after {
  opacity: 1;
}

/* Callout — .post-callout / .post-callout--tip|warn|note, documented
   in content/README.md for an author to drop straight into a post's
   markdown source (raw HTML passthrough — markdown.php's own rule). */
.post-callout {
  margin: var(--space-md) 0;
  padding: var(--space-md);
  border-radius: 12px;
  border-left: 3px solid var(--color-brand-green);
  background: rgba(14, 143, 94, 0.06);
}
.post-callout > :first-child { margin-top: 0; }
.post-callout > :last-child { margin-bottom: 0; }
.post-callout--warn {
  border-left-color: #b23a3a;
  background: rgba(178, 58, 58, 0.08);
}
.post-callout--note {
  border-left-color: var(--color-ink-muted);
  background: color-mix(in srgb, var(--color-bg) 90%, var(--color-ink-muted) 6%);
}

/* FAQ block — .post-faq > .post-faq__item (a <details>, same native
   accordion pattern as homepage-faq.php) > .post-faq__q (<summary>) +
   .post-faq__a. Scraped server-side (lib/post-meta.php's
   iitc_extract_post_faq()) into real FAQPage JSON-LD automatically —
   the class names are load-bearing, not just styling hooks. */
.post-faq {
  margin: var(--space-md) 0;
}
.post-faq__item {
  padding: var(--space-sm) var(--space-sm);
  border-bottom: 1px solid var(--color-border);
  border-radius: 8px;
  transition: background-color 0.2s ease, box-shadow 0.2s ease;
}
.post-faq__item:first-child {
  border-top: 1px solid var(--color-border);
}
/* Hover — a tech-savvy 3D-ish lift + green glow on the whole item,
   not just an underline (direct instruction, 2026-08-21: "sound fx
   AND tech savvy 3d effects on FAQ open, close, and hover"). Sound is
   deliberately NOT added on hover — this site's own "polite sound"
   rule (ui-sound.js's own comment) is explicit that nothing plays on
   hover, only on a real action; open/close below are real actions. */
.post-faq__item:hover {
  background: rgba(14, 143, 94, 0.05);
  box-shadow: inset 3px 0 0 0 var(--color-brand-green);
}
.post-faq__q {
  cursor: pointer;
  font-family: var(--font-display);
  font-weight: 500;
  list-style: none;
  transition: transform 0.2s ease, color 0.2s ease;
  transform-origin: left center;
}
.post-faq__item:hover .post-faq__q {
  transform: perspective(400px) rotateY(-3deg) translateX(2px);
  color: var(--color-brand-green);
}
.post-faq__q::-webkit-details-marker {
  display: none;
}
.post-faq__q::before {
  content: "+";
  display: inline-block;
  width: 1em;
  margin-right: 0.35em; /* direct feedback (2026-08-21): the minus sign sat flush against the question text on open — this gap applies to both +/- states, not just the open one, so it doesn't shift width on toggle */
  color: var(--color-brand-green);
  transition: transform 0.25s ease;
}
.post-faq__item[open] .post-faq__q::before {
  content: "–";
  transform: rotate(180deg);
}
.post-faq__a {
  margin-top: var(--space-xs);
  padding-left: 1em;
  color: var(--color-ink-muted);
  transform-origin: top center;
  transform-style: preserve-3d;
}
.post-faq__a > :first-child { margin-top: 0; }
.post-faq__a > :last-child { margin-bottom: 0; }
/* Open — a quick 3D flip-down reveal (perspective rotateX from -90deg
   to flat), not just the browser's own instant show/hide. */
.post-faq__item[open] .post-faq__a {
  animation: post-faq-open 0.32s cubic-bezier(0.2, 0.8, 0.3, 1);
}
@keyframes post-faq-open {
  from { opacity: 0; transform: perspective(500px) rotateX(-75deg); }
  to   { opacity: 1; transform: perspective(500px) rotateX(0deg); }
}
/* Close — the reverse flip, played by assets/js/post-faq-fx.js BEFORE
   the <details> actually closes (native <details> has no closing
   transition otherwise — the content just vanishes the instant `open`
   flips false, too fast to animate on its own). */
.post-faq__item--closing .post-faq__a {
  animation: post-faq-close 0.26s cubic-bezier(0.4, 0, 0.6, 1) forwards;
}
@keyframes post-faq-close {
  from { opacity: 1; transform: perspective(500px) rotateX(0deg); }
  to   { opacity: 0; transform: perspective(500px) rotateX(-75deg); }
}
@media (prefers-reduced-motion: reduce) {
  .post-faq__item,
  .post-faq__q,
  .post-faq__q::before {
    transition: none !important;
  }
  .post-faq__item:hover .post-faq__q {
    transform: none !important;
  }
  .post-faq__item[open] .post-faq__a,
  .post-faq__item--closing .post-faq__a {
    animation: none !important;
  }
}

/* Source divider — a labeled <hr>-equivalent an author drops before a
   citations run mid-article (separate from the auto-rendered Sources
   list at the bottom, which comes from frontmatter, not body markup). */
.post-source-divider {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin: var(--space-lg) 0;
  color: var(--color-ink-muted);
  font-size: var(--step-caption);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.post-source-divider::before,
.post-source-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--color-border);
}

/* Embed — a responsive wrapper for a hand-authored <iframe>. Fixed
   16:9 by default (the common case); an author can override via an
   inline style="aspect-ratio:..." on the same element if a specific
   embed needs a different ratio. No CLS either way — the ratio is set
   before the iframe loads, not guessed after. */
.post-embed {
  position: relative;
  aspect-ratio: 16 / 9;
  margin: var(--space-lg) 0;
  border-radius: 12px;
  overflow: hidden;
  background: var(--color-surface);
}
.post-embed iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* Table — .post-table-wrap (glass-secondary) > .glass-content >
   table.post-table. "Tech-savvy, effectful, hoverable" per direct
   instruction (2026-08-21) — a monospace uppercase header row, and
   each body row lifts + glows on hover rather than a plain background
   swap. Optional/documented, like every other raw-HTML component —
   an author includes one only when a post's own content actually
   calls for tabular data, per content/README.md. */
.post-table-wrap {
  margin: var(--space-lg) 0;
  border-radius: 14px;
  overflow: hidden;
}
.post-table-wrap .glass-content {
  padding: 0;
  overflow-x: auto;
}
.post-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--step-caption);
}
.post-table caption {
  padding: var(--space-sm) var(--space-md) 0;
  text-align: left;
  font-size: var(--step-caption);
  color: var(--color-ink-muted);
  caption-side: top;
}
.post-table thead th {
  padding: var(--space-sm) var(--space-md);
  text-align: left;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 11px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--color-brand-green);
  border-bottom: 1px solid rgba(14, 143, 94, 0.35);
  white-space: nowrap;
}
.post-table tbody td {
  padding: var(--space-sm) var(--space-md);
  color: var(--color-ink);
  border-bottom: 1px solid var(--color-border);
  transition: transform 0.18s ease, box-shadow 0.18s ease, background-color 0.18s ease;
}
.post-table tbody tr:last-child td {
  border-bottom: 0;
}
.post-table tbody tr:hover td {
  background: rgba(14, 143, 94, 0.08);
  transform: translateY(-1px);
  box-shadow: inset 0 -2px 0 0 var(--color-brand-green);
}
@media (prefers-reduced-motion: reduce) {
  .post-table tbody td {
    transition: none !important;
  }
  .post-table tbody tr:hover td {
    transform: none !important;
  }
}

/* Carousel — .post-carousel (glass-secondary) — a horizontal
   scroll-snap slide track (assets/js/post-carousel.js only wires the
   prev/next buttons + dot indicators; the actual scrolling is native
   CSS scroll-snap, so touch/trackpad swipe works with zero JS too). */
.post-carousel {
  position: relative;
  margin: var(--space-lg) 0;
  border-radius: 14px;
  overflow: hidden;
}
.post-carousel .glass-content {
  /* Horizontal padding removed (2026-08-22, "image and text start not
     aligned") — the slide images themselves now sit flush with the
     card's own left/right edge, matching the plain-paragraph/gallery
     left edge outside this component. Vertical padding stays so the
     nav row below the track still gets breathing room. */
  padding: var(--space-md) 0;
}
.post-carousel__track {
  display: flex;
  gap: var(--space-sm);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.post-carousel__track::-webkit-scrollbar {
  display: none;
}
.post-carousel__slide {
  /* Two slides per view — direct instruction (2026-08-22, "show two
     images in one view"). calc() accounts for the track's own gap
     (--space-sm) between the pair, so both slides plus the gap
     between them exactly fill the track's width. */
  flex: 0 0 calc(50% - var(--space-sm) / 2);
  scroll-snap-align: start;
}
@media (max-width: 480px) {
  .post-carousel__slide {
    flex: 0 0 100%; /* one at a time on narrow phones — two 4:5 slides side by side would be too cramped to read */
  }
}
.post-carousel__slide img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5; /* direct instruction (2026-08-22) — was 1:1, now 4:5, regardless of the source image's own ratio */
  object-fit: cover;
  border-radius: var(--post-media-radius);
}
.post-carousel__nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  margin-top: var(--space-sm);
}
.post-carousel__btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  min-width: 0; /* same base.css button{min-width/height:44px} touch-target override needed here as the share row — see that comment */
  min-height: 0;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  background: var(--color-bg);
  color: var(--color-ink-muted);
  cursor: pointer;
  transition: border-color 0.2s ease, color 0.2s ease;
}
.post-carousel__btn:hover,
.post-carousel__btn:focus-visible {
  border-color: rgba(14, 143, 94, 0.55);
  color: var(--color-brand-green);
}
.post-carousel__btn svg {
  width: 16px;
  height: 16px;
}
.post-carousel__dots {
  display: flex;
  gap: 6px;
}
.post-carousel__dot {
  width: 7px;
  height: 7px;
  min-width: 0; /* real bug, confirmed live (2026-08-22): base.css's sitewide button{min-width/min-height:44px} touch-target rule was silently inflating these to 44px — the "dots should be smaller, like 1rem max" report. 7px already clears that ceiling once the override actually applies. */
  min-height: 0;
  border-radius: 50%;
  border: 0;
  padding: 0;
  background: var(--color-border);
  cursor: pointer;
}
.post-carousel__dot--active {
  background: var(--color-brand-green);
}

/* Chart / graph — .post-chart / .post-graph (glass-secondary), each
   wrapping one hand-authored inline SVG (no charting library — this
   site has no server-side build step and CSP blocks third-party
   script origins anyway). Bars/points get a hover highlight + a
   native <title> tooltip (real SVG accessibility, not a JS tooltip
   library) baked into the SVG markup itself by whoever authors the
   chart. Always caption real, labeled data — "Example data" in the
   caption whenever a chart illustrates a concept rather than reporting
   a real measured figure, so it never reads as a fabricated statistic. */
.post-chart,
.post-graph {
  margin: var(--space-lg) 0;
  border-radius: 14px;
  overflow: hidden;
}
.post-chart .glass-content,
.post-graph .glass-content {
  /* Horizontal padding removed (2026-08-22, "image and text start not
     aligned") — the SVG's own left edge now matches the plain-paragraph
     left edge outside this component; the figcaption below stays
     centered so it doesn't look lopsided. */
  padding: var(--space-md) 0;
}
.post-chart svg,
.post-graph svg {
  display: block;
  width: 100%;
  height: auto;
}
.post-chart figcaption,
.post-graph figcaption {
  margin-top: var(--space-sm);
  padding: 0 var(--space-md);
  font-size: var(--step-caption);
  color: var(--color-ink-muted);
  text-align: center;
}
.post-chart .post-chart__bar {
  transition: opacity 0.15s ease;
}
.post-chart .post-chart__bar:hover {
  opacity: 0.75;
}
.post-graph .post-graph__point {
  transition: r 0.15s ease;
}
.post-graph .post-graph__point:hover {
  r: 6;
}

/* Body images — a second/plain body image beyond the featured one
   (markdown.php's own <figure> output, no extra class needed — the
   sitewide .post-lightbox-trigger styling already applies via
   assets/js/post-lightbox.js). Two-column pairs and full galleries
   are the two extra layouts below. */
.post-two-col-images {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-sm);
  margin: var(--space-lg) 0;
}
.post-two-col-images figure {
  margin: 0;
}
.post-two-col-images img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--post-media-radius);
}
.post-two-col-images figcaption {
  margin-top: var(--space-xs);
  font-size: var(--step-caption);
  color: var(--color-ink-muted);
  text-align: center;
}
@media (max-width: 560px) {
  .post-two-col-images {
    grid-template-columns: 1fr;
  }
}

.post-gallery {
  display: grid;
  grid-template-columns: repeat(4, 1fr); /* max 4 per line, direct instruction (2026-08-22) — was auto-fill(minmax 160px), which could pack in more than 4 on a wide viewport */
  gap: var(--space-sm);
  margin: var(--space-lg) 0;
}
@media (max-width: 720px) {
  .post-gallery {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (max-width: 420px) {
  .post-gallery {
    grid-template-columns: 1fr;
  }
}
.post-gallery figure {
  margin: 0;
}
.post-gallery img {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: var(--post-media-radius);
}

/* ---- 6. Sources ---- */
.post-page__sources {
  margin: var(--space-lg) 0;
}
.post-page__sources h2 {
  margin-top: 0;
}
.post-page__sources ol {
  color: var(--color-ink-muted);
  font-size: var(--step-caption);
}
.post-page__sources li + li {
  margin-top: 4px;
}

/* ---- 7. License line ---- */
.post-page__license {
  margin: var(--space-lg) 0;
  padding-top: var(--space-md);
  border-top: 1px solid var(--color-border);
  max-width: none;
  color: var(--color-ink-muted);
  font-size: var(--step-caption);
}

/* ---- 8. Author box ---- */
.post-page__author {
  margin: var(--space-lg) 0;
  border-radius: 18px;
  overflow: hidden;
  /* Off-palette amber accent (2026-08-22, "make the author box a
     different color from our brand palette otherwise all is looking
     same") — same amber already used for the "Contact the author"
     link inside this box, now carried onto the card itself so it
     reads as visually distinct from the CTA card right below it,
     which uses the same brand-green glass-primary look otherwise. */
  --glass-primary-border: rgba(224, 169, 58, 0.4);
}
.post-page__author::before {
  /* Layered on TOP of the existing theme-aware --glass-primary-bg
     gradient (glass.css) rather than replacing it — the amber wash
     tints the card without losing the automatic light/dark tinting
     that gradient already provides underneath. */
  background: linear-gradient(165deg, rgba(224, 169, 58, 0.16), rgba(224, 169, 58, 0.03) 55%), var(--glass-primary-bg);
  box-shadow: var(--glass-shadow), inset 0 1px 0 rgba(255, 255, 255, 0.3), 0 0 28px rgba(224, 169, 58, 0.1);
}
.post-page__author-inner {
  display: flex;
  gap: var(--space-md);
  padding: var(--space-lg);
}
/* Real photo (AUTHOR_PHOTO, config/site.config.php), on-brand: circular,
   a brand-green ring + soft glow, background-removed (a whiteness-
   threshold cutout with a feathered edge — the source was a clean
   near-white studio background, so a real alpha channel exists rather
   than a baked-in flat color) so the CIRCLE's own background — the
   glass card's tint, itself theme-aware — shows through behind the
   subject in both light and dark mode automatically, never a fixed
   color that would look wrong in one of the two themes. */
.post-page__author-photo {
  flex: none;
  width: 72px;
  height: 72px;
  border-radius: 50%;
  border: 2px solid var(--color-brand-green);
  box-shadow: 0 0 0 4px rgba(14, 143, 94, 0.12), 0 4px 16px rgba(14, 143, 94, 0.25);
  object-fit: cover;
  background: color-mix(in srgb, var(--color-bg) 85%, var(--color-brand-green) 6%);
}
.post-page__author-text {
  min-width: 0;
}
.post-page__author-name {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--step-body);
  font-weight: 500;
}
.post-page__author-name a {
  color: inherit;
  text-decoration: none;
}
.post-page__author-name a:hover {
  color: var(--color-brand-green);
}
.post-page__author-title {
  margin: 2px 0 var(--space-xs);
  color: var(--color-ink-muted);
  font-size: var(--step-caption);
}
.post-page__author-bio {
  margin: 0 0 var(--space-sm);
  max-width: none;
  color: var(--color-ink-muted);
}
.post-page__author-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  font-size: var(--step-caption);
}
.post-page__author-links a {
  color: var(--color-brand-green);
}
/* Differentiated from the plain Medium/Dev.to/etc. publication links
   beside it (direct instruction, 2026-08-22) — same link row, same
   hover/3D-tilt treatment (still matched by the shared
   .post-page__author-links a selectors above), just its own color and
   weight so it doesn't read as one more item in that list. A deliberate
   OFF-palette amber, not --color-ink — direct follow-up instruction:
   "different color from our brand palette, otherwise all is looking
   same." WCAG-checked against both themes' own page background (not
   eyeballed): #E0A93A on --color-bg dark (#0B1215) is 8.91:1 (AAA);
   #7A5A0E on --color-bg light (#F9FDFE) is 6.22:1 (AAA). */
.post-page__author-links a.post-page__author-contact {
  color: #E0A93A;
  font-weight: 600;
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .post-page__author-links a.post-page__author-contact {
    color: #7A5A0E;
  }
}
:root[data-theme="light"] .post-page__author-links a.post-page__author-contact {
  color: #7A5A0E;
}
/* Explicit hover/focus override — without this, the shared
   .post-page__author-links a:hover rule above (same selector
   specificity, but defined earlier in this file) would lose to the
   rule above on hover too, leaving the text stuck at --color-ink
   instead of joining the rest of the row's brand-green hover
   feedback. */
.post-page__author-links a.post-page__author-contact:hover,
.post-page__author-links a.post-page__author-contact:focus-visible {
  color: var(--color-brand-green);
}
@media (max-width: 480px) {
  .post-page__author-inner {
    flex-direction: column;
  }
}

/* ---- 9. Bottom CTA ---- */
.post-page__cta {
  position: relative;
  margin: var(--space-lg) 0 var(--space-sm); /* not --space-lg on the bottom too — direct instruction (2026-08-22): stacked with .post-page__body-wrap's own padding-bottom, the gap before the footer read too large, same fix already applied on newsletter-page.css/sitemap-page.css */
  border-radius: 18px;
  overflow: hidden;
}
/* Ambient three.js backdrop (assets/js/post-cta-3d.js) — sits behind
   .glass-content via plain DOM order (a glass-primary element's own
   ::before blur/tint pseudo always paints first regardless, so no
   extra z-index juggling is needed here). */
.post-page__cta-bg {
  position: absolute;
  inset: 0;
}
.post-page__cta-bg canvas {
  display: block;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 0.6s ease;
}
.post-page__cta-bg--ready canvas {
  opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
  .post-page__cta-bg canvas {
    transition: none !important;
  }
}
.post-page__cta-inner {
  position: relative;
  padding: var(--space-lg);
}
.post-page__cta-inner h2 {
  margin-top: 0;
}
.post-page__cta-inner p {
  max-width: none;
  color: var(--color-ink-muted);
}

/* ---- Author photo — ambient (non-hover) three.js ring behind the
   circular photo, assets/js/post-author-photo-3d.js. The frame is
   sized larger than the 72px photo so the ring/particles can peek out
   around its edge; canvas sits BEHIND the photo (z-index), photo stays
   on top so its own brand-green ring/shadow (.post-page__author-photo,
   above) still reads as the primary frame. */
.post-page__author-photo-frame {
  position: relative;
  flex: none;
  width: 112px;
  height: 112px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.post-page__author-photo-frame canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 0.4s ease;
}
.post-page__author-photo-frame--ready canvas {
  opacity: 1;
}
.post-page__author-photo-frame .post-page__author-photo {
  position: relative;
  z-index: 1;
}
@media (prefers-reduced-motion: reduce) {
  .post-page__author-photo-frame canvas {
    transition: none !important;
  }
}
@media (max-width: 480px) {
  .post-page__author-photo-frame {
    width: 96px;
    height: 96px;
  }
}

/* ---- Copy-link toast — Clear Glass, on-brand, shown for 3s
   (assets/js/post-share.js). Fixed to the bottom of the viewport, not
   the page, so it stays visible regardless of scroll position. */
.post-page__toast {
  position: fixed;
  left: 50%;
  bottom: var(--space-lg);
  transform: translate(-50%, 12px);
  z-index: 200;
  border-radius: 999px;
  overflow: hidden;
  opacity: 0;
  transition: opacity 0.25s ease, transform 0.25s ease;
  pointer-events: none;
}
.post-page__toast--visible {
  opacity: 1;
  transform: translate(-50%, 0);
}
.post-page__toast-inner {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  padding: 10px 18px;
  font-size: var(--step-caption);
  font-weight: 500;
  color: var(--color-ink);
}
.post-page__toast-inner svg {
  flex: none;
  width: 16px;
  height: 16px;
  color: var(--color-brand-green);
}
@media (prefers-reduced-motion: reduce) {
  .post-page__toast {
    transition: none !important;
  }
}

/* ---- Nerdy lightbox — assets/js/post-lightbox.js. Scoped to
   .post-body img only (the featured image sits outside .post-body and
   isn't a lightbox trigger). A dark scrim, animated corner brackets
   (same targeting-reticle language as archive-page.css's own card
   hover), and a monospace HUD caption line — "nerdy" per direct
   instruction, not a generic photo-viewer plugin look. */
.post-body img.post-lightbox-trigger {
  cursor: zoom-in;
  transition: filter 0.2s ease;
}
.post-body img.post-lightbox-trigger:hover,
.post-body img.post-lightbox-trigger:focus-visible {
  filter: brightness(1.06) saturate(1.1);
  outline: 2px solid rgba(14, 143, 94, 0.5);
  outline-offset: 2px;
}
.post-lightbox {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-lg);
  background: rgba(6, 12, 10, 0.88);
  backdrop-filter: blur(6px);
  opacity: 0;
  pointer-events: none; /* real bug, confirmed live (2026-08-22): this class's own `display: flex` was overriding the browser's native [hidden]{display:none} behavior (equal specificity, author stylesheet wins) — the closed overlay stayed laid out full-screen forever, invisible but still capturing every click sitewide. This + the [hidden] rule below are the actual fix; JS still also sets the hidden attribute for correctness/SR purposes. */
  transition: opacity 0.25s ease;
}
.post-lightbox[hidden] {
  display: none !important;
}
.post-lightbox--visible {
  opacity: 1;
  pointer-events: auto;
}
.post-lightbox__frame {
  position: relative;
  max-width: min(90vw, 1100px);
  max-height: 85vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  transform: scale(0.96);
  transition: transform 0.25s ease;
}
.post-lightbox--visible .post-lightbox__frame {
  transform: scale(1);
}
.post-lightbox__img {
  display: block;
  max-width: 100%;
  max-height: 75vh;
  border-radius: var(--post-media-radius);
  box-shadow: 0 0 0 1px rgba(75, 232, 155, 0.3), 0 20px 60px rgba(0, 0, 0, 0.5);
  opacity: 1;
  transition: opacity 0.18s ease;
}
/* Prev/next fade — assets/js/post-lightbox.js's step() toggles this
   class off/on around the src swap (2026-08-22, "need fade transition
   when going next or previous"). Not used on the initial open() —
   that already gets its own frame scale-in transition. */
.post-lightbox__img--fading {
  opacity: 0;
}
@media (prefers-reduced-motion: reduce) {
  .post-lightbox__img {
    transition: none !important;
  }
}
.post-lightbox__caption {
  margin: var(--space-sm) 0 0;
  max-width: none;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 12px;
  letter-spacing: 0.02em;
  color: #9fe6c4;
  text-align: center;
}
.post-lightbox__close {
  position: absolute;
  top: -44px;
  right: 0;
  width: 34px;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid rgba(75, 232, 155, 0.4);
  background: rgba(15, 23, 27, 0.8);
  color: #f2fff8;
  cursor: pointer;
}
.post-lightbox__close svg {
  width: 16px;
  height: 16px;
}
.post-lightbox__close:hover,
.post-lightbox__close:focus-visible {
  border-color: rgba(75, 232, 155, 0.8);
  background: rgba(14, 143, 94, 0.3);
}
/* Album prev/next — only rendered when the opened image has 2+
   siblings in the same container (.post-gallery, .post-carousel,
   .post-two-col-images — see post-lightbox.js's grouping logic), so a
   lone body image never shows dead-end arrows. Same circular
   dark-glass language as the close button, positioned at the frame's
   own left/right edge rather than the viewport edge, so they stay
   anchored to the image regardless of how narrow it renders. */
.post-lightbox__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid rgba(75, 232, 155, 0.4);
  background: rgba(15, 23, 27, 0.8);
  color: #f2fff8;
  cursor: pointer;
}
.post-lightbox__nav svg {
  width: 20px;
  height: 20px;
}
.post-lightbox__nav:hover,
.post-lightbox__nav:focus-visible {
  border-color: rgba(75, 232, 155, 0.8);
  background: rgba(14, 143, 94, 0.3);
}
.post-lightbox__nav[hidden] {
  /* Same specificity pitfall already fixed once on .post-lightbox
     itself (2026-08-22): this rule's own unconditional `display: flex`
     would otherwise beat the browser's native [hidden]{display:none}
     at equal specificity, leaving a single-image lightbox showing dead
     prev/next arrows. */
  display: none;
}
.post-lightbox__nav--prev {
  left: -14px;
}
.post-lightbox__nav--next {
  right: -14px;
}
@media (max-width: 640px) {
  .post-lightbox__nav--prev {
    left: 0;
  }
  .post-lightbox__nav--next {
    right: 0;
  }
}
.post-lightbox__counter {
  color: #9fe6c4;
  opacity: 0.8;
}
/* Corner brackets — identical recipe to archive-page.css's own
   .archive-card__corner, reused rather than inventing a second
   "tech frame" language for the lightbox specifically. */
.post-lightbox__corner {
  position: absolute;
  width: 18px;
  height: 18px;
  border: 0 solid #4be89b;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease 0.15s, transform 0.3s ease 0.15s;
}
.post-lightbox__corner--tl { top: -8px; left: -8px; border-width: 2px 0 0 2px; transform: translate(6px, 6px); }
.post-lightbox__corner--tr { top: -8px; right: -8px; border-width: 2px 2px 0 0; transform: translate(-6px, 6px); }
.post-lightbox__corner--bl { bottom: -8px; left: -8px; border-width: 0 0 2px 2px; transform: translate(6px, -6px); }
.post-lightbox__corner--br { bottom: -8px; right: -8px; border-width: 0 2px 2px 0; transform: translate(-6px, -6px); }
.post-lightbox--visible .post-lightbox__corner {
  opacity: 1;
  transform: translate(0, 0);
}
body.post-lightbox-open {
  overflow: hidden;
}
@media (prefers-reduced-motion: reduce) {
  .post-lightbox,
  .post-lightbox__frame,
  .post-lightbox__corner {
    transition: none !important;
  }
}

@media (prefers-reduced-motion: reduce) {
  .post-page__share-btn {
    transition: none !important;
  }
}
