EHS-Studio Claude Sonnet 5.5 commited on
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 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 a ~20 kB engine, 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
 
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 a ~20 kB engine, 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
 
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 — Domain Specific Style Language",
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"); }}>v0.8.0</a>
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> — domain specific style language. Built v0.1 → v0.8.0 while
155
- building this site; every feature it needed, it grew, and every bug you sent got
156
- fixed for real. This page runs on its own engine, no CSS files attached —
157
- the engine styles this DOM directly.
158
  </div>
159
  <div className="footer-fine">
160
- © 2026 <strong>DSSL. inc</strong> — named after the product, because naming things
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 — the reveal tab: seen + stagger, both engine-native
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 — no specificity math, no <code>!important</code> — design
99
- tokens as the vocabulary, states you can read, motion in four words, and
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>~20 kB engine</span>
113
  <span>zero dependencies</span>
114
  <span>any HTML, any JS</span>
115
  </div>
116
  </div>
117
 
118
- {/* live demo stage — the tabs swap real DSSL, the card below is styled by it */}
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 — states, tokens and motion are all handled by the engine.</p>
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 — 14 lines of machinery</span>
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 — 8 lines of design</span>
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 — it matches selectors
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 — ever.</p>
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 — references compile to inspectable custom properties.</p>
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 — compose by nesting.</p>
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 — reduced-motion is respected by default.</p>
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 — focus rings, chips — and use it anywhere with one line.</p>
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 — each fails with file, line and a suggestion.</p>
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 — DSSL re-evaluates
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"} — click
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 — inspect the html element to see the mirror attribute.</span>
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 — dash, ash and busybox included, bash not
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 — the errors will keep you company.
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 — every decision below is implemented by the engine serving this
14
- page. The engine itself has kept growing since: it is now at v0.8.0 (see the
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
+ }