diff --git a/apps/web/src/app.css b/apps/web/src/app.css index 1d66c81..bba85e1 100644 --- a/apps/web/src/app.css +++ b/apps/web/src/app.css @@ -9,6 +9,14 @@ @source '../node_modules/@atmo-dev/events-ui/dist'; @import '@foxui/core/theme.css'; +/* Blento semantic design tokens. Each Tailwind theme value reads an overridable `--blento-*` CSS + variable with a default that matches today's look, so components use normal utilities (e.g. + `rounded-card`) that resolve through page-overridable tokens (set on by ThemeScript from + page.style). Colors stay on the @foxui accent/base mechanism above. */ +@theme inline { + --radius-card: var(--blento-radius-card, 1.5rem); +} + @custom-variant dark (&:where(.dark, .dark *):not(:where(.light, .light *))); @custom-variant accent (&:where(.accent, .accent *)); diff --git a/apps/web/src/lib/cards/_base/BaseCard/BaseCard.svelte b/apps/web/src/lib/cards/_base/BaseCard/BaseCard.svelte index bddd479..59fcbcc 100644 --- a/apps/web/src/lib/cards/_base/BaseCard/BaseCard.svelte +++ b/apps/web/src/lib/cards/_base/BaseCard/BaseCard.svelte @@ -39,7 +39,7 @@ bind:this={ref} draggable={false} class={[ - 'card group/card selection:bg-accent-600/50 @container/card relative isolate z-0 h-full w-full rounded-3xl outline-offset-2 transition-[outline] duration-200', + 'card group/card selection:bg-accent-600/50 rounded-card @container/card relative isolate z-0 h-full w-full outline-offset-2 transition-[outline] duration-200', isEditing ? 'transition-all' : '', color ? (colors[color] ?? colors.accent) : colors.base, color !== 'accent' && item.color !== 'base' && item.color !== 'transparent' ? color : '', @@ -50,7 +50,7 @@ >
` CSS-var pairs for ThemeScript. Colors stay + * on the @foxui accent/base class mechanism; radius/space/font flow through the token vars (e.g. + * `style.radius.card` -> `radius-card` -> `--blento-radius-card`). Empty when no page tokens are set. + */ +export function getStyleTokens(data: WebsiteData): Record { + const style = data.publication?.style; + if (!style) return {}; + const out: Record = {}; + for (const group of ['radius', 'space', 'font'] as const) { + const g = style[group]; + if (!g) continue; + for (const [k, v] of Object.entries(g)) out[`${group}-${k}`] = String(v); + } + return out; +} + export function getName(data: WebsiteData): string { return data.publication?.name || data.profile.displayName || data.handle; } diff --git a/apps/web/src/lib/types.ts b/apps/web/src/lib/types.ts index cd20c0d..4d0e42c 100644 --- a/apps/web/src/lib/types.ts +++ b/apps/web/src/lib/types.ts @@ -1,6 +1,6 @@ import type { Blob } from '@atcute/lexicons'; import type { AppBskyActorDefs } from '@atcute/bluesky'; -import type { Node } from '@blento/schema'; +import type { Node, StyleTokens } from '@blento/schema'; export type Item = { id: string; @@ -91,6 +91,9 @@ export type WebsiteData = { // explicit layout sync mode (overrides editedOn when set) layoutMode?: 'desktop-leads' | 'mobile-leads' | 'independent'; }; + + /** Semantic design tokens (radius/space/font) applied as --blento-* CSS vars by ThemeScript. */ + style?: StyleTokens; }; profile: AppBskyActorDefs.ProfileViewDetailed; pronouns?: string; diff --git a/apps/web/src/lib/website/view/Head.svelte b/apps/web/src/lib/website/view/Head.svelte index 909edbc..8548005 100644 --- a/apps/web/src/lib/website/view/Head.svelte +++ b/apps/web/src/lib/website/view/Head.svelte @@ -7,7 +7,8 @@ image, description, accentColor, - baseColor + baseColor, + tokens }: { favicon: string | null; title: string | null; @@ -15,10 +16,11 @@ description?: string; accentColor?: string; baseColor?: string; + tokens?: Record; } = $props(); - + {#if favicon} diff --git a/apps/web/src/lib/website/view/ThemeScript.svelte b/apps/web/src/lib/website/view/ThemeScript.svelte index 04922f5..47b2234 100644 --- a/apps/web/src/lib/website/view/ThemeScript.svelte +++ b/apps/web/src/lib/website/view/ThemeScript.svelte @@ -3,10 +3,14 @@ let { accentColor = 'pink', - baseColor = 'stone' + baseColor = 'stone', + tokens = {} }: { accentColor?: string; baseColor?: string; + /** Page-level design tokens, applied as `--blento-` CSS vars on . e.g. + * { 'radius-card': '2rem' }. Empty = every token uses its default (identical to today). */ + tokens?: Record; } = $props(); const allAccentColors = [ @@ -32,11 +36,17 @@ const safeJson = (v: string) => JSON.stringify(v).replace(/(function(){document.documentElement.classList.add(${safeJson(accentColor)},${safeJson(baseColor)});})();<` + + // SSR: inline script for initial page load (no FOUC) — set color classes + token vars before paint. + // setProperty contains each value, so a token value can't break out of its declaration. + let script = $derived.by(() => { + const setProps = Object.entries(tokens) + .map(([k, v]) => `e.style.setProperty(${safeJson('--blento-' + k)},${safeJson(v)});`) + .join(''); + return ( + ` diff --git a/apps/web/src/lib/website/view/Website.svelte b/apps/web/src/lib/website/view/Website.svelte index f2c5abd..b0e910e 100644 --- a/apps/web/src/lib/website/view/Website.svelte +++ b/apps/web/src/lib/website/view/Website.svelte @@ -5,7 +5,8 @@ getDescription, getHideProfileSection, getProfilePosition, - getName + getName, + getStyleTokens } from '$lib/helpers/website'; import { innerWidth } from 'svelte/reactivity/window'; import { setDidContext, setHandleContext, setIsMobile } from '../data/context'; @@ -59,6 +60,7 @@ description={getDescription(data)} accentColor={data.publication?.preferences?.accentColor} baseColor={data.publication?.preferences?.baseColor} + tokens={getStyleTokens(data)} /> -- 2.51.2 From 559261a07b82d8fff9dab0dfaf9d44d40e7a3f6d Mon Sep 17 00:00:00 2001 From: Florian <45694132+flo-bit@users.noreply.github.com> Date: Wed, 1 Jul 2026 16:48:42 +0200 Subject: [PATCH 2/5] =?UTF-8?q?theming:=20dev-only=20keyboard=20theme=20sw?= =?UTF-8?q?itcher=20(press=201=E2=80=936)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A dev tool to eyeball the token system across theme presets without saving. Press 1–6 on any site page to flip accent/base colors + --blento-* tokens (radius) live; reload restores the page's real theme. Rendered only in dev ({#if dev}), so it never ships. 6 presets (Default/Ocean/Sunset/Forest/Grape/Rose) vary color + card radius — demonstrating the page.style token pipeline end-to-end. svelte-check 0/0. --- .../lib/website/view/DevThemeSwitcher.svelte | 83 +++++++++++++++++++ apps/web/src/lib/website/view/Website.svelte | 6 ++ 2 files changed, 89 insertions(+) create mode 100644 apps/web/src/lib/website/view/DevThemeSwitcher.svelte diff --git a/apps/web/src/lib/website/view/DevThemeSwitcher.svelte b/apps/web/src/lib/website/view/DevThemeSwitcher.svelte new file mode 100644 index 0000000..7ed0a7b --- /dev/null +++ b/apps/web/src/lib/website/view/DevThemeSwitcher.svelte @@ -0,0 +1,83 @@ + + +
+ {#if current >= 0} + {current + 1}. {presets[current].name} + · + {/if} + theme 1–{presets.length} +
diff --git a/apps/web/src/lib/website/view/Website.svelte b/apps/web/src/lib/website/view/Website.svelte index b0e910e..9015244 100644 --- a/apps/web/src/lib/website/view/Website.svelte +++ b/apps/web/src/lib/website/view/Website.svelte @@ -22,6 +22,8 @@ import { user } from '$lib/atproto'; import { env } from '$env/dynamic/public'; import { page } from '$app/state'; + import { dev } from '$app/environment'; + import DevThemeSwitcher from './DevThemeSwitcher.svelte'; let { data }: { data: WebsiteData } = $props(); @@ -102,4 +104,8 @@
+ + {#if dev} + + {/if} -- 2.51.2 From cda2c6d17c013db4045960079fc54046391e3fab Mon Sep 17 00:00:00 2001 From: Florian <45694132+flo-bit@users.noreply.github.com> Date: Wed, 1 Jul 2026 16:55:08 +0200 Subject: [PATCH 3/5] theming: add shadow + font tokens; enrich switcher presets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two more token dimensions, same pattern (default = today → 0px): - --blento-shadow-card on the card wrapper via [box-shadow:var(...,0 0 #0000)] — default invisible, so no change until a theme sets it (elevation). - --blento-font-body on body via font-family: var(...,inherit) — default inherits the current stack; a theme swaps typography. - getStyleTokens now flattens the `shadow` group too. - DevThemeSwitcher presets (Ocean/Sunset/Forest/Grape/Rose) now vary color + radius + shadow + font, so the presets are genuinely distinct themes. Validated: defaults pixel 0px on cards (only the dev indicator differs, 120px); forced shadow+font override = 0.6% visible change (elevation + serif). svelte-check 0/0. --- apps/web/src/app.css | 5 +++ .../lib/cards/_base/BaseCard/BaseCard.svelte | 2 +- apps/web/src/lib/helpers/website.ts | 2 +- .../lib/website/view/DevThemeSwitcher.svelte | 45 ++++++++++++++++--- 4 files changed, 46 insertions(+), 8 deletions(-) diff --git a/apps/web/src/app.css b/apps/web/src/app.css index bba85e1..56e4c79 100644 --- a/apps/web/src/app.css +++ b/apps/web/src/app.css @@ -17,6 +17,11 @@ --radius-card: var(--blento-radius-card, 1.5rem); } +/* Body font token — defaults to `inherit` (the current stack), overridden per page/theme. */ +body { + font-family: var(--blento-font-body, inherit); +} + @custom-variant dark (&:where(.dark, .dark *):not(:where(.light, .light *))); @custom-variant accent (&:where(.accent, .accent *)); diff --git a/apps/web/src/lib/cards/_base/BaseCard/BaseCard.svelte b/apps/web/src/lib/cards/_base/BaseCard/BaseCard.svelte index 59fcbcc..8026009 100644 --- a/apps/web/src/lib/cards/_base/BaseCard/BaseCard.svelte +++ b/apps/web/src/lib/cards/_base/BaseCard/BaseCard.svelte @@ -39,7 +39,7 @@ bind:this={ref} draggable={false} class={[ - 'card group/card selection:bg-accent-600/50 rounded-card @container/card relative isolate z-0 h-full w-full outline-offset-2 transition-[outline] duration-200', + 'card group/card selection:bg-accent-600/50 rounded-card @container/card relative isolate z-0 h-full w-full [box-shadow:var(--blento-shadow-card,0_0_#0000)] outline-offset-2 transition-[outline] duration-200', isEditing ? 'transition-all' : '', color ? (colors[color] ?? colors.accent) : colors.base, color !== 'accent' && item.color !== 'base' && item.color !== 'transparent' ? color : '', diff --git a/apps/web/src/lib/helpers/website.ts b/apps/web/src/lib/helpers/website.ts index e568eba..4c6ff8b 100644 --- a/apps/web/src/lib/helpers/website.ts +++ b/apps/web/src/lib/helpers/website.ts @@ -9,7 +9,7 @@ export function getStyleTokens(data: WebsiteData): Record { const style = data.publication?.style; if (!style) return {}; const out: Record = {}; - for (const group of ['radius', 'space', 'font'] as const) { + for (const group of ['radius', 'shadow', 'space', 'font'] as const) { const g = style[group]; if (!g) continue; for (const [k, v] of Object.entries(g)) out[`${group}-${k}`] = String(v); diff --git a/apps/web/src/lib/website/view/DevThemeSwitcher.svelte b/apps/web/src/lib/website/view/DevThemeSwitcher.svelte index 7ed0a7b..6234c65 100644 --- a/apps/web/src/lib/website/view/DevThemeSwitcher.svelte +++ b/apps/web/src/lib/website/view/DevThemeSwitcher.svelte @@ -14,11 +14,44 @@ const presets: Preset[] = [ { name: 'Default', accent: 'pink', base: 'stone', tokens: { 'radius-card': '1.5rem' } }, - { name: 'Ocean', accent: 'sky', base: 'slate', tokens: { 'radius-card': '0.5rem' } }, - { name: 'Sunset', accent: 'orange', base: 'neutral', tokens: { 'radius-card': '2rem' } }, - { name: 'Forest', accent: 'emerald', base: 'stone', tokens: { 'radius-card': '1rem' } }, - { name: 'Grape', accent: 'violet', base: 'zinc', tokens: { 'radius-card': '0.25rem' } }, - { name: 'Rose', accent: 'rose', base: 'gray', tokens: { 'radius-card': '9999px' } } + { + name: 'Ocean', + accent: 'sky', + base: 'slate', + tokens: { 'radius-card': '0.5rem', 'shadow-card': '0 8px 30px -8px rgb(2 20 40 / 0.25)' } + }, + { + name: 'Sunset', + accent: 'orange', + base: 'neutral', + tokens: { + 'radius-card': '2rem', + 'shadow-card': '0 10px 40px -12px rgb(120 40 0 / 0.3)', + 'font-body': "Georgia, 'Times New Roman', serif" + } + }, + { + name: 'Forest', + accent: 'emerald', + base: 'stone', + tokens: { 'radius-card': '1rem', 'shadow-card': '0 4px 16px -6px rgb(0 40 20 / 0.25)' } + }, + { + name: 'Grape', + accent: 'violet', + base: 'zinc', + tokens: { + 'radius-card': '0.25rem', + 'shadow-card': '0 2px 0 2px rgb(80 40 120 / 0.2)', + 'font-body': "ui-monospace, 'SF Mono', 'Cascadia Code', monospace" + } + }, + { + name: 'Rose', + accent: 'rose', + base: 'gray', + tokens: { 'radius-card': '9999px', 'shadow-card': '0 12px 32px -10px rgb(120 20 60 / 0.3)' } + } ]; const allAccent = [ @@ -41,7 +74,7 @@ 'rose' ]; const allBase = ['gray', 'stone', 'zinc', 'neutral', 'slate']; - const tokenKeys = ['radius-card']; + const tokenKeys = ['radius-card', 'shadow-card', 'font-body']; let current = $state(-1); -- 2.51.2 From b287dae2d6a4766cd44c4d46f7f8706cd40f00eb Mon Sep 17 00:00:00 2001 From: Florian <45694132+flo-bit@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:22:55 +0200 Subject: [PATCH 4/5] theming: apply radius token beyond BaseCard (hero decorations, section chrome) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hero section renders its own decoration cards outside BaseCard (hardcoded rounded-3xl), so they ignored the radius token. Swept card-level rounded-3xl -> rounded-card across sections (HeroSection Decoration, section drop-zones, SectionChrome) so all card-level corners follow --blento-radius-card. Default (1.5rem) == old rounded-3xl so it's identical; a theme's radius now reaches the hero too. (Media-card rounded-2xl embeds are clipped by BaseCard's overflow-hidden, so not visible — left as-is.) Gate 0px; svelte-check 0/0. --- .../sections/ColumnsSection/EditingColumnsSection.svelte | 2 +- .../src/lib/sections/GridSection/EditingGridSection.svelte | 2 +- apps/web/src/lib/sections/HeroSection/Decoration.svelte | 6 +++--- .../src/lib/sections/RowsSection/EditingRowsSection.svelte | 2 +- apps/web/src/lib/sections/SectionChrome.svelte | 2 +- .../SingleCardSection/EditingSingleCardSection.svelte | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/web/src/lib/sections/ColumnsSection/EditingColumnsSection.svelte b/apps/web/src/lib/sections/ColumnsSection/EditingColumnsSection.svelte index dd2c872..dd81550 100644 --- a/apps/web/src/lib/sections/ColumnsSection/EditingColumnsSection.svelte +++ b/apps/web/src/lib/sections/ColumnsSection/EditingColumnsSection.svelte @@ -84,7 +84,7 @@