Add light theme overrides and documentation

This commit is contained in:
thePR0M3TH3AN committed 2025-10-11 14:10:57 -04:00
1 parent 317463619c
commit b20895ec32
4 files changed
+110 -8

No files matched your search

+6 -6
View File
@@ -38,7 +38,7 @@
}
.btn-ghost {
@apply btn border border-border border-opacity-60 bg-transparent text-white shadow-none;
@apply btn border border-border border-opacity-60 bg-transparent text-text shadow-none;
}
.btn-ghost:hover {
@@ -280,7 +280,7 @@
}
.bv-modal__panel {
@apply focus-ring relative z-[var(--z-modal-content)] w-full max-w-3xl rounded-xl border border-border border-opacity-40 bg-surface px-6 py-8 text-left text-white shadow-modal transition duration-base ease-out;
@apply focus-ring relative z-[var(--z-modal-content)] w-full max-w-3xl rounded-xl border border-border border-opacity-40 bg-surface px-6 py-8 text-left text-text shadow-modal transition duration-base ease-out;
}
.bv-modal__panel:focus-visible {
@@ -556,7 +556,7 @@
}
.profile-switcher__add {
@apply card hover:bg-info/10 flex cursor-pointer items-center gap-4 border border-dashed border-border border-opacity-40 bg-surface text-left text-muted transition duration-base ease-out hover:border-info hover:border-opacity-60 hover:text-white focus-visible:border-info focus-visible:border-opacity-60;
@apply card hover:bg-info/10 flex cursor-pointer items-center gap-4 border border-dashed border-border border-opacity-40 bg-surface text-left text-muted transition duration-base ease-out hover:border-info hover:border-opacity-60 hover:text-text focus-visible:border-info focus-visible:border-opacity-60;
}
.profile-switcher__addAvatar {
@@ -600,7 +600,7 @@
}
.watch-history-card__thumbnail {
@apply block flex-none text-white no-underline sm:w-56;
@apply block flex-none text-text no-underline sm:w-56;
}
.watch-history-card__thumbnailInner {
@@ -616,7 +616,7 @@
}
.watch-history-card__title {
@apply cursor-pointer text-lg font-semibold leading-snug text-white transition duration-base ease-out;
@apply cursor-pointer text-lg font-semibold leading-snug text-text transition duration-base ease-out;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
@@ -661,7 +661,7 @@
}
.watch-history-card__action {
@apply inline-flex items-center justify-center gap-2 rounded-full border border-transparent bg-panel px-4 py-2 text-sm font-medium text-muted transition duration-base ease-out hover:border-border hover:bg-surface-alt hover:text-white focus-visible:border-border focus-visible:bg-surface-alt focus-visible:text-white;
@apply inline-flex items-center justify-center gap-2 rounded-full border border-transparent bg-panel px-4 py-2 text-sm font-medium text-muted transition duration-base ease-out hover:border-border hover:bg-surface-alt hover:text-text focus-visible:border-border focus-visible:bg-surface-alt focus-visible:text-text;
}
.watch-history-card__action--primary {
+41
View File
@@ -170,3 +170,44 @@
--motion-ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
--modal-motion-duration: var(--motion-duration-base);
}
[data-theme="light"] {
/* Light theme overrides */
--color-surface: rgb(248 250 252);
--color-surface-alt: rgb(241 245 249);
--color-bg: var(--color-surface);
--color-card: rgb(255 255 255);
--color-card-private: rgb(244 236 255);
--color-card-critical: rgb(255 228 230);
--color-panel: rgb(255 255 255);
--color-panel-hover: rgb(226 232 240);
--color-border: rgb(148 163 184);
--color-border-subtle: rgb(203 213 225);
--color-primary: rgb(220 38 38);
--color-text: rgb(15 23 42);
--color-text-strong: rgb(15 23 42);
--color-muted: rgb(71 85 105);
--color-muted-alt: rgb(100 116 139);
--color-muted-strong: rgb(51 65 85);
--color-overlay: rgb(15 23 42 / 0.55);
--color-overlay-muted: rgb(15 23 42 / 0.35);
--color-overlay-strong: rgb(15 23 42 / 0.65);
--color-overlay-darker: rgb(15 23 42 / 0.72);
--color-overlay-panel: rgb(15 23 42 / 0.78);
--color-overlay-panel-soft: rgb(15 23 42 / 0.6);
--color-overlay-panel-tint: rgb(148 163 184 / 0.2);
--color-overlay-panel-glass: rgb(255 255 255 / 0.82);
--color-overlay-veil: rgb(148 163 184 / 0.5);
--color-overlay-panel-gradient-start: rgb(255 255 255 / 0.92);
--color-overlay-panel-gradient-end: rgb(226 232 240 / 0.92);
--color-surface-tint-subtle: rgb(15 23 42 / 0.08);
--color-border-translucent: rgb(148 163 184 / 0.18);
--color-border-translucent-strong: rgb(148 163 184 / 0.22);
--color-border-translucent-bright: rgb(148 163 184 / 0.28);
--color-border-translucent-stronger: rgb(148 163 184 / 0.32);
--color-border-translucent-strongest: rgb(148 163 184 / 0.36);
--color-border-translucent-glow: rgb(148 163 184 / 0.45);
--color-border-translucent-medium: rgb(148 163 184 / 0.2);
--color-border-neutral-soft: rgb(148 163 184 / 0.25);
--color-overlay-banner: rgb(148 163 184 / 0.7);
}
+12
View File
@@ -2,6 +2,18 @@
bitvid's visual language is now powered by Tailwind utilities backed by our design tokens. This document lists the primitives available via `css/tailwind.source.css`, how they behave across states, and when to mix them with Tailwind utilities.
## Theme System
The default experience renders the production dark palette. Pages and embeds can opt into alternates by setting the `data-theme` attribute on the `<html>` (or any ancestor that wraps the component). `data-theme="light"` promotes the light tokens defined in `css/tokens.css`, swapping surface, panel, border, muted, and overlay colors while keeping semantic accents consistent. Omit the attribute or set it back to `default` to inherit the dark baseline. Reserve additional theme keys (for example, `data-theme="contrast"`) for upcoming accessibility palettes—the same hook will activate them once their tokens land.
### Per-page overrides
Add `<html data-theme="light">` to force a page into light mode. The attribute can also target specific islands—wrap your component tree in a container with `data-theme="light"` when you need to render light cards inside a dark shell.
### Embedding in third-party surfaces
Components consume design tokens via CSS custom properties, so they pick up the active theme from the closest ancestor. When embedding widgets into a host site, include the `css/tokens.css` bundle and set `data-theme` on the wrapper to control contrast. If the host already defines its own theme, namespace our tokens (e.g., nest them inside a dedicated `.bitvid-embed[data-theme="light"]`) to avoid collisions while still letting overlay, border, and panel tokens cascade.
## Focus Handling
All interactive primitives mix in the shared `.focus-ring` helper. It removes default outlines and applies a branded ring using `focus-visible` only. When building bespoke components, add `.focus-ring` to your root interactive element or replicate its rules:
+51 -2
View File
@@ -10,10 +10,18 @@
<div class="min-h-screen bg-page-alt">
<header class="bg-surface text-text shadow-modal">
<div
class="mx-auto flex max-w-6xl items-center justify-between px-md py-sm"
class="mx-auto flex max-w-6xl items-center justify-between gap-sm px-md py-sm"
>
<h1 class="text-2xl font-semibold">bitvid UI Kitchen Sink</h1>
<button class="btn">Primary Action</button>
<div class="flex items-center gap-sm">
<label class="sr-only" for="themeSwitcher">Theme</label>
<select id="themeSwitcher" class="select h-10" aria-label="Select theme">
<option value="default">Default (Dark)</option>
<option value="light">Light</option>
<option value="contrast">High Contrast (Preview)</option>
</select>
<button class="btn">Primary Action</button>
</div>
</div>
</header>
@@ -708,5 +716,46 @@
</section>
</main>
</div>
<script>
(() => {
const storageKey = "bitvid-docs-theme";
const root = document.documentElement;
const switcher = document.getElementById("themeSwitcher");
const applyTheme = (value) => {
if (!value || value === "default") {
delete root.dataset.theme;
} else {
root.dataset.theme = value;
}
};
let savedTheme;
try {
savedTheme = window.localStorage.getItem(storageKey) || undefined;
} catch (error) {
savedTheme = undefined;
}
applyTheme(savedTheme);
if (switcher) {
switcher.value = savedTheme ?? "default";
switcher.addEventListener("change", (event) => {
const { value } = event.target;
applyTheme(value);
try {
if (!value || value === "default") {
window.localStorage.removeItem(storageKey);
} else {
window.localStorage.setItem(storageKey, value);
}
} catch (error) {
/* Ignore storage failures (private mode, etc.) */
}
});
}
})();
</script>
</body>
</html>