From 971cc4988e46adfd112a2dc1afe531783a0f8ff8 Mon Sep 17 00:00:00 2001 From: khannurien Date: Sat, 19 Sep 2026 19:25:53 +0000 Subject: [PATCH] =?UTF-8?q?v3:=20modernized=20smooth=20theme=20=E2=80=94?= =?UTF-8?q?=20surface=20ladder,=20hairline=20borders,=20soft=20tabs,=20cal?= =?UTF-8?q?mer=20accents,=20focus=20ring,=20AA=20contrast?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 --- src/App.css | 110 +++++++++++++++++++--------------- src/index.css | 22 +++++-- src/themes/smooth.css | 136 ++++++++++++++++++++++-------------------- src/tokens.css | 60 +++++++++++++++---- 4 files changed, 198 insertions(+), 130 deletions(-) diff --git a/src/App.css b/src/App.css index 277e6556..a63b0db9 100644 --- a/src/App.css +++ b/src/App.css @@ -87,6 +87,20 @@ padding-left: 1.2em; } +/* ── Panels ── + Large surface blocks that have no border of their own. Themes that want + them outlined set --panel-ring (an inset shadow, so blocks that sit flush + against each other keep their spacing). */ +.dump-post-header, +.dump-rich-content, +.form-page-header, +.dump-edit-preview, +.form-page .form, +.playlist-detail-header, +.comment-node-inner { + box-shadow: var(--panel-ring), var(--card-shadow); +} + /* ── Dump detail page ── */ .dump-detail { display: flex; @@ -182,9 +196,9 @@ .profile-email-input, .playlist-edit-input, .playlist-edit-textarea { - border: 2px solid var(--color-border); + border: var(--border-width) solid var(--color-border); border-radius: var(--radius-lg); - background: var(--color-bg); + background: var(--color-surface-sunken); color: var(--color-text); font-family: inherit; outline: none; @@ -253,7 +267,7 @@ display: inline-block; word-break: break-all; padding: 1rem; - border: 2px solid var(--color-border); + border: var(--border-width) solid var(--color-border); border-radius: var(--radius-lg); width: 100%; box-sizing: border-box; @@ -338,8 +352,8 @@ box-sizing: border-box; padding: 0.65rem 1rem; border-radius: var(--radius-lg); - border: 2px solid var(--color-border); - background-color: var(--color-bg); + border: var(--border-width) solid var(--color-border); + background-color: var(--color-surface-sunken); color: var(--color-text); font-size: var(--text-base); font-family: var(--font-body); @@ -376,11 +390,7 @@ .form input:focus, .form textarea:focus { border-color: var(--color-accent); - background-color: color-mix( - in srgb, - var(--color-accent) 4%, - var(--color-bg) 96% - ); + background-color: var(--input-focus-bg); box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 18%, transparent); } @@ -900,7 +910,7 @@ var(--color-accent) 8%, var(--color-surface) 92% ); - border: 2px solid var(--color-border); + border: var(--border-width) solid var(--color-border); border-radius: 0 0 var(--radius-xl) var(--radius-xl); } @@ -920,7 +930,7 @@ width: 100%; border-radius: 0 0 var(--radius-xl) var(--radius-xl); overflow: hidden; - border: 2px solid var(--color-border); + border: var(--border-width) solid var(--color-border); position: relative; } @@ -990,7 +1000,7 @@ var(--color-surface) 92% ); border-radius: 0 0 var(--radius-xl) var(--radius-xl); - border: 2px solid var(--color-border); + border: var(--border-width) solid var(--color-border); width: 100%; box-sizing: border-box; min-width: 0; @@ -1175,7 +1185,7 @@ height: 80vh; min-height: 600px; border-radius: var(--radius-lg); - border: 2px solid var(--color-border); + border: var(--border-width) solid var(--color-border); display: block; } @@ -1205,7 +1215,7 @@ .rich-content-card { display: flex; - border: 2px solid var(--color-border); + border: var(--border-width) solid var(--color-border); border-radius: var(--radius-xl); overflow: hidden; transition: border-color var(--dur-slow); @@ -1263,7 +1273,7 @@ max-width: 860px; z-index: var(--z-modal); background: var(--color-surface); - border: 2px solid var(--color-border); + border: var(--border-width) solid var(--color-border); border-radius: var(--radius-xl); box-shadow: var(--shadow-3); animation: player-enter 0.25s cubic-bezier(0.34, 1.56, 0.64, 1) both; @@ -1787,7 +1797,7 @@ body.has-player .chat-fab { align-items: center; gap: 0.3rem; padding: 0.25rem 0.6rem; - border: 2px solid var(--color-border); + border: var(--border-width) solid var(--color-border); border-radius: var(--radius-md); background: transparent; color: var(--color-text); @@ -1985,7 +1995,7 @@ body.has-player .chat-fab { .profile-tabs-scroller { margin-top: 0.5rem; padding-bottom: 0.5rem; - border-bottom: 2px solid var(--color-border); + border-bottom: var(--border-width) solid var(--color-border); } .profile-columns { @@ -2112,7 +2122,7 @@ body.has-player .chat-fab { .profile-section > h2, .profile-section-header { margin-bottom: 0.75rem; - border-bottom: 2px solid var(--color-border); + border-bottom: var(--border-width) solid var(--color-border); padding-bottom: 0.4rem; } @@ -2565,8 +2575,8 @@ body.has-player .chat-fab { gap: 1rem; padding: 0 1.25rem; min-height: 3.75rem; - background: var(--color-surface); - border-bottom: 2px solid var(--color-border); + background: var(--header-bg); + border-bottom: var(--border-width) solid var(--color-border); } @media (min-width: 860px) { @@ -2614,8 +2624,8 @@ body.has-player .chat-fab { display: flex; align-items: center; padding: 0.5rem 1rem; - background: var(--color-surface); - border-bottom: 2px solid var(--color-border); + background: var(--header-bg); + border-bottom: var(--border-width) solid var(--color-border); } .header-search-below .search-bar { @@ -2792,7 +2802,7 @@ body.has-player .chat-fab { right: 0; min-width: 150px; background: var(--color-surface); - border: 2px solid var(--color-border-subtle); + border: var(--border-width) solid var(--color-border-subtle); border-radius: var(--radius-xl); padding: 0.35rem; display: flex; @@ -2823,7 +2833,7 @@ body.has-player .chat-fab { min-width: 180px; overflow-y: auto; background: var(--color-surface); - border: 2px solid var(--color-border-subtle); + border: var(--border-width) solid var(--color-border-subtle); border-radius: var(--radius-xl); padding: 0.35rem; display: flex; @@ -2882,7 +2892,7 @@ body.has-player .chat-fab { flex-direction: column; gap: 1.25rem; background: var(--color-surface); - border: 2px solid var(--color-border); + border: var(--border-width) solid var(--color-border); border-radius: var(--radius-xl); padding: 2rem; } @@ -3089,14 +3099,12 @@ body.has-player .chat-fab { background: var(--color-accent); color: var(--color-on-accent); border: none; - box-shadow: 0 2px 8px - color-mix(in srgb, var(--color-accent) 40%, transparent); + box-shadow: var(--btn-primary-shadow); } .btn-primary:hover { background: var(--color-accent-hover); - box-shadow: 0 4px 14px - color-mix(in srgb, var(--color-accent) 50%, transparent); + box-shadow: var(--btn-primary-shadow-hover); transform: translateY(-1px); } @@ -3130,6 +3138,7 @@ body.has-player .chat-fab { .btn-danger:hover { background: var(--color-danger-hover); + color: var(--color-on-danger); } /* ── Ghost button — chromeless icon/toggle action (e.g. global player) ── */ @@ -3284,23 +3293,27 @@ body.has-player .chat-fab { .feed-sort-btn { padding: 0.25rem 0.8rem; border-radius: var(--radius-md); - border: 2px solid var(--color-border); + border: var(--border-width) solid var(--tab-border); background: transparent; color: var(--color-text-secondary); cursor: pointer; font-size: var(--text-xs); font-weight: 600; - letter-spacing: 0.04em; - text-transform: uppercase; + letter-spacing: var(--tab-tracking); + text-transform: var(--tab-text-transform); transition: border-color var(--dur-base), background var(--dur-base), color var(--dur-base); white-space: nowrap; flex-shrink: 0; } +.feed-sort-btn:hover:not(.active) { + color: var(--color-text); +} + .feed-sort-btn.active { - border-color: var(--color-accent); - background: var(--color-accent); - color: var(--color-on-accent); + border-color: var(--tab-active-border, var(--color-accent)); + background: var(--tab-active-bg); + color: var(--tab-active-fg); } /* ── Dump feed ── */ @@ -3343,15 +3356,16 @@ body.has-player .chat-fab { /* ── Shared card skin (dump-card + playlist-card) ── */ .dump-card, .playlist-card { - border: 2px solid var(--color-border); + border: var(--border-width) solid var(--color-border); border-radius: var(--radius-xl); background: var(--color-surface); + box-shadow: var(--card-shadow); min-width: 0; } .dump-card:hover, .playlist-card:hover { - border-color: var(--color-accent); + border-color: var(--card-hover-border); } /* Card-specific layout */ @@ -3640,7 +3654,7 @@ body.has-player .chat-fab { .playlist-card-description { margin: 0; font-size: var(--text-sm); - opacity: 0.65; + color: var(--color-text-secondary); word-break: break-word; line-height: 1.4; } @@ -3659,7 +3673,7 @@ body.has-player .chat-fab { .dump-card-date, .playlist-card-meta { font-size: var(--text-xs); - opacity: 0.45; + color: var(--color-text-muted); margin-top: 0.2rem; } @@ -3781,6 +3795,7 @@ body.has-player .chat-fab { position: fixed; inset: 0; background: var(--color-overlay); + backdrop-filter: var(--overlay-filter); display: flex; align-items: center; justify-content: center; @@ -3792,6 +3807,7 @@ body.has-player .chat-fab { .modal-card, .confirm-modal { background: var(--color-surface); + border: var(--border-width) solid var(--modal-border); border-radius: var(--radius-xl); box-shadow: var(--shadow-3); width: 100%; @@ -4588,7 +4604,7 @@ body.has-player .chat-fab { align-items: center; justify-content: center; padding: 0.25rem 0.6rem; - border: 2px solid var(--color-border); + border: var(--border-width) solid var(--color-border); border-radius: var(--radius-lg); background: none; cursor: pointer; @@ -4643,7 +4659,7 @@ body.has-player .chat-fab { .related-section { margin-top: 2.5rem; padding-top: 1.75rem; - border-top: 2px solid var(--color-border); + border-top: var(--border-width) solid var(--color-border); } .comment-section-title, @@ -5239,7 +5255,7 @@ body.has-player .chat-fab { position: relative; display: flex; flex-direction: column; - border: 2px solid var(--color-border); + border: var(--border-width) solid var(--color-border); border-radius: var(--radius-xl); background: var(--color-surface); overflow: hidden; @@ -5968,7 +5984,7 @@ body.has-player .chat-fab { flex: 1; padding: 0.3rem 0.7rem; border-radius: var(--radius-lg); - border: 2px solid var(--color-border-subtle); + border: var(--border-width) solid var(--color-border-subtle); background: var(--color-bg); color: var(--color-text); font-size: var(--text-sm); @@ -6010,7 +6026,7 @@ body.has-player .chat-fab { .search-bar-btn { padding: 0.3rem 0.55rem; background: transparent; - border: 2px solid var(--color-border-subtle); + border: var(--border-width) solid var(--color-border-subtle); border-radius: var(--radius-lg); cursor: pointer; font-size: var(--text-sm); @@ -6064,7 +6080,7 @@ body.has-player .chat-fab { padding: 0.75rem 1rem; background: var(--color-surface); border-radius: var(--radius-xl); - border: 2px solid var(--color-border-subtle); + border: var(--border-width) solid var(--color-border-subtle); text-decoration: none; color: var(--color-text); transition: border-color var(--dur-base); @@ -6202,7 +6218,7 @@ body.has-player .chat-fab { flex-direction: column; gap: 0.15rem; background: var(--color-surface); - border: 2px solid var(--color-border-subtle); + border: var(--border-width) solid var(--color-border-subtle); border-radius: var(--radius-xl); box-shadow: var(--shadow-2); } diff --git a/src/index.css b/src/index.css index 6c54514a..8596ba96 100644 --- a/src/index.css +++ b/src/index.css @@ -11,6 +11,8 @@ body { scrollbar-gutter: stable; background-color: var(--color-bg); color: var(--color-text); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } #root { @@ -37,19 +39,29 @@ h1 { button { border-radius: var(--radius-lg); - border: 1px solid var(--color-border); + border: var(--border-width) solid var(--color-border); padding: 0.6em 1.2em; font-size: 1em; font-weight: 500; font-family: inherit; + color: inherit; background-color: var(--color-surface); cursor: pointer; - transition: border-color 0.25s, background-color var(--dur-slow); + transition: + border-color var(--dur-base), + background-color var(--dur-base); } button:hover { border-color: var(--color-accent); } -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; + +/* One keyboard focus ring for every interactive element. Mouse clicks don't + trigger :focus-visible on buttons, so pointer users never see it. */ +:focus-visible { + outline: 2px solid var(--color-accent); + outline-offset: 2px; +} + +::selection { + background: color-mix(in srgb, var(--color-accent) 30%, transparent); } diff --git a/src/themes/smooth.css b/src/themes/smooth.css index 923d29c2..b8a5bdf3 100644 --- a/src/themes/smooth.css +++ b/src/themes/smooth.css @@ -2,95 +2,101 @@ Smooth theme — clean, modern, Saira typeface Dark: Prussian Blue · Deep Teal · Jasmine · Brick Ember · Blood Red Light: Cream · Deep Teal · Terracotta - Applied when data-style is absent or "smooth". Also the base every other - theme builds on: anything a theme leaves unset falls back to these. + Applied when data-style is absent or "smooth". Every colour is `light-dark(, )`, resolved against the root `color-scheme` (see tokens.css), so auto / light / dark share this one block. ══════════════════════════════════════════════════════════════════════ */ -:root, +:root:not([data-style]), :root[data-style="smooth"] { --font-body: "Saira", sans-serif; - --font-mono: monospace; - - font-family: var(--font-body); - font-optical-sizing: auto; - font-weight: 400; - font-style: normal; - line-height: 1.5; /* Text */ - --color-text: light-dark(#0e3a42, #eef0f3); - --color-text-secondary: light-dark(#2c6671, #8aaabb); - --color-text-muted: light-dark(#5c8a91, #506878); - --color-on-accent: light-dark(#fef8e6, #001427); + --color-text: light-dark(#0e3a42, #e9eef2); + --color-text-secondary: light-dark(#35626b, #a3b9c7); + --color-text-muted: light-dark(#4d7178, #7b93a6); + --color-on-accent: light-dark(#fffaf0, #001427); - /* Surfaces */ - --color-bg: light-dark(#fef8e6, #001427); - --color-surface: light-dark(#fffdf6, #0a1f35); + /* Surfaces — sunken (inputs, wells) < bg < surface (cards) < raised + (menus, modals, hovered chips). Dark tiers step up in Prussian Blue; + light tiers step from cream to white. */ + --color-bg: light-dark(#faf5e9, #01152a); + --color-surface: light-dark(#fffdf8, #0a2139); + --color-surface-raised: light-dark(#ffffff, #112a46); + --color-surface-sunken: light-dark(#f3ecdd, #000f20); - /* Borders */ - --color-border: transparent; + /* Borders — hairlines tinted from the text colour */ + --color-border: light-dark(rgb(14 58 66 / 0.13), rgb(163 185 199 / 0.14)); --color-border-subtle: light-dark( - rgba(48, 144, 161, 0.18), - rgba(112, 141, 129, 0.2) + rgb(14 58 66 / 0.08), + rgb(163 185 199 / 0.09) ); - /* Accent */ - --color-accent: light-dark(#bc5148, #f4d58d); - --color-accent-hover: light-dark(#97392f, #d4aa5a); + /* Accent — Jasmine (dark) / Terracotta (light) */ + --color-accent: light-dark(#b54a41, #f4d58d); + --color-accent-hover: light-dark(#a3423a, #f8e2ae); + --color-accent-soft: light-dark( + rgb(181 74 65 / 0.1), + rgb(244 213 141 / 0.12) + ); + --color-focus-ring: light-dark( + rgb(181 74 65 / 0.35), + rgb(244 213 141 / 0.4) + ); /* Links */ - --color-link: light-dark(#bc5148, #f4d58d); - --color-link-hover: light-dark(#97392f, #d4aa5a); + --color-link: light-dark(#b54a41, #f4d58d); + --color-link-hover: light-dark(#a3423a, #f8e2ae); - /* Danger */ - --color-danger: light-dark(#bc5148, #bf0603); - --color-danger-bg: light-dark(#f8e3e0, #220404); - --color-danger-hover: light-dark(#97392f, #8d0801); + /* Danger — Brick Ember, lifted for legibility on Prussian Blue */ + --color-danger: light-dark(#b3392f, #f0706a); + --color-danger-bg: light-dark(#f9e3df, rgb(191 6 3 / 0.18)); + --color-danger-hover: light-dark(#b3392f, #bf0603); - /* Success */ - --color-success: light-dark(#2e8a86, #708d81); - --color-success-bg: light-dark(#ddf2f1, #0d2820); - --color-success-hover: light-dark(#1f6b67, #8aaa9a); + /* Success — Deep Teal */ + --color-success: light-dark(#25746f, #8fb3a3); + --color-success-bg: light-dark(#dff1ef, rgb(112 141 129 / 0.18)); + --color-success-hover: light-dark(#1f6b67, #a6c6b8); + + --color-warning: light-dark(#a86a00, #e8b44f); /* Overlays */ - --color-overlay: light-dark(rgba(14, 58, 66, 0.35), rgba(0, 10, 20, 0.55)); - --color-header-user-bg: light-dark( - rgba(188, 81, 72, 0.08), - rgba(244, 213, 141, 0.06) - ); + --color-overlay: light-dark(rgb(24 30 34 / 0.32), rgb(0 6 14 / 0.6)); + --color-header-user-bg: transparent; --color-header-user-bg-hover: light-dark( - rgba(188, 81, 72, 0.15), - rgba(244, 213, 141, 0.12) + rgb(14 58 66 / 0.07), + rgb(233 238 242 / 0.08) ); /* Misc */ - --color-option-bg: light-dark(#fff9ec, #0d2340); - --color-option-border: light-dark(#ece4cc, #001427); + --color-option-bg: light-dark(#fffdf8, #112a46); + --color-option-border: light-dark(rgb(14 58 66 / 0.08), rgb(163 185 199 / 0.09)); - /* Service brand colors (fixed, not theme-dependent). `on-brand` is the - foreground for text sitting on any of these — also fixed, so the contrast - never flips with the theme's text colour. */ - --color-youtube: #c00; - --color-youtube-hover: #f00; - --color-bandcamp: #1da0c3; - --color-bandcamp-hover: #25c8f0; - --color-soundcloud: #f50; - --color-soundcloud-hover: #f73; - --color-on-brand: #fff; -} - -/* ══════════════════════════════════════════════════════════════════════ - Structural overrides - ══════════════════════════════════════════════════════════════════════ */ - -/* On hover the danger button fills with the solid dark danger colour. The - resting text colour is --color-text, which is dark in the light variant, - so force white on hover to keep it legible. */ -:root:not([data-style]) .btn-danger:hover, -:root[data-style="smooth"] .btn-danger:hover { - color: #fff; + /* Shape & depth */ + --border-width: 1px; + --shadow-color: light-dark(rgb(14 58 66 / 0.16), rgb(0 0 0 / 0.5)); + --shadow-color-soft: light-dark(rgb(14 58 66 / 0.07), rgb(0 0 0 / 0.3)); + --shadow-color-strong: light-dark(rgb(14 58 66 / 0.26), rgb(0 0 0 / 0.7)); + --shadow-1: 0 1px 2px var(--shadow-color-soft); + --shadow-2: 0 8px 24px -6px var(--shadow-color), 0 1px 2px var(--shadow-color-soft); + --shadow-3: 0 24px 56px -12px var(--shadow-color-strong), 0 2px 6px var(--shadow-color-soft); + + /* Component skins */ + --header-bg: var(--color-bg); + --card-shadow: var(--shadow-1); + --panel-ring: inset 0 0 0 1px var(--color-border); + --card-hover-border: color-mix(in srgb, var(--color-text) 24%, transparent); + --input-focus-bg: var(--color-surface); + --overlay-filter: blur(3px); + --modal-border: var(--color-border); + --tab-border: transparent; + --tab-active-bg: var(--color-accent-soft); + --tab-active-fg: var(--color-accent); + --tab-active-border: transparent; + --tab-text-transform: none; + --tab-tracking: 0; + --btn-primary-shadow: var(--shadow-1); + --btn-primary-shadow-hover: var(--shadow-1); } diff --git a/src/tokens.css b/src/tokens.css index 07b561db..37a24f1f 100644 --- a/src/tokens.css +++ b/src/tokens.css @@ -10,6 +10,12 @@ palette block covers auto, forced-light and forced-dark. */ color-scheme: light dark; + font-family: var(--font-body); + font-optical-sizing: auto; + font-weight: 400; + font-style: normal; + line-height: 1.5; + /* ── Space (4px grid) ── */ --space-1: 0.25rem; --space-2: 0.5rem; @@ -56,6 +62,17 @@ --font-display: var(--font-body); --font-mono: ui-monospace, "SFMono-Regular", Menlo, monospace; + /* ── Service brand colors ── + Fixed across themes and schemes. `on-brand` is the foreground for text + sitting on any of them — also fixed, so contrast never flips. */ + --color-youtube: #c00; + --color-youtube-hover: #f00; + --color-bandcamp: #1da0c3; + --color-bandcamp-hover: #25c8f0; + --color-soundcloud: #f50; + --color-soundcloud-hover: #f73; + --color-on-brand: #fff; + /* ── Elevation ── */ --shadow-1: 0 1px 2px rgb(0 0 0 / 0.12); --shadow-2: 0 4px 16px rgb(0 0 0 / 0.2); @@ -78,25 +95,42 @@ --z-popover: 1100; --z-toast: 9999; + /* ── Borders ── */ + --border-width: 2px; + /* ── Derived colors ── - Defaults computed from each theme's base palette, so every theme gets - the full semantic set; a theme overrides any of these when the derived - value isn't right for it. */ - --color-surface-raised: color-mix( - in srgb, - var(--color-surface) 94%, - var(--color-text) - ); - --color-surface-sunken: color-mix( - in srgb, - var(--color-bg) 96%, - var(--color-text) - ); + Defaults reproduce the flat two-level (bg / surface) look; a theme that + wants more depth sets its own raised/sunken tiers. */ + --color-surface-raised: var(--color-surface); + --color-surface-sunken: var(--color-bg); --color-accent-soft: color-mix(in srgb, var(--color-accent) 14%, transparent); --color-focus-ring: color-mix(in srgb, var(--color-accent) 45%, transparent); --color-warning: #d4a017; --color-on-danger: #fff; + /* ── Component skins ── + Knobs for the handful of treatments themes disagree on. */ + --header-bg: var(--color-surface); + --card-shadow: 0 0 #0000; + --panel-ring: 0 0 #0000; + --card-hover-border: var(--color-accent); + --input-focus-bg: color-mix( + in srgb, + var(--color-accent) 4%, + var(--color-surface-sunken) 96% + ); + --overlay-filter: none; + --modal-border: transparent; + --tab-border: var(--color-border); + --tab-active-bg: var(--color-accent); + --tab-active-fg: var(--color-on-accent); + --tab-text-transform: uppercase; + --tab-tracking: 0.04em; + --btn-primary-shadow: 0 2px 8px + color-mix(in srgb, var(--color-accent) 40%, transparent); + --btn-primary-shadow-hover: 0 4px 14px + color-mix(in srgb, var(--color-accent) 50%, transparent); + /* Legacy names still referenced by a few rules. */ --color-muted: var(--color-text-muted); --color-error: var(--color-danger);