Feat/cyber theme #1

Closed
toor wants to merge 0 commits from feat/cyber-theme into main
Owner

合并代码

合并代码
toor added 15 commits 2026-10-19 16:33:10 +08:00
Replace the Pure Ink light/dark/comic theme system with the CYBER
single-theme dark palette from the reviewed prototype: OKLCH surfaces,
one neon-cyan accent, and monospace-first typography.

Arco components were reading colors from arco.css because no
ConfigProvider was mounted, so the theme values in arco-theme.ts never
took effect. Bridge CYBER into Arco through its --color-* variables in
global.scss instead, and align arco-theme.ts to match.

Brightness and style switches are gone: useTheme now pins dark, the
comic font faces and locale keys are removed, and ThemeSwitch.vue is
deleted along with its sidebar slot and display setting.
The previous commit only swapped theme tokens, so buttons kept the solid
blue from arco.css: a-button fills with rgb(var(--primary-6)), which maps
to --arcoblue-6, not --color-primary. Bridge both naming families so no
Arco blue survives.

Style buttons as hollow tubes rather than filled blocks: transparent
fill, 1px cyan wall, inner glow plus two outer halos, and a glowing
label. Hover brightens the wall, active pinches the halo inward,
loading pulses, disabled cuts power entirely. Same treatment in red for
destructive actions, and hollow-line chips for inputs, tags, switches
and popups.

Rebuild LoginView to match the approved prototype: 32px masked grid,
scanline, IsometricWave, neon card edge, badge, probe status lamp, and
default-credential chips. Logic, API calls and lock/reset messaging are
unchanged.
The neon tube rules never applied. Arco emits .arco-btn-primary along
with three attribute variants ([type='button'], [type='submit'],
[type='reset']) that lift specificity to (0,2,0), and their :hover forms
reach (0,3,0) — all of which beat a bare .arco-btn-primary. Every rule
now uses .arco-btn.arco-btn-X[type='...'] so the skin wins regardless of
the rendered html-type; login is a submit button, which is why it stayed
solid.

Verified with jsdom computed styles against the real arco.css.

Also remove the default-credential chips from the login page: printing
admin / 123456 on a sign-in screen leaks the credentials of a reachable
deployment. The one-click fill shortcut stays. The test that asserted
the hint is now shown is inverted to assert it is absent.
Port the sweeping highlight from the prototype: a 38%-wide white band
travels left to right on a 3.4s loop, idle for the first 62% so it reads
as an occasional flare rather than a constant shimmer. The tube mixes
now clip overflow so the band stays inside the button, and the loading
state suppresses it in favour of the existing pulse.

Drop the masked grid and scanline from the login page — low-contrast
texture behind a credential form makes the text harder to read. Only the
IsometricWave remains.

Brighten input text to near-white and bump it to 14.5px; Arco's default
--color-text-1 sits too dark on the recessed input fill. Avoid
-webkit-text-fill-color, which would defeat the password field's masking.
Remove the deployment blurb and the token-storage hint from the sign-in
page. They described implementation details rather than helping anyone
sign in, and the token line exposed the storage mechanism to anyone
looking at the screen. Also removes the now-unused .login-legal rule and
the two locale keys behind it.
Chrome flags the filled-in credentials as an autofill match and paints
its own light background and muted fill over the field, which read as a
white block with grey text against the dark card. Take the fill color
back with -webkit-text-fill-color and lay the theme input color over the
injected background with a large inset shadow.

Safe for the password field: masking is done by -webkit-text-security
rendering dots, which fill-color does not affect.
The angled slab texture came from the first prototype pass and clashed
with the flat neon treatment that replaced it. Drop the component
usage, its decor wrapper and the positioning rules.

The vendored IsometricWave directory stays — it is shared vendor code
and JourneyView may still want it.
The inline boot styles and FOUC script still branched on the removed
light/dark and ink/comic settings, so the first paint flashed the old
light surface before Vue mounted. Match the single dark theme and give
the logo sweep the neon tint.
The nav still used the ink palette: flat fills, oversized rows, and a
tinted background with no marker for the active route. Adopt the CYBER
treatment from the prototype — a 2px glowing cyan bar on the left of the
active item, hairline borders instead of full-contrast ones, monospace
group labels with wide tracking, and a faint cyan wash at the top of the
rail so it does not read as a dead dark slab.

PageSidebarNav gets the same active treatment as AppSidebar so the
conversation routes do not feel like a different product.

Header height stays at 64px — style-system.test.ts asserts it as a
layout contract.
The scroll-to-bottom control, the approval and queue float panels, and
the inline code chips still carried the light-theme fills (#ffffff,
rgba(255,255,255,.94/.68)) with .dark overrides pinned to old greys.
Point all of them at --bg-card and its rgb triple so the alpha layers
keep working, then drop the overrides that became identical to the base
rule.

Low-alpha white overlays are left alone — on a dark surface they read as
a lift, which is the intent.
code-block.scss carried two full highlight.js themes — a light set plus a
.dark override — left over from the ink era, with keywords in violet and
no relationship to the rest of the surface. CYBER has a single dark
theme, so keep one rule set and pull every token color from the existing
semantic variables: cyan for keywords, green for strings, amber for
literals, red for types. Keywords matching the accent keeps a code block
from competing with the chrome.

Diff line chrome also moves off the old slate values onto tokens, and the
code header now reads as part of the block instead of a light strip.
The canvas renderer branched on isDark for every color, but isDark has
been a constant true since the theme switch was removed, so half of each
ternary was unreachable. Resolve the values directly and shift the
hues from the old pale blue to the accent cyan, keeping alpha as the only
thing that varies between idle and focused edges.

useTheme is no longer referenced here and is dropped from the imports.
Dark is now the only theme, so every `.dark` selector matched
unconditionally while each non-dark branch was unreachable. That left
fourteen `.dark &` blocks sitting on top of token-based base rules and
silently overriding them with hardcoded grays from the previous palette
(#333333, #999999, #cccccc, #f2f2f2, #1f1f1f, #2a2a2a). Merged each value
down into its base rule and deleted the wrapper, then replaced fifteen
more legacy grays with the CYBER tokens.

Three bugs surfaced along the way:

- FileEditor read `documentElement.classList.contains('dark')`, but the
  `dark` class is bound on `.app-root`, so Monaco always loaded the
  light `vs` theme inside the dark editor surface.
- CodexLoginModal, CopilotLoginModal and NousLoginModal wrote
  `border-color: var(--arcoblue-6, #165dff)` and VoiceApiFormModal wrote
  `color: var(--primary-6, #18a058)`. Those Arco variables hold bare
  RGB triplets, not colors, so each declaration was invalid and
  discarded — the hover border and link color stayed stock Arco
  blue/green instead of the cyan accent.
- JourneyView drew the pinned-node ring in amber, a leftover from the
  old palette.

Rendering-layer only: no store logic, no /chat-run payload, no message
rendering path.
The token swap made every control dark, but the pages themselves kept
their Arco defaults — bare `padding: 20px` with components laid straight
on the background. That is why the running app still read as "not the
prototype" no matter how many variables were replaced: the prototype's
page structure (panel / panel-h / panel-b / kv / tabs) was never built.

Rather than restate that shell in each of the 25 views, it now lives in
global.scss as .cy-view / .cy-panel / .cy-panel-h / .cy-panel-b /
.cy-kv / .cy-tabs / .cy-tab / .cy-stack / .cy-sec-t, and the settings
components share a global .settings-section + .section-title so all nine
panes pick up the mono section headers at once. The per-component
duplicates of those two rules were deleted.

Settings is converted as the reference case: Arco's a-tabs/a-tab-pane
became the underline tab strip, and the pane body sits in a panel. The
URL-query sync moved to a watcher on activeTab so pane order and deep
links still hold. Pane order is a contract, so it is asserted.

Two tests lock the shell in place — one for the global classes and the
cyan underline selected state, one for the settings structure and pane
order. Without them the next edit easily slides back to bare Arco.
split one accent into three colours that each mean one thing
Playwright / e2e (pull_request) Canceled after 0s
Build / build (pull_request) Canceled after 0s
a623398fe8
The app had a single cyan accent, so every emphasis in the chat chain
was a shade of the same hue. An approval waiting on the user, a tool
that had already run, a streaming response and an outright error all
looked equally urgent — the hue carried no information at all.

The colours are now split by meaning rather than by shade, and the rule
is documented at the top of variables.scss so it survives future edits:

  cyan   the system itself   brand, primary buttons, selection, user bubbles
  amber  needs you           approval cards, context about to fill
  lime   the machine works   tool execution, assistant output, streaming
  red    something failed    errors only — deliberately not a palette member

Red staying out is the load-bearing part: when failure reuses the accent
family the urgency signal disappears entirely.

`--warning` moves from yellow (hue 85) to amber (hue 68) and `--success`
from a muted green to fluorescent lime (hue 128). Because both are global
tokens, every page using them changes hue in the same pass; the Arco
`--green-*` / `--orange-*` bridges are updated with them so solid controls
follow instead of staying behind. Six translucent companions
(`--success-soft`, `--warning-line`, …) were added for fills and borders.

On the chat surface: the approval card goes amber with a slow pulse on
its icon — the only moving part in the chain, marking the one thing that
is genuinely waiting. Tool-change cards and assistant bubbles take a 2px
lime left edge meaning "already ran"; user bubbles get a cyan right edge
so the two sides read apart without labels. The context meter is green
under 60%, amber to 80%, red above; those thresholds already existed and
only the colours were wrong.

Rendering-layer only. No store logic, no /chat-run payload, no markup or
event wiring.
toor closed this pull request 2026-10-19 17:08:11 +08:00
toor deleted branch feat/cyber-theme 2026-10-19 17:08:11 +08:00

Pull request closed

This pull request cannot be reopened because the branch was deleted.
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: toor/hermes-ui#1