aboutsummaryrefslogtreecommitdiffstats
path: root/src/style.css
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-08-02 23:04:16 -0700
committerAdam Soltys <[email protected]>2026-08-02 23:04:16 -0700
commitbae797fb402e0d9b86c43c5c1dd356cb056e2838 (patch)
treec4ad46b037c60132636ee278e6201fd779ed493e /src/style.css
parent24fbf5e2b75efff43a22b129dbe30510b1b34dd0 (diff)
downloadcoinosv3-bae797fb402e0d9b86c43c5c1dd356cb056e2838.tar.gz
coinosv3-bae797fb402e0d9b86c43c5c1dd356cb056e2838.zip
Motion and polish: navigation choreography, counting balance, gold ribbon
The renderer rebuilds the DOM on every render, so naive CSS mount animations would replay on every background repaint. Two primitives fix that: uiChanged() gates one-shot animations to the render where the thing actually happened, and animWindow() lets a rebuild that lands mid-animation RESUME it via a negative animation-delay instead of killing or restarting it. On top of those: every real navigation rises in (screen/tab/subpage/ chat/profile — one keyed check in render()); the balance counts to its new value and breathes green when money arrives; the avatar menu pops from its corner; the move panel unfolds; the success check springs and rings once when its page lands. Micro-transitions on buttons, tabs, segs, inputs, list rows, avatars, and the settings chevrons. One chromatic signature on the monochrome brand: a slow-drifting bitcoin-gold ribbon across the top of the balance card, echoed by a faint warm wash pooling at the top of the page, plus a film-grain overlay for texture. All of it honors prefers-reduced-motion. The nostr sign-in mark also gets a proper inset ostrich and a size bump. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
Diffstat (limited to 'src/style.css')
-rw-r--r--src/style.css62
1 files changed, 62 insertions, 0 deletions
diff --git a/src/style.css b/src/style.css
index ca85a8c..f01df60 100644
--- a/src/style.css
+++ b/src/style.css
@@ -27,6 +27,7 @@
--info-soft: #eef2ff;
--info-line: #d4ddfb;
--radius: 16px;
+ --wash: rgba(247, 147, 26, 0.05); /* warm glow behind the page top */
--mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
--sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
@@ -55,6 +56,7 @@
--toast-bg: #2c323b; /* raised toast that reads against the dark page */
--toast-fg: #f3f5f8;
--tab-bg: #20242b;
+ --wash: rgba(247, 147, 26, 0.07);
--info: #9db4ff;
--info-soft: #1a2030;
--info-line: #2c3a5e;
@@ -406,3 +408,63 @@ hr.divider { margin: 16px 0; }
.chat-avatar.fallback.loading { background: var(--surface2); }
.wallet-row-btn { font-size: 19px; line-height: 1; padding: 8px 12px; }
.chat-avatar.header-ava.fallback { align-items: center; justify-content: center; display: flex; color: var(--muted); }
+
+/* ============================ motion & polish ============================ */
+/* Navigation animates (the renderer adds .anim-page only when the visible
+ page changed); background repaints re-create the DOM silently. */
+@keyframes rise-in { from { opacity: 0; transform: translateY(10px); } }
+.anim-page { animation: rise-in .32s cubic-bezier(.16, .84, .3, 1) both; }
+
+@keyframes menu-pop { from { opacity: 0; transform: translateY(-6px) scale(.95); } }
+.anim-menu { animation: menu-pop .2s cubic-bezier(.2, .9, .3, 1.35) both; transform-origin: top right; }
+
+@keyframes unfold-in { from { opacity: 0; transform: translateY(-10px); } }
+.anim-unfold { animation: unfold-in .28s cubic-bezier(.16, .84, .3, 1) both; }
+
+@keyframes flash-green { 0% { color: var(--green); } 100% { color: inherit; } }
+.amt-flash { animation: flash-green 1.2s ease-out; }
+
+/* the success check pops and rings, once, when its page arrives */
+@keyframes check-pop { 0% { transform: scale(.3); opacity: 0; } 60% { transform: scale(1.12); } 100% { transform: scale(1); opacity: 1; } }
+@keyframes check-ring { from { box-shadow: 0 0 0 0 color-mix(in srgb, var(--green) 45%, transparent); } to { box-shadow: 0 0 0 30px transparent; } }
+.anim-page .check-badge { animation: check-pop .55s cubic-bezier(.34, 1.56, .64, 1) both, check-ring 1.1s ease-out .25s both; }
+
+/* micro-interactions */
+button, .btn { transition: filter .12s, background .15s, border-color .15s, box-shadow .15s, transform .1s; }
+.btn-primary:hover { filter: brightness(1.15); }
+.tabs button, .seg button { transition: background .18s, color .18s, box-shadow .18s; }
+.item { transition: background .15s; border-radius: 10px; }
+.item:hover { background: var(--surface2); }
+.clickable { transition: transform .15s ease; }
+.chat-avatar.clickable:hover { transform: scale(1.07); }
+.header-avatar .chat-avatar { transition: transform .15s ease; }
+.header-avatar:hover .chat-avatar { transform: scale(1.08); }
+input[type=text], input[type=password], input[type=number], textarea, select { transition: border-color .15s, box-shadow .15s; }
+.settings-nav::after { transition: transform .18s ease, color .18s; }
+.settings-nav:hover::after { transform: translateX(3px); color: var(--ink); }
+
+/* the one chromatic signature: a slow bitcoin-gold ribbon on the balance card */
+.balance { position: relative; overflow: hidden; }
+.balance::before {
+ content: ''; position: absolute; inset: 0 0 auto 0; height: 3px;
+ background: linear-gradient(90deg, #f7931a, #ffd166 30%, #f2a900 55%, #ffb84d 75%, #f7931a);
+ background-size: 200% 100%;
+ animation: ribbon 8s linear infinite;
+}
+@keyframes ribbon { to { background-position: -200% 0; } }
+
+/* atmosphere: a faint warm wash pooling at the top, plus film grain */
+body {
+ background-image: radial-gradient(900px 420px at 50% -160px, var(--wash), transparent 70%);
+ background-attachment: fixed;
+}
+body::after {
+ content: ''; position: fixed; inset: 0; pointer-events: none; z-index: 2147483647;
+ opacity: .028;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .anim-page, .anim-menu, .anim-unfold, .amt-flash, .check-badge, .balance::before { animation: none !important; }
+ *, *::before, *::after { transition-duration: .01ms !important; }
+}