fix: persist theme via html[data-theme] + theme.css custom properties
- Blocking <head> script restores canvas-theme from localStorage with prefers-color-scheme fallback (no FOUC) - New theme.css with CSS custom properties for dark/light - Removed data-theme from body/header/tab-row (now on <html>) - Theme toggle writes to documentElement instead of body Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b77fb30001
commit
8bd899d146
|
|
@ -470,7 +470,9 @@ export function renderExternalAppShell(opts: ExternalAppShellOptions): string {
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🌌</text></svg>">
|
||||
<title>${escapeHtml(title)}</title>
|
||||
<link rel="stylesheet" href="/shell.css?v=6">
|
||||
<script>(function(){var t=localStorage.getItem('canvas-theme');if(!t)t=matchMedia('(prefers-color-scheme:light)').matches?'light':'dark';document.documentElement.setAttribute('data-theme',t)})()</script>
|
||||
<link rel="stylesheet" href="/theme.css?v=1">
|
||||
<link rel="stylesheet" href="/shell.css?v=7">
|
||||
<style>
|
||||
html.rspace-embedded .rstack-header { display: none !important; }
|
||||
html.rspace-embedded .rstack-tab-row { display: none !important; }
|
||||
|
|
@ -478,9 +480,8 @@ export function renderExternalAppShell(opts: ExternalAppShellOptions): string {
|
|||
</style>
|
||||
<script>if (window.self !== window.parent) document.documentElement.classList.add('rspace-embedded');</script>
|
||||
</head>
|
||||
<body data-theme="${theme}">
|
||||
<script>(function(){try{var t=localStorage.getItem('canvas-theme');if(t)document.body.setAttribute('data-theme',t)}catch(e){}})()</script>
|
||||
<header class="rstack-header" data-theme="${theme}">
|
||||
<body>
|
||||
<header class="rstack-header">
|
||||
<div class="rstack-header__left">
|
||||
<a href="/" style="display:flex;align-items:center;margin-right:4px"><img src="/favicon.png" alt="rSpace" class="rstack-header__logo"></a>
|
||||
<rstack-app-switcher current="${escapeAttr(moduleId)}"></rstack-app-switcher>
|
||||
|
|
@ -494,7 +495,7 @@ export function renderExternalAppShell(opts: ExternalAppShellOptions): string {
|
|||
<rstack-identity></rstack-identity>
|
||||
</div>
|
||||
</header>
|
||||
<div class="rstack-tab-row" data-theme="${theme}">
|
||||
<div class="rstack-tab-row">
|
||||
<rstack-tab-bar space="${escapeAttr(spaceSlug)}" active="" view-mode="flat"></rstack-tab-bar>
|
||||
</div>
|
||||
<div class="rspace-iframe-wrap">
|
||||
|
|
@ -514,8 +515,7 @@ export function renderExternalAppShell(opts: ExternalAppShellOptions): string {
|
|||
</div>
|
||||
|
||||
<script type="module">
|
||||
import '/shell.js?v=6';
|
||||
(function(){try{var t=localStorage.getItem('canvas-theme');if(t)document.querySelectorAll('[data-theme]').forEach(function(el){el.setAttribute('data-theme',t)})}catch(e){}})();
|
||||
import '/shell.js?v=7';
|
||||
document.querySelector('rstack-app-switcher')?.setModules(${moduleListJSON});
|
||||
|
||||
const tabBar = document.querySelector('rstack-tab-bar');
|
||||
|
|
@ -723,13 +723,14 @@ export function renderModuleLanding(opts: ModuleLandingOptions): string {
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>${mod.icon}</text></svg>">
|
||||
<title>${escapeHtml(mod.name)} — rSpace</title>
|
||||
<link rel="stylesheet" href="/shell.css?v=6">
|
||||
<script>(function(){var t=localStorage.getItem('canvas-theme');if(!t)t=matchMedia('(prefers-color-scheme:light)').matches?'light':'dark';document.documentElement.setAttribute('data-theme',t)})()</script>
|
||||
<link rel="stylesheet" href="/theme.css?v=1">
|
||||
<link rel="stylesheet" href="/shell.css?v=7">
|
||||
${cssBlock}
|
||||
<script defer src="https://rdata.online/collect.js" data-website-id="6ee7917b-0ed7-44cb-a4c8-91037638526b"></script>
|
||||
</head>
|
||||
<body data-theme="${theme}">
|
||||
<script>(function(){try{var t=localStorage.getItem('canvas-theme');if(t)document.body.setAttribute('data-theme',t)}catch(e){}})()</script>
|
||||
<header class="rstack-header" data-theme="${theme}">
|
||||
<body>
|
||||
<header class="rstack-header">
|
||||
<div class="rstack-header__left">
|
||||
<a href="/" style="display:flex;align-items:center;margin-right:4px"><img src="/favicon.png" alt="rSpace" class="rstack-header__logo"></a>
|
||||
<rstack-app-switcher current="${escapeAttr(mod.id)}"></rstack-app-switcher>
|
||||
|
|
@ -744,8 +745,7 @@ export function renderModuleLanding(opts: ModuleLandingOptions): string {
|
|||
</header>
|
||||
${bodyContent}
|
||||
<script type="module">
|
||||
import '/shell.js?v=6';
|
||||
(function(){try{var t=localStorage.getItem('canvas-theme');if(t)document.querySelectorAll('[data-theme]').forEach(function(el){el.setAttribute('data-theme',t)})}catch(e){}})();
|
||||
import '/shell.js?v=7';
|
||||
document.querySelector('rstack-app-switcher')?.setModules(${moduleListJSON});
|
||||
function _updateDemoBtn() {
|
||||
var btn = document.querySelector('.rstack-header__demo-btn');
|
||||
|
|
|
|||
|
|
@ -1266,8 +1266,7 @@ export class RStackIdentity extends HTMLElement {
|
|||
e.stopPropagation();
|
||||
const newTheme = themeToggle.checked ? "dark" : "light";
|
||||
localStorage.setItem("canvas-theme", newTheme);
|
||||
document.body.setAttribute("data-theme", newTheme);
|
||||
document.querySelectorAll(".rstack-header, .rstack-tab-row").forEach(el => el.setAttribute("data-theme", newTheme));
|
||||
document.documentElement.setAttribute("data-theme", newTheme);
|
||||
this.dispatchEvent(new CustomEvent("theme-change", { bubbles: true, composed: true, detail: { theme: newTheme } }));
|
||||
this.#render();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,193 @@
|
|||
/* ── rSpace Theme System ──
|
||||
* CSS custom properties for dark/light theming.
|
||||
* Dark is default on :root; light overrides on [data-theme="light"].
|
||||
* prefers-color-scheme fallback applies when no data-theme is set yet. */
|
||||
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
|
||||
/* Surface */
|
||||
--rs-bg-page: #0f172a;
|
||||
--rs-bg-surface: #1e293b;
|
||||
--rs-bg-surface-raised: #334155;
|
||||
--rs-bg-surface-sunken: #0f172a;
|
||||
--rs-bg-overlay: rgba(15, 23, 42, 0.85);
|
||||
--rs-bg-hover: rgba(255, 255, 255, 0.05);
|
||||
--rs-bg-active: rgba(6, 182, 212, 0.1);
|
||||
|
||||
/* Text */
|
||||
--rs-text-primary: #e2e8f0;
|
||||
--rs-text-secondary: #94a3b8;
|
||||
--rs-text-muted: #64748b;
|
||||
--rs-text-inverse: #0f172a;
|
||||
|
||||
/* Borders */
|
||||
--rs-border: rgba(255, 255, 255, 0.1);
|
||||
--rs-border-subtle: rgba(255, 255, 255, 0.06);
|
||||
--rs-border-strong: rgba(255, 255, 255, 0.2);
|
||||
|
||||
/* Glass */
|
||||
--rs-glass-bg: rgba(15, 23, 42, 0.85);
|
||||
--rs-glass-border: rgba(255, 255, 255, 0.08);
|
||||
|
||||
/* Shadows */
|
||||
--rs-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||
--rs-shadow-md: 0 4px 20px rgba(0, 0, 0, 0.4);
|
||||
--rs-shadow-lg: 0 8px 30px rgba(0, 0, 0, 0.4);
|
||||
|
||||
/* Spinner */
|
||||
--rs-spinner-track: rgba(255, 255, 255, 0.1);
|
||||
--rs-spinner-head: #14b8a6;
|
||||
|
||||
/* Accent (same both themes) */
|
||||
--rs-accent: #14b8a6;
|
||||
--rs-accent-hover: #0d9488;
|
||||
|
||||
/* Primary (same both themes) */
|
||||
--rs-primary: #4f46e5;
|
||||
--rs-primary-hover: #6366f1;
|
||||
|
||||
/* Semantic (same both themes) */
|
||||
--rs-error: #ef4444;
|
||||
--rs-success: #22c55e;
|
||||
--rs-warning: #fbbf24;
|
||||
|
||||
/* Gradients (same both themes) */
|
||||
--rs-gradient-brand: linear-gradient(135deg, #14b8a6, #22d3ee);
|
||||
--rs-gradient-primary: linear-gradient(135deg, #6366f1, #4f46e5);
|
||||
--rs-gradient-cta: linear-gradient(135deg, #14b8a6, #0d9488);
|
||||
|
||||
/* Component-specific tokens */
|
||||
--rs-input-bg: #0f172a;
|
||||
--rs-input-border: #334155;
|
||||
--rs-input-text: #e2e8f0;
|
||||
--rs-btn-secondary-bg: rgba(255, 255, 255, 0.08);
|
||||
--rs-btn-secondary-text: #94a3b8;
|
||||
--rs-card-bg: rgba(255, 255, 255, 0.03);
|
||||
--rs-card-border: rgba(255, 255, 255, 0.06);
|
||||
|
||||
/* Canvas */
|
||||
--rs-canvas-bg: #0f172a;
|
||||
--rs-canvas-grid: rgba(255, 255, 255, 0.04);
|
||||
|
||||
/* Toolbar */
|
||||
--rs-toolbar-bg: #1e293b;
|
||||
--rs-toolbar-btn-bg: #334155;
|
||||
--rs-toolbar-btn-hover: #475569;
|
||||
--rs-toolbar-btn-text: #e2e8f0;
|
||||
--rs-toolbar-sep: #334155;
|
||||
--rs-toolbar-panel-bg: #1e293b;
|
||||
--rs-toolbar-panel-border: #334155;
|
||||
}
|
||||
|
||||
[data-theme="light"] {
|
||||
color-scheme: light;
|
||||
|
||||
/* Surface */
|
||||
--rs-bg-page: #f8fafc;
|
||||
--rs-bg-surface: #ffffff;
|
||||
--rs-bg-surface-raised: #f1f5f9;
|
||||
--rs-bg-surface-sunken: #f8fafc;
|
||||
--rs-bg-overlay: rgba(255, 255, 255, 0.9);
|
||||
--rs-bg-hover: rgba(0, 0, 0, 0.04);
|
||||
--rs-bg-active: #e0f2fe;
|
||||
|
||||
/* Text */
|
||||
--rs-text-primary: #0f172a;
|
||||
--rs-text-secondary: #374151;
|
||||
--rs-text-muted: #64748b;
|
||||
--rs-text-inverse: #e2e8f0;
|
||||
|
||||
/* Borders */
|
||||
--rs-border: rgba(0, 0, 0, 0.1);
|
||||
--rs-border-subtle: rgba(0, 0, 0, 0.06);
|
||||
--rs-border-strong: rgba(0, 0, 0, 0.2);
|
||||
|
||||
/* Glass */
|
||||
--rs-glass-bg: rgba(255, 255, 255, 0.9);
|
||||
--rs-glass-border: rgba(0, 0, 0, 0.08);
|
||||
|
||||
/* Shadows */
|
||||
--rs-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
--rs-shadow-md: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
--rs-shadow-lg: 0 8px 30px rgba(0, 0, 0, 0.18);
|
||||
|
||||
/* Spinner */
|
||||
--rs-spinner-track: rgba(0, 0, 0, 0.08);
|
||||
--rs-spinner-head: #14b8a6;
|
||||
|
||||
/* Component-specific tokens */
|
||||
--rs-input-bg: #f8fafc;
|
||||
--rs-input-border: #e2e8f0;
|
||||
--rs-input-text: #334155;
|
||||
--rs-btn-secondary-bg: rgba(0, 0, 0, 0.05);
|
||||
--rs-btn-secondary-text: #374151;
|
||||
--rs-card-bg: rgba(0, 0, 0, 0.02);
|
||||
--rs-card-border: rgba(0, 0, 0, 0.06);
|
||||
|
||||
/* Canvas */
|
||||
--rs-canvas-bg: #ffffff;
|
||||
--rs-canvas-grid: #f1f5f9;
|
||||
|
||||
/* Toolbar */
|
||||
--rs-toolbar-bg: #ffffff;
|
||||
--rs-toolbar-btn-bg: #f1f5f9;
|
||||
--rs-toolbar-btn-hover: #e2e8f0;
|
||||
--rs-toolbar-btn-text: #0f172a;
|
||||
--rs-toolbar-sep: #e2e8f0;
|
||||
--rs-toolbar-panel-bg: #ffffff;
|
||||
--rs-toolbar-panel-border: #e2e8f0;
|
||||
}
|
||||
|
||||
/* prefers-color-scheme fallback: if JS hasn't set data-theme yet */
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root:not([data-theme]) {
|
||||
color-scheme: light;
|
||||
|
||||
--rs-bg-page: #f8fafc;
|
||||
--rs-bg-surface: #ffffff;
|
||||
--rs-bg-surface-raised: #f1f5f9;
|
||||
--rs-bg-surface-sunken: #f8fafc;
|
||||
--rs-bg-overlay: rgba(255, 255, 255, 0.9);
|
||||
--rs-bg-hover: rgba(0, 0, 0, 0.04);
|
||||
--rs-bg-active: #e0f2fe;
|
||||
|
||||
--rs-text-primary: #0f172a;
|
||||
--rs-text-secondary: #374151;
|
||||
--rs-text-muted: #64748b;
|
||||
--rs-text-inverse: #e2e8f0;
|
||||
|
||||
--rs-border: rgba(0, 0, 0, 0.1);
|
||||
--rs-border-subtle: rgba(0, 0, 0, 0.06);
|
||||
--rs-border-strong: rgba(0, 0, 0, 0.2);
|
||||
|
||||
--rs-glass-bg: rgba(255, 255, 255, 0.9);
|
||||
--rs-glass-border: rgba(0, 0, 0, 0.08);
|
||||
|
||||
--rs-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
--rs-shadow-md: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
--rs-shadow-lg: 0 8px 30px rgba(0, 0, 0, 0.18);
|
||||
|
||||
--rs-spinner-track: rgba(0, 0, 0, 0.08);
|
||||
--rs-spinner-head: #14b8a6;
|
||||
|
||||
--rs-input-bg: #f8fafc;
|
||||
--rs-input-border: #e2e8f0;
|
||||
--rs-input-text: #334155;
|
||||
--rs-btn-secondary-bg: rgba(0, 0, 0, 0.05);
|
||||
--rs-btn-secondary-text: #374151;
|
||||
--rs-card-bg: rgba(0, 0, 0, 0.02);
|
||||
--rs-card-border: rgba(0, 0, 0, 0.06);
|
||||
|
||||
--rs-canvas-bg: #ffffff;
|
||||
--rs-canvas-grid: #f1f5f9;
|
||||
|
||||
--rs-toolbar-bg: #ffffff;
|
||||
--rs-toolbar-btn-bg: #f1f5f9;
|
||||
--rs-toolbar-btn-hover: #e2e8f0;
|
||||
--rs-toolbar-btn-text: #0f172a;
|
||||
--rs-toolbar-sep: #e2e8f0;
|
||||
--rs-toolbar-panel-bg: #ffffff;
|
||||
--rs-toolbar-panel-border: #e2e8f0;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue