/* Hugin — lantern companion.
 *
 * A small hanging lantern fixed to the bottom-right corner. The user
 * clicks it to "light" their lantern; the flame catches, a soft ember
 * aura spreads across the canvas, and the lantern follows them as
 * they scroll. Companion light, not chrome.
 *
 * State is persisted in localStorage so the lantern stays lit between
 * sessions. The aura uses mix-blend-mode: screen so it adds warmth
 * without occluding text — a real lantern lights the page, it does
 * not paint over it.
 */

.lantern {
  position: fixed;
  right: var(--s-5);
  bottom: var(--s-5);
  width: 44px;
  height: 64px;
  padding: 0;
  border: 0;
  background: transparent;
  /* grab reads as "you can hold and move this". Switches to grabbing
     while a drag is active. Tap toggles lit/dark, drag relocates. */
  cursor: grab;
  /* touch-action: none lets pointermove deliver touch events without
     the browser stealing the gesture for scroll. */
  touch-action: none;
  z-index: 60;
  color: var(--ink-dim);
  opacity: 0.55;
  /* Ignition crescendo — color and opacity rise slowly, filter (the
     drop-shadow halo) blooms over ~900ms so the lantern's outer glow
     swells in instead of snapping. Transform stays snappy for hover
     lift. */
  transition:
    color 700ms ease-out,
    opacity 700ms ease-out,
    filter 900ms ease-out 80ms,
    transform 240ms ease;
}
.lantern.lantern--dragging {
  cursor: grabbing;
  /* While dragging, suspend the lift-on-hover so the lantern stays
     anchored to the pointer instead of bobbing. */
  transition: none;
}
.lantern:hover {
  opacity: 0.9;
  color: var(--ink-mute);
  transform: translateY(-1px);
}
.lantern.lantern--dragging:hover { transform: none; }
.lantern:focus-visible {
  outline: 1px solid var(--warm-line);
  outline-offset: 6px;
  border-radius: var(--r-sm);
}

.lantern[data-state="lit"] {
  opacity: 1;
  color: var(--warm);
  /* Static drop-shadow glow at full strength. The previous version
     animated the blur radius via `lantern-glow-breath`, but animating
     filter blur forces the shadow to re-rasterize every frame — one
     of the heaviest CSS animation costs. The "alive" signal comes from
     the four flame sub-animations inside the icon (halo, sway, breath,
     flicker, base pulse). The body's halo holding steady reads as the
     lantern's outer glass, not the flame itself. */
  filter: drop-shadow(0 0 7px var(--warm-glow)) drop-shadow(0 0 18px var(--warm-soft));
}

.lantern-icon {
  width: 100%;
  height: 100%;
  display: block;
}

/* Flame is built from four overlapping SVG layers (halo, outer body,
   inner core, blue base point). Each layer breathes/sways on its own
   non-harmonic cycle so the combined motion never visibly repeats —
   that's the difference between a flame and a wobbling icon.
   Each layer is anchored at its own bottom (the wick) via fill-box
   so the tip moves more than the base, as a real flame does. */
.lantern-flame {
  opacity: 0;
  /* Ignition crescendo — the whole flame group grows from a small
     ember at the wick (scale 0.25) to its full life. Cubic-bezier
     gives a slow rise that settles softly into place, with the
     transform anchored at the wick (bottom-center of the group's own
     bounding box) so the flame climbs upward rather than expanding
     from its centre. */
  transform: scale(0.25);
  transform-box: fill-box;
  transform-origin: 50% 100%;
  transition:
    opacity 850ms ease-out 100ms,
    transform 1050ms cubic-bezier(0.16, 0.84, 0.44, 1) 100ms;
}
.lantern[data-state="lit"] .lantern-flame {
  opacity: 1;
  transform: scale(1);
}

.flame-halo,
.flame-outer,
.flame-inner,
.flame-base {
  transform-box: fill-box;
  transform-origin: 50% 100%;
  will-change: transform, opacity;
}

/* All flame sub-animations are delayed past the ignition crescendo
   (~1s) so the flame catches and grows clean, then comes alive with
   its sways, flickers and pulses. Without the delay the cycles would
   run during the grow-in and the flame would visibly twitch before
   reaching its full size. */
.lantern[data-state="lit"] .flame-halo {
  animation: flame-halo 3.7s ease-in-out 1000ms infinite;
}
.lantern[data-state="lit"] .flame-outer {
  animation:
    flame-outer-sway 2.3s ease-in-out 1000ms infinite,
    flame-outer-breath 3.1s ease-in-out 1000ms infinite;
}
.lantern[data-state="lit"] .flame-inner {
  animation:
    flame-inner-sway 1.9s ease-in-out 1000ms infinite,
    flame-inner-flicker 0.46s steps(1, end) 1000ms infinite;
}
.lantern[data-state="lit"] .flame-base {
  animation: flame-base-pulse 0.74s ease-in-out 1000ms infinite;
}

@keyframes flame-halo {
  0%, 100% { opacity: 0.65; transform: scale(1); }
  45%      { opacity: 1;    transform: scale(1.14); }
}
@keyframes flame-outer-sway {
  0%   { transform: rotate(-1.6deg) scaleY(1); }
  35%  { transform: rotate(2deg)    scaleY(1.05); }
  62%  { transform: rotate(-0.7deg) scaleY(0.96); }
  100% { transform: rotate(-1.6deg) scaleY(1); }
}
@keyframes flame-outer-breath {
  0%, 100% { opacity: 0.95; }
  30%      { opacity: 0.74; }
  60%      { opacity: 1; }
}
@keyframes flame-inner-sway {
  0%   { transform: rotate(2.2deg)  scaleY(0.94); }
  42%  { transform: rotate(-1.8deg) scaleY(1.08); }
  100% { transform: rotate(2.2deg)  scaleY(0.94); }
}
@keyframes flame-inner-flicker {
  0%, 100% { opacity: 0.88; }
  50%      { opacity: 0.7; }
}
@keyframes flame-base-pulse {
  0%, 100% { opacity: 0.4; }
  50%      { opacity: 0.7; }
}

/* Companion aura — fills the viewport with a warm radial centred on the
   lantern. Screen blend mode so it lifts brightness without painting over
   text. Pointer-events none. Fades in/out with the lantern state. */
.lantern-aura {
  position: fixed;
  /* Sized element centered on the lantern instead of inset:0 across
     the whole viewport. mix-blend-mode (applied below in the [lit]
     selector) composites the full element's bounds every time anything
     inside changes — the animated flame above the aura was forcing a
     full-viewport recomposite per frame. A 1100×1100 box contains the
     gradient extent (which fades out by ~50% radius) at a fraction of
     the pixel area, so the compositor work drops with it. */
  width: 1100px;
  height: 1100px;
  left: var(--lantern-cx, calc(100% - 42px));
  top: var(--lantern-cy, calc(100% - 62px));
  transform: translate(-50%, -50%);
  pointer-events: none;
  z-index: 1;
  background: radial-gradient(
    circle at center,
    rgba(196, 91, 46, 0.22) 0%,
    rgba(196, 91, 46, 0.10) 28%,
    transparent 50%
  );
  opacity: 0;
  /* Aura swells in slower than the flame and with a small head-start
     delay so the room appears to warm just after the wick catches. */
  transition: opacity 1100ms ease-out 200ms;
  will-change: opacity;
}
/* `mix-blend-mode: screen` is expensive — it forces the compositor to
   re-blend the full viewport every frame. Apply it ONLY while the
   lantern is lit so the dark state pays zero compositing cost.

   The aura is intentionally STATIC once lit: any animated property on
   a screen-blended full-viewport element keeps the compositor busy
   every frame, which dropped lit-state framerate to ~42fps. The
   "alive" signal lives in the lantern's own filter breath and the
   sub-flame layers inside the icon — the aura just holds the room
   warm. */
body[data-lantern="lit"] .lantern-aura {
  opacity: 0.85;
  mix-blend-mode: screen;
}

@media (prefers-reduced-motion: reduce) {
  .lantern[data-state="lit"] .flame-halo,
  .lantern[data-state="lit"] .flame-outer,
  .lantern[data-state="lit"] .flame-inner,
  .lantern[data-state="lit"] .flame-base,
  .lantern[data-state="lit"],
  body[data-lantern="lit"] .lantern-aura { animation: none; }
  .lantern,
  .lantern-flame,
  .lantern-aura { transition: none; }
  /* Ignition crescendo collapses to an instant switch — the flame's
     scale transform is removed so the lit flame is shown at full size
     without the grow-in. */
  .lantern[data-state="lit"] .lantern-flame { transform: scale(1); }
}
@media (prefers-reduced-transparency: reduce) {
  .lantern-aura { display: none; }
  .lantern[data-state="lit"] { filter: none; }
}

/* On narrow viewports, dock just above the bottom edge but smaller, and
   respect the iOS home-bar safe area so the lantern doesn't sit on top
   of the gesture handle. Aura intensity is reduced because dim mobile
   displays don't need as much warm wash, and it saves a bit of paint. */
@media (max-width: 640px) {
  .lantern {
    width: 36px;
    height: 52px;
    right: max(var(--s-4), env(safe-area-inset-right));
    bottom: max(var(--s-4), env(safe-area-inset-bottom));
  }
  .lantern-aura {
    /* Smaller aura box on mobile — saves compositor work on low-power
       devices, matches the smaller lantern (36×52). */
    width: 720px;
    height: 720px;
  }
  body[data-lantern="lit"] .lantern-aura {
    /* Mobile aura is dimmer to save paint on lower-power devices. */
    opacity: 0.62;
  }
}
