smirky 1.0.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.
- package/.bashrc +1 -0
- package/README.md +203 -0
- package/content/keeping-things-simple.md +13 -0
- package/content/three-small-steps.md +14 -0
- package/content/welcome-to-the-blog.md +14 -0
- package/dist/about/index.html +60 -0
- package/dist/assets/input.css +92 -0
- package/dist/assets/site.css +630 -0
- package/dist/blog/about/index.html +88 -0
- package/dist/blog/building-a-static-site-generator/index.html +86 -0
- package/dist/blog/hello-world/index.html +86 -0
- package/dist/blog/index.html +125 -0
- package/dist/blog/keeping-things-simple/index.html +70 -0
- package/dist/blog/three-small-steps/index.html +70 -0
- package/dist/blog/welcome-to-the-blog/index.html +70 -0
- package/dist/blog/why-kiss-matters/index.html +86 -0
- package/dist/contact/index.html +83 -0
- package/dist/index.html +56 -0
- package/dist/tags/index.html +65 -0
- package/dist/tags/javascript/index.html +125 -0
- package/dist/tags/webdev/index.html +125 -0
- package/output/assets/input.css +92 -0
- package/output/assets/site.css +630 -0
- package/package.json +31 -0
- package/pages/about.md +21 -0
- package/pages/contact.md +44 -0
- package/smirky.js +391 -0
- package/theme/assets/input.css +92 -0
- package/theme/assets/site.css +630 -0
- package/theme/blog.html +8 -0
- package/theme/debug.html +5 -0
- package/theme/index.html +10 -0
- package/theme/layout.html +23 -0
- package/theme/navbar.html +16 -0
- package/theme/page.html +5 -0
- package/theme/partials/blog_post_card.html +12 -0
- package/theme/partials/footer.html +4 -0
- package/theme/partials/head.html +2 -0
- package/theme/partials/navbar.html +14 -0
- package/theme/partials/tag_pill.html +5 -0
- package/theme/post.html +10 -0
- package/theme/site.json +8 -0
- package/theme/tags.html +8 -0
package/pages/contact.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Contact"
|
|
3
|
+
description: "Get in touch"
|
|
4
|
+
template: "page"
|
|
5
|
+
date: "2026-01-02"
|
|
6
|
+
tags:
|
|
7
|
+
- contact
|
|
8
|
+
- form
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Contact
|
|
12
|
+
|
|
13
|
+
If you'd like to reach out, feel free to send a message using the form below.
|
|
14
|
+
|
|
15
|
+
<!--
|
|
16
|
+
This contact form uses the Fabform.io backend service.
|
|
17
|
+
Fabform handles form submissions without requiring a custom server.
|
|
18
|
+
Learn more at: https://fabform.io
|
|
19
|
+
Documentation: https://fabform.io/docs
|
|
20
|
+
-->
|
|
21
|
+
|
|
22
|
+
<form action="https://fabform.io/f/YOUR_FORM_ID_HERE" method="POST" class="space-y-4">
|
|
23
|
+
|
|
24
|
+
<div>
|
|
25
|
+
<label class="block mb-1 font-medium">Your Name</label>
|
|
26
|
+
<input type="text" name="name" required class="w-full border p-2 rounded">
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div>
|
|
30
|
+
<label class="block mb-1 font-medium">Your Email</label>
|
|
31
|
+
<input type="email" name="email" required class="w-full border p-2 rounded">
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div>
|
|
35
|
+
<label class="block mb-1 font-medium">Message</label>
|
|
36
|
+
<textarea name="message" rows="5" required class="w-full border p-2 rounded"></textarea>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<button type="submit" class="bg-blue-600 text-white px-4 py-2 rounded">
|
|
40
|
+
Send Message
|
|
41
|
+
</button>
|
|
42
|
+
|
|
43
|
+
</form>
|
|
44
|
+
|
package/smirky.js
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import matter from "gray-matter";
|
|
6
|
+
import { marked } from "marked";
|
|
7
|
+
|
|
8
|
+
// --------------------------------------------------
|
|
9
|
+
// Directories
|
|
10
|
+
// --------------------------------------------------
|
|
11
|
+
const CONTENT_DIR = "./content";
|
|
12
|
+
const PAGES_DIR = "./pages";
|
|
13
|
+
const THEME_DIR = "./theme";
|
|
14
|
+
const OUTPUT_DIR = "./dist";
|
|
15
|
+
|
|
16
|
+
// --------------------------------------------------
|
|
17
|
+
// Helpers
|
|
18
|
+
// --------------------------------------------------
|
|
19
|
+
function ensureDir(dir) {
|
|
20
|
+
if (!fs.existsSync(dir)) {
|
|
21
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function slugify(str) {
|
|
26
|
+
return String(str)
|
|
27
|
+
.trim()
|
|
28
|
+
.toLowerCase()
|
|
29
|
+
.replace(/[\s_]+/g, "-")
|
|
30
|
+
.replace(/[^a-z0-9-]/g, "")
|
|
31
|
+
.replace(/-+/g, "-");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// --------------------------------------------------
|
|
35
|
+
// SUPER SIMPLE TEMPLATE ENGINE
|
|
36
|
+
// One final pass over the entire HTML
|
|
37
|
+
// --------------------------------------------------
|
|
38
|
+
function applyTemplate(html, vars) {
|
|
39
|
+
let out = html;
|
|
40
|
+
for (const key in vars) {
|
|
41
|
+
const token = `{{ ${key} }}`;
|
|
42
|
+
out = out.split(token).join(vars[key] ?? "");
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// --------------------------------------------------
|
|
48
|
+
// Load theme files
|
|
49
|
+
// --------------------------------------------------
|
|
50
|
+
function readTheme(file) {
|
|
51
|
+
return fs.readFileSync(path.join(THEME_DIR, file), "utf8");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const layoutTemplate = readTheme("layout.html");
|
|
55
|
+
const indexTemplate = readTheme("index.html");
|
|
56
|
+
const pageTemplate = readTheme("page.html");
|
|
57
|
+
const postTemplate = readTheme("post.html");
|
|
58
|
+
const blogTemplate = readTheme("blog.html");
|
|
59
|
+
const tagsTemplate = readTheme("tags.html");
|
|
60
|
+
|
|
61
|
+
// Partials
|
|
62
|
+
const headPartial = readTheme("partials/head.html");
|
|
63
|
+
const navbarPartial = readTheme("partials/navbar.html");
|
|
64
|
+
const footerPartial = readTheme("partials/footer.html");
|
|
65
|
+
const blogPostCardTemplate = readTheme("partials/blog_post_card.html");
|
|
66
|
+
const tagPillTemplate = readTheme("partials/tag_pill.html");
|
|
67
|
+
|
|
68
|
+
// Site config
|
|
69
|
+
const siteConfig = JSON.parse(
|
|
70
|
+
fs.readFileSync(path.join(THEME_DIR, "site.json"), "utf8")
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// --------------------------------------------------
|
|
74
|
+
// Copy static assets
|
|
75
|
+
// --------------------------------------------------
|
|
76
|
+
function copyAssets() {
|
|
77
|
+
const src = path.join(THEME_DIR, "assets");
|
|
78
|
+
const dest = path.join(OUTPUT_DIR, "assets");
|
|
79
|
+
|
|
80
|
+
ensureDir(dest);
|
|
81
|
+
|
|
82
|
+
for (const file of fs.readdirSync(src)) {
|
|
83
|
+
fs.copyFileSync(path.join(src, file), path.join(dest, file));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.log("Assets copied");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// --------------------------------------------------
|
|
90
|
+
// Navbar builder
|
|
91
|
+
// --------------------------------------------------
|
|
92
|
+
function buildNavbarLinks(currentSlug) {
|
|
93
|
+
const files = fs.existsSync(PAGES_DIR)
|
|
94
|
+
? fs.readdirSync(PAGES_DIR).filter((f) => f.endsWith(".md"))
|
|
95
|
+
: [];
|
|
96
|
+
|
|
97
|
+
const links = files.map((file) => {
|
|
98
|
+
const raw = fs.readFileSync(path.join(PAGES_DIR, file), "utf8");
|
|
99
|
+
const { data } = matter(raw);
|
|
100
|
+
|
|
101
|
+
const slug = slugify(data.slug || path.basename(file, ".md"));
|
|
102
|
+
const title = data.title || slug;
|
|
103
|
+
|
|
104
|
+
const isActive = slug === currentSlug;
|
|
105
|
+
const cls = isActive
|
|
106
|
+
? "text-indigo-600 font-semibold"
|
|
107
|
+
: "text-slate-700 hover:text-slate-900";
|
|
108
|
+
|
|
109
|
+
return `<a href="/${slug}/" class="${cls}">${title}</a>`;
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Blog link
|
|
113
|
+
links.push(
|
|
114
|
+
`<a href="/blog/" class="${
|
|
115
|
+
currentSlug === "blog"
|
|
116
|
+
? "text-indigo-600 font-semibold"
|
|
117
|
+
: "text-slate-700 hover:text-slate-900"
|
|
118
|
+
}">Blog</a>`
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// Tags link
|
|
122
|
+
links.push(
|
|
123
|
+
`<a href="/tags/" class="${
|
|
124
|
+
currentSlug === "tags"
|
|
125
|
+
? "text-indigo-600 font-semibold"
|
|
126
|
+
: "text-slate-700 hover:text-slate-900"
|
|
127
|
+
}">Tags</a>`
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
return links.join("\n ");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// --------------------------------------------------
|
|
134
|
+
// Tag pills & blog cards
|
|
135
|
+
// --------------------------------------------------
|
|
136
|
+
function renderTagPills(tags) {
|
|
137
|
+
return (tags || [])
|
|
138
|
+
.map((tag) =>
|
|
139
|
+
applyTemplate(tagPillTemplate, {
|
|
140
|
+
tag_name: tag,
|
|
141
|
+
tag_slug: slugify(tag)
|
|
142
|
+
})
|
|
143
|
+
)
|
|
144
|
+
.join("\n");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function renderBlogCards(posts) {
|
|
148
|
+
return posts
|
|
149
|
+
.map((post) =>
|
|
150
|
+
applyTemplate(blogPostCardTemplate, {
|
|
151
|
+
title: post.title,
|
|
152
|
+
url: post.url,
|
|
153
|
+
date: post.date,
|
|
154
|
+
tags: renderTagPills(post.tags)
|
|
155
|
+
})
|
|
156
|
+
)
|
|
157
|
+
.join("\n");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// --------------------------------------------------
|
|
161
|
+
// NEW renderPage() — assemble first, replace last
|
|
162
|
+
// --------------------------------------------------
|
|
163
|
+
function renderPage(innerHTML, vars) {
|
|
164
|
+
const navbarLinks = buildNavbarLinks(vars.currentSlug || "");
|
|
165
|
+
|
|
166
|
+
// Build page/post HTML from the chosen template
|
|
167
|
+
const pageHtml = applyTemplate(vars.template, {
|
|
168
|
+
...vars,
|
|
169
|
+
content: innerHTML
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Assemble full HTML BEFORE variable replacement
|
|
173
|
+
let fullHtml = layoutTemplate
|
|
174
|
+
.replace("{{ head }}", headPartial)
|
|
175
|
+
.replace("{{ navbar }}", navbarPartial)
|
|
176
|
+
.replace("{{ footer }}", footerPartial)
|
|
177
|
+
.replace("{{ content }}", pageHtml);
|
|
178
|
+
|
|
179
|
+
// Final variable pass — replaces everywhere
|
|
180
|
+
fullHtml = applyTemplate(fullHtml, {
|
|
181
|
+
...vars,
|
|
182
|
+
site_title: siteConfig.site.title,
|
|
183
|
+
site_description: siteConfig.site.description,
|
|
184
|
+
site_footer: siteConfig.site.footer,
|
|
185
|
+
navbar_links: navbarLinks
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
return fullHtml;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// --------------------------------------------------
|
|
192
|
+
// Collect tags
|
|
193
|
+
// --------------------------------------------------
|
|
194
|
+
function collectTags(posts) {
|
|
195
|
+
const tagMap = {};
|
|
196
|
+
|
|
197
|
+
posts.forEach((post) => {
|
|
198
|
+
(post.tags || []).forEach((tag) => {
|
|
199
|
+
const key = slugify(tag);
|
|
200
|
+
if (!tagMap[key]) {
|
|
201
|
+
tagMap[key] = { name: tag, posts: [] };
|
|
202
|
+
}
|
|
203
|
+
tagMap[key].posts.push(post);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
return tagMap;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// --------------------------------------------------
|
|
211
|
+
// Build static pages
|
|
212
|
+
// --------------------------------------------------
|
|
213
|
+
function buildPages() {
|
|
214
|
+
if (!fs.existsSync(PAGES_DIR)) return;
|
|
215
|
+
|
|
216
|
+
const files = fs.readdirSync(PAGES_DIR).filter((f) => f.endsWith(".md"));
|
|
217
|
+
|
|
218
|
+
files.forEach((file) => {
|
|
219
|
+
const raw = fs.readFileSync(path.join(PAGES_DIR, file), "utf8");
|
|
220
|
+
const { data, content: md } = matter(raw);
|
|
221
|
+
|
|
222
|
+
const slug = slugify(data.slug || path.basename(file, ".md"));
|
|
223
|
+
const html = marked(md);
|
|
224
|
+
|
|
225
|
+
const final = renderPage(html, {
|
|
226
|
+
title: data.title || slug,
|
|
227
|
+
currentSlug: slug,
|
|
228
|
+
template: pageTemplate
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const outDir = path.join(OUTPUT_DIR, slug);
|
|
232
|
+
ensureDir(outDir);
|
|
233
|
+
fs.writeFileSync(path.join(outDir, "index.html"), final);
|
|
234
|
+
|
|
235
|
+
console.log(`Page → /${slug}/`);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// --------------------------------------------------
|
|
240
|
+
// Build blog posts
|
|
241
|
+
// --------------------------------------------------
|
|
242
|
+
function buildPosts() {
|
|
243
|
+
if (!fs.existsSync(CONTENT_DIR)) return [];
|
|
244
|
+
|
|
245
|
+
const files = fs.readdirSync(CONTENT_DIR).filter((f) => f.endsWith(".md"));
|
|
246
|
+
const posts = [];
|
|
247
|
+
|
|
248
|
+
files.forEach((file) => {
|
|
249
|
+
const raw = fs.readFileSync(path.join(CONTENT_DIR, file), "utf8");
|
|
250
|
+
const { data, content: md } = matter(raw);
|
|
251
|
+
|
|
252
|
+
const slug = slugify(data.slug || path.basename(file, ".md"));
|
|
253
|
+
const html = marked(md);
|
|
254
|
+
|
|
255
|
+
const title =
|
|
256
|
+
data.title ||
|
|
257
|
+
slug.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
258
|
+
|
|
259
|
+
const tags = data.tags || [];
|
|
260
|
+
const tagPills = renderTagPills(tags);
|
|
261
|
+
|
|
262
|
+
const final = renderPage(html, {
|
|
263
|
+
title,
|
|
264
|
+
currentSlug: "blog",
|
|
265
|
+
template: postTemplate,
|
|
266
|
+
tag_pills: tagPills
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
const outDir = path.join(OUTPUT_DIR, "blog", slug);
|
|
270
|
+
ensureDir(outDir);
|
|
271
|
+
fs.writeFileSync(path.join(outDir, "index.html"), final);
|
|
272
|
+
|
|
273
|
+
posts.push({
|
|
274
|
+
title,
|
|
275
|
+
url: `/blog/${slug}/`,
|
|
276
|
+
date: data.date || "",
|
|
277
|
+
tags
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
console.log(`Post → /blog/${slug}/`);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
return posts;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// --------------------------------------------------
|
|
287
|
+
// Blog index
|
|
288
|
+
// --------------------------------------------------
|
|
289
|
+
function buildBlogIndex(posts) {
|
|
290
|
+
const listHtml = renderBlogCards(
|
|
291
|
+
posts.sort((a, b) => (a.date < b.date ? 1 : -1))
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
const final = renderPage(listHtml, {
|
|
295
|
+
title: "Blog",
|
|
296
|
+
currentSlug: "blog",
|
|
297
|
+
template: blogTemplate
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const outDir = path.join(OUTPUT_DIR, "blog");
|
|
301
|
+
ensureDir(outDir);
|
|
302
|
+
fs.writeFileSync(path.join(outDir, "index.html"), final);
|
|
303
|
+
|
|
304
|
+
console.log("Blog index → /blog/");
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// --------------------------------------------------
|
|
308
|
+
// Tags index
|
|
309
|
+
// --------------------------------------------------
|
|
310
|
+
function buildTagsPage(tagMap) {
|
|
311
|
+
const tagLinks = Object.values(tagMap)
|
|
312
|
+
.map((t) =>
|
|
313
|
+
applyTemplate(tagPillTemplate, {
|
|
314
|
+
tag_name: `${t.name} (${t.posts.length})`,
|
|
315
|
+
tag_slug: slugify(t.name)
|
|
316
|
+
})
|
|
317
|
+
)
|
|
318
|
+
.join("\n");
|
|
319
|
+
|
|
320
|
+
const final = renderPage(tagLinks, {
|
|
321
|
+
title: "Tags",
|
|
322
|
+
currentSlug: "tags",
|
|
323
|
+
template: tagsTemplate
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
const outDir = path.join(OUTPUT_DIR, "tags");
|
|
327
|
+
ensureDir(outDir);
|
|
328
|
+
fs.writeFileSync(path.join(outDir, "index.html"), final);
|
|
329
|
+
|
|
330
|
+
console.log("Tags index → /tags/");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// --------------------------------------------------
|
|
334
|
+
// Tag pages
|
|
335
|
+
// --------------------------------------------------
|
|
336
|
+
function buildTagPages(tagMap) {
|
|
337
|
+
Object.values(tagMap).forEach((tag) => {
|
|
338
|
+
const listHtml = renderBlogCards(tag.posts);
|
|
339
|
+
|
|
340
|
+
const final = renderPage(listHtml, {
|
|
341
|
+
title: `Posts tagged "${tag.name}"`,
|
|
342
|
+
currentSlug: "tags",
|
|
343
|
+
template: blogTemplate
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const outDir = path.join(OUTPUT_DIR, "tags", slugify(tag.name));
|
|
347
|
+
ensureDir(outDir);
|
|
348
|
+
fs.writeFileSync(path.join(outDir, "index.html"), final);
|
|
349
|
+
|
|
350
|
+
console.log(`Tag → /tags/${slugify(tag.name)}/`);
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// --------------------------------------------------
|
|
355
|
+
// Home page
|
|
356
|
+
// --------------------------------------------------
|
|
357
|
+
function buildHome() {
|
|
358
|
+
const final = renderPage("", {
|
|
359
|
+
title: siteConfig.site.title,
|
|
360
|
+
currentSlug: "home",
|
|
361
|
+
template: indexTemplate
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
fs.writeFileSync(path.join(OUTPUT_DIR, "index.html"), final);
|
|
365
|
+
console.log("Home → /");
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// --------------------------------------------------
|
|
369
|
+
// Build everything
|
|
370
|
+
// --------------------------------------------------
|
|
371
|
+
function build() {
|
|
372
|
+
ensureDir(OUTPUT_DIR);
|
|
373
|
+
|
|
374
|
+
console.log("Building...");
|
|
375
|
+
|
|
376
|
+
copyAssets();
|
|
377
|
+
|
|
378
|
+
const posts = buildPosts();
|
|
379
|
+
buildPages();
|
|
380
|
+
|
|
381
|
+
const tagMap = collectTags(posts);
|
|
382
|
+
buildBlogIndex(posts);
|
|
383
|
+
buildTagsPage(tagMap);
|
|
384
|
+
buildTagPages(tagMap);
|
|
385
|
+
|
|
386
|
+
buildHome();
|
|
387
|
+
console.log("Done.");
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
build();
|
|
391
|
+
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
/* ---------------------------
|
|
4
|
+
Global Markdown Typography
|
|
5
|
+
Tailwind v4 — No Config File
|
|
6
|
+
---------------------------- */
|
|
7
|
+
|
|
8
|
+
/* Headings */
|
|
9
|
+
h1 {
|
|
10
|
+
@apply text-4xl font-bold leading-tight mt-8 mb-4;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
h2 {
|
|
14
|
+
@apply text-3xl font-semibold leading-snug mt-6 mb-3;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
h3 {
|
|
18
|
+
@apply text-2xl font-semibold leading-snug mt-5 mb-2;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
h4 {
|
|
22
|
+
@apply text-xl font-semibold mt-4 mb-2;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
h5 {
|
|
26
|
+
@apply text-lg font-medium mt-3 mb-2;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
h6 {
|
|
30
|
+
@apply text-base font-medium mt-2 mb-2;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Paragraphs */
|
|
34
|
+
p {
|
|
35
|
+
@apply text-base leading-relaxed mb-4;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Links */
|
|
39
|
+
a {
|
|
40
|
+
@apply text-blue-600 underline hover:text-blue-800;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Images */
|
|
44
|
+
img {
|
|
45
|
+
@apply rounded-lg my-4;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Lists */
|
|
49
|
+
ul {
|
|
50
|
+
@apply list-disc pl-6 mb-4;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
ol {
|
|
54
|
+
@apply list-decimal pl-6 mb-4;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
li {
|
|
58
|
+
@apply mb-1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Blockquotes */
|
|
62
|
+
blockquote {
|
|
63
|
+
@apply border-l-4 border-gray-300 pl-4 italic text-gray-700 my-4;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* Code blocks */
|
|
67
|
+
pre {
|
|
68
|
+
@apply bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto my-4;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
code {
|
|
72
|
+
@apply bg-gray-100 text-red-600 px-1 py-0.5 rounded;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* Horizontal rule */
|
|
76
|
+
hr {
|
|
77
|
+
@apply border-gray-300 my-8;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* Tables */
|
|
81
|
+
table {
|
|
82
|
+
@apply w-full border-collapse my-6;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
th {
|
|
86
|
+
@apply border-b font-semibold p-2 text-left;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
td {
|
|
90
|
+
@apply border-b p-2;
|
|
91
|
+
}
|
|
92
|
+
|