/* ============================================================
   SCROLL VINE
   Decorative growing-vine ornament in the right margin of article
   pages. Purely decorative - laptop/desktop only, never intercepts
   clicks or text selection.

   Technique: two stacked copies of the same PNG (see scroll-vine.js).
   The lower copy ("ghost") sits at low opacity and is always fully
   visible, so the vine's full shape reads immediately. The upper
   copy ("growth") is bright, and JS reveals it top-to-bottom with a
   soft gradient mask as the reader scrolls through the article -
   the mask feathers the growing edge so it reads as growth rather
   than a hard wipe cutting across the artwork.
   ============================================================ */

:root {
  /* Top edge of the vine - flush against the sticky header's own
     measured height, so the vine touches the bar instead of leaving
     a gap under it. */
  --vine-top-offset: 83px;
  /* Distance from the right edge of the viewport. Scales with
     viewport width between a 24px floor and a 96px ceiling. */
  --vine-right: clamp(24px, 5vw, 96px);
  /* Height always fills exactly from the header to the bottom of
     whatever screen it's on. */
  --vine-available-height: calc(100dvh - var(--vine-top-offset));
  /* vine_true_transparent_trimmed.png (see scripts/clean_vine_transparency.py)
     is 212x1307px - a fixed, tall-and-narrow aspect ratio (~0.1622).
     Width is DERIVED from the available height using that exact ratio,
     not set independently, so the whole vine always fits the box with
     no cropping and no empty space, on any screen. A fixed width (the
     original approach) only worked by coincidence at some viewport
     heights and cropped the vine's tail off at others - deriving it
     removes that guesswork entirely.
     clamp() keeps this derived width inside the same safe range as
     before: never so wide it risks the article's right-hand gutter
     (see main.css .article max-width math - the gutter only gets
     wider above 1320px), and never so narrow it disappears on short
     windows. When the derived value hits either edge of the clamp,
     the height-driven fit is no longer exact, so object-fit: contain
     (below) takes over and simply leaves a small gap rather than
     cropping - the vine is never cut off, worst case it's just not
     perfectly flush top-and-bottom in that edge case. */
  --vine-width: clamp(115px, calc(var(--vine-available-height) * 0.1622), 220px);
}

.scroll-vine {
  position: fixed;
  top: var(--vine-top-offset);
  right: var(--vine-right);
  width: var(--vine-width);
  height: var(--vine-available-height);
  pointer-events: none;
  z-index: 1;
  overflow: hidden;
  /* Hidden until the reader has scrolled past the header photo/banner
     (see the reveal logic in scroll-vine.js) - otherwise the vine can
     show through in the space below a hero image shorter than the
     viewport, or on top of the plain text header on non-hero articles,
     before the reader has scrolled at all. */
  opacity: 0;
  transition: opacity 0.5s ease;
}
.scroll-vine.is-revealed {
  opacity: 1;
}

/* Scrolled past the article body, right before the "Written by" /
   endmark section (see #article-colophon in article.njk and the freeze
   logic in scroll-vine.js) - the vine stops following the viewport and
   is left behind at a fixed document position instead, so it never
   scrolls down over that section. */
.scroll-vine.is-scroll-frozen {
  position: absolute;
}

.scroll-vine__image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* contain, not cover: --vine-width is already derived from the
     available height using the image's own aspect ratio (see above),
     so in the common case this fits exactly with no letterboxing at
     all. contain is what guarantees the vine is never cropped even in
     the rare edge case where the clamp() above caps the width. */
  object-fit: contain;
  object-position: center top;
  user-select: none;
}

/* Full vine, always faintly present. The site's background is near-black,
   so the ghost needs more opacity than a lighter page would to actually
   read at all - 0.11 (a common default for this technique) was invisible
   here in testing. No darkening filter either, since the gold linework
   is already dim against black. */
.scroll-vine__ghost {
  opacity: 0.25;
}

/* Bright vine. Its visible portion is set entirely by JS via
   mask-image (a soft top-to-bottom gradient), not by a hard clip. */
.scroll-vine__growth {
  opacity: 1;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  will-change: -webkit-mask-image, mask-image;
}

/* Laptop/desktop only. Reuses the breakpoint this site already uses
   to swap the table-of-contents sidebar between its fixed desktop
   position and the mobile accordion (see .article-toc in main.css),
   so "laptop and up" stays consistent across the page. */
@media (max-width: 1320px) {
  .scroll-vine {
    display: none;
  }
}

/* Readers who prefer reduced motion get the full vine, fully
   revealed, with no scroll-driven animation (see scroll-vine.js). The
   show/hide past the header photo still happens (it's not an animation
   effect, just visibility), but instantly rather than fading. */
@media (prefers-reduced-motion: reduce) {
  .scroll-vine__growth {
    -webkit-mask-image: none;
    mask-image: none;
  }
  .scroll-vine {
    transition: none;
  }
}
