/* ═══════════════════════════════════════════════════════════════════════════
   ARTIFICER · Whimsy · v0.10.0  (the sanctioned exception)
   ─────────────────────────────────────────────────────────────────────────
   This file is the ONE place in Artificer where the "no looping decoration"
   and "no raw color" rules are deliberately relaxed. Read themes/CLAUDE.md
   § Whimsy before reaching for it.

   What it is: a flowing sine-wave rainbow — the feeling of Claude Code's
   "ultrathink" shimmer — for *user-defined fun elements* and whimsical
   operations (celebrations, easter eggs, the brand wordmark, long "thinking"
   states). Opt-in only. Never on chrome, never on anything load-bearing.

   Two palettes ship:
     • DEFAULT (spectrum) — a BURNISHED full-spectrum rainbow, generated in
       oklch at the palette's own lightness + chroma. The "normal RGB" look,
       kept in-family. Dial --whimsy-c up only when you mean it.
     • .whimsy--brand — cycles through Artificer's REAL semantic tokens
       (gold → rose → brand-purple → steel → green → gold). It reads the live
       CSS vars, so it tracks light/dark for free and is unmistakably ours.
       More tonal variation than the spectrum by design — the brand colors
       aren't uniform lightness, so the brand cycle breathes.

   Configure easily: --whimsy-gradient IS the knob. Override it on any scope to
   bring your own palette, e.g.
     style="--whimsy-gradient: linear-gradient(100deg, var(--success), var(--accent), var(--success))"
   The two presets above are just two values of that one variable.

   Reduced-motion: the flow stops, the burnished gradient stays. Still joyful,
   still accessible. Whimsy text is display/bold only — never body copy.
   Depends on artificer.css (--ease, --font-mono, brand tokens).
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  /* The knobs. Override on a scope to retune locally. */
  --whimsy-l:      0.80;    /* lightness of the spectrum (dark mode)   */
  --whimsy-c:      0.115;   /* chroma — palette-muted. ~0.20 = vivid.  */
  --whimsy-angle:  100deg;  /* gradient direction across the text      */
  --whimsy-speed:  7s;      /* one full hue cycle. Calm, not frantic.  */
  --whimsy-bob:    2.6s;    /* sine-wave bob period (wave variant)     */
  --whimsy-rise:   0.16em;  /* sine-wave bob amplitude                 */
  --whimsy-settle-speed: 150s; /* glacial rest cycle — ~2.5min, sub-perceptual */

  /* Burnished spectrum — hue stops walk Artificer's own brand hues and loop
     (first == last so the flow is seamless). Built lazily from the knobs,
     so a light-mode --whimsy-l/c override below just works. */
  --whimsy-gradient: linear-gradient(
    var(--whimsy-angle),
    oklch(var(--whimsy-l) var(--whimsy-c)  85),   /* gold        */
    oklch(var(--whimsy-l) var(--whimsy-c) 150),   /* apothecary  */
    oklch(var(--whimsy-l) var(--whimsy-c) 200),   /* cyan        */
    oklch(var(--whimsy-l) var(--whimsy-c) 235),   /* steel       */
    oklch(var(--whimsy-l) var(--whimsy-c) 290),   /* brand purple*/
    oklch(var(--whimsy-l) var(--whimsy-c) 340),   /* rose        */
    oklch(var(--whimsy-l) var(--whimsy-c)  30),   /* amber/brick */
    oklch(var(--whimsy-l) var(--whimsy-c)  85)    /* → gold      */
  );

  /* Brand palette — the system's real tokens in motion. Auto-tracks theme
     because it references the live vars. First == last so the loop is seamless. */
  --whimsy-gradient-brand: linear-gradient(
    var(--whimsy-angle),
    var(--accent,       #dbbb6f),               /* gold        */
    var(--attention,    #c4808a),               /* rose        */
    var(--brand-purple, #5a3a9a),               /* purple      */
    var(--steel,        #b8cad4),               /* steel       */
    var(--success,      #4a8a5e),               /* apothecary  */
    var(--accent,       #dbbb6f)                /* → gold      */
  );

  /* Sine-wave mask for the rainbow rule (one period = 64px). */
  --whimsy-wave-mask: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='64' height='22' viewBox='0 0 64 22'><path d='M0 11 Q 8 2 16 11 T 32 11 T 48 11 T 64 11' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round'/></svg>");
}

/* Light mode (ivory paper) — the spectrum must darken to read on cream.
   Same hues, lower lightness, a touch more chroma so it stays present. */
:root[data-theme="light"],
:root:not([data-theme]) {
  --whimsy-l: 0.80;
}
:root[data-theme="light"] {
  --whimsy-l: 0.52;
  --whimsy-c: 0.135;
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) { --whimsy-l: 0.52; --whimsy-c: 0.135; }
}

/* Silver inverts on cream: warm graphite/ink so it reads on the paper bg,
   with a faint bone undertone (hue ~75) instead of a cold grey. */
:root[data-theme="light"] .whimsy--silver {
  --whimsy-gradient: linear-gradient(
    var(--whimsy-angle),
    oklch(0.42 0.012 75),
    oklch(0.26 0.012 75),
    oklch(0.46 0.012 75),
    oklch(0.22 0.012 75),
    oklch(0.42 0.012 75)
  );
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) .whimsy--silver {
    --whimsy-gradient: linear-gradient(
      var(--whimsy-angle),
      oklch(0.42 0.012 75),
      oklch(0.26 0.012 75),
      oklch(0.46 0.012 75),
      oklch(0.22 0.012 75),
      oklch(0.42 0.012 75)
    );
  }
}

/* ─── The signature: flowing burnished rainbow on text ─────────────────────
   Apply to a word or short phrase set large + bold. The gradient is clipped
   to the glyphs and flows. This is the "ultrathink" look. */
.whimsy,
.whimsy-char {
  background-image: var(--whimsy-gradient);
  background-size: 200% 100%;
  background-repeat: repeat;     /* TILE — so glyphs are always covered.
                                    no-repeat let the gradient scroll off and
                                    the (transparent) text vanish, then pop. */
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  animation: whimsy-flow var(--whimsy-speed) linear infinite;
  /* recommended, not enforced — gradients read best heavy + tight */
  font-weight: 700;
}
@keyframes whimsy-flow {
  from { background-position:   0% 50%; }
  to   { background-position: 200% 50%; }   /* exactly one tile → seamless loop */
}

/* ─── Wordmark integration: carry the gradient through to the burnished stop ─
   When .wordmark and .whimsy share one element, -webkit-text-fill-color:
   transparent is inherited by ::after but background-image is not, so the
   accent stop vanishes behind a transparent fill with no gradient to clip to.
   Re-establish the same gradient plumbing on ::after here so the stop flows
   with the mark. The :not(.wordmark--stop-none) guard ensures period-bearing
   marks that suppress the pseudo are unaffected even when .whimsy is present.
   animation-delay: 0 is implicit (matches the parent's start position) so
   the stop hue stays continuous with the flowing mark. */
.wordmark.whimsy:not(.wordmark--stop-none)::after {
  background-image: var(--whimsy-gradient);
  background-size: 200% 100%;
  background-repeat: repeat;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  animation: whimsy-flow var(--whimsy-speed) linear infinite;
}

/* Vivid — for the rare moment you want full saturation. Dial consciously. */
.whimsy--vivid { --whimsy-c: 0.21; }

/* Brand cycle — swap the spectrum for Artificer's real semantic colors.
   Works on .whimsy text, .whimsy--wave, and .whimsy-rule alike (they all read
   --whimsy-gradient). Brand mode ignores --whimsy-c/l — it uses the tokens. */
.whimsy--brand { --whimsy-gradient: var(--whimsy-gradient-brand); }

/* ─── Silver: a near-neutral metal sheen — the most restrained whimsy ──────
   Black/white/grey/silver on dark; warm graphite on cream (text must contrast
   its surface, so light mode inverts to ink-with-a-bone-undertone, not bone).
   Quiet enough to consider for section headers. Defaults SLOW — a dignified
   sheen, not a sweep. For a still metallic fill (recommended header default,
   no motion at all) add .whimsy--no-flow. Ignores --whimsy-c. */
.whimsy--silver {
  --whimsy-speed: 16s;
  --whimsy-gradient: linear-gradient(
    var(--whimsy-angle),
    oklch(0.66 0.006 255),   /* grey            */
    oklch(0.88 0.005 255),   /* silver          */
    oklch(0.74 0.006 255),   /* steel-grey      */
    oklch(0.96 0.004 255),   /* near-white sheen */
    oklch(0.66 0.006 255)    /* → grey (loop)   */
  );
}

/* ─── Wave variant: per-character sine bob + travelling hue ────────────────
   Needs Whimsy.hydrate() (artificer-whimsy.js) to split text into
   .whimsy-char spans and stagger --d. Without JS it degrades to a flat
   gradient on the original text — still fine. */
.whimsy--wave > .whimsy-char {
  display: inline-block;
  white-space: pre;            /* preserve spaces between split chars */
  animation-name:            whimsy-flow,        whimsy-bob;
  animation-duration:        var(--whimsy-speed), var(--whimsy-bob);
  animation-timing-function: linear,             var(--ease);
  animation-iteration-count: infinite,           infinite;
  animation-delay: var(--d, 0s), var(--d, 0s);
  will-change: transform;
}
@keyframes whimsy-bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(calc(-1 * var(--whimsy-rise))); }
}

/* ─── Rainbow rule: a sine wave the spectrum flows through ─────────────────
   A divider / flourish for section breaks and celebration moments. */
.whimsy-rule {
  display: block;
  width: 100%;
  height: 22px;
  border: 0;
  margin: var(--s-lg) 0;
  background-image: var(--whimsy-gradient);
  background-size: 200% 100%;
  background-repeat: repeat;
  animation: whimsy-flow var(--whimsy-speed) linear infinite;
  -webkit-mask-image: var(--whimsy-wave-mask);
          mask-image: var(--whimsy-wave-mask);
  -webkit-mask-repeat: repeat-x;
          mask-repeat: repeat-x;
  -webkit-mask-size: 64px 22px;
          mask-size: 64px 22px;
  -webkit-mask-position: center;
          mask-position: center;
}
.whimsy-rule--sm { height: 16px; -webkit-mask-size: 48px 16px; mask-size: 48px 16px;
  -webkit-mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='48' height='16' viewBox='0 0 48 16'><path d='M0 8 Q 6 1 12 8 T 24 8 T 36 8 T 48 8' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/></svg>");
          mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='48' height='16' viewBox='0 0 48 16'><path d='M0 8 Q 6 1 12 8 T 24 8 T 36 8 T 48 8' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/></svg>"); }

/* ─── Glow accent (optional) — a soft halo for hero whimsy moments ─────────
   Use sparingly; pairs with .whimsy on a large wordmark. */
.whimsy--glow { filter: drop-shadow(0 0 10px oklch(var(--whimsy-l) var(--whimsy-c) 290 / 0.35)); }

/* ─── One-shot ignite state (Whimsy.watch / .celebrate toggle this) ────────
   A trigger word matched, or a whimsical operation fired. */
.is-whimsical { /* host hook — JS adds .whimsy to the target itself */ }

/* ─── Per-effect toggles — flip any one layer off independently ────────────
   Whimsy is layered: hue FLOW, the per-char BOB, and the optional GLOW halo.
   Each switches off on its own; the others keep going. (Glow is itself opt-in
   via .whimsy--glow, so "glow off" is just leaving the class off.) */

/* Flow off — freeze the hue on a held slice. Gradient stays, motion stops. */
.whimsy--no-flow.whimsy,
.whimsy--no-flow.whimsy-rule { animation: none; background-position: 30% 50%; }
.whimsy--no-flow.whimsy--wave > .whimsy-char {
  animation-name: whimsy-bob;            /* keep the bob, drop the flow */
  animation-duration: var(--whimsy-bob);
  animation-timing-function: var(--ease);
  animation-iteration-count: infinite;
  background-position: 30% 50%;
}
/* Bob off — wave chars hold still; the hue keeps flowing. */
.whimsy--no-bob.whimsy--wave > .whimsy-char {
  animation-name: whimsy-flow;           /* keep the flow, drop the bob */
  animation-duration: var(--whimsy-speed);
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  transform: none;
}

/* ─── Settle — after a finite run, whimsy comes to rest ────────────────────
   Whimsy.watch / .run / .scheduleSettle add one of these after N hue-cycles
   so a moment doesn't loop forever. Two rests, both honoring the doctrine
   that whimsy decorates and then gets out of the way:

     .whimsy--settled  Motion stops cold; a burnished gradient stays (static).
                       Same held slice as reduced-motion — a frozen rainbow.
     .whimsy--glacial  Motion "stops" perceptually: the hue drifts through one
                       full cycle over --whimsy-settle-speed (default 150s,
                       ~2.5min), and ALL secondary motion (bob) is off. It
                       never quite stills, but you'd never catch it moving. */

.whimsy--settled.whimsy,
.whimsy--settled.whimsy-rule,
.whimsy--settled .whimsy-char {
  animation: none !important;
  background-position: 30% 50% !important;
}
.whimsy--settled .whimsy-char { transform: none !important; }

.whimsy--glacial.whimsy,
.whimsy--glacial.whimsy-rule,
.whimsy--glacial .whimsy-char {
  animation-name: whimsy-flow !important;
  animation-duration: var(--whimsy-settle-speed) !important;
  animation-timing-function: linear !important;
  animation-iteration-count: infinite !important;
}
.whimsy--glacial .whimsy-char { transform: none !important; }

/* ═══ Accessibility — flow stops, burnish stays ═══════════════════════════
   The glacial selectors are listed explicitly (#221): .whimsy--glacial.whimsy
   re-establishes animation-* longhands !important at (0,2,0), which out-ranks
   a bare .whimsy guard at (0,1,0) — among !important declarations, specificity
   decides per property. Matching the compound here ties the specificity, and
   this block's later source position wins the tie: under reduced motion,
   glacial degrades to the --settled rest (static burnish), as doctrine #6
   demands. Found in the field by a consumer on the 0.10.1→0.18.0 upgrade. */
@media (prefers-reduced-motion: reduce) {
  .whimsy, .whimsy-char, .whimsy-rule,
  .whimsy--glacial.whimsy,
  .whimsy--glacial.whimsy-rule,
  .whimsy--glacial .whimsy-char {
    animation: none !important;
    background-position: 30% 50%;   /* hold a pleasant slice of spectrum */
  }
  .whimsy--wave > .whimsy-char { transform: none !important; }
}


/* ─── Whimsy focus ring (opt-in · explicit request only) ───────────────────
   A burnished ring that circles a focused control — delight layered OVER
   the standard :focus-visible outline, never replacing it. The system
   outline still guarantees WCAG focus visibility; this spins on top. It is
   the ONE sanctioned whimsy use on an interactive control, and only when the
   owner explicitly asks (see themes/CLAUDE.md § Whimsy). One per view.
   Reduced-motion: the ring holds still (still visible); the outline still
   does the a11y work. Reuses --whimsy-l/c/speed — no new tokens. */
@property --whimsy-spin { syntax: "<angle>"; inherits: false; initial-value: 0deg; }
.whimsy-focus { position: relative; }
.whimsy-focus::after {
  content: ""; position: absolute; inset: -5px; border-radius: inherit;
  padding: 2px; pointer-events: none;
  background: conic-gradient(from var(--whimsy-spin),
    oklch(var(--whimsy-l) var(--whimsy-c)  85),
    oklch(var(--whimsy-l) var(--whimsy-c) 200),
    oklch(var(--whimsy-l) var(--whimsy-c) 290),
    oklch(var(--whimsy-l) var(--whimsy-c) 340),
    oklch(var(--whimsy-l) var(--whimsy-c)  30),
    oklch(var(--whimsy-l) var(--whimsy-c)  85));
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
  opacity: 0; transition: opacity var(--dur-fast) var(--ease);
}
.whimsy-focus.whimsy--brand::after {
  background: conic-gradient(from var(--whimsy-spin),
    var(--accent), var(--attention), var(--brand-purple),
    var(--steel), var(--success), var(--accent));
}
.whimsy-focus:focus-visible::after,
.whimsy-focus:hover::after {
  opacity: 1;
  animation: whimsy-spin var(--whimsy-speed) linear infinite;
}
@keyframes whimsy-spin { to { --whimsy-spin: 360deg; } }
@media (prefers-reduced-motion: reduce) {
  .whimsy-focus:focus-visible::after,
  .whimsy-focus:hover::after { animation: none; opacity: 1; }
}


/* ═══ Word vs character gradient — granularity (v0.11.0) ══════════════════════
   Flow has two granularities. DEFAULT = WORD gradient: one continuous ramp
   across the whole element (bare .whimsy) — colour ramps through and between
   letters. OPT-IN = CHARACTER gradient: the gradient is mapped per glyph
   (Whimsy.hydrate() / data-whimsy="wave" → .whimsy-char cells, each painting its
   own slice). Word is the default; character is the opt-in capability the
   travelling wave/bob rides on. See CLAUDE.md § Whimsy. ════════════════════════════════════════════════════════ */

/* Tiling sine for rainbow underlines — one clean period per 40px; start/end at
   the same phase so repeat-x butts seamlessly. */
:root { --whimsy-sine: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='40' height='10' viewBox='0 0 40 10'><path d='M0 5 Q 10 1.5 20 5 T 40 5' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round'/></svg>"); }

/* .whimsy--gold — a metal palette, cousin of .whimsy--silver. Slow (16s) so it
   reads as near-static metal; add .whimsy--no-flow for a still gold fill. Metal
   palettes pair best with a CHARACTER gradient (per-glyph metal = brushed/faceted). */
.whimsy--gold {
  --whimsy-speed: 16s;
  --whimsy-gradient: linear-gradient(var(--whimsy-angle),
    oklch(0.72 0.12 82), oklch(0.90 0.10 92), oklch(0.66 0.13 72),
    oklch(0.95 0.06 96), oklch(0.72 0.12 82));
}
:root[data-theme="light"] .whimsy--gold {
  --whimsy-gradient: linear-gradient(var(--whimsy-angle),
    oklch(0.56 0.12 72), oklch(0.68 0.13 84), oklch(0.47 0.12 66),
    oklch(0.73 0.11 88), oklch(0.56 0.12 72));
}

/* .whimsy-underline — a flowing rainbow sine that draws in under a link, in
   lockstep with the mark's own flow. Contiguous tiling sine, faded ends,
   travels; reveals L→R. Put it on the text element (made inline-block) so it
   shares the mark's exact width + gradient. The sanctioned replacement for a
   flat link underline on a .whimsy wordmark — see CLAUDE.md § Whimsy + § Brand. */
.whimsy-underline { position: relative; display: inline-block; }
.whimsy-underline::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -0.3em; height: 10px;
  background-image: var(--whimsy-gradient); background-size: 200% 100%;
  animation: whimsy-flow var(--whimsy-speed) linear infinite, whimsy-wave-travel 3.2s linear infinite;
  -webkit-mask-image: var(--whimsy-sine), linear-gradient(90deg, transparent, #000 14%, #000 86%, transparent);
          mask-image: var(--whimsy-sine), linear-gradient(90deg, transparent, #000 14%, #000 86%, transparent);
  -webkit-mask-repeat: repeat-x, no-repeat; mask-repeat: repeat-x, no-repeat;
  -webkit-mask-size: 40px 10px, 100% 100%; mask-size: 40px 10px, 100% 100%;
  -webkit-mask-position: left center, center; mask-position: left center, center;
  -webkit-mask-composite: source-in; mask-composite: intersect;
  clip-path: inset(0 100% 0 0); opacity: 0;
  transition: clip-path var(--dur-max) var(--ease), opacity var(--dur-fast) var(--ease);
}
.whimsy-underline:hover::after,
.whimsy-underline:focus-visible::after { clip-path: inset(0 0 0 0); opacity: 1; }
/* --on — latch the underline drawn-in permanently (the standing ".poke"). For
   whimsy that doesn't wait for a pointer: the Pride greeting wears it all month.
   Same revealed end-state as :hover; reduced-motion still freezes the flow
   (the rule below) while leaving it visible. */
.whimsy-underline--on::after { clip-path: inset(0 0 0 0); opacity: 1; }
@keyframes whimsy-wave-travel {
  from { -webkit-mask-position: 0 center, center;     mask-position: 0 center, center; }
  to   { -webkit-mask-position: -40px center, center; mask-position: -40px center, center; }
}
@media (prefers-reduced-motion: reduce) {
  .whimsy-underline::after { animation: none !important; background-position: 30% 50% !important; }
}
