Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion badges/badge-optimal.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"schemaVersion": 1,
"label": "optimal",
"message": "19.4 kB gzip",
"message": "19.5 kB gzip",
"color": "brightgreen",
"namedLogo": "css3"
}
7 changes: 7 additions & 0 deletions configurator/src/lib/preview/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ export interface ApiClass {
export const TOKENS: ApiToken[] = (apiIndex as { tokens: ApiToken[] }).tokens;
export const CLASSES: ApiClass[] = (classIndex as { classes: ApiClass[] }).classes;

/** Solid-colour gradient families (--sf-gradient-*, excluding the directional
* --sf-gradient-fade--* and --sf-scrim-gradient). Shared between sections.ts
* (which renders the swatches) and skin.ts (which generates the
* .pv-grad--{family} rules they use) — the two must stay in lockstep or a
* swatch silently renders blank. */
export const GRADS = ["primary", "secondary", "tertiary", "action", "brand", "surface"];

export const isPublic = (t: ApiToken) => t.tier === "PUBLIC";
export const isAdvanced = (t: ApiToken) => t.tier === "PUBLIC-ADVANCED";

Expand Down
248 changes: 233 additions & 15 deletions configurator/src/lib/preview/sections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@
// Foundations enumerate the real API via ./catalog; components are curated.

import { page, section, specimen, tag, cluster, grid, stack, well, frame, esc } from "./specimen";
import { familySteps, classesOfKind, tokensMatching, tokensByGroup, classesByKind } from "./catalog";
import { familySteps, classesOfKind, tokensMatching, tokensByGroup, classesByKind, GRADS } from "./catalog";

const RAMP_STEPS = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950];
const RAMP_FAMILIES = ["primary", "secondary", "tertiary", "action", "base", "neutral"];
const STATUS = ["success", "warning", "danger", "info"];
const GRADS = ["primary", "brand", "tertiary", "surface"];

const rampRow = (family: string) =>
specimen(
Expand All @@ -35,7 +34,7 @@ export function color(): string {
const surfaces = grid(
10,
...surfaceTokens.map((name) =>
specimen(name, `<div class="sf-card" style="background:var(${name});block-size:56px"></div>`),
specimen(name, `<div class="sf-card" style="inline-size:100%;background:var(${name});block-size:56px"></div>`),
),
);
const onColor = grid(
Expand All @@ -59,7 +58,7 @@ export function color(): string {
...["border", "border--subtle", "border--strong"].map((b) =>
specimen(
`--sf-color-${b}`,
`<div style="block-size:48px;border:2px solid var(--sf-color-${b});border-radius:var(--sf-radius-m)"></div>`,
`<div style="inline-size:100%;block-size:48px;border:2px solid var(--sf-color-${b});border-radius:var(--sf-radius-m)"></div>`,
),
),
);
Expand Down Expand Up @@ -299,15 +298,24 @@ export function layout(): string {
),
);
const bentoDemo = specimen(
"sf-bento",
`<div class="sf-bento">${["a", "b", "c", "d", "e"].map((t) => `<div class="pv-box pv-box--tall">${t}</div>`).join("")}</div>`,
"sf-bento — sf-bento-featured / -wide / -tall span modifiers",
`<div class="sf-bento">${[
["sf-bento-featured", "featured (2×2)"],
["", "b"],
["", "c"],
["sf-bento-wide", "wide (2×1)"],
["sf-bento-tall", "tall (1×2)"],
["", "f"],
]
.map(([mods, t]) => `<div class="pv-box pv-box--tall ${mods}">${esc(t)}</div>`)
.join("")}</div>`,
);
const frameDemo = grid(
9,
...(["video", "square", "portrait"] as const).map((r) =>
specimen(
`sf-frame--${r}`,
`<div class="sf-frame sf-frame--${r}" style="background:var(--sf-color-primary-100);border-radius:var(--sf-radius-m)"></div>`,
`<div class="sf-frame sf-frame--${r}" style="inline-size:100%;background:var(--sf-color-primary-100);border-radius:var(--sf-radius-m)"></div>`,
),
),
);
Expand Down Expand Up @@ -429,27 +437,237 @@ export function components(): string {
}

// ── Macros & utilities ─────────────────────────────────────────────────────
// Every one of the 58 shipped macro classes is applied here for real — this
// gallery is a coverage floor (tests/preview-coverage.test.ts), not just a
// reference; a bare class-name chip doesn't satisfy that spirit, so each
// macro gets an actual specimen instead of only appearing in a text list.
const LOREM_MACRO =
"SLASHED ships a token-driven CSS framework with no build step and no JavaScript required to render a page.";

export function macros(): string {
// Surfaces — the generic form (any colour via --sf-surface-color) plus
// all 10 precomputed named variants.
const surfaceNamed = ["primary", "secondary", "tertiary", "action", "neutral", "inverse", "success", "warning", "info", "danger"];
const surfaces = grid(
12,
...["primary", "secondary", "tertiary", "action", "neutral", "success", "warning", "info"].map((s) =>
specimen(
"sf-surface (custom --sf-surface-color)",
`<div class="sf-surface" style="--sf-surface-color:var(--sf-color-primary-100);padding:var(--sf-space-m);border-radius:var(--sf-radius-m)">Aa</div>`,
),
...surfaceNamed.map((s) =>
specimen(
`sf-surface--${s}`,
`<div class="sf-surface sf-surface--${s}" style="padding:var(--sf-space-m);border-radius:var(--sf-radius-m)">Aa</div>`,
),
),
);
const macroClasses = classesOfKind("macro").filter((c) => c.selector.startsWith(".sf-"));
const list = well(
`<div class="sf-cluster sf-cluster--xs">${macroClasses
.map((c) => `<code class="pv-swatch-label">${esc(c.selector)}</code>`)

// Prose, not-prose & flow — real rhythm, not a swatch.
const prose = well(`<div class="sf-prose">
<h3 style="margin:0">Article heading</h3>
<p>Paragraphs get automatic vertical rhythm from <code>.sf-prose</code> — no utility classes needed between them.</p>
<ul><li>First point</li><li>Second point</li></ul>
<div class="sf-not-prose" style="padding:var(--sf-space-s);border:var(--sf-border-width-1) dashed var(--sf-color-border);border-radius:var(--sf-radius-s)">
<p style="margin:0"><code>.sf-not-prose</code> opts this box out of the prose rhythm above.</p>
</div>
<p>…and rhythm resumes after it.</p>
</div>`);
const flow = well(`<div class="sf-flow">
<div class="pv-box">One</div>
<div class="pv-box">Two — margin-block-start</div>
<div class="pv-box">Three — margin-block-start</div>
</div>`);

// Truncation — one line, then 2/3/N-line clamps, all on the same copy.
const truncation = grid(
14,
specimen("sf-truncate", `<div class="sf-truncate" style="max-inline-size:12rem">${LOREM_MACRO}</div>`),
specimen("sf-line-clamp-2", `<p class="sf-line-clamp-2" style="max-inline-size:12rem;margin:0">${LOREM_MACRO}</p>`),
specimen("sf-line-clamp-3", `<p class="sf-line-clamp-3" style="max-inline-size:12rem;margin:0">${LOREM_MACRO}</p>`),
specimen(
"sf-line-clamp-N (--sf-line-clamp:5)",
`<p class="sf-line-clamp-N" style="max-inline-size:12rem;margin:0;--sf-line-clamp:5">${LOREM_MACRO} ${LOREM_MACRO}</p>`,
),
);

// Equal height — three unevenly-tall children forced level.
const equalHeight = well(
`<div class="sf-equal-height" style="gap:var(--sf-space-s)"><div class="pv-box">Short</div><div class="pv-box">Two<br>lines</div><div class="pv-box">Three<br>lines<br>here</div></div>`,
);

// Aspect ratio — content-agnostic, distinct from the .sf-frame primitive.
const aspect = well(
`<div class="sf-cluster sf-cluster--m">${["16 / 9", "1 / 1", "4 / 3"]
.map(
(r) =>
`<div class="sf-stack sf-stack--xs sf-stack--center pv-center-text"><div class="sf-aspect" style="--sf-aspect:${r};inline-size:8rem;background:var(--sf-color-primary-100);border-radius:var(--sf-radius-m)"></div>${tag(`--sf-aspect:${r}`)}</div>`,
)
.join("")}</div>`,
);

// Tabular numbers — a real numeric column so digits visibly align.
const tabularNums = well(`<table class="sf-tabular-nums" style="border-collapse:collapse">
<tbody>
<tr><td class="pv-secondary" style="padding-inline-end:var(--sf-space-m)">Invoice #1</td><td>1,024.50</td></tr>
<tr><td class="pv-secondary" style="padding-inline-end:var(--sf-space-m)">Invoice #82</td><td>398.00</td></tr>
<tr><td class="pv-secondary" style="padding-inline-end:var(--sf-space-m)">Invoice #913</td><td>12,760.75</td></tr>
</tbody>
</table>`);

// Links — external marker + underline affordances, as real anchors.
const links = well(`<div class="sf-cluster sf-cluster--l">
<a href="#" onclick="event.preventDefault()" class="sf-link-external">sf-link-external</a>
<a href="#" onclick="event.preventDefault()" class="sf-link--subtle">sf-link--subtle (hover me)</a>
<a href="#" onclick="event.preventDefault()" class="sf-link--reverse">sf-link--reverse (hover me)</a>
</div>`);

// Drop shadows — filter: drop-shadow() follows alpha shape, so a ring
// (mask-cut circle) shows the hole surviving the shadow, unlike box-shadow.
const dropShadows = grid(
9,
...(["xs", "s", "m", "l", "xl"] as const).map((s) =>
specimen(
`sf-drop-shadow-${s}`,
`<div class="sf-drop-shadow-${s}" style="inline-size:48px;block-size:48px;background:var(--sf-color-primary-500);-webkit-mask-image:radial-gradient(circle at center,transparent 30%,black 32%);mask-image:radial-gradient(circle at center,transparent 30%,black 32%)"></div>`,
),
),
);

// Scrolling — shadow mask, snap, and content-visibility, all with real
// overflowing content so the effect is actually observable.
const scrollShadow = well(
`<div class="sf-scroll-shadow" style="block-size:12rem;overflow-y:auto"><div class="sf-stack sf-stack--xs" style="padding-block:var(--sf-space-m)">${Array.from(
{ length: 8 },
(_, i) => `<div class="pv-box">Row ${i + 1}</div>`,
).join("")}</div></div>`,
);
const scrollSnap = well(
`<div class="sf-scroll-snap" style="block-size:8rem;overflow-y:auto;border-radius:var(--sf-radius-m)">${["a", "b", "c"]
.map(
(l, i) =>
`<div style="block-size:8rem;background:var(--sf-color-primary-${100 + i * 100});display:flex;align-items:center;justify-content:center">${tag(`section ${l}`)}</div>`,
)
.join("")}</div>`,
);
const contentAuto = well(
`<section class="sf-content-auto" style="--sf-content-intrinsic-size:4rem;padding:var(--sf-space-m);background:var(--sf-color-inset);border-radius:var(--sf-radius-m)"><p class="pv-secondary" style="margin:0">content-visibility:auto — off-screen instances of this section skip layout/paint until scrolled near.</p></section>`,
);

// Overflow fade — genuinely overflowing content in the axis each variant
// fades: a single-line nowrap row for the horizontal/inline variants, and
// a fixed-height column for the vertical/block variants. flex-wrap must
// be forced off on the row: .sf-cluster wraps by default, which would
// just break it onto multiple lines instead of overflowing it.
const fadeRow = () =>
`<div style="display:flex;flex-wrap:nowrap;gap:var(--sf-space-xs);overflow:hidden;inline-size:9rem">${Array.from(
{ length: 10 },
(_, i) => `<span class="pv-box" style="flex:none">${i + 1}</span>`,
).join("")}</div>`;
const fadeColumn = () =>
`<div style="display:flex;flex-direction:column;gap:var(--sf-space-xs);overflow:hidden;block-size:6rem">${Array.from(
{ length: 6 },
(_, i) => `<span class="pv-box" style="flex:none">${i + 1}</span>`,
).join("")}</div>`;
const overflowFade = grid(
11,
...([
["", fadeRow],
["--right", fadeRow],
["--left", fadeRow],
["--inline", fadeRow],
["--top", fadeColumn],
["--bottom", fadeColumn],
["--block", fadeColumn],
] as const).map(([m, shape]) => specimen(`sf-overflow-fade${m}`, `<div class="sf-overflow-fade${m}">${shape()}</div>`)),
);

// Scrim, surface-bg & text-protect — text over a token-driven gradient
// "photo" (no external asset needed).
const photo = "var(--sf-gradient-brand, var(--sf-color-primary))";
const scrimCard = (mods: string, label: string) =>
`<div class="sf-scrim ${mods}" style="position:relative;background:${photo};border-radius:var(--sf-radius-m);block-size:8rem;overflow:hidden">
<div class="sf-scrim__content" style="position:absolute;inset-block-end:0;padding:var(--sf-space-s);color:#fff">${esc(label)}</div>
</div>`;
const scrims = grid(
12,
specimen("sf-scrim sf-scrim--bottom", scrimCard("sf-scrim--bottom", "Legible headline")),
specimen("sf-scrim sf-scrim--top", scrimCard("sf-scrim--top", "Legible headline")),
specimen("sf-scrim sf-scrim--full", scrimCard("sf-scrim--full", "Legible headline")),
);
const surfaceBg = well(
`<div class="sf-surface-bg" style="--sf-surface-bg-image:${photo};--sf-surface-bg-overlay:var(--sf-scrim-gradient);block-size:8rem;border-radius:var(--sf-radius-m);display:flex;align-items:flex-end;padding:var(--sf-space-s);color:#fff">sf-surface-bg — named background preset</div>`,
);
const textProtect = `<div style="position:relative;background:${photo};border-radius:var(--sf-radius-m);padding:var(--sf-space-l)">
<h3 class="sf-text-protect" style="margin:0;color:#fff">Readable over a photo</h3>
</div>`;

// Text gradient.
const textGradient = well(`<h2 class="sf-text-gradient pv-strong" style="margin:0;font-size:var(--sf-text-2xl)">Gradient headline</h2>`);

// Corner scoop — the notch reveals whatever sits behind the element, so
// the card sits on a contrasting frame.
const scoopCard = (mods: string) =>
`<div class="sf-card ${mods}" style="background:var(--sf-color-surface);inline-size:6rem;block-size:6rem;border-radius:var(--sf-radius-s)"></div>`;
const cornerScoop = frame(
`<div class="sf-cluster sf-cluster--m" style="background:var(--sf-color-primary-100);padding:var(--sf-space-m);border-radius:var(--sf-radius-m)">${[
["sf-corner-scoop", "default (top-right)"],
["sf-corner-scoop sf-corner-scoop--top-left", "top-left"],
["sf-corner-scoop sf-corner-scoop--bottom-left", "bottom-left"],
["sf-corner-scoop sf-corner-scoop--bottom-right", "bottom-right"],
]
.map(([mods, label]) => specimen(label, scoopCard(mods)))
.join("")}</div>`,
);

// Overlap — an avatar pulled down over the card that follows it
// (.sf-overlap--down; the plain .sf-overlap pulls UP onto whatever
// precedes it instead — not what a leading avatar needs here), plus the
// remaining directional variants on their own swatches.
const avatar = `<div style="inline-size:3rem;block-size:3rem;border-radius:var(--sf-radius-full);background:${photo};border:var(--sf-border-width-2) solid var(--sf-color-surface)"></div>`;
const overlapHost = frame(`<div style="max-inline-size:14rem;margin-inline:auto">
<div class="sf-overlap--down">${avatar}</div>
<div class="sf-card sf-overlap-host"><p class="pv-secondary" style="margin:0">.sf-overlap-host reserves space so in-flow content clears the intruding avatar above.</p></div>
</div>`);
const overlapDirections = grid(
10,
...([
["", "sf-overlap (pulls up)"],
["--start", "sf-overlap--start"],
["--end", "sf-overlap--end"],
] as const).map(([m, label]) =>
specimen(label, `<div style="position:relative;inline-size:3rem;block-size:3rem;border-radius:var(--sf-radius-full);background:${photo}" class="sf-overlap${m}"></div>`),
),
);

// No-tap-highlight — no visual difference to show (it only suppresses the
// mobile tap-highlight overlay); still applied to a real element for
// coverage rather than left to a text mention.
const noTapHighlight = well(
`<a href="#" onclick="event.preventDefault()" class="sf-no-tap-highlight">sf-no-tap-highlight — suppresses the mobile tap-highlight overlay (no on-screen difference here)</a>`,
);

return page(
"Macros & utilities",
"Surface macros and the full set of shipped macro utility classes.",
section("Surfaces (sf-surface--*)", surfaces),
section("All macro classes", list, `${macroClasses.length} classes in @layer slashed.macros`),
"Every shipped macro (@layer slashed.macros), applied for real — not just named.",
section("Surfaces (sf-surface, sf-surface--*)", surfaces),
section("Prose, not-prose & flow", well(stack("m", prose, flow))),
section("Truncation (sf-truncate, sf-line-clamp-*)", truncation),
section("Equal height (sf-equal-height)", equalHeight),
section("Aspect ratio (sf-aspect)", aspect),
section("Tabular numbers (sf-tabular-nums)", tabularNums),
section("Links (sf-link-external, sf-link--subtle, sf-link--reverse)", links),
section("Drop shadows (sf-drop-shadow-*)", dropShadows, "Alpha-following filter: drop-shadow() — note the shadow survives the masked hole."),
section("Scroll shadow (sf-scroll-shadow)", scrollShadow),
section("Scroll snap (sf-scroll-snap)", scrollSnap),
section("Content-visibility (sf-content-auto)", contentAuto),
section("Overflow fade (sf-overflow-fade + edge/axis modifiers)", overflowFade),
section("Scrim over media (sf-scrim, sf-scrim--*, sf-scrim__content)", scrims),
section("Named background preset (sf-surface-bg)", surfaceBg),
section("Text protect (sf-text-protect)", well(textProtect)),
section("Text gradient (sf-text-gradient)", textGradient),
section("Corner scoop (sf-corner-scoop + corner modifiers)", cornerScoop),
section("Overlap (sf-overlap, sf-overlap-host)", well(stack("m", overlapHost, overlapDirections))),
section("No tap highlight (sf-no-tap-highlight)", noTapHighlight),
);
}

Expand Down
Loading