diff --git a/DESIGN.md b/DESIGN.md index 1925b63..d86f1f7 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -72,10 +72,21 @@ stage presence, and the streamer is the director. The visuals moved with the met 12. **Nothing in the app is only a color.** Status carries a label as well as a hue (the orbs keep their text at every width), because a row of unlabeled dots is decoration, not status. +13. **Paint is not free.** The app runs with hardware acceleration off on purpose — it shares + a machine, and a GPU, with OBS and a game — so every pixel is rasterised on the CPU and the + cost grows with the window. Effects that force a repaint of a large area on every frame are + therefore banned on anything that scrolls or sits over something that scrolls: + **no `backdrop-filter`** on cards, feeds or bars, and **no `background-attachment: fixed`**. + A non-scrolling background belongs on its own fixed layer (`.studio::before`). Blur is + allowed only on a transient overlay that appears above a still page, like the command + palette. This is not a micro-optimisation: at fullscreen these two rules were the difference + between a 17ms frame and a 220ms one. + ## Tokens ([public/theme.css](public/theme.css)) - **Surfaces:** `--bg` `--bg-2` (base gradient) · `--panel` (solid) · `--glass` `--glass-2` - (translucent fills) · `--border` `--border-strong`. + (translucent fills) · `--glass-card` (card fill; carries the panel on its own, since nothing + blurs behind it) · `--border` `--border-strong`. - **Text:** `--text` `--dim` `--faint`. - **Accent (the human):** `--accent` `--accent-2` `--on-accent` `--accent-soft` `--accent-line`. Themeable at runtime (violet/cyan/emerald/amber/magenta) by overriding these diff --git a/public/dashboard.html b/public/dashboard.html index 59236fa..d80bf7d 100644 --- a/public/dashboard.html +++ b/public/dashboard.html @@ -16,7 +16,9 @@ header { display: flex; align-items: center; gap: 14px; flex: none; padding: 10px 18px; border-bottom: 1px solid var(--border); - background: rgba(10,10,16,.5); backdrop-filter: blur(12px); + /* No blur: the feed scrolls in its own container below this bar, so there + is never anything moving behind it to blur — only cost. */ + background: rgba(10,10,16,.72); flex-wrap: wrap; row-gap: 8px; } header .brand { display: flex; align-items: center; gap: 8px; padding-right: 14px; border-right: 1px solid var(--border); } @@ -226,7 +228,7 @@ #toast.show { opacity: 1; transform: translateX(-50%) translateY(0); } - +
👻Hype Ghost
00:00:00
diff --git a/public/settings.html b/public/settings.html index c741634..f2a28bf 100644 --- a/public/settings.html +++ b/public/settings.html @@ -101,7 +101,7 @@ } - +

⚙ Settings

diff --git a/public/setup.html b/public/setup.html index eeb9b3e..425b9e4 100644 --- a/public/setup.html +++ b/public/setup.html @@ -88,7 +88,7 @@ #energyName { font-weight: 700; font-size: var(--fs-lg); } - +
👻 Hype Ghost 3.0
diff --git a/public/theme.css b/public/theme.css index 103fdda..ac27585 100644 --- a/public/theme.css +++ b/public/theme.css @@ -15,6 +15,7 @@ --panel: #14141d; /* solid card fallback */ --glass: rgba(28, 28, 40, 0.62); /* translucent card fill */ --glass-2: rgba(20, 20, 30, 0.5); /* recessed wells */ + --glass-card: rgba(26, 26, 37, 0.88); /* card fill: opaque enough to read as a panel with no blur behind it */ --border: rgba(255, 255, 255, 0.09); --border-strong: rgba(255, 255, 255, 0.16); @@ -70,12 +71,33 @@ body { margin: 0; color: var(--text); font: var(--fs-base)/1.55 "Inter", "Segoe UI", system-ui, -apple-system, sans-serif; + background: var(--bg); + -webkit-font-smoothing: antialiased; +} + +/* The studio gradient, on its own fixed layer. + It used to be a background-attachment: fixed on , which looks the same + and scrolls appallingly: a fixed attachment cannot be scrolled as a layer, so + the browser repaints the whole viewport's gradient every frame. The app runs + with hardware acceleration off on purpose (it shares a GPU with OBS and a + game), so that repaint is on the CPU, and its cost grows with the window — + which is why it was worst exactly where it hurts, fullscreen. + + Opt-in rather than automatic, and that direction is deliberate: the overlay + is a transparent guest on someone's live stream, and a page that forgets + this class merely looks flat, where an automatic layer would paint over the + broadcast. `background: transparent` on would not save it — a + pseudo-element is not the body's background. */ +.studio::before { + content: ""; + position: fixed; + inset: 0; + z-index: -1; + pointer-events: none; background: radial-gradient(1100px 620px at 82% -8%, rgba(167, 139, 250, 0.12), transparent 60%), radial-gradient(900px 560px at 6% 108%, rgba(90, 209, 255, 0.09), transparent 55%), linear-gradient(180deg, var(--bg-2), var(--bg)); - background-attachment: fixed; - -webkit-font-smoothing: antialiased; } /* ---------- typography ---------- */ @@ -146,9 +168,11 @@ input:focus:not(:focus-visible), textarea:focus:not(:focus-visible), select:focu /* ---------- components ---------- */ .card { - background: var(--glass); - backdrop-filter: blur(14px); - -webkit-backdrop-filter: blur(14px); + /* No backdrop blur. Blurring a backdrop means re-reading and re-blurring it + every frame the card moves, and on a CPU rasteriser that was the single + biggest cause of scroll jank — for an effect that is nearly invisible over + a dark, low-contrast gradient. The fill carries the panel instead. */ + background: var(--glass-card); border: 1px solid var(--border); border-radius: var(--r-xl); padding: 20px 24px;