/* Overlay+ Goals+ - how one is drawn.
 *
 * Loaded by the overlay and by the panel, whose Goals+ dialog draws its
 * preview with the same elements (goal-view.js), so what the dialog shows is
 * what goes on screen.
 *
 * Everything that moves here moves by transform or opacity, so OBS composites
 * it rather than painting it again: the fill slides, the light runs, the bar
 * swells, the amount floats up, sparks fly, stars twinkle, colors flow. The
 * looks are gradients and shadows painted once and never animated. Never a
 * filter (CLAUDE.md: a filter over an overlay cost OBS a whole core).
 */

/* ---------------------------------- size ---------------------------------- */

/* As wide as its Width asks, until the box says otherwise: wider when the box
   is (it stretches to fill, unless told not to), narrower when the box is -
   a wrap width, a box of its own width, a line it shares. Never so narrow the
   bar is gone. */
.op-goal {
  display: flex;
  flex-direction: column;
  gap: 0.25em;
  white-space: normal;
  width: var(--goal-w, 560px);
  max-width: 100%;
  flex: 1 1 auto;
  min-width: min(var(--goal-w, 560px), calc(var(--goal-h, 36px) * 4));
}
.op-goal.no-stretch { flex-grow: 0; }
/* A fixed-size Text+ scales what is in it to fit, the way it scales a picture:
   the bar keeps the shape its Width and Height give it, and grows or shrinks
   with the words. The rows are held at least this wide while the fit is
   measured (_fitRows), so it is measured as it will be drawn. */
.op-box.fit .op-goal:not(.op-goal-number):not(.op-goal-ring) { min-width: var(--goal-w, 560px); }

.op-goal-title { font-size: 0.6em; opacity: 0.8; letter-spacing: 0.08em; text-transform: uppercase; }
.op-goal-title[hidden], .op-goal-text[hidden] { display: none !important; }
.op-goal-text { font-size: 0.62em; font-variant-numeric: tabular-nums; }
.op-goal-barwrap { position: relative; width: 100%; }

/* ---------------------------------- bar ----------------------------------- */

.op-goal-bar {
  position: relative;
  height: var(--goal-h, 36px);
  border-radius: var(--goal-r, 999px);
  overflow: hidden;
  background: var(--goal-track, rgba(0, 0, 0, 0.4));
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.14), inset 0 2px 6px rgba(0, 0, 0, 0.35);
  isolation: isolate;
}
/* The fill is the whole width of the bar, slid left out of it by however much
   is still to go: its own rounded end is the leading edge. */
.op-goal-fill {
  position: absolute;
  inset: 0;
  border-radius: var(--goal-r, 999px);
  overflow: hidden;
  background: var(--goal-grad, linear-gradient(90deg, #7C5CFF, #A98BFF));
  transform: translateX(-100%);
}
/* Moves with the fill, but is not clipped by the bar: where the stars and the
   sparks come from. */
.op-goal-tip {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: var(--goal-h, 36px);
  pointer-events: none;
  transform: translateX(-100%);
}
.op-goal-flow, .op-goal-reached, .op-goal-stripes, .op-goal-gloss, .op-goal-sheen, .op-goal-edge, .op-goal-shine {
  position: absolute;
  top: 0;
  bottom: 0;
  pointer-events: none;
}
/* Reached: the reached color fades in over the colors, under the shine. */
.op-goal-reached {
  left: 0;
  right: 0;
  background: var(--goal-done, #35D39A);
  opacity: 0;
  transition: opacity 700ms ease;
}
.op-goal.done .op-goal-reached { opacity: 1; }

/* --------------------------------- looks ---------------------------------- */

.op-goal-gloss { left: 0; right: 0; border-radius: inherit; }
/* A light at the leading edge, inside the bar's rounded end. */
.op-goal-edge {
  display: none;
  right: 0;
  width: calc(var(--goal-h, 36px) * 2.2);
  max-width: 45%;
  background: radial-gradient(ellipse 100% 95% at 100% 50%, rgba(255, 255, 255, 0.72),
    rgba(255, 255, 255, 0.22) 38%, rgba(255, 255, 255, 0) 72%);
}

/* Glossy: a wet shine across the top, darker underneath, the track sunk in. */
.op-goal.look-glossy .op-goal-bar {
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.5), inset 0 0 0 1px rgba(255, 255, 255, 0.1),
    0 1px 0 rgba(255, 255, 255, 0.14);
}
.op-goal.look-glossy .op-goal-gloss {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.7) 0%, rgba(255, 255, 255, 0.3) 42%,
    rgba(255, 255, 255, 0) 50%, rgba(0, 0, 0, 0) 70%, rgba(0, 0, 0, 0.22) 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.55);
}
.op-goal.look-glossy .op-goal-edge { display: block; }

/* Glass: a bright rim and a clear reflection, over a frosted track. */
.op-goal.look-glass .op-goal-bar {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.05)),
    var(--goal-track, rgba(0, 0, 0, 0.4));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.65), inset 0 0 0 1px rgba(255, 255, 255, 0.3),
    inset 0 -1px 0 rgba(255, 255, 255, 0.15);
}
.op-goal.look-glass .op-goal-gloss {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.75) 0%, rgba(255, 255, 255, 0.28) 16%,
    rgba(255, 255, 255, 0.07) 48%, rgba(255, 255, 255, 0) 60%, rgba(255, 255, 255, 0.24) 100%);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.35);
}
.op-goal.look-glass .op-goal-edge { display: block; }

/* Neon: the bar glows its own color, and lights up from inside. */
.op-goal.look-neon .op-goal-bar {
  box-shadow: 0 0 calc(var(--goal-h, 36px) * 0.5) var(--goal-glow, rgba(124, 92, 255, 0.6)),
    inset 0 0 0 2px var(--goal-glow, rgba(124, 92, 255, 0.6));
  transition: box-shadow 700ms ease;
}
.op-goal.look-neon.done .op-goal-bar {
  box-shadow: 0 0 calc(var(--goal-h, 36px) * 0.5) var(--goal-done-glow, rgba(53, 211, 154, 0.6)),
    inset 0 0 0 2px var(--goal-done-glow, rgba(53, 211, 154, 0.6));
}
.op-goal.look-neon .op-goal-gloss {
  box-shadow: inset 0 0 calc(var(--goal-h, 36px) * 0.32) rgba(255, 255, 255, 0.7);
}
.op-goal.look-neon .op-goal-edge { display: block; }

/* Chrome: polished bands, like metal. */
.op-goal.look-chrome .op-goal-bar {
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.55), 0 1px 0 rgba(255, 255, 255, 0.25),
    0 -1px 0 rgba(0, 0, 0, 0.3);
}
.op-goal.look-chrome .op-goal-gloss {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.78) 0%, rgba(255, 255, 255, 0.2) 36%,
    rgba(0, 0, 0, 0.26) 50%, rgba(0, 0, 0, 0.08) 68%, rgba(255, 255, 255, 0.36) 100%);
}
.op-goal.look-chrome .op-goal-edge { display: block; }

/* Flat: just the colors, with a hairline of light along the top. */
.op-goal.look-flat .op-goal-gloss {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35), inset 0 -2px 0 rgba(0, 0, 0, 0.18);
}

/* ------------------------------ while it waits ----------------------------- */

/* Colors flowing toward the leading edge: a strip twice as wide, two copies
   of the gradient, slid one copy's width - so the loop has no seam. */
.op-goal-flow {
  display: none;
  left: 0;
  width: 200%;
  background: var(--goal-flow, none);
  animation: op-goal-flow 4.5s linear infinite;
}
.op-goal.idle-flow .op-goal-flow { display: block; }
@keyframes op-goal-flow { from { transform: translateX(-50%); } to { transform: translateX(0); } }

/* Stripes slide by exactly one repeat, so the loop has no seam: 24px apart at
   45 degrees is 24 * sqrt(2) along the bar. */
.op-goal-stripes {
  display: none;
  left: -68px;
  right: 0;
  background-image: repeating-linear-gradient(-45deg, rgba(255, 255, 255, 0.16) 0 12px, transparent 12px 24px);
  animation: op-goal-stripes 1.1s linear infinite;
}
.op-goal.idle-stripes .op-goal-stripes { display: block; }
@keyframes op-goal-stripes { to { transform: translateX(33.94px); } }

/* A glint: a bright streak sweeps along now and then. */
.op-goal-sheen {
  display: none;
  left: 0;
  width: 24%;
  background: linear-gradient(105deg, rgba(255, 255, 255, 0) 15%, rgba(255, 255, 255, 0.6) 50%,
    rgba(255, 255, 255, 0) 85%);
  animation: op-goal-sheen 3.6s ease-in-out infinite;
}
.op-goal.idle-shimmer .op-goal-sheen { display: block; }
@keyframes op-goal-sheen { 0% { transform: translateX(-120%); } 55%, 100% { transform: translateX(430%); } }

/* Stars twinkling at the leading edge. Shaped once by clip-path; only their
   scale, turn and opacity move. */
.op-goal-twinkle { display: none; position: absolute; top: 50%; left: 100%; width: 0; height: 0; }
.op-goal.idle-twinkle .op-goal-twinkle { display: block; }
.op-goal-star {
  position: absolute;
  width: calc(var(--goal-h, 36px) * 0.55);
  height: calc(var(--goal-h, 36px) * 0.55);
  margin: calc(var(--goal-h, 36px) * -0.275) 0 0 calc(var(--goal-h, 36px) * -0.275);
  background: #FFFFFF;
  clip-path: polygon(50% 0, 60% 40%, 100% 50%, 60% 60%, 50% 100%, 40% 60%, 0 50%, 40% 40%);
  opacity: 0;
  animation: op-goal-twinkle 2.4s ease-in-out infinite;
}
.op-goal-star:nth-child(1) { left: calc(var(--goal-h, 36px) * -0.35); top: calc(var(--goal-h, 36px) * -0.5); }
.op-goal-star:nth-child(2) { left: calc(var(--goal-h, 36px) * -1.05); top: calc(var(--goal-h, 36px) * 0.42); animation-delay: 0.8s; }
.op-goal-star:nth-child(3) { left: calc(var(--goal-h, 36px) * 0.12); top: calc(var(--goal-h, 36px) * 0.18); animation-delay: 1.6s; }
@keyframes op-goal-twinkle {
  0%, 100% { opacity: 0; transform: scale(0.2) rotate(0deg); }
  50% { opacity: 1; transform: scale(1) rotate(45deg); }
}

/* ------------------------------ when it goes up ---------------------------- */

.op-goal-shine {
  left: 0;
  width: 80px;
  background: linear-gradient(90deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.75), rgba(255, 255, 255, 0));
  opacity: 0;
}
.op-goal-text.inside {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: calc(var(--goal-h, 36px) * 0.5);
  line-height: 1;
  white-space: nowrap;
  /* Readable on a pale fill as well as on the empty part. */
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.75), 0 0 6px rgba(0, 0, 0, 0.35);
}
.op-goal-gain {
  position: absolute;
  bottom: 100%;
  left: 0;
  font-size: 0.55em;
  font-weight: 800;
  line-height: 1.1;
  color: var(--goal-fill2, #A98BFF);
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
}
/* Sparks off the leading edge. Each is made for one burst and removed. */
.op-goal-spark {
  position: absolute;
  top: 50%;
  left: 100%;
  border-radius: 50%;
  background: radial-gradient(circle, #FFFFFF 0 32%, var(--goal-fill2, #A98BFF) 62%, rgba(255, 255, 255, 0) 70%);
  opacity: 0;
  pointer-events: none;
}
.op-goal.done .op-goal-spark {
  background: radial-gradient(circle, #FFFFFF 0 32%, var(--goal-done, #35D39A) 62%, rgba(255, 255, 255, 0) 70%);
}
.op-goal-spark.star, .op-goal.done .op-goal-spark.star {
  border-radius: 0;
  background: #FFFFFF;
  clip-path: polygon(50% 0, 60% 40%, 100% 50%, 60% 60%, 50% 100%, 40% 60%, 0 50%, 40% 40%);
}

/* ------------------------------- just the number --------------------------- */

/* "Subs today: 12" - the words at the size of the line, no bar. */
.op-goal.op-goal-number { width: auto; flex: 0 1 auto; min-width: 0; }
.op-goal-numwrap { position: relative; }
.op-goal-text.number { font-size: 1em; }
.op-goal-number .op-goal-gain { left: 50%; }
.op-goal-number .op-goal-spark { left: 50%; }

/* ------------------------------- ring and gauge ------------------------------ */

/* A ring that closes as it fills, or a gauge - most of one, open at the
   bottom. Its own size, never stretched, with its words beside it (or under
   it), so it sits on a line with other things. The fill slides by
   stroke-dashoffset on one small SVG, only while it moves; the tip turns by
   transform. Nothing here is ever a filter. */
.op-goal.op-goal-ring {
  width: auto; flex: 0 0 auto; min-width: 0;
  flex-direction: row; align-items: center; gap: 0.45em;
}
.op-goal-ring.words-left { flex-direction: row-reverse; }
.op-goal-ring.words-below { flex-direction: column; }
.op-goal-ring.words-none .op-ring-words { display: none; }
.op-ring { position: relative; flex: 0 0 auto; width: var(--ring-size, 96px); height: var(--ring-size, 96px); }
.op-ring-svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.op-ring-track { stroke: var(--goal-track, rgba(0, 0, 0, 0.4)); }
.op-ring-glow { opacity: 0.22; }
.op-ring-done { stroke: var(--goal-done, #35D39A); opacity: 0; }
.op-goal.done .op-ring-done { opacity: 1; }
.op-ring-gloss { stroke: #FFFFFF; opacity: 0.4; }
.op-ring-ticks line { stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; opacity: 0.45; }
.op-ring-seg { opacity: 0; transition: opacity 320ms ease; }
.op-goal.done .op-ring-seg { stroke: var(--goal-done, #35D39A); }

/* The tip: a dot at the end of the fill, turned round the middle. */
.op-ring-tip, .op-ring-glint { position: absolute; inset: 0; pointer-events: none; }
.op-ring-dot, .op-ring-glint-dot {
  position: absolute;
  left: calc(50% + var(--ring-r, 40px));
  top: 50%;
  width: var(--ring-dot, 14px);
  height: var(--ring-dot, 14px);
  border-radius: 50%;
  transform: translate(-50%, -50%);
}
.op-ring-dot {
  background: radial-gradient(circle, #FFFFFF 0 30%, var(--goal-fill2, #A98BFF) 62%, rgba(255, 255, 255, 0) 72%);
}
.op-goal.done .op-ring-dot {
  background: radial-gradient(circle, #FFFFFF 0 30%, var(--goal-done, #35D39A) 62%, rgba(255, 255, 255, 0) 72%);
}
.op-ring-dot .op-goal-twinkle { left: 50%; }
.op-ring-dot .op-goal-spark { left: 50%; }
.op-ring-glint[hidden] { display: none; }
.op-ring-glint-dot {
  width: calc(var(--ring-dot, 14px) * 1.6);
  height: calc(var(--ring-dot, 14px) * 1.6);
  background: radial-gradient(circle, rgba(255, 255, 255, 0.95) 0 22%, rgba(255, 255, 255, 0) 70%);
}

/* What is in the middle: how far, sized to the ring. A gauge's middle sits a
   little low, where its open bottom leaves room. */
.op-ring-center {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: calc(var(--ring-size, 96px) * 0.24);
  line-height: 1;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.op-goal-ring.gauge .op-ring-center { padding-top: calc(var(--ring-size, 96px) * 0.06); }
.op-goal-ring.no-center .op-ring-center { display: none; }
.op-ring-center > span { grid-area: 1 / 1; }
.op-ring-center { display: grid; place-items: center; }
.op-ring-gain { opacity: 0; font-weight: 800; color: var(--goal-fill2, #A98BFF); }

.op-ring-words { display: flex; flex-direction: column; align-items: flex-start; text-align: left; min-width: 0; }
.op-goal-ring.words-left .op-ring-words { align-items: flex-end; text-align: right; }
.op-goal-ring.words-below .op-ring-words { align-items: center; text-align: center; }
.op-ring-words .op-goal-text { font-size: 0.8em; }

/* Looks, on a ring: the highlight and the glow, painted once. */
.op-goal-ring.look-flat .op-ring-gloss, .op-goal-ring.look-flat .op-ring-glow { opacity: 0; }
.op-goal-ring.look-glass .op-ring-gloss { opacity: 0.65; }
.op-goal-ring.look-glass .op-ring-track { stroke: rgba(255, 255, 255, 0.16); }
.op-goal-ring.look-neon .op-ring-glow { opacity: 0.5; }
.op-goal-ring.look-neon .op-ring-gloss { opacity: 0.15; }
.op-goal-ring.look-chrome .op-ring-gloss { opacity: 0.75; }
/* Its colors flowing, on a ring: the glow breathes. */
.op-goal-ring.idle-flow .op-ring-glow { animation: op-ring-breathe 2.8s ease-in-out infinite; }
@keyframes op-ring-breathe { 0%, 100% { opacity: 0.12; } 50% { opacity: 0.6; } }

