/* Trianer polish — pure CSS motion layer (cravburgers-inspired).
 *
 * Loaded globally from base.html. Every rule is opt-in via a class so
 * components that don't want the motion stay unchanged. No new dep,
 * no GSAP — just CSS animations + a tiny IntersectionObserver in
 * polish.js that toggles `.is-revealed` when an element enters view.
 *
 * Respects `prefers-reduced-motion: reduce` everywhere — when the OS
 * asks for less motion, every animation collapses to 0.01s so the
 * end state lands instantly without visual jitter. */

@media (prefers-reduced-motion: reduce) {
  .tn-reveal, .tn-reveal-stagger > *,
  .tn-lift, .tn-flash, .tn-pulse, .tn-count,
  .gk-nav-burger-toggle ~ .gk-nav-center,
  .gk-nav-burger-toggle:checked ~ .gk-nav-center {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
  }
}

/* ── 1. Fade-up reveal on viewport entry (cravburgers ingredients) ─── */
.tn-reveal { opacity: 0; transform: translateY(12px);
             transition: opacity .45s ease-out, transform .45s ease-out; }
.tn-reveal.is-revealed { opacity: 1; transform: translateY(0); }

/* Stagger children of a `.tn-reveal-stagger` parent. JS sets
   --tn-i: <index> on each child when the parent enters view. */
.tn-reveal-stagger > * {
  opacity: 0; transform: translateY(10px);
  transition: opacity .4s ease-out, transform .4s ease-out;
  transition-delay: calc(var(--tn-i, 0) * 80ms);
}
.tn-reveal-stagger.is-revealed > * { opacity: 1; transform: translateY(0); }

/* ── 2. Universal hover-lift for CTAs (cravburgers Order Now) ──────── */
.tn-lift {
  transition: transform .15s ease-out, box-shadow .15s ease-out, filter .15s ease-out;
  will-change: transform;
}
.tn-lift:hover {
  transform: translateY(-2px);
  box-shadow: 0 6px 18px rgba(15, 23, 42, .14);
  filter: brightness(1.04);
}
.tn-lift:active { transform: translateY(0); transition-duration: .05s; }

/* ── 3. Drop-flash for successful actions (swap landed, meal added) ─ */
@keyframes tn-flash-green {
  0%   { box-shadow: 0 0 0 0 rgba(16, 185, 129, .55); background-color: rgba(16, 185, 129, .12); }
  60%  { box-shadow: 0 0 0 8px rgba(16, 185, 129, .00); }
  100% { box-shadow: 0 0 0 0 rgba(16, 185, 129, .00); background-color: transparent; }
}
.tn-flash { animation: tn-flash-green .9s ease-out 1; }

/* ── 4. Counter ticker (numbers count from 0 → target on mount) ─────
 *
 * No CSS work needed beyond keeping the wrapper's font-variant tabular
 * so the counter doesn't jitter horizontally as digits change width.
 * Polish.js handles the actual increment.
 */
.tn-count { font-variant-numeric: tabular-nums; }

/* ── 5. Sequential ripple through path bubbles (the "journey" motif)
 *
 * Polish.js iterates every `.ir-path-bubble` in DOM order (today →
 * race) and adds `.tn-path-lit` with a staggered delay. Each bubble
 * flashes a quick scale + glow, then settles. The race bubble keeps
 * the existing infinite `ir-path-pulse` so the eye lands there.
 */
@keyframes tn-path-ripple {
  0%   { transform: scale(1);    box-shadow: 0 0 0 0 rgba(14, 165, 233, .45); }
  50%  { transform: scale(1.08); box-shadow: 0 0 0 10px rgba(14, 165, 233, .15); }
  100% { transform: scale(1);    box-shadow: 0 0 0 0 rgba(14, 165, 233, .0); }
}
.tn-path-lit { animation: tn-path-ripple .6s ease-out 1; }

/* ── 6. Mobile burger — smooth open instead of instant flip ────────── */
@media (max-width: 640px) {
  .gk-nav-burger-toggle ~ .gk-nav-center {
    /* When NOT checked, the canonical rule sets display:none — we can't
       transition display, but we can pre-position the dropdown for the
       moment :checked unhides it, so the entry animation has somewhere
       to start from. */
    transform-origin: top right;
  }
  .gk-nav-burger-toggle:checked ~ .gk-nav-center {
    animation: tn-burger-open .22s ease-out 1;
  }
  @keyframes tn-burger-open {
    from { opacity: 0; transform: translateY(-8px); }
    to   { opacity: 1; transform: translateY(0); }
  }
  /* Subtle bar→cross transform on the burger icon when the menu is
     open — visual confirmation of state. */
  .gk-nav-burger-toggle:checked ~ .gk-nav-burger-btn i {
    transform: rotate(90deg);
  }
  .gk-nav-burger-btn i { transition: transform .22s ease-out; }
}

/* ── 8. Cursor follower with discipline trail (cravburgers ingredients)
 *
 * A 28 px translucent disc tracks the cursor; three tiny discipline
 * glyphs (🏃 🚴 🏊) trail behind it with eased lag. Builds the
 * "Trianer is alive" feeling without being intrusive — the disc is
 * mostly transparent and pointer-events:none so it never blocks a
 * click. Mobile/touch hides everything (no cursor to follow); the
 * matching script in polish.js detects pointerType=mouse before
 * activating.
 *
 * Disabled by default for users who haven't moved their mouse yet to
 * avoid one disc parked at 0,0 on page load. polish.js sets
 * data-tn-cursor="on" on <html> after the first real mouse-move. */
.tn-cursor-host { position: fixed; left: 0; top: 0; pointer-events: none;
                  z-index: 9999; opacity: 0; transition: opacity .35s ease; }
html[data-tn-cursor="on"] .tn-cursor-host { opacity: 1; }
.tn-cursor-dot {
  position: absolute; left: -14px; top: -14px;
  width: 28px; height: 28px; border-radius: 50%;
  background: radial-gradient(circle at 35% 30%,
                rgba(252, 76, 2, .35), rgba(252, 76, 2, .10) 60%, transparent 75%);
  /* No mix-blend-mode — multiply was tinting solid-coloured buttons
     ("Connect as" went teal on a blue background, looked like a bug).
     The radial gradient is translucent enough to read against any
     backdrop on its own. */
  transition: transform .08s ease-out, opacity .18s ease;
}
.tn-cursor-trail--ghost {
  /* The Pac-Man-eater slot wears a 60% bigger silhouette + a centred
     larger footprint so the chomping mouth + eyes read clearly against
     the 22 px discipline glyphs ahead of it. left/top stay = -width/2
     so the icon centres on the same trail position the loop computes.
     z-index is also handled in JS (style.zIndex = 100) so the ghost
     overlaps any prey it catches — the "eaten" visual cue. */
  width: 36px !important; height: 36px !important;
  left: -18px !important; top: -18px !important;
}
.tn-cursor-trail {
  position: absolute; left: -11px; top: -11px;
  width: 22px; height: 22px;
  /* Filter + opacity are set per-frame by polish.js (depth-based blur,
     atmospheric perspective opacity). Keep CSS minimal so the JS
     writes win cleanly. */
  opacity: 0;
  transition: opacity .22s ease;
  user-select: none; -webkit-user-drag: none;
  /* GPU hint — the trail is animating transform + filter every frame. */
  will-change: transform, opacity, filter;
}
/* Idle behaviour: the trail glyphs sit stacked on the dot (their
   transform is set to 0,0 spread by polish.js when cursor velocity
   is ~0). No CSS bob — the collapse-on-idle effect IS the design
   (cravburgers feel). When you start moving, they fan out behind. */
html[data-tn-cursor="on"] .tn-cursor-trail { opacity: .85; }
.tn-cursor-host.is-near-action .tn-cursor-dot {
  /* Near-action cue is JUST a slight scale-up — no colour change.
     The previous green wash blended with solid-colour admin buttons
     ("Connect as" went teal) and read as a styling bug rather than
     a hover cue. Keeping the dot's original orange means the button's
     own colour stays the dominant visual. The trail STAYS visible
     (the fade-out rule was too aggressive and masked the icons on
     any admin panel). */
  transform: scale(1.2);
}
@media (max-width: 768px), (hover: none), (pointer: coarse) {
  /* No cursor on touch — hide entirely so the script can no-op. */
  .tn-cursor-host { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .tn-cursor-host { display: none; }
}

/* ── 9. Lens shape (cravburgers "ORDER NOW" pill) ───────────────────
 *
 * Asymmetric border-radius: horizontal radius 50% (fully round on the
 * sides), vertical radius 30% (gentle dome top + bottom). Gives the
 * classic squashed-ellipse / lens look on rectangles taller than wide
 * → tall portrait tile. On wider elements (buttons), it reads as a
 * fat capsule.
 *
 * Opt-in via .tn-lens — never applied to grid-laid-out elements
 * (calendar cells stay rectangular so they tile cleanly). Action
 * buttons + KPI value pills are the sweet spot.
 */
.tn-lens {
  border-radius: 50% / 30% !important;
  /* Without overflow:hidden, the border-radius clips children but
     not absolutely-positioned overlays (focus rings, glows). Let
     callers override if they need bleed-out. */
  overflow: hidden;
}
/* On portrait-shaped containers (taller than wide), the lens reads
   as a stretched egg — usually NOT what we want. Force a wider aspect
   ratio with this companion so the shape reads correctly. */
.tn-lens.tn-lens--squash {
  padding-left: 24px; padding-right: 24px;
}
/* Bright accent variant — borrows cravburgers' red gradient
   directly. The shadow lifts the pill off the surface so it reads
   as a 3-D lozenge, not a flat shape. */
.tn-lens--accent {
  background: linear-gradient(135deg, #fc4c02 0%, #dc2626 100%) !important;
  color: #fff !important;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  box-shadow: 0 6px 18px rgba(220, 38, 38, .35),
              0 2px 6px rgba(220, 38, 38, .25);
  border: 0 !important;
}
.tn-lens--accent:hover {
  filter: brightness(1.06);
  box-shadow: 0 8px 22px rgba(220, 38, 38, .45),
              0 3px 8px rgba(220, 38, 38, .30);
}

/* ── 10. Day tile = lens shape (cravburgers ORDER NOW pill) ────────
 *
 * Each .ir-cal-cell wears the lens directly: border-radius 50%/30%
 * gives the squashed ellipse, padding pushes the inner content
 * inboard so it doesn't get clipped at the corners, border-spacing
 * bumped to 10 px so the lozenges float as individual pills with
 * breathing room.
 *
 * The accent line that lived on border-left is moved INSIDE as an
 * inset shadow — same visual cue (discipline color band on the left)
 * but it survives the rounded shape.
 */
/* The actual cravburgers ORDER NOW shape is a hand-drawn SVG blob
   path with cubic Beziers — not a CSS ellipse. Path verbatim from
   their /_next/static SVG, fill swapped per cell state. Painted as
   a background-image with preserveAspectRatio:none so it stretches
   to the cell. The cell stays rectangular underneath (no
   border-radius); only the blob silhouette is visible because the
   cell's own background goes transparent. drop-shadow filter
   shadows the blob (not the cell) so the silhouette reads cleanly. */
.iron-root .ir-cal { border-spacing: 10px !important; }
.iron-root .ir-cal-cell {
  background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="-10 -10 602 475" preserveAspectRatio="none"><path stroke="%23ffffff" stroke-width="10" fill="%23f1f5f9" d="M310.777 0.20434C424.154 2.91791 540.733 50.9739 574.176 159.34C606.479 264.014 533.962 365.999 442.064 425.623C364.995 475.626 270.863 455.893 193.524 406.309C93.8313 342.395 -27.3608 259.503 5.48889 145.729C40.0621 25.9857 186.179 -2.77783 310.777 0.20434Z"/></svg>') no-repeat center/100% 100% !important;
  /* Kill EVERY rectangle source: border, outline (focus + drag-target
     indicators all draw rectangles around the underlying td which
     look wrong against the blob silhouette), border-radius. */
  border: 0 !important;
  outline: 0 !important;
  border-radius: 0 !important;
  padding: 22px 34px !important;
  min-height: 0 !important;
  /* drop-shadow follows the non-rect silhouette (box-shadow would
     shadow the underlying rectangle and leak past the blob edge). */
  filter: drop-shadow(0 5px 10px rgba(15, 23, 42, .12))
          drop-shadow(0 1px 2px rgba(15, 23, 42, .08));
  text-align: center !important;
  vertical-align: middle !important;
  transition: filter .25s ease-out, transform .25s ease-out !important;
}
/* Also kill the rectangle on focus / drag-target / active states. */
.iron-root .ir-cal-cell:focus,
.iron-root .ir-cal-cell:focus-visible,
.iron-root .ir-cal-cell.is-drop-target,
.iron-root .ir-cal-cell.is-dragging {
  outline: 0 !important;
  box-shadow: none !important;
  border: 0 !important;
}
/* Drag target gets a brighter drop-shadow as the cue instead. */
.iron-root .ir-cal-cell.is-drop-target {
  filter: drop-shadow(0 0 0 4px rgba(14, 165, 233, .35))
          drop-shadow(0 8px 16px rgba(14, 165, 233, .35)) !important;
}
/* Hover — punchy enough to actually see AND morphs the shape (uneven
   scale 1.07 × 0.94 squashes the blob horizontally so the silhouette
   visibly changes, not just lifts). Preserves the existing
   `translateX(var(--iron-x))` so cells stay on their sinusoid track
   in the week-strip view. */
.iron-root .ir-cal-cell:hover {
  transform: translateX(var(--iron-x, 0px)) translateY(-5px) scale(1.07, 0.94) !important;
  filter: drop-shadow(0 14px 26px rgba(15, 23, 42, .26))
          drop-shadow(0 3px 6px rgba(15, 23, 42, .14))
          brightness(1.06) !important;
}

/* ── 10b. Week-strip view — same blob, higher specificity ─────────
 *
 * The render_iron CSS (line ~1547) declares .iron-root .ir-week-strip
 * .ir-cal-cell with a solid white background + 1.5 px border +
 * border-radius 18 px + box-shadow rectangle. That selector has higher
 * specificity than the generic .iron-root .ir-cal-cell so my blob
 * never landed in the sinusoid view. Re-apply the cravburgers blob
 * here with matching specificity. */
.iron-root .ir-week-strip .ir-cal-cell {
  background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="-10 -10 602 475" preserveAspectRatio="none"><path stroke="%23ffffff" stroke-width="10" fill="%23f1f5f9" d="M310.777 0.20434C424.154 2.91791 540.733 50.9739 574.176 159.34C606.479 264.014 533.962 365.999 442.064 425.623C364.995 475.626 270.863 455.893 193.524 406.309C93.8313 342.395 -27.3608 259.503 5.48889 145.729C40.0621 25.9857 186.179 -2.77783 310.777 0.20434Z"/></svg>') no-repeat center/100% 100% !important;
  border: 0 !important;
  border-radius: 0 !important;
  box-shadow: none !important;
  outline: 0 !important;
  /* The drop-shadow follows the blob silhouette here too. */
  filter: drop-shadow(0 5px 12px rgba(15, 23, 42, .14))
          drop-shadow(0 1px 2px rgba(15, 23, 42, .08)) !important;
  transition: filter .25s ease-out, transform .25s ease-out !important;
}
.iron-root .ir-week-strip .ir-cal-today {
  background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="-10 -10 602 475" preserveAspectRatio="none"><path stroke="%23ffffff" stroke-width="10" fill="%23fc4c02" d="M310.777 0.20434C424.154 2.91791 540.733 50.9739 574.176 159.34C606.479 264.014 533.962 365.999 442.064 425.623C364.995 475.626 270.863 455.893 193.524 406.309C93.8313 342.395 -27.3608 259.503 5.48889 145.729C40.0621 25.9857 186.179 -2.77783 310.777 0.20434Z"/></svg>') no-repeat center/100% 100% !important;
  color: #fff !important;
  filter: drop-shadow(0 6px 14px rgba(252, 76, 2, .35))
          drop-shadow(0 2px 4px rgba(252, 76, 2, .20)) !important;
}
.iron-root .ir-week-strip .ir-cal-actual {
  background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="-10 -10 602 475" preserveAspectRatio="none"><path stroke="%23ffffff" stroke-width="10" fill="%2386efac" d="M310.777 0.20434C424.154 2.91791 540.733 50.9739 574.176 159.34C606.479 264.014 533.962 365.999 442.064 425.623C364.995 475.626 270.863 455.893 193.524 406.309C93.8313 342.395 -27.3608 259.503 5.48889 145.729C40.0621 25.9857 186.179 -2.77783 310.777 0.20434Z"/></svg>') no-repeat center/100% 100% !important;
}
/* Week-strip hover — same morph, preserved translateX. */
.iron-root .ir-week-strip .ir-cal-cell:hover {
  transform: translateX(var(--iron-x, 0px)) translateY(-5px) scale(1.07, 0.94) !important;
  filter: drop-shadow(0 14px 26px rgba(15, 23, 42, .26))
          drop-shadow(0 3px 6px rgba(15, 23, 42, .14))
          brightness(1.06) !important;
  border-color: transparent !important;
  box-shadow: none !important;
}
/* Today cell — brand-red fill, the cravburgers "ORDER NOW" itself. */
.iron-root .ir-cal-today {
  background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="-10 -10 602 475" preserveAspectRatio="none"><path stroke="%23ffffff" stroke-width="10" fill="%23fc4c02" d="M310.777 0.20434C424.154 2.91791 540.733 50.9739 574.176 159.34C606.479 264.014 533.962 365.999 442.064 425.623C364.995 475.626 270.863 455.893 193.524 406.309C93.8313 342.395 -27.3608 259.503 5.48889 145.729C40.0621 25.9857 186.179 -2.77783 310.777 0.20434Z"/></svg>') no-repeat center/100% 100% !important;
  color: #fff !important;
  filter: drop-shadow(0 6px 14px rgba(252, 76, 2, .35))
          drop-shadow(0 2px 4px rgba(252, 76, 2, .20));
}
.iron-root .ir-cal-today * { color: inherit !important; }
/* Realised cells — green blob (adherence streak). */
.iron-root .ir-cal-actual {
  background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="-10 -10 602 475" preserveAspectRatio="none"><path stroke="%23ffffff" stroke-width="10" fill="%2386efac" d="M310.777 0.20434C424.154 2.91791 540.733 50.9739 574.176 159.34C606.479 264.014 533.962 365.999 442.064 425.623C364.995 475.626 270.863 455.893 193.524 406.309C93.8313 342.395 -27.3608 259.503 5.48889 145.729C40.0621 25.9857 186.179 -2.77783 310.777 0.20434Z"/></svg>') no-repeat center/100% 100% !important;
}
/* Drop-flash hue stays compatible with the rounded shape. */
.iron-root .ir-cal-cell.just-swapped { animation: tn-flash-green .9s ease-out 1; }
/* Mobile: cells get narrower, dial the radius back so the inner text
   stays readable on a small screen. */
@media (max-width: 640px) {
  .iron-root .ir-cal { border-spacing: 6px !important; }
  .iron-root .ir-cal-cell { padding: 14px 22px !important; }
}

/* ── 7. KPI tiles slow-pulse on a first-render flag (calls attention)
 *
 * Render-side adds `.tn-kpi-fresh` to a KPI value the first time it
 * changes versus the prior load (we can ship without this and add
 * later — for v1 it's just a hook). */
@keyframes tn-kpi-fresh {
  0%   { color: var(--accent, #0ea5e9); }
  100% { color: inherit; }
}
.tn-kpi-fresh { animation: tn-kpi-fresh 1.4s ease-out 1; }
