clipper-css 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,184 @@
1
+ ---
2
+ // Theme toggle component - system preference fallback with localStorage override
3
+ ---
4
+
5
+ <button class="theme-toggle" aria-label="Toggle dark mode" aria-pressed={false} title="Toggle light/dark mode">
6
+ <span class="theme-icon" data-mode="light" aria-hidden="true">☀️</span>
7
+ <span class="theme-icon" data-mode="system" aria-hidden="true">🖥️</span>
8
+ <span class="theme-icon" data-mode="dark" aria-hidden="true">🌙</span>
9
+ </button>
10
+
11
+ <script>
12
+ // Theme toggle functionality with system preference fallback
13
+ const themeToggle = document.querySelector(".theme-toggle");
14
+ const html = document.documentElement;
15
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
16
+ const THEME_KEY = "clipper-theme";
17
+
18
+ type ThemeMode = "system" | "dark" | "light";
19
+ type ThemeWindow = Window & { __clipperThemeToggleAbortController?: AbortController };
20
+ const themeWindow = window as ThemeWindow;
21
+
22
+ themeWindow.__clipperThemeToggleAbortController?.abort();
23
+ const listenerController = new AbortController();
24
+ themeWindow.__clipperThemeToggleAbortController = listenerController;
25
+
26
+ function getSystemMode(): Exclude<ThemeMode, "system"> {
27
+ return mediaQuery.matches ? "dark" : "light";
28
+ }
29
+
30
+ function getStoredMode(): ThemeMode {
31
+ const stored = localStorage.getItem(THEME_KEY);
32
+ if (stored === "dark" || stored === "light" || stored === "system") {
33
+ return stored;
34
+ }
35
+
36
+ return "system";
37
+ }
38
+
39
+ function applyTheme(mode: ThemeMode) {
40
+ const resolvedMode = mode === "system" ? getSystemMode() : mode;
41
+ html.classList.toggle("dark", resolvedMode === "dark");
42
+ html.classList.toggle("light", resolvedMode === "light");
43
+ }
44
+
45
+ const legacyTheme = localStorage.getItem("theme");
46
+ if (legacyTheme !== null) {
47
+ localStorage.removeItem("theme");
48
+ }
49
+
50
+ // Initialize theme from localStorage or system preference
51
+ function initTheme() {
52
+ const mode = getStoredMode();
53
+ applyTheme(mode);
54
+ updateToggleState(mode);
55
+ }
56
+
57
+ function updateToggleState(mode: ThemeMode) {
58
+ themeToggle?.setAttribute("title", `Theme: ${mode} (click icon to set)`);
59
+ themeToggle?.setAttribute("aria-label", `Theme mode ${mode}. Click an icon to set theme.`);
60
+
61
+ themeToggle?.querySelectorAll<HTMLElement>(".theme-icon").forEach((icon) => {
62
+ const iconMode = icon.dataset.mode as ThemeMode | undefined;
63
+ if (!iconMode) {
64
+ return;
65
+ }
66
+
67
+ const isActive = iconMode === mode;
68
+ icon.dataset.active = String(isActive);
69
+ icon.setAttribute("aria-hidden", "true");
70
+ });
71
+ }
72
+
73
+ function setMode(mode: ThemeMode) {
74
+ applyTheme(mode);
75
+ localStorage.setItem(THEME_KEY, mode);
76
+ updateToggleState(mode);
77
+ }
78
+
79
+ function selectTheme(event: Event) {
80
+ const target = event.target;
81
+ if (!(target instanceof Element)) {
82
+ return;
83
+ }
84
+
85
+ const icon = target.closest<HTMLElement>(".theme-icon");
86
+ const mode = icon?.dataset.mode as ThemeMode | undefined;
87
+ if (!mode) {
88
+ return;
89
+ }
90
+
91
+ setMode(mode);
92
+ }
93
+
94
+ // Event listener
95
+ themeToggle?.addEventListener("click", selectTheme, { signal: listenerController.signal });
96
+
97
+ // Initialize on page load
98
+ initTheme();
99
+
100
+ // Listen for system preference changes
101
+ mediaQuery.addEventListener(
102
+ "change",
103
+ () => {
104
+ if (getStoredMode() === "system") {
105
+ initTheme();
106
+ }
107
+ },
108
+ { signal: listenerController.signal },
109
+ );
110
+ </script>
111
+
112
+ <style>
113
+ /* Theme toggle button */
114
+ .theme-toggle {
115
+ display: inline-flex;
116
+ align-items: center;
117
+ gap: 0.25rem;
118
+ background-color: var(--background);
119
+ border: 1px solid var(--border);
120
+ border-radius: 9999px;
121
+ cursor: pointer;
122
+ font-size: 1rem;
123
+ padding: 0.25rem 0.4rem;
124
+ transition:
125
+ opacity 0.2s ease,
126
+ border-color 0.2s ease;
127
+ color: inherit;
128
+ }
129
+
130
+ .theme-toggle:hover {
131
+ opacity: 0.9;
132
+ }
133
+
134
+ .theme-toggle:active {
135
+ opacity: 0.75;
136
+ }
137
+
138
+ .theme-icon {
139
+ align-items: center;
140
+ border-radius: 9999px;
141
+ display: inline-flex;
142
+ height: 1.8rem;
143
+ justify-content: center;
144
+ opacity: 0.55;
145
+ transition:
146
+ opacity 0.2s ease,
147
+ background-color 0.2s ease;
148
+ width: 1.8rem;
149
+ }
150
+
151
+ .theme-icon[data-active="true"] {
152
+ background-color: var(--muted);
153
+ cursor: default;
154
+ opacity: 1;
155
+ pointer-events: none;
156
+ }
157
+
158
+ .theme-toggle:hover .theme-icon[data-active="false"] {
159
+ opacity: 0.72;
160
+ }
161
+
162
+ .theme-toggle:focus-visible {
163
+ outline: 2px solid var(--primary);
164
+ outline-offset: 2px;
165
+ }
166
+
167
+ .theme-icon[data-mode="system"],
168
+ .theme-icon[data-mode="light"],
169
+ .theme-icon[data-mode="dark"] {
170
+ font-size: 1em;
171
+ line-height: 1;
172
+ }
173
+
174
+ .theme-toggle:focus-visible .theme-icon[data-active="true"] {
175
+ box-shadow: inset 0 0 0 1px var(--border);
176
+ }
177
+
178
+ @media (prefers-reduced-motion: reduce) {
179
+ .theme-toggle,
180
+ .theme-icon {
181
+ transition: none;
182
+ }
183
+ }
184
+ </style>
@@ -0,0 +1,21 @@
1
+ ---
2
+ import Html from "../layouts/Html.astro";
3
+ import Header from "./Header.astro";
4
+ import Footer from "./Footer.astro";
5
+
6
+ interface Props {
7
+ title: string;
8
+ }
9
+
10
+ const { title } = Astro.props;
11
+ ---
12
+
13
+ <Html title={title}>
14
+ <body>
15
+ <Header />
16
+ <main>
17
+ <slot />
18
+ </main>
19
+ <Footer />
20
+ </body>
21
+ </Html>
@@ -0,0 +1,331 @@
1
+ ---
2
+ import Html from "../layouts/Html.astro";
3
+ import ThemeToggle from "../components/ThemeToggle.astro";
4
+ import variablesCss from "../styles/variables.css?raw";
5
+
6
+ interface Props {
7
+ title: string;
8
+ }
9
+
10
+ const { title } = Astro.props;
11
+
12
+ const headerLinks = [
13
+ { href: "#overview", label: "Overview" },
14
+ { href: "#defaults", label: "Defaults" },
15
+ { href: "#lean", label: "Less Classes" },
16
+ { href: "#components", label: "Components" },
17
+ { href: "#colors", label: "Colors" },
18
+ { href: "#starter", label: "Starter" },
19
+ ];
20
+
21
+ const cardClass = "min-w-0 rounded-xl border border-border bg-muted p-5";
22
+ const codeBlockClass =
23
+ "min-w-0 max-w-full overflow-x-auto rounded-xl border border-border bg-background p-4 text-sm leading-relaxed text-muted-foreground";
24
+ const swatchClass = "rounded-xl border bg-muted p-3";
25
+ const swatchChipClass = "h-14 w-full rounded-md border border-[color-mix(in_oklch,var(--foreground)_16%,transparent)]";
26
+
27
+ const defaultsCards = [
28
+ {
29
+ title: "Sections are layout containers",
30
+ text: "Sections already have page-width grid constraints, vertical padding, and row gaps. Usually an id plus semantic content is enough.",
31
+ code: `<section id=\"hero\">\n <h1>Landing headline</h1>\n <p class=\"readable\">Long copy stays readable without custom CSS.</p>\n</section>`,
32
+ },
33
+ {
34
+ title: "Divs are modern by default",
35
+ text: "In Clipper, div/nav/article/figure are flex-column with tokenized spacing. Add a .row class when you need horizontal layout.",
36
+ code: `<div>\n <h3>No flex classes needed</h3>\n <p>Children stack with preset spacing.</p>\n</div>\n\n<div class=\"row\">\n <a href=\"#\">Primary</a>\n <a href=\"#\">Secondary</a>\n</div>`,
37
+ },
38
+ {
39
+ title: "Typography is predefined",
40
+ text: "Heading scale and heading font is fluent. Body font and text wrapping are configured in tokens + base rules.",
41
+ code: `<h1>Display headline from theme scale</h1>\n<h2>Section title with heading font</h2>\n<p>Paragraph text uses global body font and pretty wrapping.</p>`,
42
+ },
43
+ {
44
+ title: "Anchors are pre-styled",
45
+ text: "Plain <a> tags are styled from tokens in clipper base styles. Underline is controlled by --link-underline and --link-underline-hover.",
46
+ code: `/* variables.css */\n:root {\n --link-underline: none;\n --link-underline-hover: underline;\n}\n\n<p>Read the <a href=\"#starter\">starter snippet</a> and <a href=\"#colors\">token palette</a>.</p>`,
47
+ },
48
+ ];
49
+
50
+ const createSpacingExample = (level: string) => ({
51
+ title: level,
52
+ className: level,
53
+ code: `<div class="${level}">\n <span>Item</span>\n <span>Item</span>\n <span>Item</span>\n</div>`,
54
+ });
55
+
56
+ const spacingLevels = [
57
+ "gap-4xs",
58
+ "gap-3xs",
59
+ "gap-2xs",
60
+ "gap-xs",
61
+ "gap-sm",
62
+ "gap-base",
63
+ "gap-lg",
64
+ "gap-xl",
65
+ "gap-2xl",
66
+ "gap-3xl",
67
+ "gap-4xl",
68
+ ] as const;
69
+ const gapExamples = spacingLevels.map((level) => createSpacingExample(level));
70
+
71
+ const leanComparison = {
72
+ Clipper: `<section id=\"feature\">\n <h2>Built with Clipper defaults</h2>\n <div>\n <p>Layout and rhythm come from base rules.</p>\n <a href=\"#\">Action</a>\n </div>\n</section>`,
73
+ utilityHeavy: `<section id=\"feature\" class=\"mx-auto max-w-6xl px-6 py-20\">\n <h2 class=\"text-4xl font-bold leading-tight tracking-tight\">Built manually</h2>\n <div class=\"mt-4 flex flex-col gap-4\">\n <p class=\"max-w-prose text-base leading-7 text-slate-600\">Layout and rhythm configured inline.</p>\n <a class=\"text-blue-600 hover:text-blue-700\" href=\"#\">Action</a>\n </div>\n</section>`,
74
+ };
75
+
76
+ const quickCopySnippet = `---\nimport Body from \"../layouts/Body.astro\";\n---\n\n<Body title=\"My Page\">\n <section id=\"intro\">\n <h1>Hello Clipper</h1>\n <p class=\"readable\">Start semantic, then add only the few utilities you really need.</p>\n <div class=\"row\">\n <a href=\"#\">Primary action</a>\n <a href=\"#\">Secondary action</a>\n </div>\n </section>\n</Body>`;
77
+
78
+ const tokenRegex = /--(primary|secondary)-(\d+)\s*:\s*([^;]+);/g;
79
+ const palette = {
80
+ primary: [] as Array<{ name: string; shade: number; value: string }>,
81
+ secondary: [] as Array<{ name: string; shade: number; value: string }>,
82
+ };
83
+ const componentSnippet = `<article class="card">\n <span class="badge">New</span>\n <h3>Reusable primitives</h3>\n <p>Use .card, .badge, and .btn for common UI patterns.</p>\n <div class="row">\n <a href="#" class="btn">Primary action</a>\n <a href="#" class="btn btn-outline">Secondary action</a>\n </div>\n</article>`;
84
+
85
+ for (const match of variablesCss.matchAll(tokenRegex)) {
86
+ const family = match[1] as "primary" | "secondary";
87
+ const shade = Number(match[2]);
88
+ const value = match[3].trim();
89
+
90
+ palette[family].push({
91
+ name: `--${family}-${shade}`,
92
+ shade,
93
+ value,
94
+ });
95
+ }
96
+
97
+ palette.primary.sort((left, right) => left.shade - right.shade);
98
+ palette.secondary.sort((left, right) => left.shade - right.shade);
99
+
100
+ const paletteGroups = [
101
+ {
102
+ title: "Primary scale",
103
+ ariaLabel: "Primary color scale",
104
+ tokens: palette.primary,
105
+ },
106
+ {
107
+ title: "Secondary scale",
108
+ ariaLabel: "Secondary color scale",
109
+ tokens: palette.secondary,
110
+ },
111
+ ].filter((group) => group.tokens.length > 0);
112
+ ---
113
+
114
+ <Html title={title}>
115
+ <body>
116
+ <header id="header" class="header-sticky border-b bg-background/80">
117
+ <div class="row flex-wrap items-center justify-between py-4">
118
+ <a href="#header" class="font-heading text-2xl font-bold no-underline text-foreground" aria-label="Clipper home"
119
+ >Clipper ⛵</a
120
+ >
121
+
122
+ <nav aria-label="Primary" class="order-3 basis-full sm:order-2 sm:basis-auto">
123
+ <ul class="row flex-wrap m-0 list-none p-0 sm:gap-4 whitespace-nowrap">
124
+ {
125
+ headerLinks.map((link) => (
126
+ <li>
127
+ <a href={link.href} class="text-muted-foreground no-underline hover:text-foreground">
128
+ {link.label}
129
+ </a>
130
+ </li>
131
+ ))
132
+ }
133
+ </ul>
134
+ </nav>
135
+
136
+ <div class="order-2 sm:order-3">
137
+ <ThemeToggle />
138
+ </div>
139
+ </div>
140
+ </header>
141
+
142
+ <main>
143
+ <section class="bg-muted" id="overview">
144
+ <div class="font-heading text-primary text-sm font-semibold tracking-wide uppercase">Clipper demo</div>
145
+ <div class="readable">
146
+ <h1>How to use Clipper</h1>
147
+ <p>
148
+ Clipper is a simple tailwind-based framework for building pages fast without fighting CSS and adding too
149
+ many utility classes. It is designed for designers and developers alike: semantic markup by default,
150
+ token-driven styling, and just enough utilities to stay productive.
151
+ </p>
152
+ <p>
153
+ The basic page is just semantic HTML (header, footer, main) with <code>&lt;section&gt;</code> containers inside.
154
+ </p>
155
+ </div>
156
+ <div class="bg-primary-muted py-3">
157
+ As default, elements directly below sections will respect the page max width (with a margin for smaller
158
+ screens).
159
+ </div>
160
+ <div class="full-width bg-primary-muted py-3">
161
+ <span class="page-width">
162
+ But breaking out of a layout is no problem with the <code>.full-width</code> class. And just add <code
163
+ >.page-width</code
164
+ > on the child element to constrain it back to the page width.
165
+ </span>
166
+ </div>
167
+ <p class="readable">
168
+ Elements can have a <code>.readable</code> utility class applied, which sets a max-width for easier reading without
169
+ needing a custom class or wrapper.
170
+ </p>
171
+ </section>
172
+ <section id="defaults">
173
+ <div class="font-heading text-primary text-sm font-semibold tracking-wide uppercase">Built-in behavior</div>
174
+ <h2>What Clipper gives you out of the box</h2>
175
+ <div class="grid md:grid-cols-2">
176
+ {
177
+ defaultsCards.map((card) => (
178
+ <article class={cardClass}>
179
+ <h3>{card.title}</h3>
180
+ <p>{card.text}</p>
181
+ {/* prettier-ignore */}
182
+ <pre class={codeBlockClass}><code>{card.code}</code></pre>
183
+ </article>
184
+ ))
185
+ }
186
+ </div>
187
+ <p class="readable">
188
+ Example: plain style <a href="#starter">Starter</a> and <a href="#colors">Color demo</a>; set link underline
189
+ behavior globally in <code>variables.css</code>.
190
+ </p>
191
+
192
+ <h3>Spacing utilities</h3>
193
+ <p class="readable">
194
+ Clipper includes spacing utilities (<code>gap-4xs</code> through <code>gap-4xl</code>) that apply consistent
195
+ spacing containers. They work on sections and regular elements, making it easy to control vertical and
196
+ horizontal rhythm.
197
+ </p>
198
+
199
+ <p class="readable">
200
+ The default spacing for non-section elements is the <code>base</code> level, which is tokenized as <code
201
+ >var(--spacing-base)</code
202
+ >. This means you can adjust the global spacing rhythm by changing a single token value.
203
+ </p>
204
+
205
+ <p class="readable">
206
+ What makes this system powerful is that <strong>every spacing token is fluid</strong>. Using CSS <code
207
+ >clamp()</code
208
+ >, spacing scales smoothly from mobile to desktop. Change viewport width by 100px and spacing adjusts
209
+ seamlessly — no breakpoints needed. The entire rhythm system breathes with the content, maintaining visual
210
+ harmony at every size.
211
+ </p>
212
+
213
+ <div>
214
+ <h4>All spacing scales</h4>
215
+ <div class="grid md:grid-cols-4">
216
+ {
217
+ gapExamples.map((example) => (
218
+ <article class={cardClass}>
219
+ <h5>{example.title}</h5>
220
+ <div class={example.className}>
221
+ <span class="card bg-background text-xs">Item</span>
222
+ <span class="card bg-background text-xs">Item</span>
223
+ <span class="card bg-background text-xs">Item</span>
224
+ </div>
225
+ </article>
226
+ ))
227
+ }
228
+ </div>
229
+ </div>
230
+ </section>
231
+
232
+ <section id="lean">
233
+ <div class="font-heading text-primary text-sm font-semibold tracking-wide uppercase">Less Tailwind clutter</div>
234
+ <h2>Same intent with fewer classes</h2>
235
+ <p class="readable">
236
+ Because Clipper already handles section width, spacing rhythm, flex stacking, and typography defaults, most
237
+ sections require only semantic HTML plus a few targeted utilities.
238
+ </p>
239
+ <div class="grid gap-4 md:grid-cols-2">
240
+ <article class={cardClass}>
241
+ <h3>Clipper-first markup</h3>
242
+ <pre class={codeBlockClass}><code>{leanComparison.Clipper}</code></pre>
243
+ </article>
244
+ <article class={cardClass}>
245
+ <h3>Utility-heavy equivalent</h3>
246
+ <pre class={codeBlockClass}><code>{leanComparison.utilityHeavy}</code></pre>
247
+ </article>
248
+ </div>
249
+ </section>
250
+
251
+ <section id="components">
252
+ <div class="font-heading text-primary text-sm font-semibold tracking-wide uppercase">Reusable components</div>
253
+ <h2>Component primitives in action</h2>
254
+ <p class="readable">
255
+ Clipper includes three generic reusable primitives in <code>components.css</code>: <code>.btn</code>, <code
256
+ >.card</code
257
+ >, and <code>.badge</code>, compatible with dark mode, purely for "getting started" convenience. They can be
258
+ replaced by any UI framework or custom styles.
259
+ </p>
260
+
261
+ <div class="grid gap-4 md:grid-cols-2">
262
+ <article class="card">
263
+ <span class="badge">New</span>
264
+ <h3>Card + badge</h3>
265
+ <p>Cards provide a consistent container style, while badges highlight metadata or state.</p>
266
+ <div class="row">
267
+ <a href="#starter" class="btn btn-inline">View starter</a>
268
+ </div>
269
+ </article>
270
+
271
+ <article class="card">
272
+ <h3>Button primitive</h3>
273
+ <p>The button style keeps typography, color, hover, and focus states consistent.</p>
274
+ <div class="row flex-wrap">
275
+ <a href="#" class="btn">Primary action</a>
276
+ <a href="#" class="btn btn-outline">Secondary action</a>
277
+ <span class="badge">Accessible focus</span>
278
+ </div>
279
+ </article>
280
+ </div>
281
+
282
+ <pre class={codeBlockClass}><code>{componentSnippet}</code></pre>
283
+ </section>
284
+
285
+ <section id="colors">
286
+ <div class="font-heading text-primary text-sm font-semibold tracking-wide uppercase">Color demo</div>
287
+ <h2>Token-driven color scales</h2>
288
+ <p class="readable">
289
+ All colors can be easily configured with <code>variables.css</code>, with <i>easy-to-use</i> support for dark mode.
290
+ </p>
291
+ <h3>Color tokens parsed from variables.css</h3>
292
+ {
293
+ paletteGroups.map((group) => (
294
+ <div>
295
+ <h4>{group.title}</h4>
296
+ <div
297
+ class="grid gap-3 grid-cols-[repeat(auto-fit,minmax(120px,1fr))]"
298
+ role="list"
299
+ aria-label={group.ariaLabel}
300
+ >
301
+ {group.tokens.map((token) => (
302
+ <article class={swatchClass} role="listitem">
303
+ <div class={swatchChipClass} style={`background-color: var(${token.name});`} aria-hidden="true" />
304
+ <div class="mt-2 text-sm text-muted-foreground">
305
+ <span class="whitespace-nowrap">{token.name}</span>
306
+ </div>
307
+ </article>
308
+ ))}
309
+ </div>
310
+ </div>
311
+ ))
312
+ }
313
+ </section>
314
+
315
+ <section id="starter">
316
+ <div class="font-heading text-primary text-sm font-semibold tracking-wide uppercase">Starter</div>
317
+ <h2>Copy this minimal page pattern</h2>
318
+ <pre class={codeBlockClass}><code>{quickCopySnippet}</code></pre>
319
+ </section>
320
+
321
+ <slot />
322
+ </main>
323
+
324
+ <footer class="border-t bg-foreground py-base text-muted-foreground">
325
+ <div class="row justify-between">
326
+ <p>Built with Clipper primitives and token-driven theme scales.</p>
327
+ <p>Enjoy the simplicity!</p>
328
+ </div>
329
+ </footer>
330
+ </body>
331
+ </Html>
@@ -0,0 +1 @@
1
+ <footer></footer>
@@ -0,0 +1 @@
1
+ <header class="header-sticky"></header>
@@ -0,0 +1,28 @@
1
+ ---
2
+ import "../styles/global.css";
3
+
4
+ interface Props {
5
+ title: string;
6
+ lang?: string;
7
+ }
8
+
9
+ const { title, lang = "en" } = Astro.props;
10
+ ---
11
+
12
+ <!doctype html>
13
+ <html {lang} class="scroll-smooth">
14
+ <head>
15
+ <meta charset="UTF-8" />
16
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
17
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
18
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
19
+ <link
20
+ href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"
21
+ rel="stylesheet"
22
+ />
23
+ <title>{title}</title>
24
+ </head>
25
+ <slot />
26
+
27
+
28
+ </html>
@@ -0,0 +1,43 @@
1
+ ---
2
+ import Body from "../layouts/Body.astro";
3
+ import type { MarkdownLayoutProps } from "astro";
4
+
5
+ type Props = MarkdownLayoutProps<{
6
+ title: string;
7
+ }>;
8
+
9
+ const { frontmatter } = Astro.props;
10
+ const { title } = frontmatter;
11
+ ---
12
+
13
+ <Body title={title}>
14
+ <section class="mx-auto max-w-[100ch] markdown-content">
15
+ <slot />
16
+ </section>
17
+ </Body>
18
+
19
+ <style>
20
+ .markdown-content {
21
+ :where(ul, ol) {
22
+ display: block;
23
+ padding-inline-start: 1.5em;
24
+ margin-block: var(--space-base);
25
+ }
26
+
27
+ ul {
28
+ list-style: disc;
29
+ }
30
+
31
+ ol {
32
+ list-style: decimal;
33
+ }
34
+
35
+ :where(li) {
36
+ display: list-item;
37
+ }
38
+
39
+ :where(ul ul, ol ol, ul ol, ol ul) {
40
+ margin-block: var(--space-2xs);
41
+ }
42
+ }
43
+ </style>
@@ -0,0 +1,8 @@
1
+ ---
2
+ // To start from scratch, simply import from Body.astro instead.
3
+ import Body from "../layouts/Demo.astro";
4
+ ---
5
+
6
+ <Body title="Clipper ⛵">
7
+ <section></section>
8
+ </Body>