/* Saltado Sessions: the rating flame, lifted VERBATIM from client/src/styles.css (the .stars /
   .star / .flame rules and every flame-* keyframe) on 2026-09-14 so the homepage can show the
   five-rank flame flickering at the app's own speed. Do not edit here: re-copy from the app
   when the app's flame changes. The site-only overrides sit at the foot of this file. */

.stars {
  display: inline-flex;
  gap: 1px;
  align-items: flex-end;
}

.star {
  background: none;
  border: none;
  color: var(--border);
  font-size: 15px;
  cursor: pointer;
  padding: 0 1px;
  line-height: 1;
  transition: color 0.1s, transform 0.08s;
  /* flames stand on the baseline, so hover growth should rise, not balloon */
  transform-origin: bottom center;
}

.star svg {
  display: block;
}

/* Filled flames flicker, and the motion RAMPS with the step (--fl-i, set
   inline by StarRating): the ember barely breathes, the big flame dances.
   Amplitude, speed and phase all derive from the one var, and the negative
   delays de-phase neighbours, because flames wagging in sync read as a
   metronome, not a fire.

   The THREE LAYERS (body silhouette, inner tongue, hot core) each run their
   own animation on their own clock, the tongue counter-phased against the
   body: one path swaying as a rigid sticker was the second "looks cheap"
   verdict. transform-box: fill-box is load-bearing, or every layer would
   pivot around the svg viewport corner instead of its own base. Transform +
   opacity only, so it stays on the compositor even with a table full of
   rows. Unfilled outlines never move. */
.star svg {
  /* The glow ellipse deliberately reaches past the cropped viewBox: the spill
     IS the light. Never clip it back. */
  overflow: visible;
}

.star.filled svg {
  --fl-sway: calc(0.3deg + var(--fl-i, 1) * 0.85deg);
  --fl-rise: calc(0.008 + var(--fl-i, 1) * 0.016);
  /* Glow strength ramps with the rank: rank 1 is a bare 0.04 (an ember gives
     almost no light, and that reticence is what makes rank 5 read as hot),
     rank 5 lands at 0.80. */
  --fl-glow: calc(var(--fl-i, 1) * 0.19 - 0.15);
  /* The extra layers ramp too: the detached lick, the white-hot kernel, and
     the sparks, which max() gates to nothing below rank 4. */
  --fl-lick: calc(0.08 + var(--fl-i, 1) * 0.08);
  --fl-kern: calc(var(--fl-i, 1) * 0.13);
  /* Per-spark rank gates: clamp turns "rank minus threshold" into a hard 0/1,
     so each ember simply exists or does not, and the COUNT is what ramps
     (0 / 1 / 2 / 4 / 6 embers from rank 1 to 5). */
  --fl-sp1: calc(clamp(0, calc((var(--fl-i, 1) - 1) * 10), 1) * 0.6);
  --fl-sp2: calc(clamp(0, calc((var(--fl-i, 1) - 2) * 10), 1) * 0.5);
  --fl-sp3: calc(clamp(0, calc((var(--fl-i, 1) - 3) * 10), 1) * 0.55);
  --fl-sp4: calc(clamp(0, calc((var(--fl-i, 1) - 3) * 10), 1) * 0.42);
  --fl-sp5: calc(clamp(0, calc((var(--fl-i, 1) - 4) * 10), 1) * 0.5);
  --fl-sp6: calc(clamp(0, calc((var(--fl-i, 1) - 4) * 10), 1) * 0.38);
  --fl-sp7: calc(clamp(0, calc((var(--fl-i, 1) - 4) * 10), 1) * 0.44);
  --fl-sp8: calc(clamp(0, calc((var(--fl-i, 1) - 4) * 10), 1) * 0.32);
  --fl-jit: calc(var(--fl-i, 1) * 0.07px);
  /* --fl-seed is a per-mount random from StarRating; --fl-tune multiplies
     every layer's period and shifts every phase, so two same-rank flames in
     neighbouring rows never flicker in lockstep. Lockstep is the fastest
     tell of mechanical animation. */
  --fl-tune: calc(0.85 + var(--fl-seed, 0.5) * 0.3);
  /* One compositor layer per burning flame: the glow paints OUTSIDE the svg
     box (overflow: visible), and without the layer Chromium's damage
     tracking blinked the overflowed strip along the box edge (Jules's
     "flickers with that line", 2026-08-21). The layer rasterizes the whole
     visual overflow as one unit per frame. Bounded cost: only filled flames
     carry it. */
  will-change: transform;
}

.star.filled svg .fl-body,
.star.filled svg .fl-mid,
.star.filled svg .fl-core,
.star.filled svg .fl-side,
.star.filled svg .fl-lick,
.star.filled svg .fl-kernel,
.star.filled svg .fl-spark {
  transform-box: fill-box;
  transform-origin: 50% 100%;
}

/* The temperature ramp, derived from the theme accent so every theme burns
   in its own hue: toward white = hotter, toward black = the cooling tips.
   These style SVG <stop> elements (classes on stops, colors here), which is
   what lets the fill be a gradient while the hue still follows currentcolor. */
.flame .fl-g-white { stop-color: color-mix(in srgb, currentcolor 20%, #fff); }
.flame .fl-g-bright { stop-color: color-mix(in srgb, currentcolor 55%, #fff); }
.flame .fl-g-warm { stop-color: color-mix(in srgb, currentcolor 78%, #fff); }
.flame .fl-g-cc { stop-color: currentcolor; }
.flame .fl-g-ember { stop-color: color-mix(in srgb, currentcolor 72%, #000); }

.star.filled svg .fl-glow {
  transform-box: fill-box;
  transform-origin: 50% 55%;
  opacity: var(--fl-glow);
  animation: flame-glow calc((1.8s - var(--fl-i, 1) * 0.14s) * var(--fl-tune, 1)) ease-in-out infinite;
  animation-delay: calc(var(--fl-i, 1) * -0.61s - var(--fl-seed, 0.5) * 2s);
}

.star.filled svg .fl-body {
  animation: flame-dance calc((2.4s - var(--fl-i, 1) * 0.32s) * var(--fl-tune, 1)) ease-in-out infinite;
  animation-delay: calc(var(--fl-i, 1) * -0.73s - var(--fl-seed, 0.5) * 2s);
}

.star.filled svg .fl-mid {
  animation: flame-tongue calc((1.9s - var(--fl-i, 1) * 0.26s) * var(--fl-tune, 1)) ease-in-out infinite;
  animation-delay: calc(var(--fl-i, 1) * -0.47s - var(--fl-seed, 0.5) * 1.6s);
}

.star.filled svg .fl-core {
  animation: flame-core calc((1.35s - var(--fl-i, 1) * 0.17s) * var(--fl-tune, 1)) ease-in-out infinite;
  animation-delay: calc(var(--fl-i, 1) * -0.29s - var(--fl-seed, 0.5) * 1.2s);
}

.star.filled svg .fl-side {
  animation: flame-side calc((2.1s - var(--fl-i, 1) * 0.24s) * var(--fl-tune, 1)) ease-in-out infinite;
  animation-delay: calc(var(--fl-i, 1) * -0.9s - var(--fl-seed, 0.5) * 1.8s);
}

/* The lick and the kernel carry their ramp in their opacity var, so the
   resting declaration matters for reduced-motion, where the animation that
   normally states it is off. */
.star.filled svg .fl-lick {
  opacity: var(--fl-lick);
  animation: flame-lick calc((1.6s - var(--fl-i, 1) * 0.18s) * var(--fl-tune, 1)) ease-in-out infinite;
  animation-delay: calc(var(--fl-i, 1) * -0.53s - var(--fl-seed, 0.5) * 1.4s);
}

.star.filled svg .fl-kernel {
  opacity: var(--fl-kern);
  animation: flame-kernel calc((1.1s - var(--fl-i, 1) * 0.12s) * var(--fl-tune, 1)) ease-in-out infinite;
  animation-delay: calc(var(--fl-i, 1) * -0.37s - var(--fl-seed, 0.5) * 1.1s);
}

/* Sparks rest invisible (they only exist mid-flight), so reduced-motion
   showing their resting state shows nothing, which is right. */
.star.filled svg .fl-spark {
  opacity: 0;
}

/* Each ember gets its own peak opacity, drift, rise and clock. The odd
   durations are deliberately non-harmonic so the fleet never pulses in
   unison, and --sp-rise scales with rank: a hotter fire throws further. */
.star.filled svg .fl-s1 {
  --sp-o: var(--fl-sp1);
  --sp-x: 0.5px;
  --sp-rise: calc(-4.5px - var(--fl-i, 1) * 0.5px);
  animation: flame-spark calc((1.9s - var(--fl-i, 1) * 0.15s) * var(--fl-tune, 1)) ease-out infinite;
  animation-delay: calc(var(--fl-i, 1) * -0.8s - var(--fl-seed, 0.5) * 1.7s);
}

.star.filled svg .fl-s2 {
  --sp-o: var(--fl-sp2);
  --sp-x: -1.1px;
  --sp-rise: calc(-3.8px - var(--fl-i, 1) * 0.4px);
  animation: flame-spark calc((2.3s - var(--fl-i, 1) * 0.2s) * var(--fl-tune, 1)) ease-out infinite;
  animation-delay: calc(-1.3s - var(--fl-i, 1) * 0.4s - var(--fl-seed, 0.5) * 2.1s);
}

.star.filled svg .fl-s3 {
  --sp-o: var(--fl-sp3);
  --sp-x: 1.3px;
  --sp-rise: calc(-5.2px - var(--fl-i, 1) * 0.6px);
  animation: flame-spark calc((2.05s - var(--fl-i, 1) * 0.17s) * var(--fl-tune, 1)) ease-out infinite;
  animation-delay: calc(-0.45s - var(--fl-i, 1) * 0.29s - var(--fl-seed, 0.5) * 1.45s);
}

.star.filled svg .fl-s4 {
  --sp-o: var(--fl-sp4);
  --sp-x: -0.6px;
  --sp-rise: calc(-6.1px - var(--fl-i, 1) * 0.55px);
  animation: flame-spark calc((1.62s - var(--fl-i, 1) * 0.12s) * var(--fl-tune, 1)) ease-out infinite;
  animation-delay: calc(-1.9s - var(--fl-i, 1) * 0.19s - var(--fl-seed, 0.5) * 1.15s);
}

.star.filled svg .fl-s5 {
  --sp-o: var(--fl-sp5);
  --sp-x: 1.7px;
  --sp-rise: calc(-4.1px - var(--fl-i, 1) * 0.45px);
  animation: flame-spark calc((2.47s - var(--fl-i, 1) * 0.22s) * var(--fl-tune, 1)) ease-out infinite;
  animation-delay: calc(-0.95s - var(--fl-i, 1) * 0.36s - var(--fl-seed, 0.5) * 1.95s);
}

.star.filled svg .fl-s6 {
  --sp-o: var(--fl-sp6);
  --sp-x: -1.6px;
  --sp-rise: calc(-6.8px - var(--fl-i, 1) * 0.65px);
  animation: flame-spark calc((1.78s - var(--fl-i, 1) * 0.14s) * var(--fl-tune, 1)) ease-out infinite;
  animation-delay: calc(-2.4s - var(--fl-i, 1) * 0.24s - var(--fl-seed, 0.5) * 1.35s);
}

.star.filled svg .fl-s7 {
  --sp-o: var(--fl-sp7);
  --sp-x: 0.9px;
  --sp-rise: calc(-7.6px - var(--fl-i, 1) * 0.7px);
  animation: flame-spark calc((2.17s - var(--fl-i, 1) * 0.19s) * var(--fl-tune, 1)) ease-out infinite;
  animation-delay: calc(-0.7s - var(--fl-i, 1) * 0.33s - var(--fl-seed, 0.5) * 2.3s);
}

.star.filled svg .fl-s8 {
  --sp-o: var(--fl-sp8);
  --sp-x: -2.1px;
  --sp-rise: calc(-5.4px - var(--fl-i, 1) * 0.5px);
  animation: flame-spark calc((1.94s - var(--fl-i, 1) * 0.16s) * var(--fl-tune, 1)) ease-out infinite;
  animation-delay: calc(-1.6s - var(--fl-i, 1) * 0.27s - var(--fl-seed, 0.5) * 1.75s);
}

/* The slow SURGE: the whole fire swells and settles over several seconds under
   the fast per-layer flicker. Two clocks at very different speeds is what stops
   a short loop reading as a loop, and it is the cheapest "alive" there is,
   being one transform on one group. Seeded like everything else, and scaled by
   rank so an ember barely breathes while a 5 visibly surges. */
.star.filled svg .fl-surge {
  transform-box: fill-box;
  transform-origin: 50% 100%;
  animation: flame-surge calc((5.4s - var(--fl-i, 1) * 0.4s) * var(--fl-tune, 1)) ease-in-out infinite;
  animation-delay: calc(var(--fl-seed, 0.5) * -6.5s);
}

@keyframes flame-surge {
  0%, 100% { transform: scaleY(1) scaleX(1); }
  23% { transform: scaleY(calc(1 + var(--fl-i, 1) * 0.013)) scaleX(calc(1 - var(--fl-i, 1) * 0.004)); }
  46% { transform: scaleY(calc(1 - var(--fl-i, 1) * 0.009)) scaleX(calc(1 + var(--fl-i, 1) * 0.003)); }
  71% { transform: scaleY(calc(1 + var(--fl-i, 1) * 0.019)) scaleX(calc(1 - var(--fl-i, 1) * 0.006)); }
  88% { transform: scaleY(calc(1 - var(--fl-i, 1) * 0.004)) scaleX(1); }
}

/* The light breathes and gutters on its own clock; every frame states its
   opacity relative to the same --fl-glow ramp, so animating it can never
   flatten the rank difference. */
/* The light's REACH ramps with rank too (the 0.72 + i*0.09 base term): rank 1
   glows at 0.81x, rank 5 at 1.17x, so a top rating throws a physically bigger
   light, not just a denser one. */
@keyframes flame-glow {
  0%, 100% { opacity: var(--fl-glow); transform: scale(calc(0.72 + var(--fl-i, 1) * 0.09)); }
  26% { opacity: calc(var(--fl-glow) * 1.3); transform: scale(calc(0.72 + var(--fl-i, 1) * 0.112)); }
  49% { opacity: calc(var(--fl-glow) * 0.82); transform: scale(calc(0.68 + var(--fl-i, 1) * 0.09)); }
  71% { opacity: calc(var(--fl-glow) * 1.18); transform: scale(calc(0.72 + var(--fl-i, 1) * 0.104)); }
  88% { opacity: calc(var(--fl-glow) * 0.9); transform: scale(calc(0.71 + var(--fl-i, 1) * 0.09)); }
}

@keyframes flame-dance {
  0% { transform: skewX(0deg) scaleY(1); }
  13% { transform: skewX(var(--fl-sway)) scaleY(calc(1 + var(--fl-rise))); }
  /* Asymmetric easing on the big beats: fire SNAPS upward (ease-out into the
     stretch) and settles lazily (ease-in into the drop). A keyframe's
     timing-function shapes the segment leaving it. */
  24% { transform: skewX(calc(var(--fl-sway) * -0.6)) scaleY(calc(1 + var(--fl-rise) * 0.4)); animation-timing-function: cubic-bezier(0.15, 0.6, 0.3, 1); }
  /* scaleX opposite scaleY on the big beats: a flame that stretches also
     thins (volume preservation, the animator's squash-and-stretch). */
  37% { transform: skewX(calc(var(--fl-sway) * 0.9)) scaleY(calc(1 + var(--fl-rise) * 1.7)) scaleX(calc(1 - var(--fl-rise) * 0.9)); }
  46% { transform: skewX(calc(var(--fl-sway) * 0.2)) scaleY(calc(1 + var(--fl-rise) * 0.6)); animation-timing-function: cubic-bezier(0.6, 0, 0.75, 0.4); }
  58% { transform: skewX(calc(var(--fl-sway) * -1.1)) scaleY(calc(1 - var(--fl-rise) * 0.6)) scaleX(calc(1 + var(--fl-rise) * 0.5)); }
  69% { transform: skewX(calc(var(--fl-sway) * -0.3)) scaleY(calc(1 + var(--fl-rise) * 1.2)); }
  81% { transform: skewX(calc(var(--fl-sway) * 0.7)) scaleY(calc(1 + var(--fl-rise) * 0.3)); }
  91% { transform: skewX(calc(var(--fl-sway) * -0.4)) scaleY(calc(1 + var(--fl-rise) * 0.8)); }
  100% { transform: skewX(0deg) scaleY(1); }
}

/* The tongue leans AGAINST the body (negative multipliers) and stretches
   harder: the counter-motion is what makes the layers read as separate. */
@keyframes flame-tongue {
  0% { transform: skewX(0deg) scaleY(1); }
  16% { transform: skewX(calc(var(--fl-sway) * -0.9)) scaleY(calc(1 + var(--fl-rise) * 2)) scaleX(calc(1 - var(--fl-rise) * 1.1)); }
  31% { transform: skewX(calc(var(--fl-sway) * 0.6)) scaleY(calc(1 - var(--fl-rise) * 0.7)); }
  45% { transform: skewX(calc(var(--fl-sway) * -0.2)) scaleY(calc(1 + var(--fl-rise) * 1.1)); }
  59% { transform: skewX(calc(var(--fl-sway) * -1.2)) scaleY(calc(1 + var(--fl-rise) * 1.5)) scaleX(calc(1 - var(--fl-rise) * 0.8)); }
  73% { transform: skewX(calc(var(--fl-sway) * 0.8)) scaleY(calc(1 + var(--fl-rise) * 0.2)); }
  87% { transform: skewX(calc(var(--fl-sway) * -0.5)) scaleY(calc(1 + var(--fl-rise) * 1.3)); }
  100% { transform: skewX(0deg) scaleY(1); }
}

/* The hot core flicks fastest and gutters (opacity), deeper on higher steps. */
@keyframes flame-core {
  0% { transform: skewX(0deg) scaleY(1); opacity: 1; }
  22% { transform: skewX(calc(var(--fl-sway) * 0.6)) scaleY(calc(1 + var(--fl-rise) * 1.6)); opacity: calc(0.94 - var(--fl-i, 1) * 0.03); }
  40% { transform: skewX(calc(var(--fl-sway) * -0.4)) scaleY(calc(1 - var(--fl-rise) * 0.5)); opacity: 1; }
  57% { transform: skewX(calc(var(--fl-sway) * 0.3)) scaleY(calc(1 + var(--fl-rise) * 1.1)); opacity: calc(0.9 - var(--fl-i, 1) * 0.02); }
  74% { transform: skewX(calc(var(--fl-sway) * -0.5)) scaleY(calc(1 + var(--fl-rise) * 0.4)); opacity: 1; }
  90% { transform: skewX(calc(var(--fl-sway) * 0.5)) scaleY(calc(1 + var(--fl-rise) * 0.9)); opacity: calc(0.96 - var(--fl-i, 1) * 0.02); }
  100% { transform: skewX(0deg) scaleY(1); opacity: 1; }
}

/* The side tongue leans out hardest and drifts on the jitter var. */
@keyframes flame-side {
  0%, 100% { transform: skewX(0deg) scaleY(1); }
  19% { transform: translateX(calc(var(--fl-jit) * -1)) skewX(calc(var(--fl-sway) * -1.3)) scaleY(calc(1 + var(--fl-rise) * 1.3)); }
  38% { transform: skewX(calc(var(--fl-sway) * 0.4)) scaleY(calc(1 - var(--fl-rise) * 0.5)); }
  61% { transform: translateX(var(--fl-jit)) skewX(calc(var(--fl-sway) * -0.9)) scaleY(calc(1 + var(--fl-rise) * 0.9)); }
  83% { transform: skewX(calc(var(--fl-sway) * 0.3)) scaleY(1); }
}

/* The lick breathes and half-vanishes: licks come and go, they do not wave.
   The scale range is deliberately NARROW (0.9 to 1.18, was 0.75 to 1.45): a
   slim shape stretching that far read as a streak rising out of the flame
   rather than a tongue, which is half of what Jules saw as a "line". It
   leans with skew instead, and fades far more than it grows. */
@keyframes flame-lick {
  0%, 100% { transform: scaleY(0.92) skewX(0deg); opacity: calc(var(--fl-lick) * 0.45); }
  22% { transform: scaleY(1.1) skewX(calc(var(--fl-sway) * 0.8)); opacity: var(--fl-lick); }
  41% { transform: scaleY(0.96) skewX(calc(var(--fl-sway) * -0.5)); opacity: calc(var(--fl-lick) * 0.6); }
  63% { transform: scaleY(1.18) skewX(calc(var(--fl-sway) * 1.1)); opacity: calc(var(--fl-lick) * 1.15); }
  82% { transform: scaleY(1) skewX(calc(var(--fl-sway) * -0.3)); opacity: calc(var(--fl-lick) * 0.5); }
}

@keyframes flame-kernel {
  0%, 100% { opacity: var(--fl-kern); transform: scaleY(1); }
  18% { opacity: calc(var(--fl-kern) * 1.35); transform: scaleY(1.15); }
  39% { opacity: calc(var(--fl-kern) * 0.7); transform: scaleY(0.9); }
  58% { opacity: calc(var(--fl-kern) * 1.2); transform: scaleY(1.25); }
  79% { opacity: calc(var(--fl-kern) * 0.85); transform: scaleY(1); }
}

/* Embers are born at the tip, rise, drift and burn out. Each element supplies
   its own --sp-o (0 when its rank gate is shut, which makes the whole flight
   invisible), --sp-x drift and --sp-rise height. They shrink as they cool,
   and the drift reverses late so they curl rather than travel straight. */
@keyframes flame-spark {
  0% { transform: translate(0, 0) scale(0.55); opacity: 0; }
  14% { opacity: var(--sp-o, 0); }
  55% {
    transform: translate(calc(var(--sp-x, 0.5px) * 0.75), calc(var(--sp-rise, -5px) * 0.6)) scale(1);
    opacity: calc(var(--sp-o, 0) * 0.6);
  }
  100% {
    transform: translate(calc(var(--sp-x, 0.5px) * -0.35), var(--sp-rise, -5px)) scale(0.3);
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .star.filled svg .fl-glow,
  .star.filled svg .fl-body,
  .star.filled svg .fl-mid,
  .star.filled svg .fl-core,
  .star.filled svg .fl-side,
  .star.filled svg .fl-lick,
  .star.filled svg .fl-kernel,
  .star.filled svg .fl-spark,
  .star.filled svg .fl-surge,
  .star.flame-current.flare svg,
  .flame-pick::before,
  .flame-menu {
    animation: none;
  }
}

/* Site overrides: the wrapper is a span, not a button; the app's --fl-seed comes from React,
   here it is fixed; the colour is the site accent so the gradients burn in the theme's hue. */
.site-flame { display: inline-flex; align-items: flex-end; padding: 0; cursor: default; color: var(--accent); --fl-seed: 0.5; font-size: 0; line-height: 0; }
.site-flame svg { height: 26px; width: 15px; }
