Skip to content

Commit 308f5b0

Browse files
committed
Apply homepage palettes with a persistent header theme selector
1 parent 881bed5 commit 308f5b0

9 files changed

Lines changed: 443 additions & 40 deletions

File tree

IMPLEMENTATION-NOTES.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,52 @@ keyboard default actions because the browser test interface did not advance
166166
native controls with Arrow, Space or Tab. No custom keyboard interception was
167167
added. No-JavaScript fallback coverage in this pass is source-based.
168168

169+
### Homepage-wide palettes and header dropdown — 2026-08-30
170+
171+
Starting from `881bed54a2f2172b455744ad32d67c2f232969e8`, selecting any of the six
172+
desktop themes applies a coordinated palette across the redesigned homepage:
173+
section backgrounds and gradients, text, buttons, focus accents, terminal panes,
174+
video play buttons and footer. A compact, labeled native select in the sticky
175+
header offers the same six choices plus **Sovereign**, the original default and
176+
reset option. The desktop screenshot, radio selection, dropdown and polite live
177+
status are synchronized. Sovereign has no selected desktop radio; its caption
178+
explicitly identifies the unchanged Tokyo Night screenshot as a desktop preview.
179+
180+
This is a theme-inspired adaptation of page colors, not an OS theme installer.
181+
Official green logo assets, photographs and screenshots remain unmodified.
182+
The scope is the entire redesigned homepage, not the inherited manual, gallery
183+
or other secondary pages. Global upstream root tokens and layouts are untouched.
184+
185+
Files: `index.html`, `assets/css/home.css`, new `assets/css/home-themes.css`,
186+
`assets/js/modules/home.js`, new `assets/js/theme-preference.js`,
187+
`tests/home-themes.mjs`, `bin/check`, `README.md` and this document.
188+
The existing design tokens remain the original fallback. Six optional CSS
189+
palettes override their values and derive coherent panel/border/gradient tones.
190+
The small early script restores an allowlisted saved preference before styles
191+
render. The main module handles controls and same-origin storage events; both
192+
storage access paths tolerate blocked storage and reject unknown values.
193+
Only `omarchy-sovereign-theme` is stored locally, with no transmission or tracking.
194+
Existing button movement remains, but palette color changes are immediate.
195+
196+
The dropdown is hidden until initialized; without JavaScript the original
197+
design, default screenshot and all six static screenshot links remain available.
198+
No dependencies, image assets or theme animations were added. The inherited
199+
reduced-motion behavior and all homepage destination/download link policies are
200+
preserved.
201+
202+
Regression coverage now includes both control directions for all six themes,
203+
original reset, saved and invalid values, blocked storage, cross-tab events,
204+
early restoration and static fallback links. All seven palettes meet 4.5:1 for
205+
the tested text/muted/accent roles on solid raised panels and primary-button
206+
text. This is a scoped contrast check, not a claim of full accessibility audit.
207+
Browser checks exercised all options, swatches, reload restoration, second-tab
208+
restoration and live cross-tab synchronization. Header layouts were checked at
209+
320, 390, 768, 1024 and 1440px without overlap or horizontal overflow. The mobile
210+
menu still opens, navigates to the themes section and closes after activation.
211+
Screenshots were visually reviewed; no-JavaScript fallback and reduced motion
212+
were checked by the source/regression suite. The prior keyboard-test-tool
213+
limitation remains; controls retain native select/radio semantics.
214+
169215
### Push-to-deploy automation — 2026-08-30
170216

171217
Starting from `f728d6ab0d47da60b49269affa7cced2d2bd94e7`, the existing validation

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ community. Text, navigation, controls and terminal output are code-native.
2626
The six-theme preview pairs Tokyo Night, Catppuccin and Gruvbox with Black Gold,
2727
Black Turq and VHS 80 from the official theme gallery: gold, phosphor turquoise
2828
and retro tape colors that complement the Sovereign Machine direction.
29+
Selecting a swatch now applies that palette across the redesigned homepage.
30+
A matching header dropdown includes **Sovereign**, which restores the original
31+
design. The preference is saved locally and synchronized between homepage tabs;
32+
no operating-system settings or inherited secondary pages are changed.
2933

3034
## Run locally
3135

assets/css/home-themes.css

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/* Optional homepage palettes. Unset / sovereign preserves the original design.
2+
Names describe theme-inspired site colors, not exact desktop configurations. */
3+
html[data-site-theme="tokyo-night"] .home {
4+
--sm-void: #10121d;
5+
--sm-deep: #090b13;
6+
--sm-text: #c0caf5;
7+
--sm-muted: #a9b1d6;
8+
--sm-cyan: #7dcfff;
9+
--sm-gold: #7aa2f7;
10+
--sm-magenta: #bb9af7;
11+
--sm-blue: #7aa2f7;
12+
--sm-green: #9ece6a;
13+
--sm-danger: #f7768e;
14+
}
15+
16+
html[data-site-theme="catppuccin"] .home {
17+
--sm-void: #181825;
18+
--sm-deep: #11111b;
19+
--sm-text: #cdd6f4;
20+
--sm-muted: #a6adc8;
21+
--sm-cyan: #94e2d5;
22+
--sm-gold: #cba6f7;
23+
--sm-magenta: #f5c2e7;
24+
--sm-blue: #89b4fa;
25+
--sm-green: #a6e3a1;
26+
--sm-danger: #f38ba8;
27+
}
28+
29+
html[data-site-theme="gruvbox"] .home {
30+
--sm-void: #1d2021;
31+
--sm-deep: #141617;
32+
--sm-text: #ebdbb2;
33+
--sm-muted: #bdae93;
34+
--sm-cyan: #8ec07c;
35+
--sm-gold: #fabd2f;
36+
--sm-magenta: #d3869b;
37+
--sm-blue: #83a598;
38+
--sm-green: #b8bb26;
39+
--sm-danger: #fb4934;
40+
}
41+
42+
html[data-site-theme="black-gold"] .home {
43+
--sm-void: #080909;
44+
--sm-deep: #040505;
45+
--sm-text: #e8dfc8;
46+
--sm-muted: #b4ac97;
47+
--sm-cyan: #d7b65c;
48+
--sm-gold: #e5c572;
49+
--sm-magenta: #ad9360;
50+
--sm-blue: #c6ac73;
51+
--sm-green: #b8b26c;
52+
--sm-danger: #e7a17d;
53+
}
54+
55+
html[data-site-theme="black-turq"] .home {
56+
--sm-void: #051111;
57+
--sm-deep: #020809;
58+
--sm-text: #def3ee;
59+
--sm-muted: #93b6b1;
60+
--sm-cyan: #8ce3d1;
61+
--sm-gold: #5ad6c7;
62+
--sm-magenta: #79b7ad;
63+
--sm-blue: #62c6d1;
64+
--sm-green: #a3d9a4;
65+
--sm-danger: #f0a699;
66+
}
67+
68+
html[data-site-theme="vhs-80"] .home {
69+
--sm-void: #151415;
70+
--sm-deep: #0b0c0e;
71+
--sm-text: #f3eddb;
72+
--sm-muted: #bab7aa;
73+
--sm-cyan: #54cbd0;
74+
--sm-gold: #e7c85b;
75+
--sm-magenta: #ef8390;
76+
--sm-blue: #58b2ca;
77+
--sm-green: #a4c872;
78+
--sm-danger: #ef8390;
79+
}
80+
81+
html[data-site-theme]:not([data-site-theme="sovereign"]) .home {
82+
--sm-panel: color-mix(in srgb, var(--sm-void), var(--sm-text) 4%);
83+
--sm-panel-raised: color-mix(in srgb, var(--sm-void), var(--sm-text) 7%);
84+
--sm-line: color-mix(in srgb, var(--sm-void), var(--sm-muted) 25%);
85+
--sm-line-strong: color-mix(in srgb, var(--sm-void), var(--sm-muted) 65%);
86+
--sm-cyan-soft: color-mix(in srgb, var(--sm-cyan), var(--sm-text) 35%);
87+
--sm-gold-soft: color-mix(in srgb, var(--sm-gold), var(--sm-text) 35%);
88+
--sm-aurora-cyan: color-mix(in srgb, var(--sm-cyan) 11.5%, transparent);
89+
--sm-aurora-gold: color-mix(in srgb, var(--sm-gold) 10.5%, transparent);
90+
--sm-aurora-magenta: color-mix(in srgb, var(--sm-magenta) 8.5%, transparent);
91+
--sm-aurora-blue: color-mix(in srgb, var(--sm-blue) 10.5%, transparent);
92+
--sm-aurora-green: color-mix(in srgb, var(--sm-green) 9%, transparent);
93+
--sm-focus-glow: color-mix(in srgb, var(--sm-cyan) 3.5%, transparent);
94+
--sm-band-blue: color-mix(in srgb, var(--sm-void), var(--sm-blue) 6%);
95+
--sm-band-violet: color-mix(in srgb, var(--sm-void), var(--sm-magenta) 6%);
96+
--sm-band-warm: color-mix(in srgb, var(--sm-void), var(--sm-gold) 6%);
97+
--sm-band-green: color-mix(in srgb, var(--sm-void), var(--sm-green) 6%);
98+
}
99+
100+
/* Keep these controls compact, native and usable with touch or a keyboard. */
101+
.site-nav:has(.site-theme:not([hidden])) {
102+
gap: clamp(0.75rem, 1.5vw, 1.5rem);
103+
grid-template-columns: 1fr auto auto auto;
104+
}
105+
106+
.site-theme {
107+
min-width: 0;
108+
position: relative;
109+
width: 10rem;
110+
}
111+
112+
.site-theme[hidden] {
113+
display: none;
114+
}
115+
116+
.site-theme__label {
117+
color: var(--sm-muted);
118+
font-family: var(--sm-font-interface);
119+
font-size: 0.5rem;
120+
font-weight: 700;
121+
left: 0.65rem;
122+
letter-spacing: 0.1em;
123+
pointer-events: none;
124+
position: absolute;
125+
text-transform: uppercase;
126+
top: 0.2rem;
127+
}
128+
129+
.site-theme select {
130+
appearance: none;
131+
background: var(--sm-panel);
132+
border: 1px solid var(--sm-muted);
133+
border-radius: 2px;
134+
color: var(--sm-text);
135+
cursor: pointer;
136+
display: block;
137+
font-family: var(--sm-font-interface);
138+
font-size: 0.65rem;
139+
font-weight: 700;
140+
min-height: 2.8rem;
141+
padding: 0.9rem 1.6rem 0.25rem 0.65rem;
142+
width: 100%;
143+
}
144+
145+
.site-theme::after {
146+
border-bottom: 1.5px solid var(--sm-cyan);
147+
border-right: 1.5px solid var(--sm-cyan);
148+
content: '';
149+
height: 0.4rem;
150+
pointer-events: none;
151+
position: absolute;
152+
right: 0.65rem;
153+
top: 1.15rem;
154+
transform: rotate(45deg);
155+
width: 0.4rem;
156+
}
157+
158+
.home .sm-button {
159+
transition-property: transform;
160+
}
161+
162+
.home .video__play svg path:first-child {
163+
fill: var(--sm-gold);
164+
}
165+
166+
.home .video__play svg path:last-child {
167+
fill: var(--sm-deep);
168+
}
169+
170+
@media (max-width: 39.99rem) {
171+
.site-nav:has(.site-theme:not([hidden])) {
172+
gap: 0.5rem;
173+
grid-template-columns: auto minmax(0, 1fr) auto auto;
174+
}
175+
176+
.site-theme {
177+
justify-self: end;
178+
max-width: 10rem;
179+
width: 100%;
180+
}
181+
182+
.site-theme select {
183+
font-size: 0.6rem;
184+
}
185+
}

assets/css/home.css

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
--sm-aurora-magenta: rgb(255 79 179 / 0.085);
2121
--sm-aurora-blue: rgb(122 162 247 / 0.105);
2222
--sm-aurora-green: rgb(158 206 106 / 0.09);
23+
--sm-focus-glow: rgb(85 246 226 / 0.035);
2324
--sm-band-blue: #071018;
2425
--sm-band-violet: #0d0a14;
2526
--sm-band-warm: #100c07;
@@ -401,7 +402,7 @@ html:has(.home) {
401402
background: var(--sm-deep);
402403
border: 1px solid var(--sm-line-strong);
403404
border-radius: 3px;
404-
box-shadow: 0 24px 60px rgb(0 0 0 / 0.35), 0 0 28px rgb(85 246 226 / 0.035);
405+
box-shadow: 0 24px 60px rgb(0 0 0 / 0.35), 0 0 28px var(--sm-focus-glow);
405406
min-width: 0;
406407
overflow: hidden;
407408
position: relative;
@@ -545,7 +546,7 @@ html:has(.home) {
545546
.machine[data-focus="browser"] [data-pane="browser"] {
546547
background: var(--sm-panel-raised);
547548
border-color: var(--sm-cyan);
548-
box-shadow: inset 0 0 0 1px var(--sm-cyan), inset 0 0 22px rgb(85 246 226 / 0.035);
549+
box-shadow: inset 0 0 0 1px var(--sm-cyan), inset 0 0 22px var(--sm-focus-glow);
549550
}
550551

551552
.machine[data-focus="terminal"] [data-pane="terminal"] .machine-pane__label,
@@ -938,7 +939,7 @@ html:has(.home) {
938939
.command-terminal {
939940
background: var(--sm-panel);
940941
border: 1px solid var(--sm-cyan);
941-
box-shadow: 0 0 30px rgb(85 246 226 / 0.035);
942+
box-shadow: 0 0 30px var(--sm-focus-glow);
942943
min-height: 28rem;
943944
overflow: hidden;
944945
position: relative;

assets/js/modules/home.js

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ const THEMES = {
3131
},
3232
};
3333

34+
const THEME_STORAGE_KEY = 'omarchy-sovereign-theme';
35+
3436
const COMMAND_TEXT = `~ > omarchy
3537
Omarchy command center
3638
@@ -96,23 +98,63 @@ function setupPaneFocus() {
9698
function setupThemePreview() {
9799
const preview = document.querySelector('[data-theme-preview]');
98100
const image = preview?.querySelector('[data-theme-image]');
99-
const name = preview?.querySelector('[data-theme-name]');
100101
const status = preview?.querySelector('[data-theme-status]');
101102
if (!(preview instanceof HTMLElement) || !(image instanceof HTMLImageElement)) return;
102103

103-
document.querySelectorAll('input[name="theme-preview"]').forEach((input) => {
104+
const select = document.querySelector('[data-site-theme-select]');
105+
const controls = document.querySelectorAll('input[name="theme-preview"]');
106+
const validTheme = (value) => value === 'sovereign' || Object.hasOwn(THEMES, value);
107+
108+
const applyTheme = (value, persist = false) => {
109+
const selected = validTheme(value) ? value : 'sovereign';
110+
const previewKey = selected === 'sovereign' ? 'tokyo-night' : selected;
111+
const theme = THEMES[previewKey];
112+
113+
document.documentElement.dataset.siteTheme = selected;
114+
preview.dataset.themePreview = previewKey;
115+
image.src = theme.image;
116+
image.alt = theme.alt;
117+
controls.forEach((input) => { input.checked = input.value === selected; });
118+
if (select instanceof HTMLSelectElement) select.value = selected;
119+
if (status != null) {
120+
status.textContent = selected === 'sovereign'
121+
? 'Original site palette · Tokyo Night desktop preview.'
122+
: `${theme.name} palette applied to this page · ${theme.name} desktop preview.`;
123+
}
124+
125+
if (persist) {
126+
try {
127+
window.localStorage.setItem(THEME_STORAGE_KEY, selected);
128+
} catch {
129+
// The control still works when browser storage is unavailable.
130+
}
131+
}
132+
};
133+
134+
let savedTheme = 'sovereign';
135+
try {
136+
savedTheme = window.localStorage.getItem(THEME_STORAGE_KEY) ?? 'sovereign';
137+
} catch {
138+
// Keep the original palette if storage is blocked or unavailable.
139+
}
140+
applyTheme(savedTheme);
141+
142+
controls.forEach((input) => {
104143
input.addEventListener('change', () => {
105144
if (!(input instanceof HTMLInputElement) || !input.checked) return;
106-
const theme = THEMES[input.value];
107-
if (theme == null) return;
108-
109-
preview.dataset.themePreview = input.value;
110-
image.src = theme.image;
111-
image.alt = theme.alt;
112-
if (name != null) name.textContent = theme.name;
113-
if (status != null) status.textContent = `${theme.name} preview selected.`;
145+
if (validTheme(input.value)) applyTheme(input.value, true);
114146
});
115147
});
148+
149+
if (select instanceof HTMLSelectElement) {
150+
select.addEventListener('change', () => applyTheme(select.value, true));
151+
const label = select.closest('.site-theme');
152+
if (label instanceof HTMLElement) label.hidden = false;
153+
}
154+
155+
window.addEventListener('storage', (event) => {
156+
if (event.key === THEME_STORAGE_KEY || event.key === null) applyTheme(event.newValue);
157+
});
116158
}
117159

118160
function setupCommandReplay() {

assets/js/theme-preference.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Restore only this homepage's palette before its styles render. No tracking.
2+
(() => {
3+
const themes = ['sovereign', 'tokyo-night', 'catppuccin', 'gruvbox', 'black-gold', 'black-turq', 'vhs-80'];
4+
try {
5+
const saved = window.localStorage.getItem('omarchy-sovereign-theme');
6+
if (themes.includes(saved)) document.documentElement.dataset.siteTheme = saved;
7+
} catch {
8+
// Storage can be unavailable; the original design remains the fallback.
9+
}
10+
})();

bin/check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Dir.chdir(root) do
2626
%w[bin/serve bin/prepare-preview bin/deploy-preview bin/check].each do |path|
2727
run!(RbConfig.ruby, "-c", path)
2828
end
29-
%w[assets/js/script.js assets/js/modules/home.js assets/js/modules/video.js].each do |path|
29+
%w[assets/js/script.js assets/js/theme-preference.js assets/js/modules/home.js assets/js/modules/video.js].each do |path|
3030
run!("node", "--input-type=module", "--check", stdin_data: File.read(path))
3131
end
3232
run!("git", "diff", "--check")

0 commit comments

Comments
 (0)