Z User commited on
Commit ·
7c1922e
1
Parent(s): 0699c28
sync work in progress
Browse files- public/dssl/engine.js +34 -2
- public/dssl/index.js +1 -1
- public/dssl/resolver.js +20 -0
- public/styles/docs.dssl +1 -1
- public/styles/home.dssl +1 -1
- public/styles/site.dssl +4 -1
- scripts/dssl-smoke.mjs +17 -0
- src/app/layout.tsx +1 -0
- src/components/dssl/SiteRoot.tsx +2 -2
- src/components/dssl/views/ChangelogView.tsx +17 -0
- src/components/dssl/views/CheatSheetView.tsx +2 -0
- src/components/dssl/views/DocsView.tsx +2 -1
- src/components/dssl/views/SpecView.tsx +1 -1
public/dssl/engine.js
CHANGED
|
@@ -19,6 +19,22 @@
|
|
| 19 |
|
| 20 |
const LAYER_RANK = { tokens: 0, base: 1, components: 2, utilities: 3, pin: 4 };
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
// state pseudo-classes that may appear inside a rule selector. Used to strip
|
| 23 |
// them back OFF when collecting recompute candidates (see stateEvent).
|
| 24 |
const STATE_PSEUDO_RE = /:(hover|focus|focus-visible|active|focus-within|visited|target|disabled|enabled|checked|placeholder-shown|read-only|read-write|open|closed|empty|required|invalid|valid|link|indeterminate)\b/g;
|
|
@@ -326,9 +342,26 @@ export class DsslEngine {
|
|
| 326 |
let cancelAnims = false;
|
| 327 |
let stack = false;
|
| 328 |
|
|
|
|
|
|
|
| 329 |
for (const rule of matched) {
|
| 330 |
const imp = rule.layer === 'pin' ? 'important' : '';
|
| 331 |
-
for (const d of rule.decls) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 332 |
if (d.prop === '@animate') {
|
| 333 |
if (d.value === null) { cancelAnims = true; continue; }
|
| 334 |
animSpecs.push({ ...d.value, key: rule.id + '|' + rule.media.join('&') + '|' + rule.signals.map(s => s.name + s.op + s.value).join('&') });
|
|
@@ -346,7 +379,6 @@ export class DsslEngine {
|
|
| 346 |
if (!this._warned) this._warned = new Set();
|
| 347 |
if (!this._warned.has(msg)) { this._warned.add(msg); console.warn(msg); }
|
| 348 |
}
|
| 349 |
-
}
|
| 350 |
}
|
| 351 |
|
| 352 |
this.ownMap.set(el, newOwned);
|
|
|
|
| 19 |
|
| 20 |
const LAYER_RANK = { tokens: 0, base: 1, components: 2, utilities: 3, pin: 4 };
|
| 21 |
|
| 22 |
+
// v0.3.4 — alignment implies a formatting context.
|
| 23 |
+
// place-items / place-content / align-* / justify-* only arrange children
|
| 24 |
+
// inside flex or grid containers. On a plain <span> or <button> they used to
|
| 25 |
+
// silently do nothing — which is exactly how the feature icons, the theme
|
| 26 |
+
// toggle and the token dots ended up pinned top-left inside their circles.
|
| 27 |
+
// The language promises loud failures over silent ones, so when a winning
|
| 28 |
+
// declaration asks for alignment but the element has no layout context, the
|
| 29 |
+
// engine PROMOTES it: inline elements → inline-grid (flow preserved), blockish
|
| 30 |
+
// elements → grid. Explicit `display:` in the same cascade still wins, because
|
| 31 |
+
// the promotion is applied first and "later wins" is the whole cascade.
|
| 32 |
+
const ALIGN_PROPS = new Set([
|
| 33 |
+
'place-items', 'place-content', 'align-items', 'justify-items',
|
| 34 |
+
'align-content', 'justify-content',
|
| 35 |
+
]);
|
| 36 |
+
const HAS_CONTEXT_RE = /^(flex|inline-flex|grid|inline-grid)$/;
|
| 37 |
+
|
| 38 |
// state pseudo-classes that may appear inside a rule selector. Used to strip
|
| 39 |
// them back OFF when collecting recompute candidates (see stateEvent).
|
| 40 |
const STATE_PSEUDO_RE = /:(hover|focus|focus-visible|active|focus-within|visited|target|disabled|enabled|checked|placeholder-shown|read-only|read-write|open|closed|empty|required|invalid|valid|link|indeterminate)\b/g;
|
|
|
|
| 342 |
let cancelAnims = false;
|
| 343 |
let stack = false;
|
| 344 |
|
| 345 |
+
// flatten matched decls in cascade order (the promotion below may prepend)
|
| 346 |
+
const flat = [];
|
| 347 |
for (const rule of matched) {
|
| 348 |
const imp = rule.layer === 'pin' ? 'important' : '';
|
| 349 |
+
for (const d of rule.decls) flat.push({ d, imp, rule });
|
| 350 |
+
}
|
| 351 |
+
|
| 352 |
+
// v0.3.4: alignment implies a formatting context — see ALIGN_PROPS above.
|
| 353 |
+
// Read AFTER the wipe so the natural (parent-blockified) display decides;
|
| 354 |
+
// none/contents are never promoted (promoting would unhide or boxify them).
|
| 355 |
+
if (flat.some(x => ALIGN_PROPS.has(x.d.prop))) {
|
| 356 |
+
let disp = '';
|
| 357 |
+
try { disp = getComputedStyle(el).display; } catch { }
|
| 358 |
+
if (disp && !HAS_CONTEXT_RE.test(disp) && disp !== 'none' && disp !== 'contents') {
|
| 359 |
+
const inlineCtx = disp === 'inline' || disp.startsWith('inline-');
|
| 360 |
+
flat.unshift({ imp: '', d: { prop: 'display', value: inlineCtx ? 'inline-grid' : 'grid' } });
|
| 361 |
+
}
|
| 362 |
+
}
|
| 363 |
+
|
| 364 |
+
for (const { d, imp, rule } of flat) {
|
| 365 |
if (d.prop === '@animate') {
|
| 366 |
if (d.value === null) { cancelAnims = true; continue; }
|
| 367 |
animSpecs.push({ ...d.value, key: rule.id + '|' + rule.media.join('&') + '|' + rule.signals.map(s => s.name + s.op + s.value).join('&') });
|
|
|
|
| 379 |
if (!this._warned) this._warned = new Set();
|
| 380 |
if (!this._warned.has(msg)) { this._warned.add(msg); console.warn(msg); }
|
| 381 |
}
|
|
|
|
| 382 |
}
|
| 383 |
|
| 384 |
this.ownMap.set(el, newOwned);
|
public/dssl/index.js
CHANGED
|
@@ -18,7 +18,7 @@ import { parse } from './parser.js';
|
|
| 18 |
import { resolve, DEFAULT_TOKENS } from './resolver.js';
|
| 19 |
import { DsslEngine } from './engine.js';
|
| 20 |
|
| 21 |
-
export const VERSION = '0.3.
|
| 22 |
|
| 23 |
let counter = 0;
|
| 24 |
const engines = new Map();
|
|
|
|
| 18 |
import { resolve, DEFAULT_TOKENS } from './resolver.js';
|
| 19 |
import { DsslEngine } from './engine.js';
|
| 20 |
|
| 21 |
+
export const VERSION = '0.3.4';
|
| 22 |
|
| 23 |
let counter = 0;
|
| 24 |
const engines = new Map();
|
public/dssl/resolver.js
CHANGED
|
@@ -344,6 +344,26 @@ const SEMANTIC = {
|
|
| 344 |
}
|
| 345 |
return [{ prop: 'place-items', value: `${p[0]} ${p[1]}` }, { prop: 'place-content', value: `${p[0]} ${p[1]}` }];
|
| 346 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 347 |
cover(v, ctx) {
|
| 348 |
const pos = (v || 'absolute').trim();
|
| 349 |
return [{ prop: 'position', value: pos }, { prop: 'inset', value: '0' }];
|
|
|
|
| 344 |
}
|
| 345 |
return [{ prop: 'place-items', value: `${p[0]} ${p[1]}` }, { prop: 'place-content', value: `${p[0]} ${p[1]}` }];
|
| 346 |
},
|
| 347 |
+
// v0.3.4 — the one-word centering primitive, because "how do I center this
|
| 348 |
+
// thing" is the first question everyone asks. Emits the alignment pair only;
|
| 349 |
+
// the ENGINE guarantees a formatting context for alignment declarations
|
| 350 |
+
// (inline elements → inline-grid, blocks → grid), so `center:` can never
|
| 351 |
+
// silently no-op the way raw place-items did on spans and buttons.
|
| 352 |
+
// center: both axes · center: x horizontal only · center: y vertical only
|
| 353 |
+
center(v, ctx) {
|
| 354 |
+
const mode = (v || '').trim().toLowerCase() || 'both';
|
| 355 |
+
if (mode === 'x') {
|
| 356 |
+
return [{ prop: 'justify-items', value: 'center' }, { prop: 'justify-content', value: 'center' }];
|
| 357 |
+
}
|
| 358 |
+
if (mode === 'y') {
|
| 359 |
+
return [{ prop: 'align-items', value: 'center' }, { prop: 'align-content', value: 'center' }];
|
| 360 |
+
}
|
| 361 |
+
if (mode !== 'both') {
|
| 362 |
+
ctx.errors.push({ file: ctx.file, line: ctx.line, message: `unknown center axis "${v}"`, hint: didYouMean(mode, ['both', 'x', 'y']) });
|
| 363 |
+
return [];
|
| 364 |
+
}
|
| 365 |
+
return [{ prop: 'place-items', value: 'center center' }, { prop: 'place-content', value: 'center center' }];
|
| 366 |
+
},
|
| 367 |
cover(v, ctx) {
|
| 368 |
const pos = (v || 'absolute').trim();
|
| 369 |
return [{ prop: 'position', value: pos }, { prop: 'inset', value: '0' }];
|
public/styles/docs.dssl
CHANGED
|
@@ -268,7 +268,7 @@
|
|
| 268 |
.tok-dot {
|
| 269 |
size: 34
|
| 270 |
circle: 34
|
| 271 |
-
|
| 272 |
font-family: font.mono
|
| 273 |
font-size: 11
|
| 274 |
border: 1 solid color.line
|
|
|
|
| 268 |
.tok-dot {
|
| 269 |
size: 34
|
| 270 |
circle: 34
|
| 271 |
+
center: both
|
| 272 |
font-family: font.mono
|
| 273 |
font-size: 11
|
| 274 |
border: 1 solid color.line
|
public/styles/home.dssl
CHANGED
|
@@ -366,7 +366,7 @@ motion rise-in {
|
|
| 366 |
.feature-icon {
|
| 367 |
size: 40
|
| 368 |
circle: 40
|
| 369 |
-
|
| 370 |
background: color.accent/12
|
| 371 |
color: color.accent
|
| 372 |
font-size: 19
|
|
|
|
| 366 |
.feature-icon {
|
| 367 |
size: 40
|
| 368 |
circle: 40
|
| 369 |
+
center: both
|
| 370 |
background: color.accent/12
|
| 371 |
color: color.accent
|
| 372 |
font-size: 19
|
public/styles/site.dssl
CHANGED
|
@@ -126,7 +126,10 @@ select { use focus-ring }
|
|
| 126 |
|
| 127 |
.theme-btn {
|
| 128 |
size: 36
|
| 129 |
-
|
|
|
|
|
|
|
|
|
|
| 130 |
color: color.muted
|
| 131 |
transition: 150
|
| 132 |
border: 1 solid color.ink/40
|
|
|
|
| 126 |
|
| 127 |
.theme-btn {
|
| 128 |
size: 36
|
| 129 |
+
// auto (system) mode shows the sun+moon PAIR, light/dark show one icon.
|
| 130 |
+
// `center:` centers a single-content box, but a two-item grid would stack
|
| 131 |
+
// the pair — an explicit flex row centers either state (v0.3.4)
|
| 132 |
+
row: center gap 2
|
| 133 |
color: color.muted
|
| 134 |
transition: 150
|
| 135 |
border: 1 solid color.ink/40
|
scripts/dssl-smoke.mjs
CHANGED
|
@@ -73,6 +73,14 @@ gallery {
|
|
| 73 |
.span-2 { grid-column: span 2 }
|
| 74 |
.auto-grid { grid-template-columns: repeat(auto-fill, minmax(200, 1fr)) }
|
| 75 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
@media (width >= 768) {
|
| 77 |
.card { padding: space.6 }
|
| 78 |
}
|
|
@@ -152,6 +160,15 @@ const span2 = doc.rules.find(r => r.sel === '.span-2');
|
|
| 152 |
assert(span2 && span2.decls.some(d => d.value === 'span 2'), `grid-column: span 2 unitless (got: ${span2?.decls[0]?.value})`);
|
| 153 |
const autoGrid = doc.rules.find(r => r.sel === '.auto-grid');
|
| 154 |
assert(autoGrid && autoGrid.decls.some(d => d.value === 'repeat(auto-fill, minmax(200px, 1fr))'), `repeat(auto-fill, minmax) works (got: ${autoGrid?.decls[0]?.value})`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
assert(doc.rules.some(r => r.sel === '.card' && r.media.length === 1), 'media rule for .card');
|
| 156 |
assert(doc.rules.some(r => r.media.includes('(prefers-color-scheme: dark)')), '@media dark sugar');
|
| 157 |
assert(doc.rules.some(r => r.media.includes('(prefers-color-scheme: light)')), '@media light sugar');
|
|
|
|
| 73 |
.span-2 { grid-column: span 2 }
|
| 74 |
.auto-grid { grid-template-columns: repeat(auto-fill, minmax(200, 1fr)) }
|
| 75 |
|
| 76 |
+
// v0.3.4: the one-word centering primitive
|
| 77 |
+
.icon-chip {
|
| 78 |
+
size: 40
|
| 79 |
+
center: both
|
| 80 |
+
}
|
| 81 |
+
.chip-x { center: x }
|
| 82 |
+
.chip-y { center: y }
|
| 83 |
+
|
| 84 |
@media (width >= 768) {
|
| 85 |
.card { padding: space.6 }
|
| 86 |
}
|
|
|
|
| 160 |
assert(span2 && span2.decls.some(d => d.value === 'span 2'), `grid-column: span 2 unitless (got: ${span2?.decls[0]?.value})`);
|
| 161 |
const autoGrid = doc.rules.find(r => r.sel === '.auto-grid');
|
| 162 |
assert(autoGrid && autoGrid.decls.some(d => d.value === 'repeat(auto-fill, minmax(200px, 1fr))'), `repeat(auto-fill, minmax) works (got: ${autoGrid?.decls[0]?.value})`);
|
| 163 |
+
// v0.3.4: center: primitive — alignment pair, engine promotes the context
|
| 164 |
+
const chip = doc.rules.find(r => r.sel === '.icon-chip');
|
| 165 |
+
assert(chip && chip.decls.some(d => d.prop === 'place-items' && d.value === 'center center'), `center: expands to place-items center center`);
|
| 166 |
+
assert(chip && chip.decls.some(d => d.prop === 'place-content' && d.value === 'center center'), `center: expands to place-content center center`);
|
| 167 |
+
const chipX = doc.rules.find(r => r.sel === '.chip-x');
|
| 168 |
+
assert(chipX && chipX.decls.some(d => d.prop === 'justify-items' && d.value === 'center') && chipX.decls.some(d => d.prop === 'justify-content' && d.value === 'center'), 'center: x axis emits justify pair');
|
| 169 |
+
const chipY = doc.rules.find(r => r.sel === '.chip-y');
|
| 170 |
+
assert(chipY && chipY.decls.some(d => d.prop === 'align-items' && d.value === 'center') && chipY.decls.some(d => d.prop === 'align-content' && d.value === 'center'), 'center: y axis emits align pair');
|
| 171 |
+
assert(!doc.errors.some(e => e.message.includes('center')), 'no false center errors');
|
| 172 |
assert(doc.rules.some(r => r.sel === '.card' && r.media.length === 1), 'media rule for .card');
|
| 173 |
assert(doc.rules.some(r => r.media.includes('(prefers-color-scheme: dark)')), '@media dark sugar');
|
| 174 |
assert(doc.rules.some(r => r.media.includes('(prefers-color-scheme: light)')), '@media light sugar');
|
src/app/layout.tsx
CHANGED
|
@@ -109,6 +109,7 @@ export default function RootLayout({
|
|
| 109 |
))}
|
| 110 |
{/* engine starts downloading with the document — module defer rules
|
| 111 |
mean it executes after parse with the full SSR DOM present */}
|
|
|
|
| 112 |
<script type="module" src="/dssl/boot.js" />
|
| 113 |
</head>
|
| 114 |
<body>{children}</body>
|
|
|
|
| 109 |
))}
|
| 110 |
{/* engine starts downloading with the document — module defer rules
|
| 111 |
mean it executes after parse with the full SSR DOM present */}
|
| 112 |
+
{/* eslint-disable-next-line @next/next/no-sync-scripts -- type="module" is deferred by spec; the rule cannot see that */}
|
| 113 |
<script type="module" src="/dssl/boot.js" />
|
| 114 |
</head>
|
| 115 |
<body>{children}</body>
|
src/components/dssl/SiteRoot.tsx
CHANGED
|
@@ -120,7 +120,7 @@ export default function SiteRoot() {
|
|
| 120 |
</nav>
|
| 121 |
|
| 122 |
<div className="nav-side">
|
| 123 |
-
<a href="#/changelog" className="ver-badge" onClick={(e) => { e.preventDefault(); go("changelog"); }}>v0.3.
|
| 124 |
<button type="button" className="theme-btn" onClick={cycleTheme} aria-label="Toggle color theme" title="Theme: auto / light / dark">
|
| 125 |
<span className="icon-sun"><SunIcon /></span>
|
| 126 |
<span className="icon-moon"><MoonIcon /></span>
|
|
@@ -148,7 +148,7 @@ export default function SiteRoot() {
|
|
| 148 |
<div className="wrap footer-inner">
|
| 149 |
<div className="footer-col">
|
| 150 |
<div className="footer-note">
|
| 151 |
-
<strong>DSSL</strong> — domain specific style language. Built v0.1 → v0.3.
|
| 152 |
building this site; every feature it needed, it grew, and every bug you sent got
|
| 153 |
fixed for real. This page runs on its own engine, no CSS files attached —
|
| 154 |
the engine styles this DOM directly.
|
|
|
|
| 120 |
</nav>
|
| 121 |
|
| 122 |
<div className="nav-side">
|
| 123 |
+
<a href="#/changelog" className="ver-badge" onClick={(e) => { e.preventDefault(); go("changelog"); }}>v0.3.4</a>
|
| 124 |
<button type="button" className="theme-btn" onClick={cycleTheme} aria-label="Toggle color theme" title="Theme: auto / light / dark">
|
| 125 |
<span className="icon-sun"><SunIcon /></span>
|
| 126 |
<span className="icon-moon"><MoonIcon /></span>
|
|
|
|
| 148 |
<div className="wrap footer-inner">
|
| 149 |
<div className="footer-col">
|
| 150 |
<div className="footer-note">
|
| 151 |
+
<strong>DSSL</strong> — domain specific style language. Built v0.1 → v0.3.4 while
|
| 152 |
building this site; every feature it needed, it grew, and every bug you sent got
|
| 153 |
fixed for real. This page runs on its own engine, no CSS files attached —
|
| 154 |
the engine styles this DOM directly.
|
src/components/dssl/views/ChangelogView.tsx
CHANGED
|
@@ -210,6 +210,23 @@ radius { md: 14 → md: 4 }
|
|
| 210 |
color { paper: #faf8f4 → #f5f0e4, accent: oklch(…) → #7c2d2d }
|
| 211 |
font { serif: "Charter", "Iowan Old Style", "Palatino", … } // new`,
|
| 212 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
];
|
| 214 |
|
| 215 |
export default function ChangelogView() {
|
|
|
|
| 210 |
color { paper: #faf8f4 → #f5f0e4, accent: oklch(…) → #7c2d2d }
|
| 211 |
font { serif: "Charter", "Iowan Old Style", "Palatino", … } // new`,
|
| 212 |
},
|
| 213 |
+
{
|
| 214 |
+
version: "v0.3.4",
|
| 215 |
+
date: "bug report → fix",
|
| 216 |
+
title: "Centering that always works",
|
| 217 |
+
why: "user report: “the {} and stuff in The Engine are still not centered in their circle”",
|
| 218 |
+
kind: "add",
|
| 219 |
+
body:
|
| 220 |
+
"Every glyph chip on this site asked for centering — place: center — and place-items/place-content only arrange children inside flex or grid containers. On a <span> or a <button> they silently did nothing, so the feature icons, the theme toggle and the docs token dots all hugged the top-left of their circles. The fix is engine-level, because the engine is the only part that knows what an element actually is: alignment declarations now IMPLY a formatting context. When a winning declaration asks to align but the element has no layout context, the engine promotes it — inline elements become inline-grid (flow preserved), blocks become grid — while an explicit display: still wins the cascade. And the vocabulary gained the word people actually reach for first: center: both / x / y.",
|
| 221 |
+
code: `.icon-chip {
|
| 222 |
+
size: 40
|
| 223 |
+
circle: 40
|
| 224 |
+
center: both // works on a span, a button, anything
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
// engine rule: alignment implies a formatting context
|
| 228 |
+
// inline → inline-grid · block → grid · explicit display: still wins`,
|
| 229 |
+
},
|
| 230 |
];
|
| 231 |
|
| 232 |
export default function ChangelogView() {
|
src/components/dssl/views/CheatSheetView.tsx
CHANGED
|
@@ -73,6 +73,8 @@ column: gap 8 start
|
|
| 73 |
grid: 3
|
| 74 |
grid: auto-fill 220
|
| 75 |
stack: center // overlay children
|
|
|
|
|
|
|
| 76 |
place: center align: center
|
| 77 |
cover: fixed sticky: 0
|
| 78 |
size: 40 100% circle: 36
|
|
|
|
| 73 |
grid: 3
|
| 74 |
grid: auto-fill 220
|
| 75 |
stack: center // overlay children
|
| 76 |
+
center: both // center: x / center: y — the engine guarantees a
|
| 77 |
+
// formatting context, so it can never silently no-op
|
| 78 |
place: center align: center
|
| 79 |
cover: fixed sticky: 0
|
| 80 |
size: 40 100% circle: 36
|
src/components/dssl/views/DocsView.tsx
CHANGED
|
@@ -324,7 +324,8 @@ background: // values can continue
|
|
| 324 |
<tr><td><code>row</code> / <code>column</code></td><td><code>row: gap 8 center wrap</code></td><td>flexbox + only the words you name (between, reverse, inline…)</td></tr>
|
| 325 |
<tr><td><code>grid</code></td><td><code>grid: 3</code> · <code>grid: auto-fill 220</code></td><td>repeat(3, 1fr) · repeat(auto-fill, minmax(220px, 1fr))</td></tr>
|
| 326 |
<tr><td><code>stack</code></td><td><code>stack: center</code></td><td>overlay grid; children share cell 1/1</td></tr>
|
| 327 |
-
<tr><td><code>place</code> / <code>align</code></td><td><code>place: center</code></td><td>place-items/content · align-items</td></tr>
|
|
|
|
| 328 |
<tr><td><code>cover</code></td><td><code>cover: fixed</code></td><td>position + inset: 0</td></tr>
|
| 329 |
<tr><td><code>sticky</code></td><td><code>sticky: 0</code></td><td>position: sticky; top: 0px</td></tr>
|
| 330 |
<tr><td><code>font</code></td><td><code>font: 16/1.4 ink</code></td><td>font-size / line-height / color</td></tr>
|
|
|
|
| 324 |
<tr><td><code>row</code> / <code>column</code></td><td><code>row: gap 8 center wrap</code></td><td>flexbox + only the words you name (between, reverse, inline…)</td></tr>
|
| 325 |
<tr><td><code>grid</code></td><td><code>grid: 3</code> · <code>grid: auto-fill 220</code></td><td>repeat(3, 1fr) · repeat(auto-fill, minmax(220px, 1fr))</td></tr>
|
| 326 |
<tr><td><code>stack</code></td><td><code>stack: center</code></td><td>overlay grid; children share cell 1/1</td></tr>
|
| 327 |
+
<tr><td><code>place</code> / <code>align</code></td><td><code>place: center</code></td><td>place-items/content · align-items — alignment implies a formatting context (see <code>center</code>)</td></tr>
|
| 328 |
+
<tr><td><code>center</code></td><td><code>center: both</code> · <code>center: x</code> · <code>center: y</code></td><td>the centering primitive. The engine promotes any element to a grid/flex context first (inline → inline-grid, block → grid; an explicit <code>display:</code> still wins), so centering works on a span, a button, anything</td></tr>
|
| 329 |
<tr><td><code>cover</code></td><td><code>cover: fixed</code></td><td>position + inset: 0</td></tr>
|
| 330 |
<tr><td><code>sticky</code></td><td><code>sticky: 0</code></td><td>position: sticky; top: 0px</td></tr>
|
| 331 |
<tr><td><code>font</code></td><td><code>font: 16/1.4 ink</code></td><td>font-size / line-height / color</td></tr>
|
src/components/dssl/views/SpecView.tsx
CHANGED
|
@@ -11,7 +11,7 @@ export default function SpecView() {
|
|
| 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.3.
|
| 15 |
changelog for what the site demanded).
|
| 16 |
</p>
|
| 17 |
</div>
|
|
|
|
| 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.3.4 (see the
|
| 15 |
changelog for what the site demanded).
|
| 16 |
</p>
|
| 17 |
</div>
|