Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8e8c513
feat(design-system): vendor shared UI tokens into first-party capsules
SashaMIT Jul 17, 2026
f9a8077
feat(home-gui): port desktop shell chrome onto the ESP host contract
SashaMIT Jul 17, 2026
a24ffdf
fix(home-gui): keep Chunk C overlays hidden at mount
SashaMIT Jul 17, 2026
a8aba77
feat(home-gui): port menubar, Spotlight, notifications, keyboard, Exp…
SashaMIT Jul 17, 2026
0d52742
feat(apps): port per-app UI/UX onto the ESP branch for 14 main-line s…
SashaMIT Jul 17, 2026
e7d4d8d
feat(home): first-boot welcome beat + arrival motion on the host cont…
SashaMIT Jul 17, 2026
3e453b0
feat(home-gui): ElastOS system menu at the far left of the bar
SashaMIT Jul 17, 2026
c8feb01
fix(gateway): open web projections without a runtime launch; drop ide…
SashaMIT Jul 17, 2026
895c789
feat(home-gui): desktop carries objects, not apps (WS1 desktop philos…
SashaMIT Jul 17, 2026
352e1e2
feat(home-gui): Control Centre — quick layer over existing state (WS2)
SashaMIT Jul 17, 2026
8f56186
feat(home-gui): ELASTOS menu polish + About ElastOS (WS5)
SashaMIT Jul 17, 2026
a7a0173
feat(home-gui): human-first error and empty-state grammar (WS6)
SashaMIT Jul 17, 2026
8f48d18
feat(home-gui): motion pass — selection slides, menus arrive (WS7 par…
SashaMIT Jul 17, 2026
121a4d7
feat(home-gui): wallet rail — the wallet as a first-class side surfac…
SashaMIT Jul 17, 2026
994947a
feat(home-gui): wallet becomes a bar icon; rail sits flush as a sidebar
SashaMIT Jul 17, 2026
202a518
fix(home-gui): wallet rail keeps the floating-card look, loses the bo…
SashaMIT Jul 17, 2026
21688be
fix(home-gui): kill purple bleed at the bottom of the wallet rail
SashaMIT Jul 18, 2026
2d828bd
fix(wallet): remove purple bottom gradient from the wallet surface
SashaMIT Jul 18, 2026
2171b6d
feat(wallet,home-gui): Connect on main slide; rail = trusted wallet f…
SashaMIT Jul 18, 2026
b47ecac
feat(home-gui,wallet): MetaMask connect is an in-rail ceremony sheet
SashaMIT Jul 18, 2026
88b924e
fix(home-gui,wallet): reload Wallet accounts after connector ceremony
SashaMIT Jul 18, 2026
600ab20
feat(wallet): denser rail chrome with hero pending and brand marks
SashaMIT Jul 18, 2026
9fdad2d
fix(wallet): slide Approvals/Settings and harden rail merge gates
SashaMIT Jul 18, 2026
4ee88d6
feat(wallet): warm rail session and 10/10 chrome polish
SashaMIT Jul 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions capsules/_shared/elastos-theme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/* ElastOS UI — shared theme runtime (single source of truth).
*
* Vendored into each participating capsule's browser/ dir by `just vendor-ui`,
* next to elastos-ui.css. Everything is served same-origin from the gateway,
* so localStorage is the one canonical store (mirrors PC2's approach):
*
* localStorage["elastos.ui.theme"] = "dark" | "light" | "auto"
* localStorage["elastos.ui.accent"] = "blue" | "purple" | "pink" | "red" |
* "orange" | "yellow" | "green" | "graphite"
*
* Dark is the default theme when unset; blue is the default accent. "auto"
* follows prefers-color-scheme. The resolved theme is applied as
* data-el-theme="light" on <html> (absence means dark); the accent as
* data-el-accent (absence means blue) — both activate token blocks in
* elastos-ui.css. Cross-document sync is the browser's own `storage` event —
* no shell messaging required, and sandboxed app frames (allow-same-origin)
* see it.
*/
(function () {
const KEY = "elastos.ui.theme";
const ACCENT_KEY = "elastos.ui.accent";
const ACCENTS = [
"blue",
"purple",
"pink",
"red",
"orange",
"yellow",
"green",
"graphite",
];
const media = window.matchMedia
? window.matchMedia("(prefers-color-scheme: light)")
: null;

function preference() {
try {
const value = localStorage.getItem(KEY);
if (value === "light" || value === "dark" || value === "auto") {
return value;
}
} catch (_error) {
// Storage may be unavailable; fall through to the default.
}
return "dark";
}

function resolve(pref) {
if (pref === "auto") {
return media && media.matches ? "light" : "dark";
}
return pref;
}

function accentPreference() {
try {
const value = localStorage.getItem(ACCENT_KEY);
if (ACCENTS.includes(value)) {
return value;
}
} catch (_error) {
// Storage may be unavailable; fall through to the default.
}
return "blue";
}

function apply() {
if (resolve(preference()) === "light") {
document.documentElement.setAttribute("data-el-theme", "light");
} else {
document.documentElement.removeAttribute("data-el-theme");
}
const accent = accentPreference();
if (accent === "blue") {
document.documentElement.removeAttribute("data-el-accent");
} else {
document.documentElement.setAttribute("data-el-accent", accent);
}
}

apply();

window.addEventListener("storage", (event) => {
if (event.key === KEY || event.key === ACCENT_KEY || event.key === null) {
apply();
}
});

if (media && typeof media.addEventListener === "function") {
media.addEventListener("change", () => {
if (preference() === "auto") {
apply();
}
});
}

window.elastosTheme = {
preference,
resolved: () => resolve(preference()),
set(value) {
const pref = value === "light" || value === "auto" ? value : "dark";
try {
localStorage.setItem(KEY, pref);
} catch (_error) {
// Applying still works for this document.
}
apply();
},
accents: ACCENTS.slice(),
accent: accentPreference,
setAccent(value) {
const accent = ACCENTS.includes(value) ? value : "blue";
try {
localStorage.setItem(ACCENT_KEY, accent);
} catch (_error) {
// Applying still works for this document.
}
apply();
},
};
})();
247 changes: 247 additions & 0 deletions capsules/_shared/elastos-ui.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
/* ElastOS UI — shared design tokens (single source of truth).
*
* This file lives at capsules/_shared/elastos-ui.css and is VENDORED into each
* participating capsule's browser/ dir by `just vendor-ui` (no gateway route,
* no shared runtime surface — capsules stay self-contained). Edit THIS file,
* then run `just vendor-ui`; `just alignment-check` fails on drift.
*
* Contents: Inter (OFL, latin variable subset), palette, type scale, radii,
* hairlines, blur/elevation steps, motion tokens, and minimal control
* primitives. Layout is each app's business — tokens only.
*/

@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 400 700;
font-display: optional;
src: url("./assets/fonts/Inter-latin-var.woff2") format("woff2");
}

:root {
color-scheme: dark;

/* Palette */
--el-bg: #0b0d10;
--el-surface: rgba(22, 24, 29, 0.92);
--el-surface-raised: #16181d;
--el-inset: rgba(255, 255, 255, 0.06);
--el-text: #f5f7fa;
--el-muted: rgba(245, 247, 250, 0.7);
--el-soft: rgba(245, 247, 250, 0.48);
--el-brand: #f6921a;
--el-brand-strong: #ffb457;
--el-focus: rgba(246, 146, 26, 0.55);
--el-danger: #ff8f8f;
--el-ok: #34d399;

/* Hairlines (one pair, everywhere) */
--el-hairline: rgba(255, 255, 255, 0.12);
--el-hairline-strong: rgba(255, 255, 255, 0.18);

/* Type: 10/11/12/13/14/16/28; weights 400/500/600/700 */
--el-font: "Inter", -apple-system, "Segoe UI", "SF Pro Text", sans-serif;
--el-font-mono: ui-monospace, "SF Mono", Menlo, monospace;
--el-text-2xs: 10px;
--el-text-xs: 11px;
--el-text-sm: 12px;
--el-text-md: 13px;
--el-text-lg: 14px;
--el-text-xl: 16px;
--el-text-display: 28px;

/* Radii: 4/6/10/12/16/20 + pill */
--el-radius-xs: 4px;
--el-radius-sm: 6px;
--el-radius-md: 10px;
--el-radius-lg: 12px;
--el-radius-xl: 16px;
--el-radius-2xl: 20px;
--el-radius-pill: 999px;

/* Blur: three steps only */
--el-blur-1: 8px;
--el-blur-2: 16px;
--el-blur-3: 24px;

/* Elevation: three levels */
--el-shadow-1: 0 2px 10px rgba(0, 0, 0, 0.22);
--el-shadow-2: 0 12px 28px rgba(0, 0, 0, 0.28);
--el-shadow-3: 0 24px 80px rgba(0, 0, 0, 0.35);

/* Motion: 80/140/180/220, exits 150; one ease */
--el-dur-1: 80ms;
--el-dur-2: 140ms;
--el-dur-3: 180ms;
--el-dur-4: 220ms;
--el-dur-exit: 150ms;
--el-ease: cubic-bezier(0.2, 0, 0, 1);

/* System accent — ONE accent, chosen by the user in System > Personalization
(macOS model), obeyed by every app. Selected via data-el-accent on <html>
(absence = blue). Apps consume the derived tones and never define their own
accent colors. */
--el-accent: #0a84ff;
--el-accent-ink: #ffffff;
--el-accent-strong: color-mix(in srgb, var(--el-accent) 78%, #ffffff);
--el-accent-soft: color-mix(in srgb, var(--el-accent) 22%, transparent);
--el-accent-faint: color-mix(in srgb, var(--el-accent) 11%, transparent);
}

/* The eight macOS accents. Yellow and orange carry dark ink on filled controls. */
:root[data-el-accent="purple"] {
--el-accent: #bf5af2;
}

:root[data-el-accent="pink"] {
--el-accent: #ff375f;
}

:root[data-el-accent="red"] {
--el-accent: #ff453a;
}

:root[data-el-accent="orange"] {
--el-accent: #f6921a;
--el-accent-ink: #161616;
}

:root[data-el-accent="yellow"] {
--el-accent: #ffd60a;
--el-accent-ink: #161616;
}

:root[data-el-accent="green"] {
--el-accent: #30b84f;
}

:root[data-el-accent="graphite"] {
--el-accent: #8e8e93;
}

/* Light theme: dark is the default (no attribute); the shell sets
data-el-theme="light" on <html> and broadcasts changes to app frames.
Values restore the pre-token light look: white raised panels over a cool
pale stage, navy ink, the same brand orange. */
:root[data-el-theme="light"] {
color-scheme: light;

--el-bg: #eef1f6;
--el-surface: rgba(255, 255, 255, 0.88);
--el-surface-raised: #ffffff;
--el-inset: rgba(29, 36, 56, 0.055);
--el-text: #1d2438;
--el-muted: rgba(29, 36, 56, 0.72);
--el-soft: rgba(29, 36, 56, 0.5);
--el-brand: #f6921a;
--el-brand-strong: #e07f0a;
--el-focus: rgba(246, 146, 26, 0.55);
--el-danger: #b14c5a;
--el-ok: #0a7a52;

--el-hairline: rgba(29, 36, 56, 0.14);
--el-hairline-strong: rgba(29, 36, 56, 0.24);

--el-shadow-1: 0 2px 10px rgba(34, 49, 88, 0.1);
--el-shadow-2: 0 12px 28px rgba(34, 49, 88, 0.12);
--el-shadow-3: 0 24px 80px rgba(34, 49, 88, 0.16);

/* "Strong" means higher contrast against the CURRENT background: lighter on
dark, deeper on light. */
--el-accent-strong: color-mix(in srgb, var(--el-accent) 72%, #1d2438);
}

/* App base: opt in by putting .el-app on <body>. Dark shell surface, shared
type. Layout untouched. */
.el-app {
margin: 0;
background: var(--el-bg);
color: var(--el-text);
font-family: var(--el-font);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}

.el-app :focus-visible {
outline: 2px solid var(--el-focus);
outline-offset: 2px;
}

/* Minimal control primitives — enough that a capsule needs no bespoke button
or input styling to look native. */
.el-button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
min-height: 32px;
padding: 0 14px;
border: 1px solid var(--el-hairline);
border-radius: var(--el-radius-md);
background: var(--el-inset);
color: var(--el-text);
font: 500 var(--el-text-md) / 1.2 var(--el-font);
cursor: pointer;
transition: background-color var(--el-dur-2) var(--el-ease);
}

/* Hover lift over inset surfaces — flips with the theme so apps don't need
their own literal rgba(255,255,255,x) hovers. */
:root {
--el-lift: rgba(255, 255, 255, 0.1);
--el-lift-strong: rgba(255, 255, 255, 0.16);
}

:root[data-el-theme="light"] {
--el-lift: rgba(29, 36, 56, 0.08);
--el-lift-strong: rgba(29, 36, 56, 0.14);
}

.el-button:hover {
background: var(--el-lift);
}

.el-button:active {
transform: scale(0.96);
}

.el-button:disabled {
cursor: default;
opacity: 0.45;
}

.el-button-primary {
border-color: transparent;
background: var(--el-brand);
color: #161616;
font-weight: 600;
}

.el-button-primary:hover {
background: var(--el-brand-strong);
}

.el-input {
min-height: 32px;
padding: 0 10px;
border: 1px solid var(--el-hairline);
border-radius: var(--el-radius-md);
background: var(--el-inset);
color: var(--el-text);
font: 400 var(--el-text-md) / 1.2 var(--el-font);
}

.el-input::placeholder {
color: var(--el-soft);
}

@media (prefers-reduced-motion: reduce) {
.el-app *,
.el-app *::before,
.el-app *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
Binary file added capsules/_shared/fonts/Inter-latin-var.woff2
Binary file not shown.
Binary file not shown.
Loading