/* ============================================================
   Micro-interactions — subtle motion for mobile & desktop
   ------------------------------------------------------------
   Progressive enhancement:
   - Reveal-on-scroll states only apply when <html> has the
     `motion-reveal` class (added inline in <head> ONLY when the
     user has NOT requested reduced motion). No JS / reduced
     motion => content is always fully visible, no layout shift.
   - Tactile :active feedback, focus-visible rings and image
     fade-ins are layered on top without affecting reading flow.
   ============================================================ */

/* ---------- Reveal on scroll (cards & list rows) ---------- */
/* Hidden initial state is scoped to .motion-reveal so it never
   hides content for no-JS or reduced-motion visitors. */
html.motion-reveal .post-entry,
html.motion-reveal .ai-tech-card,
html.motion-reveal .growth-card,
html.motion-reveal .ai-tech-list-item,
html.motion-reveal .growth-list-item,
html.motion-reveal .reveal-item,
/* Related-post cards at the foot of an article join the same cascade so
   the page keeps unfolding right to the "continue reading" zone instead
   of the grid popping in fully-formed. Staggered per row by the shared
   .related-posts-grid parent (see micro-interactions.js). */
html.motion-reveal .rpc {
  opacity: 0;
  /* translateY for the rise + a hair of scale so the card "settles"
     into place rather than just sliding — a slightly more premium
     entrance. Kept tiny (0.985) to avoid any text reflow shimmer.
     The .is-revealed state resets transform to none, and the
     reduced-motion block hard-overrides transform to none. */
  transform: translateY(16px) scale(0.985);
  will-change: opacity, transform;
  transition:
    opacity 0.55s cubic-bezier(0.22, 0.61, 0.36, 1),
    transform 0.55s cubic-bezier(0.22, 0.61, 0.36, 1);
  /* Per-element stagger set inline by JS via --reveal-delay */
  transition-delay: var(--reveal-delay, 0ms);
}

html.motion-reveal .post-entry.is-revealed,
html.motion-reveal .ai-tech-card.is-revealed,
html.motion-reveal .growth-card.is-revealed,
html.motion-reveal .ai-tech-list-item.is-revealed,
html.motion-reveal .growth-list-item.is-revealed,
html.motion-reveal .reveal-item.is-revealed,
html.motion-reveal .rpc.is-revealed {
  opacity: 1;
  transform: none;
}

/* Once revealed, drop will-change so we don't pin layers. */
html.motion-reveal .is-revealed {
  will-change: auto;
}

/* ---------- In-article block reveal (headings, quotes, code) ---------- */
/* Structural blocks ease up as they enter the viewport so a long read
   feels like it unfolds. Scoped to .motion-reveal + the .reveal-block
   class JS adds, so content is never hidden without the enhancement. */
html.motion-reveal .post-content .reveal-block {
  opacity: 0;
  transform: translateY(14px);
  transition:
    opacity 0.5s cubic-bezier(0.22, 0.61, 0.36, 1),
    transform 0.5s cubic-bezier(0.22, 0.61, 0.36, 1);
}
html.motion-reveal .post-content .reveal-block.is-revealed {
  opacity: 1;
  transform: none;
}

/* ---------- Article images: gentle fade-in on load ---------- */
html.motion-reveal .post-content img {
  opacity: 0;
  transition: opacity 0.6s ease;
}
html.motion-reveal .post-content img.img-loaded {
  opacity: 1;
}

/* ---------- Tactile press feedback (mobile + desktop) ---------- */
/* A quick scale-down on press makes taps feel responsive. */
.post-entry,
.ai-tech-card,
.growth-card,
.ai-tech-list-item,
.growth-list-item {
  -webkit-tap-highlight-color: transparent;
}

.post-entry:active,
.ai-tech-card:active,
.growth-card:active,
.growth-list-item:active,
.ai-tech-list-item:active {
  transform: scale(0.985);
  transition: transform 0.12s ease;
}

.post-tag:active,
.post-category:active,
.button:active,
button:active,
.top-link:active {
  transform: scale(0.94);
}

/* ---------- Focus-visible accessibility ring ---------- */
/* Keyboard users get a clear, consistent focus indicator. */
a:focus-visible,
button:focus-visible,
.post-entry:focus-visible,
.ai-tech-card:focus-visible,
.growth-card:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--color-accent, var(--primary));
  outline-offset: 3px;
  border-radius: var(--radius-sm, 4px);
}

/* ---------- Scroll-to-top button polish ---------- */
.top-link {
  transition:
    visibility 0.3s ease,
    opacity 0.3s ease,
    transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.top-link:hover {
  transform: translateY(-3px);
}

/* ---------- Scroll-progress ring on the back-to-top button ---------- */
/* The button doubles as a page-read indicator: a ring fills clockwise
   as the reader scrolls, so the control communicates "how far am I"
   and "tap to return" at a glance. Desktop-only (the button is hidden
   on mobile), purely additive over the existing filled circle. */
.top-link .top-link-ring {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  transform: rotate(-90deg);
  pointer-events: none;
  overflow: visible;
}
.top-link .top-link-ring-track {
  fill: none;
  stroke: rgba(255, 255, 255, 0.22);
  stroke-width: 2.5;
}
.top-link .top-link-ring-progress {
  fill: none;
  stroke: #fff;
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-dasharray: 125.66;
  stroke-dashoffset: 125.66;
  transition: stroke-dashoffset 0.12s linear;
}
/* Keep the arrow + ring high-contrast white on the accent button.
   (PaperMod's base colours the arrow with --secondary, which fades to
   --primary on hover — teal-on-teal, nearly invisible. Pin it white.) */
#top-link.top-link,
#top-link.top-link:hover,
#top-link.top-link:focus {
  color: #fff;
}
/* The arrow keeps the original centred placement and gives a tiny
   upward nudge on hover so the "go to top" intent is unmistakable. */
.top-link .top-link-arrow {
  transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@media (hover: hover) and (pointer: fine) {
  .top-link:hover .top-link-arrow {
    transform: translateY(-2px);
  }
}

/* ---------- Swipe navigation hint (mobile article pages) ---------- */
/* The puck follows the finger: --swipe-shift / --swipe-scale are set
   live by JS during the gesture for a tactile, proportional feel. */
.swipe-hint {
  --swipe-shift: 0px;
  --swipe-scale: 0.82;
  position: fixed;
  top: 50%;
  transform: translate(var(--swipe-shift), -50%) scale(var(--swipe-scale));
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--color-accent, var(--primary)) 55%, transparent);
  color: #fff;
  box-shadow: var(--shadow-md, 0 4px 16px rgba(15, 23, 42, 0.18));
  opacity: 0;
  pointer-events: none;
  transition:
    opacity 0.16s ease,
    background 0.16s ease,
    transform 0.06s linear;
}
.swipe-hint svg { width: 22px; height: 22px; }
.swipe-hint.left { left: 14px; }
.swipe-hint.right { right: 14px; }
.swipe-hint.is-active { opacity: 1; }
/* Threshold crossed: solid fill + a little pop = "release to go". */
.swipe-hint.is-armed {
  background: var(--color-accent, var(--primary));
  box-shadow:
    var(--shadow-md, 0 4px 16px rgba(15, 23, 42, 0.18)),
    0 0 0 6px color-mix(in srgb, var(--color-accent, var(--primary)) 18%, transparent);
}
/* The puck hardcodes `color:#fff` on an accent fill. In dark/EN the accent is
   light sky-blue (#7dd3fc), so the white chevron drops to ~1.67:1 — worst when
   armed (solid fill). Use the page paper tone as the on-accent ink in dark
   (dark glyph on the light dark-accent: 11:1 EN / 6.3:1 ZH). `.dark` outranks
   the base selector. Gated on .dark — light mode keeps the white chevron. */
.dark .swipe-hint {
  color: var(--color-paper);
}

/* ---------- Animated section underline (links in prose) ---------- */
/* A small left-to-right underline grow on hover for inline links. */
.post-content a:not(.anchor):not(.btn) {
  background-image: linear-gradient(var(--color-accent, var(--primary)), var(--color-accent, var(--primary)));
  background-size: 0% 1px;
  background-position: 0 100%;
  background-repeat: no-repeat;
  transition: background-size 0.3s ease;
}
.post-content a:not(.anchor):not(.btn):hover {
  background-size: 100% 1px;
}

/* ---------- Tap / click ripple (mobile + desktop) ---------- */
/* `.has-ripple` is added by JS only to elements that receive a
   ripple, so we never force clip/stacking context on unrelated
   nodes. The ink span is sized & positioned inline by JS. */
.has-ripple {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
.ripple-ink {
  position: absolute;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.28;
  transform: scale(0);
  pointer-events: none;
  z-index: 0;
  animation: ripple-spread 0.55s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
}
@keyframes ripple-spread {
  to {
    transform: scale(1);
    opacity: 0;
  }
}
/* Keep real content above the ink layer inside ripple hosts. */
.has-ripple > *:not(.ripple-ink) {
  position: relative;
  z-index: 1;
}

/* ---------- Prev / next pagination: desktop hover lift ---------- */
/* Pairs the existing :active press with a gentle hover cue so the
   prev/next blocks feel as tactile on a trackpad as a card does. */
.paginav a {
  transition:
    transform 0.18s cubic-bezier(0.22, 0.61, 0.36, 1),
    box-shadow 0.18s ease,
    background-color 0.18s ease;
}
@media (hover: hover) and (pointer: fine) {
  .paginav .prev:hover { transform: translateX(-3px); }
  .paginav .next:hover { transform: translateX(3px); }
}
.paginav a:active { transform: scale(0.985); }

/* ---------- Icon controls: tap feedback + desktop hover ---------- */
/* Header utility controls get the same tactile press as cards, plus a
   playful spin on the theme toggle for pointer users. */
#theme-toggle,
.hamburger-menu,
.copy-code,
.logo a,
.social-icons a,
.share-buttons a {
  -webkit-tap-highlight-color: transparent;
}
#theme-toggle:active,
.hamburger-menu:active,
.social-icons a:active,
.share-buttons a:active {
  transform: scale(0.9);
}
#theme-toggle {
  transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@media (hover: hover) and (pointer: fine) {
  #theme-toggle:hover { transform: rotate(18deg) scale(1.08); }
  .social-icons a {
    transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
  }
  .social-icons a:hover { transform: translateY(-2px); }
}

/* ---------- TOC links: gentle slide on hover (desktop) ---------- */
@media (hover: hover) and (pointer: fine) {
  .toc a,
  .toc-highlight a {
    transition: color 0.18s ease, transform 0.18s ease;
  }
  .toc a:hover,
  .toc-highlight a:hover { transform: translateX(3px); }
}

/* ---------- Mobile nav menu motion lives in zzz-nav-menu-motion.css ----------
   The hamburger → X morph and the menu-item stagger-in used to be defined
   here too, which meant two stylesheets drove the same elements: this file's
   keyframe animation (`nav-item-in`) and zzz-nav-menu-motion.css's transition
   approach fought over `opacity`/`transform` on `#menu > li`, and the morph
   was declared twice. zzz-nav-menu-motion.css loads later, is the canonical
   implementation, and carries its own reduced-motion handling, so the
   duplicates were removed from here to leave a single source of truth. */

/* ---------- Nav controls: tap feedback (mobile + desktop) ---------- */
/* The theme toggle and hamburger already press; extend the same tactile
   scale-down to the remaining nav controls so every tap confirms itself. */
#menu > li > a,
.nav-search-trigger,
.lang-switch a {
  -webkit-tap-highlight-color: transparent;
}
#menu > li > a:active,
.nav-search-trigger:active,
.lang-switch a:active {
  transform: scale(0.96);
}

/* ---------- Tags & categories: desktop hover lift ---------- */
/* Pointer users get a small rise (the :active scale already handles taps).
   transition lives here so the lift eases in and out smoothly. */
.post-tags a,
.post-tag,
.post-category {
  transition: transform 0.16s cubic-bezier(0.34, 1.56, 0.64, 1),
    background-color 0.16s ease, color 0.16s ease;
}
@media (hover: hover) and (pointer: fine) {
  .post-tags a:hover,
  .post-tag:hover,
  .post-category:hover { transform: translateY(-2px); }
}

/* ============================================================
   Reduced-motion: hard override. Disable all of the above so
   the experience is fully static for users who ask for it.
   ============================================================ */
@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;
  }

  .post-entry,
  .ai-tech-card,
  .growth-card,
  .ai-tech-list-item,
  .growth-list-item,
  .reveal-item,
  .rpc,
  .post-content .reveal-block,
  .post-content img {
    opacity: 1 !important;
    transform: none !important;
  }

  .swipe-hint { display: none !important; }

  /* New nav/tag motion: neutralise entirely under reduced motion. */
  #menu.mobile-open > li { animation: none !important; }
  /* Hamburger still forms the ✕, but snaps instead of animating. */
  .hamburger-menu svg line { transition: none !important; }
  #menu > li > a:active,
  .nav-search-trigger:active,
  .lang-switch a:active,
  .post-tags a:hover,
  .post-tag:hover,
  .post-category:hover { transform: none !important; }

  #theme-toggle:hover,
  #theme-toggle:active,
  .hamburger-menu:active,
  .social-icons a:hover,
  .social-icons a:active,
  .share-buttons a:active,
  .toc a:hover,
  .toc-highlight a:hover { transform: none !important; }

  /* Ripple is JS-gated off under reduced motion, but hard-hide any
     ink that slips through and neutralise the pagination shift. */
  .ripple-ink { display: none !important; }
  .paginav .prev:hover,
  .paginav .next:hover,
  .paginav a:active { transform: none !important; }

  .post-content a:not(.anchor):not(.btn) {
    background-size: 0 0 !important;
  }
}
