From 2d0bb1be366dc4d159b80adb4a4955b5779cea6d Mon Sep 17 00:00:00 2001 From: Nicholas Chadwick Date: Sat, 20 Jun 2026 02:00:04 -0400 Subject: [PATCH] =?UTF-8?q?feat(ui):=20animated=20SVG=20icons=20=E2=80=94?= =?UTF-8?q?=20hover=20lift,=20brand=20glow,=20spin/pulse=20utilities?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Give icons tasteful life without changing their color or default weight. - styles.css: isolated, clearly-commented `.ic` icon block (no :root/body/button edits). Base `.ic` gains a GPU-friendly transform/filter transition. Hover inside interactive controls (rail-btn, btn-mini, ws-recent-item, fb-item) gives a subtle 1px lift + 1.08 scale + on-brand drop-shadow glow; :active settles back for tactile feedback. Action icons get extra spark on hover. - Three opt-in micro-interaction keyframes: `.ic.spin` (loading/refresh), `.ic.ic-pulse` (bolt/spark glow), `.ic.ic-pop` (one-shot appear). All motion is gated by the existing prefers-reduced-motion global reset. - Excluded `.ctool` from the lift group — it has a deliberate color-only icon transition that would have made the lift snap without easing. - icons.ts: signature/behavior unchanged. Action glyphs (bolt, spark, refresh, send, plus, download) auto-tag a `.ic-action` hook class; `extraClass` still works (e.g. icon("refresh", 13, "spin")). Backward-compatible — emits `class="ic"` cleanly instead of a trailing-space `class="ic "`. Co-Authored-By: Claude Opus 4.8 --- desktop/renderer/icons.ts | 8 +++++++- desktop/renderer/styles.css | 40 +++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/desktop/renderer/icons.ts b/desktop/renderer/icons.ts index df94563b..dbfe03e7 100644 --- a/desktop/renderer/icons.ts +++ b/desktop/renderer/icons.ts @@ -68,9 +68,15 @@ const RAW: Record = { download: P("M12 4v9.5") + P("M8 10.5l4 4 4-4") + P("M5 18.5h14"), }; +// Action glyphs that read as "do something now" — they get the .ic-action hook +// so CSS can give them an extra spark on hover. Purely additive: behavior and +// default visual weight are unchanged. +const ACTION = new Set(["bolt", "spark", "refresh", "send", "plus", "download"]); + export function icon(name: keyof typeof RAW | string, size = 18, extraClass = ""): string { const body = RAW[name] ?? RAW.info; - return ``; + const hook = ACTION.has(name as string) ? " ic-action" : ""; + return ``; } /** The LUCID wordmark π glyph used in the titlebar. */ diff --git a/desktop/renderer/styles.css b/desktop/renderer/styles.css index 44f944cb..02fcfa68 100644 --- a/desktop/renderer/styles.css +++ b/desktop/renderer/styles.css @@ -732,3 +732,43 @@ body.resizing .sidebar,body.resizing .inspector{transition:none} /* P10.3 budget chip near-limit warning */ .statusbar .seg.warn{border-color:color-mix(in srgb,var(--red) 45%,var(--line));background:var(--red-dim)} + +/* ───────────────────────── animated icons (unit #2) ───────────────────────── + Self-contained icon motion + glow. Scoped to `.ic` only — color stays owned + by parents (stroke=currentColor). Motion is GPU-friendly (transform/filter) + and globally gated by the prefers-reduced-motion reset near the top of this + file. Do not move color/layout ownership here. */ +.ic{transition:transform var(--t) var(--ease-out),filter var(--t) var(--ease-out); + transform-origin:center;will-change:transform} + +/* Hover lift + on-brand glow when an icon sits inside an interactive control. + Targets the .ic only — never the button body (owned elsewhere). */ +.rail-btn:hover .ic, +.btn-mini:hover .ic, +.ws-recent-item:hover .ic, +.fb-item:hover .ic{ + transform:translateY(-1px) scale(1.08); + filter:drop-shadow(0 0 5px rgba(198,75,214,.45)); +} +/* Action icons get a touch more spark on hover. */ +.btn-mini:hover .ic.ic-action{filter:drop-shadow(0 0 7px rgba(224,123,240,.6))} + +/* Pressed state settles back down for tactile feedback. */ +.rail-btn:active .ic, +.btn-mini:active .ic{transform:translateY(0) scale(.96);transition-duration:var(--t-fast)} + +/* Spin utility — loading/refresh code opts in by adding `spin` via extraClass + (e.g. icon("refresh", 13, "spin")). Cubic, continuous, GPU-only. */ +.ic.spin{animation:icSpin .9s linear infinite} +@keyframes icSpin{to{transform:rotate(360deg)}} + +/* Soft draw/pulse for action glyphs (bolt, spark) — opt in with `ic-pulse`. */ +.ic.ic-pulse{animation:icPulse 2.4s var(--ease) infinite} +@keyframes icPulse{ + 0%,100%{filter:drop-shadow(0 0 2px rgba(198,75,214,.25))} + 50%{filter:drop-shadow(0 0 8px rgba(224,123,240,.7))} +} + +/* One-shot pop for an icon that just appeared / activated — opt in with `ic-pop`. */ +.ic.ic-pop{animation:icPop var(--t-slow) var(--ease-out)} +@keyframes icPop{0%{transform:scale(.6);opacity:.4}60%{transform:scale(1.12)}100%{transform:none;opacity:1}}