/* ==========================================================================
   components.css — Cards, publications, filters, tags & shared UI pieces
   --------------------------------------------------------------------------
   Depends on css/variables.css design tokens (--brand, --on-brand, --card,
   --panel, --muted, --ring, --text, radii, spacing, --transition-fast/med,
   easings) and is enhanced by js/publications.js (tasks 10.1/10.2), which:
     - toggles `.active` + `aria-pressed` on `.filter-btn` buttons
     - fades a `.pub` card's inline `style.opacity` to 0, then adds `.hidden`
       (display:none) to drop it from layout — and reverses that to reveal it
     - writes the live count into `#pub-count`
     - toggles the `#pub-empty` empty-state message in place of the list

   Requirements owned here:
     5.1 — publication card hover lift: elevated shadow + border shift toward
           --brand + scale <= 1.02, transition 200–400ms.
     5.2 — filter buttons: active/inactive color + background-color transition
           150–300ms; the active button uses --brand as its background.
   Requirement 5.3 is supported here by the `.pub` opacity transition (<=400ms)
   and the `.pub.hidden { display:none }` rule that publications.js coordinates.

   CONTRAST NOTE (Req 9.5)
   --------------------------------------------------------------------------
   The card border, the `.pub` hover border and the inactive filter border are
   DECORATIVE (non-text) uses of --brand / --ring, so the brand-on-card text
   ratio does not apply. The ACTIVE filter button pairs --brand (fill) with
   --on-brand (text) — a token pair sized for AA in both themes (dark 7.1:1,
   light 5.7:1). Inactive filter text and `.empty-state` text use --muted,
   which meets AA on the --card surface in both themes (dark 4.97:1,
   light 4.75:1).

   FILE MAP (sections are numbered; task 12.2 appends AFTER this file)
   --------------------------------------------------------------------------
     1. Card surface (.card) + card headings
     2. Generic list wrapper (.list)
     3. Publication / entry cards (.pub) + filter fade + hover lift  (Req 5.1)
     4. Filters (.filters, .filter-btn, .filter-btn.active)          (Req 5.2)
     5. Filter count (.filter-count)
     6. Tags (base pill only — hover is owned by task 12.2)
     7. Empty-state message (.empty-state)
     8. Reduced-motion overrides for the interactions in this file
     9. >>> Task 12.2 micro-interaction insertion point <<<
   ========================================================================== */

/* ==========================================================================
   1. Card surface (Catppuccin panel look) + headings
   --------------------------------------------------------------------------
   The original aesthetic: a gradient panel surface (darker --panel easing into
   the lighter --card) with a subtle brand-tinted border and a rounded
   --radius-lg corner. Section padding itself is owned by layout.css; this file
   only provides the visual treatment.
   ========================================================================== */

.card {
  position: relative;
  background-color: var(--card);
  background-image: linear-gradient(160deg, var(--panel), var(--card));
  border: 1px solid var(--ring);
  border-radius: var(--radius-lg);
  box-shadow: 0 12px 30px -20px var(--ring);
}

/* Card heading: the serif display family comes from base.css; here it gets a
   brand-tinted bottom rule and rhythm below it, echoing the original look. */
.card > h2 {
  margin-bottom: var(--space-3);
  padding-bottom: var(--space-1);
  border-bottom: 2px solid var(--ring);
}

/* ==========================================================================
   2. Generic list wrapper
   --------------------------------------------------------------------------
   Publications, projects and teaching all wrap their entries in `.list`.
   Stack entries in a column with consistent spacing.
   ========================================================================== */

.list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* Cap the publications list height so a long list scrolls internally for
   readability, instead of stretching the page (mirrors the news timeline). */
#pub-list {
  max-height: min(600px, 80vh);
  overflow-y: auto;
  overflow-x: hidden;
  padding-right: var(--space-1);      /* clearance so cards clear the scrollbar */
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;              /* Firefox */
  scrollbar-color: var(--brand) transparent;
}

#pub-list::-webkit-scrollbar { width: 6px; }
#pub-list::-webkit-scrollbar-track { background: transparent; }
#pub-list::-webkit-scrollbar-thumb {
  background: var(--brand);
  border-radius: var(--radius-pill);
}

/* ==========================================================================
   3. Publication / entry cards (.pub) + filter fade + hover lift
   --------------------------------------------------------------------------
   `.pub` is shared by the publications, projects and teaching sections. It is
   a subtle inner card: a soft border, a faintly recessed background and a
   comfortable padding. The publications filter (publications.js) fades a card's
   inline opacity to 0 and then adds `.hidden` to remove it from layout, so the
   opacity transition below drives the fade (Req 5.3) and `.hidden` finalizes it.
   ========================================================================== */

.pub {
  padding: var(--space-2);
  border: 1px solid var(--ring);
  border-radius: var(--radius-md);
  background-color: color-mix(in srgb, var(--card) 55%, transparent);
  /* One transition list covers BOTH the filter fade (opacity) and the hover
     lift (transform, box-shadow, border-color). 300ms sits inside the 200–400ms
     hover window (Req 5.1) and completes the fade within the <=400ms filter
     budget (Req 5.3), ahead of the 400ms layout-removal timeout in the JS. */
  transition:
    opacity var(--transition-med) var(--ease-out),
    transform var(--transition-med) var(--ease-out),
    box-shadow var(--transition-med) var(--ease-out),
    border-color var(--transition-med) var(--ease-out);
  will-change: opacity, transform;
}

/* Coordinated with publications.js: after the opacity fade completes the card
   is removed from layout entirely (Req 5.3). */
.pub.hidden {
  display: none;
}

.pub .title {
  font-size: 1.05rem;
  line-height: 1.4;
}

.pub .meta {
  margin-top: var(--space-1);
  font-size: 0.95rem;
  line-height: 1.55;
}

.pub .meta.muted {
  color: var(--muted);
}

/* --- Hover lift (Req 5.1) --------------------------------------------------
   Elevated shadow + border-color shift toward --brand + a <=1.02 scale, all
   eased over 300ms (within the required 200–400ms). Transform + shadow +
   border only, so it stays cheap and never reflows. Gated on a hover-capable,
   fine pointer so touch devices don't get a stuck hover state (Req 10.5). */
@media (hover: hover) and (pointer: fine) {
  .pub:hover {
    transform: scale(1.02);
    border-color: var(--brand);
    box-shadow: 0 18px 38px -18px var(--ring);
  }
}

/* ==========================================================================
   4. Filters (Req 5.2)
   --------------------------------------------------------------------------
   Pill-shaped category buttons. Inactive: transparent fill with a muted
   border and muted text. Active (`.active`, set by publications.js with
   aria-pressed=true): the --brand fill paired with --on-brand text. Only
   color + background-color (and the decorative border-color) transition, over
   200ms — inside the required 150–300ms window.
   ========================================================================== */

.filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin-bottom: var(--space-2);
}

.filter-btn {
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--ring);
  border-radius: var(--radius-pill);
  background-color: transparent;
  color: var(--muted);
  font-size: 0.9rem;
  font-weight: 600;
  line-height: 1.2;
  transition:
    color var(--transition-fast) var(--ease-out),
    background-color var(--transition-fast) var(--ease-out),
    border-color var(--transition-fast) var(--ease-out);
}

/* Hover (fine pointer only): hint the brand without committing to the active
   fill — brighten the text and border toward --brand. */
@media (hover: hover) and (pointer: fine) {
  .filter-btn:hover {
    color: var(--brand);
    border-color: var(--brand);
  }
}

/* Active state (Req 5.2): brand background + on-brand text. */
.filter-btn.active {
  background-color: var(--brand);
  border-color: var(--brand);
  color: var(--on-brand);
}

/* Keep the active button legible on hover (don't fall back to the inactive
   hover treatment above). */
@media (hover: hover) and (pointer: fine) {
  .filter-btn.active:hover {
    color: var(--on-brand);
  }
}

/* ==========================================================================
   5. Filter count (Req 5.4 support)
   --------------------------------------------------------------------------
   The live "Showing N of M" line published into #pub-count by publications.js.
   ========================================================================== */

.filter-count {
  margin-bottom: var(--space-2);
  color: var(--muted);
  font-size: 0.9rem;
}

/* ==========================================================================
   6. Tags (base pill only)
   --------------------------------------------------------------------------
   The base pill treatment for `.tag` (used in the hero, and reusable
   elsewhere). NOTE: the tag HOVER micro-interaction (1.05 scale + --brand
   background, 300ms) is intentionally OWNED BY TASK 12.2 and is deliberately
   NOT defined here to avoid overlap — this section provides resting styling
   only.
   ========================================================================== */

.tag {
  display: inline-block;
  padding: 4px var(--space-2);
  border: 1px solid var(--ring);
  border-radius: var(--radius-pill);
  background-color: color-mix(in srgb, var(--brand) 12%, transparent);
  color: var(--text);
  font-size: 0.85rem;
  font-weight: 600;
  line-height: 1.4;
  white-space: nowrap;
}

/* ==========================================================================
   7. Empty-state message (Req 5.5)
   --------------------------------------------------------------------------
   Shown by publications.js in place of the list when a filter matches nothing
   (#pub-empty). Muted + italic so it reads as a soft placeholder. The `hidden`
   attribute (toggled by the JS) must win over the display below.
   ========================================================================== */

.empty-state {
  color: var(--muted);
  font-style: italic;
}

.empty-state[hidden] {
  display: none;
}

/* ==========================================================================
   8. Reduced motion (Req 3.4 / motion-safety)
   --------------------------------------------------------------------------
   Zero the interaction transitions/transforms authored in this file. The
   filter still functions (opacity/display still change) and the active/inactive
   colors still swap — they just apply instantly, with no animated lift, no
   scale, and no eased color fade.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .pub,
  .filter-btn {
    transition: none;
  }

  .pub:hover {
    transform: none;
    box-shadow: 0 12px 30px -20px var(--ring);
  }
}

/* ==========================================================================
   9. >>> TASK 12.2 MICRO-INTERACTION INSERTION POINT <<<
   --------------------------------------------------------------------------
   Task 12.2 appends the following micro-interaction styles BELOW this marker
   (sequentially, not concurrently with this task):
     - social link hover (3px lift + platform brand color, 300ms)  — Req 8.1
     - tag hover (1.05 scale + --brand background, 300ms)           — Req 8.2
     - external-link arrow fade-in (300ms)                          — Req 8.4
     - reduced-motion / coarse-pointer instant-state overrides      — Req 8.5, 10.5
   Leave this section for that task; do not add those rules here.
   ========================================================================== */
/* ==========================================================================
   TASK 12.2 — Micro-interactions (appended below section 9 marker)
   --------------------------------------------------------------------------
   Owns the interaction polish deferred from earlier sections:
     9a. Social link hover — 3px lift + platform brand color, 300ms  (Req 8.1)
     9b. Tag hover — 1.05 scale + --brand fill + on-brand text, 300ms (Req 8.2)
     9c. External-link arrow — ↗ fades in adjacent to link, 300ms      (Req 8.4)
     9d. Cursor glow enhancement for .cursor-glow (js/cursor.js)       (Req 8.3)
     9e. Reduced-motion + coarse-pointer instant/always-visible states (Req 8.5, 10.5)

   All hover effects are gated on `(hover: hover) and (pointer: fine)` so touch
   devices never get a stuck hover state; the coarse-pointer block below then
   forces affected content into an always-visible resting state (Req 10.5), and
   the reduced-motion block collapses every animated change to an instant one
   (Req 8.5). Durations use --transition-med (300ms) with --ease-out.
   ========================================================================== */

/* --- 9a. Social link hover (Req 8.1) --------------------------------------
   `.links a` are the CV + social/profile anchors in the hero. Each carries a
   platform class (.cv/.scholar/.orcid/.github/.linkedin) and an inline SVG that
   inherits `currentColor`, so transitioning the anchor's color tints the icon
   and its label together. The anchor lifts 3px and its color eases to that
   platform's brand hue over 300ms. */
.links a {
  transition:
    transform var(--transition-med) var(--ease-out),
    color var(--transition-med) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .links a:hover,
  .links a:focus-visible {
    transform: translateY(-3px);
  }

  .links a.cv:hover,
  .links a.cv:focus-visible {
    color: #d32f2f; /* CV — red */
  }
  .links a.scholar:hover,
  .links a.scholar:focus-visible {
    color: #4285f4; /* Google Scholar — blue */
  }
  .links a.orcid:hover,
  .links a.orcid:focus-visible {
    color: #a6ce39; /* ORCID — green */
  }
  .links a.github:hover,
  .links a.github:focus-visible {
    color: #8e8e8e; /* GitHub — gray */
  }
  .links a.linkedin:hover,
  .links a.linkedin:focus-visible {
    color: #0077b5; /* LinkedIn — blue */
  }
}

/* --- 9b. Tag hover (Req 8.2) ----------------------------------------------
   The resting `.tag` pill is styled in section 6; this owns the hover only.
   On hover the pill scales to 1.05 and flips to the --brand fill with
   --on-brand text (the same AA-safe token pair used by the active filter),
   over 300ms. */
.tag {
  transition:
    transform var(--transition-med) var(--ease-out),
    background-color var(--transition-med) var(--ease-out),
    border-color var(--transition-med) var(--ease-out),
    color var(--transition-med) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .tag:hover {
    transform: scale(1.05);
    background-color: var(--brand);
    border-color: var(--brand);
    color: var(--on-brand);
  }
}

/* --- 9c. External-link arrow (Req 8.4) -------------------------------------
   Any link navigating away from the domain (href starting with http) gets a ↗
   glyph appended via ::after. The glyph reserves its space at rest (opacity 0)
   so revealing it never reflows surrounding text — it simply fades (and nudges)
   into view over 300ms on hover/focus. The social-icon anchors (.links a) and
   the primary nav (.nav-links a) opt OUT so their icon rows stay clean. */
a[href^="http"]::after {
  content: "\2197"; /* ↗ north-east arrow */
  display: inline-block;
  margin-left: 0.15em;
  font-size: 0.85em;
  line-height: 1;
  opacity: 0;
  transform: translate(-2px, 2px);
  transition:
    opacity var(--transition-med) var(--ease-out),
    transform var(--transition-med) var(--ease-out);
}

/* Opt the icon-only social links and the primary nav out of the arrow. */
.links a[href^="http"]::after,
.nav-links a[href^="http"]::after {
  content: none;
}

@media (hover: hover) and (pointer: fine) {
  a[href^="http"]:hover::after,
  a[href^="http"]:focus-visible::after {
    opacity: 1;
    transform: translate(0, 0);
  }
}

/* --- 9d. Cursor glow enhancement (Req 8.3) --------------------------------
   js/cursor.js creates `.cursor-glow` and sets its size, position, radial
   background and opacity as inline styles (so it works with no CSS). These
   rules layer on top with properties the JS does not set — a soft blur and a
   `screen` blend so the glow reads as light over content rather than a solid
   dot. It is `pointer-events: none` (set by the JS) so it never obscures
   clickable content or text. */
.cursor-glow {
  mix-blend-mode: screen;
  filter: blur(2px);
}

/* --- 9e. Reduced motion (Req 8.5) -----------------------------------------
   Collapse every micro-interaction authored above to an instant state change:
   zero-duration transitions, no lift, no scale, no arrow nudge. Hover still
   swaps colors/fills — just instantly. The cursor glow is removed entirely
   (cursor.js also declines to attach under reduced motion; this is a belt-and-
   braces guard). */
@media (prefers-reduced-motion: reduce) {
  .links a,
  .tag,
  a[href^="http"]::after {
    transition-duration: 0s;
  }

  .links a:hover,
  .links a:focus-visible,
  .tag:hover {
    transform: none;
  }

  a[href^="http"]::after {
    transform: none;
  }

  .cursor-glow {
    display: none;
  }
}

/* --- 9e. Coarse pointer / no hover (Req 10.5) -----------------------------
   Touch devices report `(hover: none) and (pointer: coarse)`. Hover animations
   can't be triggered (and would stick), so disable them and present affected
   content in an always-visible resting state: the external-link arrow is shown
   permanently, and the cursor glow is removed. */
@media (hover: none) and (pointer: coarse) {
  .links a,
  .tag {
    transition: none;
  }

  a[href^="http"]::after {
    opacity: 1;
    transform: none;
  }

  .cursor-glow {
    display: none;
  }
}
