Skip to content

UI redesign: Graphite — flat surfaces, real contrast, no more neon - #5

Merged
patrickdundas merged 5 commits into
mainfrom
feat/ui-redesign
Jul 29, 2026
Merged

UI redesign: Graphite — flat surfaces, real contrast, no more neon#5
patrickdundas merged 5 commits into
mainfrom
feat/ui-redesign

Conversation

@patrickdundas

Copy link
Copy Markdown
Owner

UI redesign: "Graphite" — flat surfaces, real contrast, no more neon

The old UI was muddy, low-contrast, and dated. This PR replaces the dark-neumorphism + neon-cyan theme with a flat graphite system, concentrated almost entirely in theme/token primitives so the diff stays reviewable and upstream syncs stay cheap.

All screenshots below are real renders of a local instance seeded with 30 days of demo data (4 trackers, one in a warned/HnR state).

What was wrong (measured, not vibes)

Problem Before After
Timestamps, torrent-table cells (10px mono) #475569 on #282a36 = 1.88:1 #858991 on #1c1d1f = 4.81:1
Description text, labels (text-tertiary) #64748b = 3.02:1 #9b9fa5 = 6.34:1
Secondary text #94a3b8 = 5.55:1 #bec1c6 = 9.34:1
Card vs page separation None — card bg == page bg (#282a36 both); separation done only by neumorphism.io double shadows Page #121315, cards #1c1d1f + solid 1px hairline borders
Borders 8% alpha (near-invisible) Solid #313337 / emphasis #4a4d53
Accent Neon cyan #00d4ff + glow box-shadows + pulse-glow animations Muted steel blue #67a5d9, glows removed everywhere
Chart "Downloaded" series getComplementaryColor() hue-rotation → clashing magenta against green trackers Semantic download orange (with a hue-collision fallback for warm tracker colors)
Type Space Mono (typewriter), 10px dense cells JetBrains Mono, dense tier bumped to 11px
Radii 8–24px bubbly 6–12px

Before

before dashboard
before charts

Directions considered

The token layer was first refactored so every color/radius/shadow flows through runtime --tt-* CSS vars (zero visual change). That made it possible to render each candidate direction against the real running app — these are actual screenshots, not mocks:

Direction Rendered
A (shipped) Graphite — pure neutral grays, flat bordered cards, muted steel-blue accent, color reserved for warn/danger/success option a
B Slate — visibly blue-gray surfaces, softer/rounder elevation, keeps a teal accent option b
C Porcelain — the same Graphite structure rendered light (what a follow-system light mode would look like) option c

A was chosen because it matches the taste you've picked before (Graphite palette, exception-based status color, dense-data legibility first). Because of the token indirection, switching to B or C later is a ~40-line value swap in globals.css :root plus the CHART_THEME hexes — say the word.

After

after dashboard
after tracker
after charts
after login

What changed

  • globals.css:root --tt-* token block (new palette), nm-* utilities redefined as flat hairline ring + subtle shadow (names kept, so hover:/active: variants and all ~140 call sites work unchanged), card-accent corner-glow → 1px tracker-tinted ring, pulse-glow made opacity-only, --text-2xs 10→11px, new nm-focus utility.
  • Inputs got a real focus ring (focus:nm-focus) — the old focus:nm-inset was a no-op (WCAG 2.4.7 failure, pre-existing but masked by the recessed-well look).
  • Primary buttons are now filled accent with dark text — previously the primary CTA was visually identical to a disabled button.
  • Card.tsx — removed drop-shadow glow hazes; dead glow/glowColor props removed; TrackerStatusBanner now signals attention with tinted background + border (exception-based color doing its actual job).
  • Glow sweep: StatCard, ProgressBar, RankProgress, DownloadClientStatusWidget, BackToTop (which still hand-copied the old neumorphic shadow values), and ~20 chart files (colored shadowBlur/shadowColor).
  • CHART_THEME (charts/lib/theme.ts) — full palette swap; surface repointed to the card color (it's used as seam/background inside cards — it was the page color, which was fine only while page==card); tooltip dot glow removed; hover emphasis no longer dims bars/donut slices.
  • Fonts: Space Mono → JetBrains Mono (--font-mono unchanged).
  • Logo SVG gradient: neon cyan→lime retargeted to steel-blue→gray. This is the one brand-asset change — trivially revertable if you want the original back (git checkout origin/main -- public/img/trackerTracker_logo*.svg).
  • DEFAULT_TRACKER_COLOR and the coordinator fallback: #00d4ff#67a5d9 (new trackers no longer default to neon cyan).

Deliberately left alone

  • src/lib/adapters/**, .github/workflows/**, package.json overrides, .trivyignore — other workstream.
  • src/lib/db/schema.ts tracker-color default (still #00d4ff) — changing a column default invites migration churn for a cosmetic default; the JS-side default covers the UI path.
  • Per-tracker user-chosen colors (your saturated greens/reds) — that's data, not theme.
  • Duplicate y-axis tick labels on some charts (e.g. "3.1, 3.1, 3.0, 3.0") — pre-existing formatter precision issue, separate fix.
  • The "Download Clients" settings tab label wrapping — pre-existing.
  • nm-* class names now describe an aesthetic that no longer exists — a mechanical rename is a good follow-up PR, kept out of this one to keep the diff reviewable.
  • The seeded/demo instance, screenshots, and audit tooling live outside the repo except docs/ui-redesign/ (the images embedded here).

Decisions for you

  1. Direction: A is shipped; B (slate) and C (light) are rendered above and cheap to switch to.
  2. Logo: kept the mark, swapped the neon gradient stops. Revert or keep?
  3. Follow-system light mode: the token indirection makes it a contained follow-up (light :root block + prefers-color-scheme + a chart-theme resolver). Want it?

🤖 Generated with Claude Code

patrickdundas and others added 3 commits July 29, 2026 00:01
Replace the dark-neumorphism + neon-cyan theme with a flat graphite system:

- Theme tokens routed through runtime --tt-* CSS vars in :root (enables
  runtime palette previews and a future follow-system light mode), then
  swapped to the new palette: page #121315, cards #1c1d1f, solid hairline
  borders, text tiers all WCAG AA (muted 1.88:1 -> 4.81:1, tertiary
  3.02:1 -> 6.34:1), accent #00d4ff neon cyan -> #67a5d9 steel blue.
- nm-* utilities redefined as flat inset-ring + subtle shadow (names kept
  so all call sites and hover:/active: variants work unchanged); raised
  tier now visibly distinct for hover; new nm-focus utility gives inputs
  a real focus ring (focus:nm-inset was a no-op).
- Primary buttons are filled accent with dark text (previously identical
  to disabled buttons); glow shadows, pulse-glow drop-shadows and all
  colored halos removed (Card, StatCard, ProgressBar, RankProgress,
  DownloadClientStatusWidget, BackToTop's hand-copied neumorphic values).
- TrackerStatusBanner signals attention via tinted bg + border instead of
  the removed glow; Card's dead glow/glowColor props removed.
- Space Mono -> JetBrains Mono; dense text tier 10px -> 11px; radii
  6/8/10/12; logo gradient stops de-neoned; DEFAULT_TRACKER_COLOR and
  client fallback updated to the new accent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- CHART_THEME hexes swapped to the graphite palette; surface repointed to
  the card color (it is used as seam/background inside cards and was only
  correct while page bg == card bg); fontSizeDense synced to 11px.
- Tooltip dot glow removed; colored shadowBlur/shadowColor emphasis
  removed across ~20 chart files (neutral black depth shadows kept).
- Downloaded series no longer uses getComplementaryColor hue rotation
  (which produced clashing neon, e.g. magenta against a green tracker):
  new downloadSeriesColor() helper uses the semantic download orange,
  falling back to sky for warm tracker accents within ~40deg of it.
- Cross-seed donut secondary slice uses neutral gray; hover emphasis now
  raises salience (border/scale) instead of dimming via opacity.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rendered evidence for the graphite redesign PR: before/after captures of
a demo-seeded local instance, plus the three palette directions rendered
against the live app via the runtime token layer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Knip Code Analysis

Found 9 total issues

Category Count
Unused Dependencies 1
Unused Dev Dependencies 3
Unused Exports 5
View details

Run pnpm knip locally to see the full report.

Use pnpm knip:filter pattern to filter results by file path.


Use /** @public */ JSDoc tags to mark intentionally exported symbols.

@github-actions

Copy link
Copy Markdown

🚫 Security audit failed

Critical Failures

Check File Details
External fetch calls have timeouts src/lib/adapters/btn.ts:69 fetch() call without a timeout signal — add signal: AbortSignal.timeout(ms)

Warnings

Check File Details
No swallowed errors in catch blocks src/lib/adapters/iptorrents.ts:108 catch block swallows error without logging or re-throwing
No swallowed errors in catch blocks src/lib/adapters/iptorrents.ts:114 catch block swallows error without logging or re-throwing
No swallowed errors in catch blocks src/lib/adapters/iptorrents.ts:175 catch block swallows error without logging or re-throwing
No swallowed errors in catch blocks src/lib/adapters/iptorrents.ts:181 catch block swallows error without logging or re-throwing

Passed (36/38)

  • ✅ Auth enforcement on protected routes (per-handler)
  • ✅ No dangerous functions (eval, innerHTML, etc.)
  • ✅ No hardcoded secrets in source
  • ✅ Security headers in next.config.ts
  • ✅ Cookie security (httpOnly, sameSite, secure)
  • ✅ No sensitive fields in API responses
  • ✅ No .env files committed to repo
  • ✅ No raw SQL in API routes
  • ✅ No fetch/redirect with unvalidated URLs in routes
  • ✅ Timing-safe comparison for secret values
  • ✅ No raw SQL migration files (schema-first only)
  • ✅ Docker container runs as non-root user
  • ✅ Public routes match proxy allowlist
  • ✅ File delete operations have path traversal defense
  • ✅ Password hashing uses Argon2 (not SHA-256/bcrypt)
  • ✅ Encrypted columns written via encrypt()
  • ✅ TOTP 2FA flow integrity
  • ✅ Emergency lockdown flow integrity
  • ✅ Scrub & delete (nuke) flow integrity
  • ✅ Backup restore flow integrity
  • ✅ Login flow integrity
  • ✅ Auth result checked before proceeding
  • ✅ Backup password inputs bounded before key derivation
  • ✅ Webhook delivery fetch uses redirect: "error"
  • ✅ SESSION_SECRET minimum-length guard in auth/crypto modules
  • ✅ Notification URL validators include SSRF protection
  • ✅ Dockerfile does not COPY sensitive files
  • ✅ No secret env vars in client components
  • ✅ Adapter Cookie headers guard against injection
  • ✅ Adapter files do not log credential values
  • ✅ No console.log in API routes
  • ✅ No TODO/FIXME in security-critical files
  • ✅ JSON.parse wrapped in try-catch
  • ✅ Request body size validation on upload routes
  • ✅ BigInt fields use string serialization
  • ✅ No raw error messages in API responses

Summary: 36/38 checks passed
1 critical failure(s)
— 1 warning(s)

See scripts/security-audit.ts for check definitions and SECURITY.md for the full security architecture.

patrickdundas and others added 2 commits July 29, 2026 09:52
Merges current main first, so this carries the dependency CVE fixes
(next 16.2.12, postcss, sharp, ip-address) and the schema-deps --prod
Dockerfile change alongside the redesign.

release.yml only publishes when package.json's version has no matching
GitHub Release, so the redesign needs its own bump to reach the server.

Verified after the merge: 2864/2865 tests pass, tsc clean. The single
failure is the pre-existing timezone-brittle chart-transforms case, which
fails outside UTC and passes in CI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@patrickdundas
patrickdundas merged commit 581655b into main Jul 29, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant