Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions public/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -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); }
Expand Down Expand Up @@ -226,7 +228,7 @@
#toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
</style>
</head>
<body>
<body class="studio">
<header>
<div class="brand"><span class="g">👻</span><span class="app-title">Hype Ghost</span></div>
<div class="meter"><span id="clock">00:00:00</span><span id="cost"></span></div>
Expand Down
2 changes: 1 addition & 1 deletion public/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
}
</style>
</head>
<body>
<body class="studio">
<div id="shell">
<header>
<h1 class="page-title">⚙ Settings</h1>
Expand Down
2 changes: 1 addition & 1 deletion public/setup.html
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
#energyName { font-weight: 700; font-size: var(--fs-lg); }
</style>
</head>
<body>
<body class="studio">
<div id="card" class="card">
<div class="topline">
<div class="eyebrow">👻 Hype Ghost 3.0</div>
Expand Down
34 changes: 29 additions & 5 deletions public/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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 <body>, 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 <body> 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 ---------- */
Expand Down Expand Up @@ -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;
Expand Down