12 KiB
Veskslop UI — Nocturne fidelity pass
The shell (header, rail, call strip, composer, TTS box, console, right column)
is faithful to the design. The drift is all in the layer added since: the Media
archive and Bot tabs, plus a few spots in style.css.
Part two (icons, settings, menus, dialogs, toasts) starts further down.
Install
<link rel="stylesheet" href="nocturne.css" />
<link rel="stylesheet" href="tokens-status.css" /> <!-- new, additive -->
<link rel="stylesheet" href="style.css" />
<link rel="stylesheet" href="media.css" /> <!-- replaces yours -->
<link rel="stylesheet" href="style-overrides.css" /> <!-- new, load last -->
<link rel="stylesheet" href="settings-popups.css" /> <!-- new, load last -->
<script src="icons.js"></script> <!-- replaces yours -->
<script src="polish.js"></script> <!-- new, after icons.js -->
tokens-status.css, style-overrides.css, settings-popups.css and polish.js
are new files. media.css and icons.js are your files with the changes
applied — diff them, don't assume.
The root cause
Nocturne ships one accent and a neutral ramp. It has no error / warning /
success role and no categorical data scale. So every feature that needed one
invented its own: #e5484d, #ffaa33, #44cc88, #ff5555, #ff8080,
#c04040, #4f8cff, #8a6cff, #ff7a45, #2fbf71, #6b7280, #d29922.
Eighteen hexes, four different reds, none on the system's lightness scale.
tokens-status.css fills that hole properly — danger, warn, ok and a five-step
data scale, generated in OKLCH on Nocturne's own shared lightness scale, so
step 400 of danger carries the same visual weight as step 400 of accent. Chroma
stays low; the accent is still the loudest thing on screen.
Everything else in this patch is downstream of having those tokens.
What changed
1. Colour
- 18 hard-coded hexes replaced with tokens. Zero remain.
- Wrong-hue fallbacks removed.
var(--color-accent-400, #4f8cff)appeared six times.#4f8cffis blue; Nocturne's accent is blurple#9184d9. Any time that fallback fired — a load-order slip, a scoping mistake — the Media and Bot tabs turned a different colour than the rest of the app. Silent, and hard to spot because it only happens when something else is already broken. - The storage bar's five saturated hues become the low-chroma
--color-data-*family. Nocturne's rule is that contrast comes from the tonal ramps, not from saturation; five alarm colours in a 10px bar broke it harder than anything else on screen. --color-mention: #d29922was declared in a stray second:rootblock 400 lines intostyle.css. It now lives with the other tokens as--color-warn-400.
2. Floods
Nocturne outlines; it does not fill.
.vk-toggle-half.vk-toggle-onwas a solidaccent-400panel with#ffftext — the one thing the guide names as a don't. Now tinted and outlined, the same treatment.vk-scope-btn.activealready uses..vk-media-badgewas#e5484dwith white text at 9px. Now adanger-800tint withdanger-200ink at 10px..vk-bot-state-*pills moved from alpha washes over saturated hues to deep ramp fills with full-opacity ink — an alpha wash over an unknown backdrop is how small text loses contrast without anyone noticing.
3. Weight
Nocturne heads cap at 500; hierarchy is size and space. Removed 11 instances of
font: 600 and font-weight: 600 in media.css, plus font-weight: 700 on
the speaking state in style.css. That last one also reflowed the row every
time someone started or stopped talking — the names jittered. Speaking now
reads in accent-200, with the level meter beside it doing the loud work.
4. Edges and radii
border: 1px solid → the system's inset 0 0 0 1px edge, so elevation stays
"hairline plus ambient darkness" rather than a drawn box. Literal 3px / 5px / 6px / 7px radii → --radius-sm / --radius-md. Six different corner radii
were in play across the Bot tab.
5. Focus
nocturne.css sets a 2px accent :focus-visible ring, but every control built
from a bare <button> that resets background and border loses it against
its own surface. Explicitly restored on 18 controls: rail rows, guild marks,
scope buttons, console tabs, chips, stars, mobile tabs, settings summaries,
media links, tree heads, group titles. This is the only keyboard path through
the app — worth checking by tabbing the whole thing once.
6. Contrast
.vk-media-done-notewascolor-mix(neutral-400 55%, transparent)at 10px — roughly 2.4:1. Nowneutral-500..vk-bot-history-entryand friends: 10px mono atneutral-500sits just under 4.5:1 on surface. Lifted one step toneutral-400.- The offline state was the worst of it.
opacity: .5plussaturate(.6)on the entire body and console pushed every label under 3:1 — in the one state where you most need to read what is on screen and work out what died. Now the chrome dims behind a scrim and the type stays legible. Input is still blocked.
7. Status marks that were saying the wrong thing
.vk-dot.errorwasneutral-200— a bright white dot, which reads as healthy. Nowdanger-400..vk-toast.errorand.vk-toast.okdiffered only by one hue step of the same near-white. Now genuinely different roles, plus awarnvariant..vk-presence.idleand dnd both landed onneutral-200, so they were indistinguishable. idle drops a step; dnd keeps the bright dot and gains the bar that makes it readable without colour.
8. Rules
Nocturne's rules fade to transparent over 48px a side. The channel header does
this; the settings panel's dozens of flat inset 0 -1px 0 rules did not, which
is most of why that panel reads as a different product bolted on. Now fading.
Needs a small markup change
The storage bar is colour-only. media.css now carries
.vk-media-usage-legend / .vk-media-usage-key — render one key per segment
beside the bar:
<div class="vk-media-usage-legend">
<span class="vk-media-usage-key image"><i></i>images 4.1 GiB</span>
<span class="vk-media-usage-key gif"><i></i>gifs 0.8</span>
<span class="vk-media-usage-key video"><i></i>video 11.2</span>
<span class="vk-media-usage-key audio"><i></i>audio 0.3</span>
<span class="vk-media-usage-key"><i></i>other 0.1</span>
</div>
Add .over to .vk-media-usage past the warn threshold.
Also: .vk-presence.dnd now exists as a state. If app.js only emits
online / idle / offline, dnd is still rendering as plain online.
Not changed — worth a conversation
The Media and Bot tabs are a second application inside the shell. They don't use the rail, the right column or the console; they take over the whole body with their own toolbar, their own grid, their own modals at their own z-index tier. The patch makes them look like the rest of the app. It doesn't make them behave like it. Worth deciding whether Media is a scope alongside Guild/DM — which is what the segmented control currently implies — or a separate surface that should stop pretending to be one.
Settings is carrying nine sections in one slide-over, several of which
(Flextalk voice profile, Bot audio, Speech) are deep enough to be their own
screens. The <details> grouping is holding for now, but the voice profile
panel already had to escape into its own draggable dialog, which is the usual
sign.
.vk-media-play is a text glyph (font-size 44px) where the rest of the app
uses Phosphor. Small, but it is the only place an icon doesn't match.
Part two — icons, settings, menus, dialogs
9. Icons
icons.js is replaced. The old set was drawn on a 16-unit grid at a fixed 1.3
stroke; the new one is Phosphor geometry on Phosphor's 256 grid.
- Optical weight is now constant. Stroke is computed per render size
(
1.35 / size * 256), so a 44px icon and an 11px icon carry the same line. Before, stroke scaled with the icon and large ones looked heavy. - Round caps and joins throughout — Phosphor regular's terminal, which the old mic, camera and gear each handled differently.
- The gear, mic, camera, refresh and warning were redrawn. The gear was a circle with eight straight spokes; the mic was a rounded rect over an arc. They read as icons now rather than as diagrams.
- 19 icons added: headphones / headphonesSlash (the listening and deafened states, which had no mark of their own), screenshare, waveform, image, film, audio, folder, pause, stop, check, plus, minus, dots, funnel, sliders, download, copy, pencil, trash, pin, popout, info, error, ok, clock.
- Every old name still resolves — aliases cover
microphone,deafen,video,close,settings,tts,magnify— so nothing needs editing. - Unknown names now warn in the console instead of rendering an empty
<svg>. That surfaced one already:starwas used before it was defined, and worked only because a later line patched it in. VK.hydrateIcons(root)fills any empty[data-icon]; it skips anything already rendered, so it won't fightapp.js.
If you would rather ship the real Phosphor package, swap the file for
@phosphor-icons/web and keep VK.icon as a wrapper — the names are Phosphor's
own, so the mapping is one to one.
10. polish.js — what CSS can't reach
Three text glyphs are doing an icon's job. This is a small idempotent pass with a MutationObserver, so it also catches the Media and Bot tabs rebuilding:
<button id="scrape-x">X</button>and its siblings → thexicon, plusaria-label="Close". There were several, in the scrape and clean modals..vk-media-play's ▶ character at 44px → theplayicon..vk-info'stitle→data-tip, so the styled tooltip below is the only one that fires, plusaria-labelandtabindexso the help is reachable at all.
No markup edits. If you'd rather fold these into the source, the script says exactly what to change.
11. Settings, menus, dialogs, toasts
settings-popups.css. Presentation only, no behaviour or markup changes.
- Scrim. 55% flat wash → 72% of the page ground with a 3px blur. The page behind was legible enough that the eye kept reading it.
- Panel. Square left edge →
--radius-lgon the leading corners, the system's hairline, and a 200ms slide from the edge it is attached to. - Head is sticky and sits on a fading rule. Settings scrolls a long way; the title used to leave.
- Section rows. The card was a flat fill with rows running together. Now the system edge, rows separated by rules that fade at both ends, values on tabular numerals so they align down the column.
.vk-infowas an unstyled letteriwith its help text trapped in a native tooltip — half a second of delay, no styling, invisible on touch. It is now a 14px ringed mark with a themed tooltip. Eleven help strings were sitting intitleattributes where most people would never see them.- Collapsible groups. The summary was a bare uppercase line with nothing saying it was a control. It is a row now — hover tint, turning caret, and the body indented against a hairline so nesting is visible.
- Native controls. Selects kept the platform arrow and a 1px border, numbers
kept their spinners, ranges kept the platform track. All three now use the
system's edge, accent thumb and focus ring. The select caret is a mask taking
--color-neutral-400, not a hex baked into a data URI. - Menus get the hairline, a 120ms entrance and an accent bar on the active item instead of a tint with nothing anchoring it.
- Dialogs. The voice-profile drawer was square-topped against the scrim and
the media modals had unrelated radii. Both on
--radius-lgand the system edge now; the profile drawer gains a grab handle, since its head was already the drag target but nothing said so. - Toasts get an icon slot coloured by role from
tokens-status.css. - All of it respects
prefers-reduced-motion.
Worth doing next
.vk-status-value:has(> .vk-select) carries the select caret. If a select ever
ships outside a status row, wrap it in .vk-select-wrap — the rule is there.