/* ============================================================
   NoBossly motion — game juice, pure CSS, zero libraries.
   Principle: motion only when the player did something (or a
   value is being revealed). Nothing loops except the streak
   flame, and everything respects prefers-reduced-motion.
   ============================================================ */

/* Progress bars fill from 0 to their value on load */
.progress-bar { animation: nb-barfill .7s ease-out; }
@keyframes nb-barfill { from { width: 0; } }

/* The Coach slides in like it walked over to say something */
.coach-card { animation: nb-rise .3s ease-out; }
@keyframes nb-rise { from { opacity: 0; transform: translateY(10px); } }

/* A live streak (3+ days) flickers; a cold one sits still */
.flame-live { animation: nb-flicker 1.6s ease-in-out infinite; transform-origin: 50% 90%; }
@keyframes nb-flicker {
  0%, 100% { transform: scale(1) rotate(0deg); opacity: 1; }
  25%      { transform: scale(1.09) rotate(-2deg); opacity: .9; }
  55%      { transform: scale(.95) rotate(2deg); }
  80%      { transform: scale(1.05) rotate(-1deg); opacity: .96; }
}

/* Task check: pop the checkmark, flash the row green, settle */
.task.just-done .task-check { animation: nb-pop .35s cubic-bezier(.3, 1.6, .5, 1); }
@keyframes nb-pop { 0% { transform: scale(.6); } 60% { transform: scale(1.25); } 100% { transform: scale(1); } }
.task.just-done { animation: nb-settle .5s ease-out; }
@keyframes nb-settle { from { background: rgba(16, 185, 129, .16); } }

/* +XP chip floats up from the checkbox and fades */
.xp-float {
  position: fixed; z-index: 90; pointer-events: none;
  font-weight: 700; font-size: .95rem; color: #10b981;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .18);
  animation: nb-float .9s ease-out forwards;
}
@keyframes nb-float {
  from { opacity: 0; transform: translateY(4px); }
  20%  { opacity: 1; }
  to   { opacity: 0; transform: translateY(-36px); }
}

/* Level-up: the one justified big moment */
.nb-overlay {
  position: fixed; inset: 0; z-index: 95; pointer-events: none;
  display: flex; align-items: center; justify-content: center;
}
.levelup-badge {
  background: var(--panel-2, #fff); border: 1px solid var(--border, #ddd);
  border-radius: 18px; padding: 1.2rem 2rem; text-align: center;
  box-shadow: 0 18px 60px rgba(0, 0, 0, .28);
  animation: nb-badge 1.8s cubic-bezier(.3, 1.4, .4, 1) forwards;
}
.levelup-badge .lvl-emoji { font-size: 2rem; display: block; margin-bottom: .2rem; }
.levelup-badge strong { font-size: 1.25rem; display: block; }
@keyframes nb-badge {
  0%   { opacity: 0; transform: scale(.5); }
  12%  { opacity: 1; transform: scale(1.07); }
  20%  { transform: scale(1); }
  85%  { opacity: 1; }
  100% { opacity: 0; transform: scale(.97); }
}
.nb-confetti { position: fixed; top: 42%; left: 50%; width: 9px; height: 13px; z-index: 94; pointer-events: none; border-radius: 2px; animation: nb-confetti 1.5s ease-out forwards; }
@keyframes nb-confetti { to { transform: translate(var(--dx), var(--dy)) rotate(var(--rot)); opacity: 0; } }

/* Quest accepted: punch-in card + expanding shockwave ring,
   paired with the "Let's do this!" soundbite in app.js */
.quest-pop {
  background: var(--panel-2, #fff); border: 1px solid var(--border, #ddd);
  border-radius: 18px; padding: 1.3rem 2.2rem; text-align: center;
  box-shadow: 0 18px 60px rgba(0, 0, 0, .28);
  animation: nb-quest 1.7s cubic-bezier(.3, 1.5, .4, 1) forwards;
}
.quest-pop .quest-emoji { font-size: 2.2rem; display: block; margin-bottom: .15rem; }
.quest-pop strong { font-size: 1.2rem; display: block; letter-spacing: .04em; }
.quest-pop em { font-style: normal; color: var(--muted, #777); font-size: .95rem; }
@keyframes nb-quest {
  0%   { opacity: 0; transform: scale(.4) rotate(-4deg); }
  14%  { opacity: 1; transform: scale(1.08) rotate(1deg); }
  22%  { transform: scale(1) rotate(0deg); }
  82%  { opacity: 1; }
  100% { opacity: 0; transform: scale(.97); }
}
.quest-ring {
  position: fixed; top: 50%; left: 50%; z-index: 94; pointer-events: none;
  width: 40px; height: 40px; margin: -20px 0 0 -20px;
  border: 3px solid #10b981; border-radius: 50%;
  animation: nb-ring .8s ease-out forwards;
}
@keyframes nb-ring {
  from { opacity: .9; transform: scale(.5); }
  to   { opacity: 0; transform: scale(9); }
}

/* Accessibility: motion off means motion off */
@media (prefers-reduced-motion: reduce) {
  .progress-bar, .coach-card, .flame-live, .task.just-done, .task.just-done .task-check,
  .xp-float, .levelup-badge, .nb-confetti, .quest-pop, .quest-ring { animation: none !important; }
  .quest-pop, .levelup-badge { opacity: 1; }
}

/* ============================================================
   Celebration FX — the four big moments, staged by public/js/fx.js.

   All four are a hosted video clip now, so this is a short file: a backdrop,
   a box for the video to fill, and the reduced-motion line that stands in for
   the clip when motion is off.
   ============================================================ */

.nb-fx-overlay {
  position: fixed; inset: 0; z-index: 96;
  display: flex; align-items: center; justify-content: center;
  padding: 1rem;
  /* Never intercept a click meant for the page underneath. */
  pointer-events: none;
  animation: nb-fx-in .22s ease-out;
}
.nb-fx-overlay.nb-fx-out { animation: nb-fx-fade .26s ease-in forwards; }
@keyframes nb-fx-in { from { opacity: 0; } }
@keyframes nb-fx-fade { to { opacity: 0; } }

/* Every variant's backdrop needs content/position here — a ::before that only
   sets a background never generates a box, so the dim never appears. */
.nb-fx-clip::before,
.nb-fx-plain::before {
  content: ""; position: absolute; inset: 0;
  animation: nb-fx-in .3s ease-out;
}

/* ---------- The clips ----------
   The clip is the whole effect, so there is no card and no type — just the
   video, letterboxed on black so nothing in it is ever cropped away. */

.nb-fx-clip::before { background: #04070a; }

.nb-fx-video {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
  max-width: 100vw;
  max-height: 100vh;
  /* contain, not cover: the clip is designed artwork, and cropping a phone
     down to a landscape frame would cut the words out of it. */
  object-fit: contain;
  background: #04070a;
}

/* The overlay centres its child; the video wants the whole box. */
.nb-fx-clip { padding: 0; }

/* Reduced motion gets a line of text where the clip would have been. */
.nb-fx-plain::before { background: rgba(4, 20, 16, .72); }
.nb-fx-plain {
  font-family: var(--font-display, 'Sora', system-ui, sans-serif);
  font-weight: 700;
  letter-spacing: -.02em;
  color: #d1fae5;
  font-size: clamp(1.3rem, 6vw, 2.4rem);
  position: relative;
  margin: 0;
}

/* The preloading clip: present in the DOM so the browser buffers it, but
   invisible, unclickable and outside layout. Adopted by the overlay on click,
   at which point .nb-fx-warm comes off and it fills the screen. */
.nb-fx-warm {
  position: fixed; left: -9999px; top: 0;
  width: 1px; height: 1px;
  opacity: 0; pointer-events: none;
}
