Commit ·
92cf18f
1
Parent(s): 47b8a5f
Site: version badge and size claim now measured from the engine
Browse files- The nav badge and footer said v0.8.0 after the 0.8.1 release, because the
version was hardcoded in SiteRoot. The site now reads VERSION from
public/dssl/index.js when it is built, so it cannot drift again.
- The hero claimed "~20 kB engine". The engine is about 190 kB as plain
source and about 58 kB gzipped. The hero, the page description and
llms.txt now say the real gzipped size, measured at build time.
- Page title, hero copy, footer and home page text no longer use em dashes.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- llms.txt +1 -1
- public/llms.txt +1 -1
- src/app/layout.tsx +5 -3
- src/app/page.tsx +2 -1
- src/components/dssl/SiteRoot.tsx +11 -7
- src/components/dssl/engine-info.tsx +9 -0
- src/components/dssl/views/HomeView.tsx +24 -22
- src/components/dssl/views/SpecView.tsx +4 -2
- src/lib/engine-info.ts +33 -0
llms.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
# DSSL
|
| 2 |
|
| 3 |
-
> DSSL is a style language for the web. For JS-guaranteed pages it replaces writing CSS; it does not replace the CSS platform. You write `.dssl` files, the page loads
|
| 4 |
|
| 5 |
- Site (docs, playground, changelog, bench): https://dssl-sable.vercel.app
|
| 6 |
- Repo + full docs in the README: https://huggingface.co/EHS-Studio/dssl
|
|
|
|
| 1 |
# DSSL
|
| 2 |
|
| 3 |
+
> DSSL is a style language for the web. For JS-guaranteed pages it replaces writing CSS; it does not replace the CSS platform. You write `.dssl` files, the page loads the engine (about 58 kB gzipped, about 190 kB as plain source), and the engine walks the DOM in real time applying every matched rule as inline styles. Nothing compiles to CSS at runtime — but v0.8.0 adds the exit lane: `dssl build` compiles the static subset to a real .css file for no-JS, strict-CSP or size-critical pages. Elements added later are styled the moment they appear; signals, states and motion share one grammar.
|
| 4 |
|
| 5 |
- Site (docs, playground, changelog, bench): https://dssl-sable.vercel.app
|
| 6 |
- Repo + full docs in the README: https://huggingface.co/EHS-Studio/dssl
|
public/llms.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
# DSSL
|
| 2 |
|
| 3 |
-
> DSSL is a style language for the web. For JS-guaranteed pages it replaces writing CSS; it does not replace the CSS platform. You write `.dssl` files, the page loads
|
| 4 |
|
| 5 |
- Site (docs, playground, changelog, bench): https://dssl-sable.vercel.app
|
| 6 |
- Repo + full docs in the README: https://huggingface.co/EHS-Studio/dssl
|
|
|
|
| 1 |
# DSSL
|
| 2 |
|
| 3 |
+
> DSSL is a style language for the web. For JS-guaranteed pages it replaces writing CSS; it does not replace the CSS platform. You write `.dssl` files, the page loads the engine (about 58 kB gzipped, about 190 kB as plain source), and the engine walks the DOM in real time applying every matched rule as inline styles. Nothing compiles to CSS at runtime — but v0.8.0 adds the exit lane: `dssl build` compiles the static subset to a real .css file for no-JS, strict-CSP or size-critical pages. Elements added later are styled the moment they appear; signals, states and motion share one grammar.
|
| 4 |
|
| 5 |
- Site (docs, playground, changelog, bench): https://dssl-sable.vercel.app
|
| 6 |
- Repo + full docs in the README: https://huggingface.co/EHS-Studio/dssl
|
src/app/layout.tsx
CHANGED
|
@@ -2,11 +2,13 @@ import type { Metadata, Viewport } from "next";
|
|
| 2 |
import { readFileSync } from "node:fs";
|
| 3 |
import path from "node:path";
|
| 4 |
import "./globals.css";
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
export const metadata: Metadata = {
|
| 7 |
-
title: "DSSL
|
| 8 |
-
description:
|
| 9 |
-
"DSSL is a styling language for the web: design tokens, its own cascade, loud errors and zero specificity wars. Ships as a ~20 kB engine, no build step.",
|
| 10 |
keywords: ["DSSL", "CSS alternative", "style language", "design tokens", "web"],
|
| 11 |
icons: { icon: "/favicon.svg" },
|
| 12 |
};
|
|
|
|
| 2 |
import { readFileSync } from "node:fs";
|
| 3 |
import path from "node:path";
|
| 4 |
import "./globals.css";
|
| 5 |
+
import { readEngineInfo } from "@/lib/engine-info";
|
| 6 |
+
|
| 7 |
+
const engine = readEngineInfo();
|
| 8 |
|
| 9 |
export const metadata: Metadata = {
|
| 10 |
+
title: "DSSL: Domain Specific Style Language",
|
| 11 |
+
description: `DSSL is a styling language for the web: design tokens, its own cascade, loud errors and zero specificity wars. The engine is ${engine.gzKb} kB gzipped, with no build step.`,
|
|
|
|
| 12 |
keywords: ["DSSL", "CSS alternative", "style language", "design tokens", "web"],
|
| 13 |
icons: { icon: "/favicon.svg" },
|
| 14 |
};
|
src/app/page.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
import SiteRoot from "@/components/dssl/SiteRoot";
|
|
|
|
| 2 |
|
| 3 |
export default function Home() {
|
| 4 |
-
return <SiteRoot />;
|
| 5 |
}
|
|
|
|
| 1 |
import SiteRoot from "@/components/dssl/SiteRoot";
|
| 2 |
+
import { readEngineInfo } from "@/lib/engine-info";
|
| 3 |
|
| 4 |
export default function Home() {
|
| 5 |
+
return <SiteRoot info={readEngineInfo()} />;
|
| 6 |
}
|
src/components/dssl/SiteRoot.tsx
CHANGED
|
@@ -9,6 +9,8 @@ import PlaygroundView from "./views/PlaygroundView";
|
|
| 9 |
import ChangelogView from "./views/ChangelogView";
|
| 10 |
import BenchView from "./views/BenchView";
|
| 11 |
import { SunIcon, MoonIcon, MenuIcon } from "./icons";
|
|
|
|
|
|
|
| 12 |
|
| 13 |
export type ViewName = "home" | "docs" | "cheat" | "spec" | "playground" | "changelog" | "bench";
|
| 14 |
|
|
@@ -62,7 +64,7 @@ export interface DsslApplyResult {
|
|
| 62 |
stats: { rules: number; shadowRules: number; motions: number; tokens: number; mediaQueries: number };
|
| 63 |
}
|
| 64 |
|
| 65 |
-
export default function SiteRoot() {
|
| 66 |
// the view IS the hash — derived, never stored twice
|
| 67 |
const view = useSyncExternalStore(
|
| 68 |
subscribeHash,
|
|
@@ -100,6 +102,7 @@ export default function SiteRoot() {
|
|
| 100 |
}, []);
|
| 101 |
|
| 102 |
return (
|
|
|
|
| 103 |
<div className="site" data-view={view}>
|
| 104 |
<header className="nav">
|
| 105 |
<div className="wrap nav-inner">
|
|
@@ -122,7 +125,7 @@ export default function SiteRoot() {
|
|
| 122 |
</nav>
|
| 123 |
|
| 124 |
<div className="nav-side">
|
| 125 |
-
<a href="#/changelog" className="ver-badge" onClick={(e) => { e.preventDefault(); go("changelog"); }}>
|
| 126 |
<button type="button" className="theme-btn" onClick={cycleTheme} aria-label="Toggle color theme" title="Theme: auto / light / dark">
|
| 127 |
<span className="icon-sun"><SunIcon /></span>
|
| 128 |
<span className="icon-moon"><MoonIcon /></span>
|
|
@@ -151,13 +154,13 @@ export default function SiteRoot() {
|
|
| 151 |
<div className="wrap footer-inner">
|
| 152 |
<div className="footer-col">
|
| 153 |
<div className="footer-note">
|
| 154 |
-
<strong>DSSL</strong>
|
| 155 |
-
building this site
|
| 156 |
-
fixed for real. This page runs on its own engine
|
| 157 |
-
|
| 158 |
</div>
|
| 159 |
<div className="footer-fine">
|
| 160 |
-
© 2026 <strong>DSSL. inc</strong>
|
| 161 |
is hard.
|
| 162 |
</div>
|
| 163 |
</div>
|
|
@@ -171,5 +174,6 @@ export default function SiteRoot() {
|
|
| 171 |
</div>
|
| 172 |
</footer>
|
| 173 |
</div>
|
|
|
|
| 174 |
);
|
| 175 |
}
|
|
|
|
| 9 |
import ChangelogView from "./views/ChangelogView";
|
| 10 |
import BenchView from "./views/BenchView";
|
| 11 |
import { SunIcon, MoonIcon, MenuIcon } from "./icons";
|
| 12 |
+
import { EngineInfoProvider } from "./engine-info";
|
| 13 |
+
import type { EngineInfo } from "@/lib/engine-info";
|
| 14 |
|
| 15 |
export type ViewName = "home" | "docs" | "cheat" | "spec" | "playground" | "changelog" | "bench";
|
| 16 |
|
|
|
|
| 64 |
stats: { rules: number; shadowRules: number; motions: number; tokens: number; mediaQueries: number };
|
| 65 |
}
|
| 66 |
|
| 67 |
+
export default function SiteRoot({ info }: { info: EngineInfo }) {
|
| 68 |
// the view IS the hash — derived, never stored twice
|
| 69 |
const view = useSyncExternalStore(
|
| 70 |
subscribeHash,
|
|
|
|
| 102 |
}, []);
|
| 103 |
|
| 104 |
return (
|
| 105 |
+
<EngineInfoProvider value={info}>
|
| 106 |
<div className="site" data-view={view}>
|
| 107 |
<header className="nav">
|
| 108 |
<div className="wrap nav-inner">
|
|
|
|
| 125 |
</nav>
|
| 126 |
|
| 127 |
<div className="nav-side">
|
| 128 |
+
<a href="#/changelog" className="ver-badge" onClick={(e) => { e.preventDefault(); go("changelog"); }}>v{info.version}</a>
|
| 129 |
<button type="button" className="theme-btn" onClick={cycleTheme} aria-label="Toggle color theme" title="Theme: auto / light / dark">
|
| 130 |
<span className="icon-sun"><SunIcon /></span>
|
| 131 |
<span className="icon-moon"><MoonIcon /></span>
|
|
|
|
| 154 |
<div className="wrap footer-inner">
|
| 155 |
<div className="footer-col">
|
| 156 |
<div className="footer-note">
|
| 157 |
+
<strong>DSSL</strong>, the domain specific style language. Built from v0.1 to v{info.version} while
|
| 158 |
+
building this site: every feature it needed, it grew, and every bug you sent got
|
| 159 |
+
fixed for real. This page runs on its own engine with no CSS files attached.
|
| 160 |
+
The engine styles this DOM directly.
|
| 161 |
</div>
|
| 162 |
<div className="footer-fine">
|
| 163 |
+
© 2026 <strong>DSSL. inc</strong>, named after the product, because naming things
|
| 164 |
is hard.
|
| 165 |
</div>
|
| 166 |
</div>
|
|
|
|
| 174 |
</div>
|
| 175 |
</footer>
|
| 176 |
</div>
|
| 177 |
+
</EngineInfoProvider>
|
| 178 |
);
|
| 179 |
}
|
src/components/dssl/engine-info.tsx
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { createContext, useContext } from "react";
|
| 4 |
+
import type { EngineInfo } from "@/lib/engine-info";
|
| 5 |
+
|
| 6 |
+
const EngineInfoContext = createContext<EngineInfo>({ version: "", rawKb: 0, gzKb: 0 });
|
| 7 |
+
|
| 8 |
+
export const EngineInfoProvider = EngineInfoContext.Provider;
|
| 9 |
+
export const useEngineInfo = () => useContext(EngineInfoContext);
|
src/components/dssl/views/HomeView.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import { useState } from "react";
|
|
| 4 |
import CodeBlock from "../CodeBlock";
|
| 5 |
import { highlightDssl } from "../highlight";
|
| 6 |
import { ArrowIcon, PlayIcon } from "../icons";
|
|
|
|
| 7 |
import type { ViewName } from "../SiteRoot";
|
| 8 |
|
| 9 |
const DEMOS = {
|
|
@@ -46,7 +47,7 @@ const DEMOS = {
|
|
| 46 |
.demo-card {
|
| 47 |
animate: rise-in 500 snap both
|
| 48 |
}`,
|
| 49 |
-
// v0.4.0
|
| 50 |
reveal: `motion rise-in {
|
| 51 |
from {
|
| 52 |
opacity: 0
|
|
@@ -68,6 +69,7 @@ const DEMOS = {
|
|
| 68 |
type DemoKey = keyof typeof DEMOS;
|
| 69 |
|
| 70 |
export default function HomeView({ go }: { go: (v: ViewName) => void }) {
|
|
|
|
| 71 |
const [demo, setDemo] = useState<DemoKey>("tokens");
|
| 72 |
const [signalOn, setSignalOn] = useState(false);
|
| 73 |
|
|
@@ -95,9 +97,9 @@ export default function HomeView({ go }: { go: (v: ViewName) => void }) {
|
|
| 95 |
</h1>
|
| 96 |
<p className="hero-sub">
|
| 97 |
A styling language for the web that pairs with any HTML and any JavaScript.
|
| 98 |
-
Its own cascade
|
| 99 |
-
tokens as the vocabulary, states you can read, motion in four words,
|
| 100 |
-
errors that actually speak up. Zero dependencies, no build step.
|
| 101 |
</p>
|
| 102 |
<div className="hero-ctas">
|
| 103 |
<button type="button" className="btn btn-primary" onClick={() => go("docs")}>
|
|
@@ -109,13 +111,13 @@ export default function HomeView({ go }: { go: (v: ViewName) => void }) {
|
|
| 109 |
<a href="#get" className="btn btn-ghost">Get the engine</a>
|
| 110 |
</div>
|
| 111 |
<div className="hero-meta" aria-label="Engine facts">
|
| 112 |
-
<span>
|
| 113 |
<span>zero dependencies</span>
|
| 114 |
<span>any HTML, any JS</span>
|
| 115 |
</div>
|
| 116 |
</div>
|
| 117 |
|
| 118 |
-
{/* live demo stage
|
| 119 |
<div className="demo" aria-label="Live DSSL demo">
|
| 120 |
<div className="demo-tabs">
|
| 121 |
{(Object.keys(DEMOS) as DemoKey[]).map(k => (
|
|
@@ -138,7 +140,7 @@ export default function HomeView({ go }: { go: (v: ViewName) => void }) {
|
|
| 138 |
<div className="demo-card">
|
| 139 |
<div className="demo-swatch" />
|
| 140 |
<h4>Styled by DSSL</h4>
|
| 141 |
-
<p>Hover me
|
| 142 |
<span className="demo-chip">live</span>
|
| 143 |
</div>
|
| 144 |
{demo === "motion" && (
|
|
@@ -213,7 +215,7 @@ export default function HomeView({ go }: { go: (v: ViewName) => void }) {
|
|
| 213 |
<h2 className="section-title">The vocabulary is the difference</h2>
|
| 214 |
<div className="compare sect-gap">
|
| 215 |
<div className="compare-col">
|
| 216 |
-
<span className="compare-tag compare-tag-css">styles.css
|
| 217 |
<CodeBlock plain code={`.card {
|
| 218 |
background-color: var(--card);
|
| 219 |
border-radius: 14px;
|
|
@@ -230,7 +232,7 @@ export default function HomeView({ go }: { go: (v: ViewName) => void }) {
|
|
| 230 |
.card:hover .chip { background: var(--accent-soft); }`} />
|
| 231 |
</div>
|
| 232 |
<div className="compare-col">
|
| 233 |
-
<span className="compare-tag compare-tag-dssl">card.dssl
|
| 234 |
<CodeBlock plain code={`.card {
|
| 235 |
background: color.card
|
| 236 |
radius: md
|
|
@@ -255,7 +257,7 @@ export default function HomeView({ go }: { go: (v: ViewName) => void }) {
|
|
| 255 |
<div className="kicker">the engine</div>
|
| 256 |
<h2 className="section-title">No stylesheet is attached to this page</h2>
|
| 257 |
<p className="section-sub">
|
| 258 |
-
Every rule below is implemented by the DSSL engine
|
| 259 |
against the live DOM itself, runs its own cascade, and drives motion through
|
| 260 |
the Web Animations API. Only pseudo-elements fall back to generated CSS.
|
| 261 |
</p>
|
|
@@ -263,12 +265,12 @@ export default function HomeView({ go }: { go: (v: ViewName) => void }) {
|
|
| 263 |
<div className="card card-hover feature">
|
| 264 |
<span className="feature-icon">{"{}"}</span>
|
| 265 |
<h3>Its own cascade</h3>
|
| 266 |
-
<p>Later wins. Layers when you care. No specificity score, no !important
|
| 267 |
</div>
|
| 268 |
<div className="card card-hover feature">
|
| 269 |
<span className="feature-icon">◆</span>
|
| 270 |
<h3>Tokens as vocabulary</h3>
|
| 271 |
-
<p>color.ink, space.4, radius.md
|
| 272 |
</div>
|
| 273 |
<div className="card card-hover feature">
|
| 274 |
<span className="feature-icon">→</span>
|
|
@@ -278,22 +280,22 @@ export default function HomeView({ go }: { go: (v: ViewName) => void }) {
|
|
| 278 |
<div className="card card-hover feature">
|
| 279 |
<span className="feature-icon">≋</span>
|
| 280 |
<h3>One-word conditions</h3>
|
| 281 |
-
<p>@media dark. @media 768. @media 480..960. Same shape for @container and @signal
|
| 282 |
</div>
|
| 283 |
<div className="card card-hover feature">
|
| 284 |
<span className="feature-icon">✦</span>
|
| 285 |
<h3>Motion in four words</h3>
|
| 286 |
-
<p>animate: rise-in 400 snap both. seen + stagger reveal grids on scroll
|
| 287 |
</div>
|
| 288 |
<div className="card card-hover feature">
|
| 289 |
<span className="feature-icon">§</span>
|
| 290 |
<h3>Behaviors</h3>
|
| 291 |
-
<p>Name a pattern once
|
| 292 |
</div>
|
| 293 |
<div className="card card-hover feature">
|
| 294 |
<span className="feature-icon">!</span>
|
| 295 |
<h3>Loud errors</h3>
|
| 296 |
-
<p>Unknown property, unknown token, bad value
|
| 297 |
</div>
|
| 298 |
<div className="card card-hover feature">
|
| 299 |
<span className="feature-icon">⇄</span>
|
|
@@ -311,18 +313,18 @@ export default function HomeView({ go }: { go: (v: ViewName) => void }) {
|
|
| 311 |
<h2 className="section-title">One call restyles the page</h2>
|
| 312 |
<div className="compare sect-gap" style={{ alignItems: "center" }}>
|
| 313 |
<div className="compare-col">
|
| 314 |
-
<CodeBlock file="theme-toggle.js" code={`// flip a signal
|
| 315 |
// every @signal rule in the page
|
| 316 |
|
| 317 |
DSSL.signal('demo', 'on') // on
|
| 318 |
DSSL.signal('demo', null) // off`} />
|
| 319 |
</div> <div className="compare-col demo-canvas" style={{ maxWidth: 480 }}>
|
| 320 |
<button type="button" className="btn btn-primary" onClick={toggleSignal}>
|
| 321 |
-
{signalOn ? "signal: demo == on" : "signal: demo == off"}
|
| 322 |
</button>
|
| 323 |
<div className="demo-signal-card">
|
| 324 |
<strong>@signal demo == on</strong>
|
| 325 |
-
<span>This card is styled by a signal-conditional rule. Flip the signal and the engine recomputes it
|
| 326 |
</div>
|
| 327 |
</div>
|
| 328 |
</div>
|
|
@@ -346,8 +348,8 @@ DSSL.signal('demo', null) // off`} />
|
|
| 346 |
<span className="get-os">macos · linux · bsd</span>
|
| 347 |
<CodeBlock plain file="terminal" code="curl -fsSL https://dssl-sable.vercel.app/install.sh | sh" />
|
| 348 |
<p className="get-note">
|
| 349 |
-
Runs on plain sh
|
| 350 |
-
required. Downloads with curl or wget, whichever you have, and
|
| 351 |
unpacks with tar, which every distro and macOS ship by default.
|
| 352 |
No tar? It falls back to unzip, then python3.
|
| 353 |
</p>
|
|
@@ -409,7 +411,7 @@ DSSL.signal('demo', null) // off`} />
|
|
| 409 |
<h2>Write styles that read like design.</h2>
|
| 410 |
<p>
|
| 411 |
The full language tour takes about ten minutes. Or paste straight into the
|
| 412 |
-
playground and break things
|
| 413 |
</p>
|
| 414 |
<div className="hero-ctas">
|
| 415 |
<button type="button" className="btn btn-primary" onClick={() => go("docs")}>
|
|
|
|
| 4 |
import CodeBlock from "../CodeBlock";
|
| 5 |
import { highlightDssl } from "../highlight";
|
| 6 |
import { ArrowIcon, PlayIcon } from "../icons";
|
| 7 |
+
import { useEngineInfo } from "../engine-info";
|
| 8 |
import type { ViewName } from "../SiteRoot";
|
| 9 |
|
| 10 |
const DEMOS = {
|
|
|
|
| 47 |
.demo-card {
|
| 48 |
animate: rise-in 500 snap both
|
| 49 |
}`,
|
| 50 |
+
// v0.4.0, the reveal tab: seen + stagger, both engine-native
|
| 51 |
reveal: `motion rise-in {
|
| 52 |
from {
|
| 53 |
opacity: 0
|
|
|
|
| 69 |
type DemoKey = keyof typeof DEMOS;
|
| 70 |
|
| 71 |
export default function HomeView({ go }: { go: (v: ViewName) => void }) {
|
| 72 |
+
const info = useEngineInfo();
|
| 73 |
const [demo, setDemo] = useState<DemoKey>("tokens");
|
| 74 |
const [signalOn, setSignalOn] = useState(false);
|
| 75 |
|
|
|
|
| 97 |
</h1>
|
| 98 |
<p className="hero-sub">
|
| 99 |
A styling language for the web that pairs with any HTML and any JavaScript.
|
| 100 |
+
Its own cascade with no specificity math and no <code>!important</code>.
|
| 101 |
+
Design tokens as the vocabulary, states you can read, motion in four words,
|
| 102 |
+
and errors that actually speak up. Zero dependencies, no build step.
|
| 103 |
</p>
|
| 104 |
<div className="hero-ctas">
|
| 105 |
<button type="button" className="btn btn-primary" onClick={() => go("docs")}>
|
|
|
|
| 111 |
<a href="#get" className="btn btn-ghost">Get the engine</a>
|
| 112 |
</div>
|
| 113 |
<div className="hero-meta" aria-label="Engine facts">
|
| 114 |
+
<span>{info.gzKb} kB gzipped</span>
|
| 115 |
<span>zero dependencies</span>
|
| 116 |
<span>any HTML, any JS</span>
|
| 117 |
</div>
|
| 118 |
</div>
|
| 119 |
|
| 120 |
+
{/* live demo stage: the tabs swap real DSSL, the card below is styled by it */}
|
| 121 |
<div className="demo" aria-label="Live DSSL demo">
|
| 122 |
<div className="demo-tabs">
|
| 123 |
{(Object.keys(DEMOS) as DemoKey[]).map(k => (
|
|
|
|
| 140 |
<div className="demo-card">
|
| 141 |
<div className="demo-swatch" />
|
| 142 |
<h4>Styled by DSSL</h4>
|
| 143 |
+
<p>Hover me. States, tokens and motion are all handled by the engine.</p>
|
| 144 |
<span className="demo-chip">live</span>
|
| 145 |
</div>
|
| 146 |
{demo === "motion" && (
|
|
|
|
| 215 |
<h2 className="section-title">The vocabulary is the difference</h2>
|
| 216 |
<div className="compare sect-gap">
|
| 217 |
<div className="compare-col">
|
| 218 |
+
<span className="compare-tag compare-tag-css">styles.css: 14 lines of machinery</span>
|
| 219 |
<CodeBlock plain code={`.card {
|
| 220 |
background-color: var(--card);
|
| 221 |
border-radius: 14px;
|
|
|
|
| 232 |
.card:hover .chip { background: var(--accent-soft); }`} />
|
| 233 |
</div>
|
| 234 |
<div className="compare-col">
|
| 235 |
+
<span className="compare-tag compare-tag-dssl">card.dssl: 8 lines of design</span>
|
| 236 |
<CodeBlock plain code={`.card {
|
| 237 |
background: color.card
|
| 238 |
radius: md
|
|
|
|
| 257 |
<div className="kicker">the engine</div>
|
| 258 |
<h2 className="section-title">No stylesheet is attached to this page</h2>
|
| 259 |
<p className="section-sub">
|
| 260 |
+
Every rule below is implemented by the DSSL engine. It matches selectors
|
| 261 |
against the live DOM itself, runs its own cascade, and drives motion through
|
| 262 |
the Web Animations API. Only pseudo-elements fall back to generated CSS.
|
| 263 |
</p>
|
|
|
|
| 265 |
<div className="card card-hover feature">
|
| 266 |
<span className="feature-icon">{"{}"}</span>
|
| 267 |
<h3>Its own cascade</h3>
|
| 268 |
+
<p>Later wins. Layers when you care. No specificity score, no !important. Ever.</p>
|
| 269 |
</div>
|
| 270 |
<div className="card card-hover feature">
|
| 271 |
<span className="feature-icon">◆</span>
|
| 272 |
<h3>Tokens as vocabulary</h3>
|
| 273 |
+
<p>color.ink, space.4, radius.md: references compile to inspectable custom properties.</p>
|
| 274 |
</div>
|
| 275 |
<div className="card card-hover feature">
|
| 276 |
<span className="feature-icon">→</span>
|
|
|
|
| 280 |
<div className="card card-hover feature">
|
| 281 |
<span className="feature-icon">≋</span>
|
| 282 |
<h3>One-word conditions</h3>
|
| 283 |
+
<p>@media dark. @media 768. @media 480..960. Same shape for @container and @signal. Compose by nesting.</p>
|
| 284 |
</div>
|
| 285 |
<div className="card card-hover feature">
|
| 286 |
<span className="feature-icon">✦</span>
|
| 287 |
<h3>Motion in four words</h3>
|
| 288 |
+
<p>animate: rise-in 400 snap both. seen + stagger reveal grids on scroll. Reduced-motion is respected by default.</p>
|
| 289 |
</div>
|
| 290 |
<div className="card card-hover feature">
|
| 291 |
<span className="feature-icon">§</span>
|
| 292 |
<h3>Behaviors</h3>
|
| 293 |
+
<p>Name a pattern once (focus rings, chips) and use it anywhere with one line.</p>
|
| 294 |
</div>
|
| 295 |
<div className="card card-hover feature">
|
| 296 |
<span className="feature-icon">!</span>
|
| 297 |
<h3>Loud errors</h3>
|
| 298 |
+
<p>Unknown property, unknown token, bad value: each fails with file, line and a suggestion.</p>
|
| 299 |
</div>
|
| 300 |
<div className="card card-hover feature">
|
| 301 |
<span className="feature-icon">⇄</span>
|
|
|
|
| 313 |
<h2 className="section-title">One call restyles the page</h2>
|
| 314 |
<div className="compare sect-gap" style={{ alignItems: "center" }}>
|
| 315 |
<div className="compare-col">
|
| 316 |
+
<CodeBlock file="theme-toggle.js" code={`// flip a signal and DSSL re-evaluates
|
| 317 |
// every @signal rule in the page
|
| 318 |
|
| 319 |
DSSL.signal('demo', 'on') // on
|
| 320 |
DSSL.signal('demo', null) // off`} />
|
| 321 |
</div> <div className="compare-col demo-canvas" style={{ maxWidth: 480 }}>
|
| 322 |
<button type="button" className="btn btn-primary" onClick={toggleSignal}>
|
| 323 |
+
{signalOn ? "signal: demo == on" : "signal: demo == off"}, click
|
| 324 |
</button>
|
| 325 |
<div className="demo-signal-card">
|
| 326 |
<strong>@signal demo == on</strong>
|
| 327 |
+
<span>This card is styled by a signal-conditional rule. Flip the signal and the engine recomputes it. Inspect the html element to see the mirror attribute.</span>
|
| 328 |
</div>
|
| 329 |
</div>
|
| 330 |
</div>
|
|
|
|
| 348 |
<span className="get-os">macos · linux · bsd</span>
|
| 349 |
<CodeBlock plain file="terminal" code="curl -fsSL https://dssl-sable.vercel.app/install.sh | sh" />
|
| 350 |
<p className="get-note">
|
| 351 |
+
Runs on plain sh (dash, ash and busybox included, bash not
|
| 352 |
+
required). Downloads with curl or wget, whichever you have, and
|
| 353 |
unpacks with tar, which every distro and macOS ship by default.
|
| 354 |
No tar? It falls back to unzip, then python3.
|
| 355 |
</p>
|
|
|
|
| 411 |
<h2>Write styles that read like design.</h2>
|
| 412 |
<p>
|
| 413 |
The full language tour takes about ten minutes. Or paste straight into the
|
| 414 |
+
playground and break things. The errors will keep you company.
|
| 415 |
</p>
|
| 416 |
<div className="hero-ctas">
|
| 417 |
<button type="button" className="btn btn-primary" onClick={() => go("docs")}>
|
src/components/dssl/views/SpecView.tsx
CHANGED
|
@@ -1,8 +1,10 @@
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
import CodeBlock from "../CodeBlock";
|
|
|
|
| 4 |
|
| 5 |
export default function SpecView() {
|
|
|
|
| 6 |
return (
|
| 7 |
<div className="wrap">
|
| 8 |
<div className="page-head">
|
|
@@ -10,8 +12,8 @@ export default function SpecView() {
|
|
| 10 |
<h1 className="page-title">DSSL v0.2 — “First Light”, ratified</h1>
|
| 11 |
<p className="page-sub">
|
| 12 |
The v0.1 draft was put to a seven-point vote. This document is the ratified
|
| 13 |
-
v0.2 result
|
| 14 |
-
page. The engine itself has kept growing since: it is now at
|
| 15 |
changelog for what the site demanded).
|
| 16 |
</p>
|
| 17 |
</div>
|
|
|
|
| 1 |
"use client";
|
| 2 |
|
| 3 |
import CodeBlock from "../CodeBlock";
|
| 4 |
+
import { useEngineInfo } from "../engine-info";
|
| 5 |
|
| 6 |
export default function SpecView() {
|
| 7 |
+
const info = useEngineInfo();
|
| 8 |
return (
|
| 9 |
<div className="wrap">
|
| 10 |
<div className="page-head">
|
|
|
|
| 12 |
<h1 className="page-title">DSSL v0.2 — “First Light”, ratified</h1>
|
| 13 |
<p className="page-sub">
|
| 14 |
The v0.1 draft was put to a seven-point vote. This document is the ratified
|
| 15 |
+
v0.2 result, and every decision below is implemented by the engine serving this
|
| 16 |
+
page. The engine itself has kept growing since: it is now at v{info.version} (see the
|
| 17 |
changelog for what the site demanded).
|
| 18 |
</p>
|
| 19 |
</div>
|
src/lib/engine-info.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { readFileSync } from "node:fs";
|
| 2 |
+
import { gzipSync } from "node:zlib";
|
| 3 |
+
import path from "node:path";
|
| 4 |
+
|
| 5 |
+
/** Facts about the engine that the site states out loud. They are measured from
|
| 6 |
+
* public/dssl/ when the page is built, so the version badge and the size claim
|
| 7 |
+
* cannot drift away from the code the way the hardcoded ones did. */
|
| 8 |
+
export interface EngineInfo {
|
| 9 |
+
version: string;
|
| 10 |
+
/** whole engine, minified: no. Plain source, as served. */
|
| 11 |
+
rawKb: number;
|
| 12 |
+
/** the same files, gzipped, which is what a browser actually downloads */
|
| 13 |
+
gzKb: number;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
const FILES = ["boot", "index", "engine", "parser", "resolver", "static", "utils"];
|
| 17 |
+
|
| 18 |
+
export function readEngineInfo(): EngineInfo {
|
| 19 |
+
const dir = path.join(process.cwd(), "public", "dssl");
|
| 20 |
+
let version = "0.0.0";
|
| 21 |
+
let raw = 0;
|
| 22 |
+
let gz = 0;
|
| 23 |
+
try {
|
| 24 |
+
const parts = FILES.map((f) => readFileSync(path.join(dir, `${f}.js`)));
|
| 25 |
+
const m = /VERSION\s*=\s*'([^']+)'/.exec(readFileSync(path.join(dir, "index.js"), "utf8"));
|
| 26 |
+
if (m) version = m[1];
|
| 27 |
+
raw = parts.reduce((n, b) => n + b.length, 0);
|
| 28 |
+
gz = gzipSync(Buffer.concat(parts), { level: 9 }).length;
|
| 29 |
+
} catch {
|
| 30 |
+
// files missing at build time: say nothing wrong rather than something invented
|
| 31 |
+
}
|
| 32 |
+
return { version, rawKb: Math.round(raw / 1000), gzKb: Math.round(gz / 1000) };
|
| 33 |
+
}
|