/* ------------------------------------------------------------------
   mobile.css — narrow-viewport overrides. Loaded last so it wins.
   Desktop: two columns, click arrows, text swaps beside the media.
   Mobile:  stacked, swipe the media, text sits underneath.
   ------------------------------------------------------------------ */

@media (max-width: 860px) {

  :root { --header-h: 56px; }

  .site-header__inner { gap: 0.75rem; }

  .site-header__name {
    font-size: 0.95rem;
    white-space: nowrap;
  }

  .site-header__brand { gap: 0.5rem; }

  /* 560–860px the header is still a single row, and there the "Light"/"Dark"
     text is the first thing to go when space runs short — same as the nav
     labels shrinking below. The 560px block re-instates them, because once
     the nav drops to its own row the brand row has width to spare. */
  .theme-switch__label { display: none; }

  .site-nav { gap: 0.85rem; }

  .site-nav a {
    font-size: 0.7rem;
    letter-spacing: 0.02em;
    white-space: nowrap;
  }

  .hero__tagline { max-width: none; }

  /* --- stacked card ---
     Order on mobile is title → media → slide text. The title lives inside
     .card__text in the markup (that's what makes the desktop two-column
     layout work), so the wrapper is dissolved with display: contents and its
     two children become direct grid items that can be ordered around the
     media pane. Padding moves onto the children, since a contents box
     paints nothing itself. */

  .card {
    grid-template-columns: 1fr;
    border-radius: var(--radius-sm);
  }

  .card__text { display: contents; }

  .card__title {
    order: 1;
    font-size: calc(var(--step-1) * 1.05);
    padding: 1.25rem 1.15rem 0.9rem;
    margin: 0;
    border-bottom: 0;
  }

  /* Edge-to-edge, but rounded on all four corners so the media reads as its
     own closed block sitting between the title and the slide text, rather
     than a band running into the card's edges. */
  .card__media  {
    order: 2;
    min-height: 0;
    max-height: none;
    aspect-ratio: 4 / 3;
    margin: 0;
    border-radius: var(--radius-sm);
  }

  /* Bottom padding is 0.15rem short of the title's 1.25rem top padding on
     purpose: the credits run at line-height 1.4 against the title's 1.2, so
     that much empty leading already sits below the last baseline. Trimming
     it makes the two gaps read as equal even though the numbers differ. */
  .card__slide-text {
    order: 3;
    min-height: 0;
    padding: 1.25rem 1.15rem 1.1rem;
    margin-top: 0;
  }

  /* the credits list is a <dl>, which carries a 1em bottom margin from the
     UA stylesheet — that was adding an invisible extra line of space below
     "Analysis:" and throwing the two gaps out of balance */
  .card__slide-credits { margin-bottom: 0; }

  /* --- swipeable strip instead of stacked absolute slides --- */

  .card__slides {
    position: static;
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    /* one slide fills the frame — no peek of the neighbouring slide */
    padding-right: 0;
  }

  .card__slides::-webkit-scrollbar { display: none; }

  .card__slide {
    /* relative, not static: the slide has to be the containing block for
       its own loading wheel, or every slide in the strip would centre its
       wheel on the media pane instead of on itself. Relative changes
       nothing about the flex layout. */
    position: relative;
    flex: 0 0 100%;
    opacity: 1;
    visibility: visible;
    scroll-snap-align: start;
    /* without a visible peek there's no partial slide to brake against, so
       pin each swipe to exactly one slide */
    scroll-snap-stop: always;
    transition: none;
  }

  /* arrows are for pointers; on touch the dots + peek carry the message */
  .card__arrow { display: none; }

  .card__controls {
    padding: 0.75rem;
    gap: 0.5rem;
  }

  .card__dot { width: 8px; height: 8px; }

  .card__hint { top: 0.65rem; right: 0.65rem; font-size: 0.94rem; }
  .hint--pointer { display: none; }
  .hint--touch   { display: inline; }

  /* --- other pages --- */

  /* two publication cards per row instead of one. The 280px minimum in the
     desktop auto-fill grid would force a single column at phone widths, so
     the track count is fixed here and the card shrunk to suit. */
  .pub-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.85rem;
  }

  /* 33 / 67 here rather than desktop's 40 / 60. A half-width card needs a
     tall text body for a four-line title and four-line author list, and at
     40% the image frame came out nearly square (1.04:1) against figures
     that average 1.94:1 — so every one of them kept a bar. Giving the text
     two thirds lets the card be shorter overall, which makes the frame
     proportionally wider and much closer to the figures' own shape. */
  .pub-card { height: 350px; }

  .pub-card__link { grid-template-rows: 1fr 2fr; }

  .pub-card__body { padding: 0.7rem 0.75rem 0.85rem; }

  /* clamps are tighter than desktop's: at half width every line holds far
     fewer words, and the tag row + journal line still have to fit under
     the title without the body clipping. */
  .pub-card__title {
    font-size: 0.8rem;
    line-height: 1.3;
    -webkit-line-clamp: 4;
  }

  .pub-card__authors {
    font-size: 0.7rem;
    line-height: 1.35;
    -webkit-line-clamp: 4;
  }

  .pub-card__meta { font-size: 0.7rem; }

  .pub-card__tag {
    font-size: 0.6rem;
    padding: 0.1rem 0.45rem;
  }

  /* On a pointer the button announces itself on hover. Touch has no hover,
     so it sat permanently in its resting grey and read as decoration —
     wear the accent state all the time instead. */
  .pub-more {
    color: var(--accent);
    border-color: var(--accent);
    background: var(--accent-dim);
  }

  /* tighten the run-up to the full list so the button reads as belonging to
     the grid above it rather than floating between the two sections.
     3.5rem -> 2.625rem (a quarter off) -> 2.1rem (a further fifth). */
  .cv-pubs { margin-top: 2.1rem; }

  /* The run-out to the footer rule was 144px on phones — 80px of page
     padding plus 64px of footer margin, both sized for a wide screen. It
     now matches the gap above the full list (2.1rem, see .cv-pubs), so the
     two buttons sit the same distance from the rule beneath them.

     Kept as a single number by zeroing the footer margin: one value to tune
     rather than two that have to be added together. */
  .page { padding-bottom: 2.1rem; }

  .site-footer { margin-top: 0; }

  /* Bring the footer down to roughly the header's height. Most of the old
     197px was padding sized for desktop; the rest was the links wrapping to
     a second line, so the type and gaps are tightened just enough to keep
     all four on one row. */
  .site-footer {
    padding: 1.15rem 0;
    font-size: 0.78rem;
  }

  .site-footer .wrap { gap: 0.55rem; }

  .site-footer__links { gap: 0.8rem; }

  /* --- contact ---
     Desktop puts the label and the URL on one line, label left, URL right.
     At phone width the URL can't fit beside the label, and because it is
     nowrap it was widening the row past the viewport and giving the whole
     page a horizontal scroll. Stacking the two solves the overflow and
     leaves the URL readable instead of ellipsised down to nothing. */

  /* full width again: the desktop rule insets the rows by 10% a side to
     stop them stretching, but a phone column has no width to give away */
  .contact-links {
    gap: 0.6rem;
    width: 100%;
  }

  .contact-links a {
    flex-direction: column;
    align-items: stretch;
    gap: 0.2rem;
    padding: 0.85rem 1rem 0.95rem;
    min-height: 3.25rem;
  }

  .contact-links .value {
    text-align: left;
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    /* long slugs (LinkedIn) break mid-token rather than overflowing */
    overflow-wrap: anywhere;
    font-size: 0.78rem;
    line-height: 1.4;
    /* line the URL up under the label text, past the icon */
    padding-left: 1.65rem;
  }

  /* The stacked row would push the copy control onto a third line of its
     own. Float it against the right edge instead, vertically centred over
     both lines, with room reserved on the address so the two never meet. */
  .contact-links__copy { position: relative; }

  .contact-links__copy .value { padding-right: 5.25rem; }

  .contact-links__copy .copy-affordance {
    position: absolute;
    right: 1rem;
    top: 50%;
    transform: translateY(-50%);
    /* no hover on touch, so it wears the accent permanently — same reason
       .pub-more does above */
    color: var(--accent);
  }
}

/* very narrow phones */
@media (max-width: 420px) {
  .wrap { width: min(100% - 1.5rem, var(--wrap)); }
  .card__media { aspect-ratio: 1 / 1; }

  /* four footer links still wrap to a second line below ~360px, which puts
     the footer back over the header's height — a little tighter keeps them
     on one row down to 320 */
  .site-footer { font-size: 0.72rem; }
  .site-footer__links { gap: 0.6rem; }
  .site-footer__links a { gap: 0.28rem; }
}

/* ------------------------------------------------------------------
   Phone header: two rows.

   On one row the brand, the theme switch and three nav links need about
   367px, but the page keeps a 20px margin each side — so below roughly
   385px the nav ran off the right edge (4px over at 375, 19px at 360,
   59px at 320). Shrinking the type to fit had already pushed the links
   to ~10px tall, well under the ~44px a fingertip needs.

   Giving the nav its own row fixes both: nothing clips, the labels go
   back to a readable size, and each link becomes a 45px target spanning
   a third of the screen. Costs 32px of sticky header.
   ------------------------------------------------------------------ */
@media (max-width: 560px) {

  :root { --header-h: 88px; }

  .site-header__inner {
    flex-direction: column;
    justify-content: center;
    gap: 0.15rem;
    padding: 0.3rem 0;
  }

  .site-header__brand {
    width: 100%;
    justify-content: space-between;
  }

  .site-header__name { font-size: 1.02rem; }

  /* Stacked header: the nav has moved to its own row, so the brand row is
     just the name and the switch with a wide gap between them. Bring the
     "Light"/"Dark" text back — a bare toggle gives no clue which end is
     which, and there is room for it here. Smaller than desktop's 0.68rem
     so it reads as a caption on the switch rather than a third nav item. */
  .theme-switch__label {
    display: inline;
    font-size: 0.58rem;
  }

  .theme-switch { gap: 0.35rem; }

  /* a slightly larger track, plus an invisible cushion around the button.
     The cushion is deliberately lopsided: extending it downward would hang
     over the nav row and swallow taps meant for CONTACT, and extending it
     right would poke past the page margin. So it grows up and left. */
  .theme-toggle {
    position: relative;
    --track-w: 44px;
    --track-h: 24px;
  }

  .theme-toggle::after {
    content: "";
    position: absolute;
    inset: -10px 0 -4px -12px;
  }

  .site-nav {
    width: 100%;
    gap: 0;
    justify-content: space-between;
  }

  .site-nav a {
    flex: 1 1 0;
    text-align: center;
    font-size: 0.78rem;
    padding: 0.7rem 0.2rem;
  }
}

/* coarse pointers of any width: never rely on hover. The hint's own pulse
   count is the same everywhere — three oscillations, then it sits still. */
