mirror of
https://github.com/litruv/lit.ruv.wtf.git
synced 2026-07-24 02:36:02 +10:00
Add static blog generator and initial blog posts
- Implement StaticBlogGenerator class for generating static HTML blog pages from markdown data. - Create initial blog index page (blog.html) with links to posts. - Add individual blog post pages for "Markdown Formatting Test", "Building Interactive Node Graphs", "Welcome to My Blog", "test d d d d", and "testddd". - Include markdown rendering capabilities for formatting content in blog posts.
This commit is contained in:
9
build.js
9
build.js
@@ -2,6 +2,7 @@ const fs = require('fs').promises;
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const esbuild = require('esbuild');
|
const esbuild = require('esbuild');
|
||||||
const { execSync } = require('child_process');
|
const { execSync } = require('child_process');
|
||||||
|
const { StaticBlogGenerator } = require('./tools/StaticBlogGenerator');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse YAML front matter from markdown content
|
* Parse YAML front matter from markdown content
|
||||||
@@ -42,6 +43,9 @@ async function processBlogPosts() {
|
|||||||
const blogDir = path.join(__dirname, 'website', 'data', 'blog');
|
const blogDir = path.join(__dirname, 'website', 'data', 'blog');
|
||||||
const outputPath = path.join(__dirname, 'website', 'data', 'blogs.json');
|
const outputPath = path.join(__dirname, 'website', 'data', 'blogs.json');
|
||||||
const graphPath = path.join(__dirname, 'website', 'data', 'graph.json');
|
const graphPath = path.join(__dirname, 'website', 'data', 'graph.json');
|
||||||
|
const staticBlogGenerator = new StaticBlogGenerator({
|
||||||
|
siteRootDir: path.join(__dirname, 'website')
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const files = await fs.readdir(blogDir);
|
const files = await fs.readdir(blogDir);
|
||||||
@@ -87,9 +91,14 @@ async function processBlogPosts() {
|
|||||||
if (posts.length > 0) {
|
if (posts.length > 0) {
|
||||||
await updateGraphWithBlogNodes(posts, graphPath);
|
await updateGraphWithBlogNodes(posts, graphPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await staticBlogGenerator.generate(posts);
|
||||||
|
console.log('✓ Generated static blog pages');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code === 'ENOENT') {
|
if (err.code === 'ENOENT') {
|
||||||
console.log('⚠ No blog directory found, skipping blog processing');
|
console.log('⚠ No blog directory found, skipping blog processing');
|
||||||
|
await staticBlogGenerator.generate([]);
|
||||||
|
console.log('✓ Generated empty static blog index');
|
||||||
} else {
|
} else {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|||||||
381
tools/StaticBlogGenerator.js
Normal file
381
tools/StaticBlogGenerator.js
Normal file
@@ -0,0 +1,381 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs').promises;
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates static blog pages and an index page from markdown post data.
|
||||||
|
*/
|
||||||
|
class StaticBlogGenerator {
|
||||||
|
/**
|
||||||
|
* @param {{ siteRootDir: string }} options
|
||||||
|
*/
|
||||||
|
constructor(options) {
|
||||||
|
/** @type {string} */
|
||||||
|
this.siteRootDir = options.siteRootDir;
|
||||||
|
/** @type {string} */
|
||||||
|
this.blogRootDir = path.join(this.siteRootDir, 'blog');
|
||||||
|
/** @type {string} */
|
||||||
|
this.blogIndexPath = path.join(this.siteRootDir, 'blog.html');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates blog index and post pages.
|
||||||
|
*
|
||||||
|
* @param {Array<{slug: string, title: string, date: string | null, author: string | null, tags?: string[], content: string}>} posts
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async generate(posts) {
|
||||||
|
await fs.rm(this.blogRootDir, { recursive: true, force: true });
|
||||||
|
await fs.mkdir(this.blogRootDir, { recursive: true });
|
||||||
|
|
||||||
|
const indexHtml = this.renderIndexPage(posts);
|
||||||
|
await fs.writeFile(this.blogIndexPath, indexHtml, 'utf-8');
|
||||||
|
|
||||||
|
for (const post of posts) {
|
||||||
|
const postDir = path.join(this.blogRootDir, post.slug);
|
||||||
|
await fs.mkdir(postDir, { recursive: true });
|
||||||
|
const postHtml = this.renderPostPage(post);
|
||||||
|
await fs.writeFile(path.join(postDir, 'index.html'), postHtml, 'utf-8');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Array<{slug: string, title: string, date: string | null, author: string | null, tags?: string[]}>} posts
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
renderIndexPage(posts) {
|
||||||
|
const cardsHtml = posts.map((post) => {
|
||||||
|
const dateLabel = post.date ? this.formatDate(post.date) : 'Undated';
|
||||||
|
const authorLabel = post.author ? this.escapeHtml(post.author) : 'Unknown author';
|
||||||
|
const safeTitle = this.escapeHtml(post.title || post.slug);
|
||||||
|
const safeSlug = encodeURIComponent(post.slug);
|
||||||
|
const tags = Array.isArray(post.tags) ? post.tags : [];
|
||||||
|
const tagsHtml = tags.length > 0
|
||||||
|
? `<div class="blog-index-tags">${tags.map((tag) => `<span class="blog-tag">${this.escapeHtml(tag)}</span>`).join('')}</div>`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
return [
|
||||||
|
'<article class="blog-index-item">',
|
||||||
|
` <h2 class="blog-index-item-title"><a href="/blog/${safeSlug}/">${safeTitle}</a></h2>`,
|
||||||
|
` <p class="blog-index-item-meta">${dateLabel} <span aria-hidden="true">-</span> ${authorLabel}</p>`,
|
||||||
|
tagsHtml,
|
||||||
|
'</article>'
|
||||||
|
].join('\n');
|
||||||
|
}).join('\n');
|
||||||
|
|
||||||
|
const content = [
|
||||||
|
'<main class="blog-shell">',
|
||||||
|
this.renderBrandBar(),
|
||||||
|
'<section class="blog-card" aria-labelledby="blog-index-title">',
|
||||||
|
' <header class="blog-post-header">',
|
||||||
|
' <h1 id="blog-index-title" class="blog-post-title">Blog</h1>',
|
||||||
|
' <p class="blog-index-subtitle">Direct links to every post.</p>',
|
||||||
|
' </header>',
|
||||||
|
` <div class="blog-index-list">${cardsHtml || '<p class="blog-empty">No posts yet.</p>'}</div>`,
|
||||||
|
'</section>',
|
||||||
|
'</main>'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
return this.renderPageTemplate('Blog', content, '/blog.html');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{slug: string, title: string, date: string | null, author: string | null, tags?: string[], content: string}} post
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
renderPostPage(post) {
|
||||||
|
const safeTitle = this.escapeHtml(post.title || post.slug);
|
||||||
|
const dateLabel = post.date ? this.formatDate(post.date) : null;
|
||||||
|
const authorLabel = post.author ? this.escapeHtml(post.author) : null;
|
||||||
|
const tags = Array.isArray(post.tags) ? post.tags : [];
|
||||||
|
|
||||||
|
const metaBits = [];
|
||||||
|
if (dateLabel) metaBits.push(`<span>${dateLabel}</span>`);
|
||||||
|
if (authorLabel) metaBits.push(`<span>${authorLabel}</span>`);
|
||||||
|
|
||||||
|
const tagsHtml = tags.length > 0
|
||||||
|
? `<div class="blog-post-tags">${tags.map((tag) => `<span class="blog-tag">${this.escapeHtml(tag)}</span>`).join('')}</div>`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
const contentHtml = StaticMarkdownRenderer.render(post.content || '');
|
||||||
|
|
||||||
|
const content = [
|
||||||
|
'<main class="blog-shell">',
|
||||||
|
this.renderBrandBar(),
|
||||||
|
'<article class="blog-card" aria-labelledby="blog-post-title">',
|
||||||
|
' <header class="blog-post-header">',
|
||||||
|
` <h1 id="blog-post-title" class="blog-post-title">${safeTitle}</h1>`,
|
||||||
|
metaBits.length > 0 ? ` <p class="blog-post-meta">${metaBits.join('<span aria-hidden="true">-</span>')}</p>` : '',
|
||||||
|
tagsHtml,
|
||||||
|
' </header>',
|
||||||
|
` <section class="blog-post-content">${contentHtml}</section>`,
|
||||||
|
'</article>',
|
||||||
|
'</main>'
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
return this.renderPageTemplate(`${safeTitle} - Blog`, content, `/blog/${encodeURIComponent(post.slug)}/`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} title
|
||||||
|
* @param {string} bodyHtml
|
||||||
|
* @param {string} canonicalPath
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
renderPageTemplate(title, bodyHtml, canonicalPath) {
|
||||||
|
const escapedTitle = this.escapeHtml(title);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'<!DOCTYPE html>',
|
||||||
|
'<html lang="en">',
|
||||||
|
'<head>',
|
||||||
|
' <meta charset="UTF-8">',
|
||||||
|
' <meta name="viewport" content="width=device-width, initial-scale=1.0">',
|
||||||
|
` <title>${escapedTitle}</title>`,
|
||||||
|
' <meta name="description" content="Lit.ruv.wtf blog posts.">',
|
||||||
|
` <link rel="canonical" href="https://lit.ruv.wtf${canonicalPath}">`,
|
||||||
|
' <link rel="icon" type="image/png" sizes="32x32" href="/logos/32px.png">',
|
||||||
|
' <link rel="icon" type="image/png" sizes="64x64" href="/logos/64px.png">',
|
||||||
|
' <link rel="stylesheet" href="/styles/main.css">',
|
||||||
|
'</head>',
|
||||||
|
'<body class="blog-page">',
|
||||||
|
this.renderHeaderLinks(),
|
||||||
|
bodyHtml,
|
||||||
|
'</body>',
|
||||||
|
'</html>'
|
||||||
|
].join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
renderHeaderLinks() {
|
||||||
|
return [
|
||||||
|
'<nav class="quick-links" aria-label="Primary">',
|
||||||
|
' <a href="/blog.html" class="quick-link">blog</a>',
|
||||||
|
' <a href="/docs/" class="quick-link">docs</a>',
|
||||||
|
' <a href="https://github.com/litruv" target="_blank" rel="noopener" class="quick-link">github</a>',
|
||||||
|
' <a href="https://bsky.app/profile/lit.mates.dev" target="_blank" rel="noopener" class="quick-link">bluesky</a>',
|
||||||
|
' <a href="/materials" class="quick-link">materials</a>',
|
||||||
|
'</nav>'
|
||||||
|
].join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
renderBrandBar() {
|
||||||
|
return [
|
||||||
|
'<a class="blog-logo-link" href="/" aria-label="Back to main site">',
|
||||||
|
' <img src="/logos/LogoFull.svg" alt="lit.ruv.wtf" class="blog-logo" />',
|
||||||
|
'</a>'
|
||||||
|
].join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} dateInput
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
formatDate(dateInput) {
|
||||||
|
const parsed = new Date(dateInput);
|
||||||
|
if (Number.isNaN(parsed.valueOf())) {
|
||||||
|
return this.escapeHtml(dateInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Intl.DateTimeFormat('en-US', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric'
|
||||||
|
}).format(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} value
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
escapeHtml(value) {
|
||||||
|
return String(value)
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lightweight markdown renderer for static blog generation.
|
||||||
|
*/
|
||||||
|
class StaticMarkdownRenderer {
|
||||||
|
/**
|
||||||
|
* @param {string} markdown
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
static render(markdown) {
|
||||||
|
const normalized = String(markdown || '').replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim();
|
||||||
|
if (!normalized) return '';
|
||||||
|
|
||||||
|
const codeBlocks = [];
|
||||||
|
const withCodePlaceholders = normalized.replace(/```([\w-]*)(?:\s*\{[^}]*\})?\s*\n([\s\S]*?)\n?```/g, (_, lang, code) => {
|
||||||
|
const index = codeBlocks.length;
|
||||||
|
codeBlocks.push({ lang: lang || '', code: code || '' });
|
||||||
|
return `\n__CODEBLOCK_${index}__\n`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const blocks = withCodePlaceholders.split(/\n{2,}/);
|
||||||
|
const parts = blocks.map((block) => {
|
||||||
|
const trimmed = block.trim();
|
||||||
|
if (!trimmed) return '';
|
||||||
|
|
||||||
|
const codeMatch = trimmed.match(/^__CODEBLOCK_(\d+)__$/);
|
||||||
|
if (codeMatch) {
|
||||||
|
const item = codeBlocks[Number(codeMatch[1])];
|
||||||
|
const languageClass = item.lang ? ` language-${this.escapeHtml(item.lang)}` : '';
|
||||||
|
return `<pre class="md-pre"><code class="${languageClass}">${this.escapeHtml(item.code)}</code></pre>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^[-*_]{3,}$/.test(trimmed)) {
|
||||||
|
return '<hr class="md-hr" />';
|
||||||
|
}
|
||||||
|
|
||||||
|
const heading = trimmed.match(/^(#{1,6})\s+(.+)$/);
|
||||||
|
if (heading && trimmed.split('\n').length === 1) {
|
||||||
|
const level = heading[1].length;
|
||||||
|
return `<h${level} class="md-h${level}">${this.renderInline(heading[2])}</h${level}>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^>\s?/.test(trimmed)) {
|
||||||
|
const quoteText = trimmed.split('\n').map((line) => line.replace(/^>\s?/, '')).join('\n');
|
||||||
|
return `<blockquote class="md-blockquote">${this.renderInline(quoteText).replace(/\n/g, '<br />')}</blockquote>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^[-*+]\s+/.test(trimmed)) {
|
||||||
|
const items = trimmed
|
||||||
|
.split('\n')
|
||||||
|
.filter((line) => /^\s*[-*+]\s+/.test(line))
|
||||||
|
.map((line) => line.replace(/^\s*[-*+]\s+/, ''));
|
||||||
|
return `<ul class="md-ul">${items.map((item) => `<li class="md-li">${this.renderInline(item)}</li>`).join('')}</ul>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^\d+\.\s+/.test(trimmed)) {
|
||||||
|
const items = trimmed
|
||||||
|
.split('\n')
|
||||||
|
.filter((line) => /^\s*\d+\.\s+/.test(line))
|
||||||
|
.map((line) => line.replace(/^\s*\d+\.\s+/, ''));
|
||||||
|
return `<ol class="md-ol">${items.map((item) => `<li class="md-li">${this.renderInline(item)}</li>`).join('')}</ol>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageOnly = trimmed.match(/^!\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)$/);
|
||||||
|
if (imageOnly) {
|
||||||
|
const imageMeta = this.parseImageMeta(imageOnly[1], imageOnly[2], imageOnly[3] || '');
|
||||||
|
const captionHtml = imageMeta.caption ? `<figcaption class="md-figcaption">${this.escapeHtml(imageMeta.caption)}</figcaption>` : '';
|
||||||
|
return `<figure class="md-figure"><img class="md-img" src="${imageMeta.src}" alt="${this.escapeHtml(imageMeta.alt)}"${imageMeta.attributes} />${captionHtml}</figure>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lines = trimmed.split('\n').map((line) => this.renderInline(line));
|
||||||
|
return `<p class="md-p">${lines.join('<br />')}</p>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
return parts.filter(Boolean).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} text
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
static renderInline(text) {
|
||||||
|
const codeSpans = [];
|
||||||
|
const withCodePlaceholders = text.replace(/`([^`]+)`/g, (_, code) => {
|
||||||
|
const index = codeSpans.length;
|
||||||
|
codeSpans.push(code);
|
||||||
|
return `__INLINE_CODE_${index}__`;
|
||||||
|
});
|
||||||
|
|
||||||
|
let html = this.escapeHtml(withCodePlaceholders);
|
||||||
|
|
||||||
|
html = html.replace(/!\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^&]*)")?\)/g, (_, alt, url, title) => {
|
||||||
|
const imageMeta = this.parseImageMeta(alt, url, title || '');
|
||||||
|
return `<img class="md-img md-img--inline" src="${imageMeta.src}" alt="${this.escapeHtml(imageMeta.alt)}"${imageMeta.attributes} />`;
|
||||||
|
});
|
||||||
|
|
||||||
|
html = html.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"([^&]*)")?\)/g, (_, label, url, title) => {
|
||||||
|
const href = this.normalizeUrl(url);
|
||||||
|
const rel = /^https?:\/\//i.test(href) ? ' rel="noopener noreferrer" target="_blank"' : '';
|
||||||
|
const titleAttr = title ? ` title="${this.escapeHtml(title)}"` : '';
|
||||||
|
return `<a class="md-link" href="${href}"${titleAttr}${rel}>${label}</a>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
html = html.replace(/\*\*\*([^*]+)\*\*\*/g, '<strong><em>$1</em></strong>');
|
||||||
|
html = html.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
||||||
|
html = html.replace(/\*([^*]+)\*/g, '<em>$1</em>');
|
||||||
|
|
||||||
|
html = html.replace(/__INLINE_CODE_(\d+)__/g, (_, idx) => {
|
||||||
|
const code = codeSpans[Number(idx)] || '';
|
||||||
|
return `<code class="md-code">${this.escapeHtml(code)}</code>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} alt
|
||||||
|
* @param {string} rawUrl
|
||||||
|
* @param {string} title
|
||||||
|
* @returns {{src: string, alt: string, caption: string, attributes: string}}
|
||||||
|
*/
|
||||||
|
static parseImageMeta(alt, rawUrl, title) {
|
||||||
|
const scale = this.parseScale(alt);
|
||||||
|
const src = this.normalizeUrl(rawUrl);
|
||||||
|
const safeAlt = scale !== null ? '' : String(alt || '');
|
||||||
|
const caption = title ? String(title) : (scale !== null ? '' : String(alt || ''));
|
||||||
|
const widthAttr = scale !== null ? ` style="width:${Math.round(scale * 10000) / 100}%;"` : '';
|
||||||
|
|
||||||
|
return {
|
||||||
|
src,
|
||||||
|
alt: safeAlt,
|
||||||
|
caption,
|
||||||
|
attributes: widthAttr
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} value
|
||||||
|
* @returns {number | null}
|
||||||
|
*/
|
||||||
|
static parseScale(value) {
|
||||||
|
const normalized = String(value || '').trim();
|
||||||
|
if (!/^\d*\.?\d+$/.test(normalized)) return null;
|
||||||
|
const parsed = Number(normalized);
|
||||||
|
if (!Number.isFinite(parsed) || parsed <= 0 || parsed > 1) return null;
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} url
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
static normalizeUrl(url) {
|
||||||
|
const value = String(url || '').trim();
|
||||||
|
if (!value) return '#';
|
||||||
|
if (/^(https?:|mailto:|tel:|#|\/)/i.test(value)) return value;
|
||||||
|
return `/${value.replace(/^\.\//, '').replace(/\\/g, '/')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} value
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
static escapeHtml(value) {
|
||||||
|
return String(value)
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
StaticBlogGenerator
|
||||||
|
};
|
||||||
53
website/blog.html
Normal file
53
website/blog.html
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Blog</title>
|
||||||
|
<meta name="description" content="Lit.ruv.wtf blog posts.">
|
||||||
|
<link rel="canonical" href="https://lit.ruv.wtf/blog.html">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/logos/32px.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="64x64" href="/logos/64px.png">
|
||||||
|
<link rel="stylesheet" href="/styles/main.css">
|
||||||
|
</head>
|
||||||
|
<body class="blog-page">
|
||||||
|
<nav class="quick-links" aria-label="Primary">
|
||||||
|
<a href="/blog.html" class="quick-link">blog</a>
|
||||||
|
<a href="/docs/" class="quick-link">docs</a>
|
||||||
|
<a href="https://github.com/litruv" target="_blank" rel="noopener" class="quick-link">github</a>
|
||||||
|
<a href="https://bsky.app/profile/lit.mates.dev" target="_blank" rel="noopener" class="quick-link">bluesky</a>
|
||||||
|
<a href="/materials" class="quick-link">materials</a>
|
||||||
|
</nav>
|
||||||
|
<main class="blog-shell">
|
||||||
|
<a class="blog-logo-link" href="/" aria-label="Back to main site">
|
||||||
|
<img src="/logos/LogoFull.svg" alt="lit.ruv.wtf" class="blog-logo" />
|
||||||
|
</a>
|
||||||
|
<section class="blog-card" aria-labelledby="blog-index-title">
|
||||||
|
<header class="blog-post-header">
|
||||||
|
<h1 id="blog-index-title" class="blog-post-title">Blog</h1>
|
||||||
|
<p class="blog-index-subtitle">Direct links to every post.</p>
|
||||||
|
</header>
|
||||||
|
<div class="blog-index-list"><article class="blog-index-item">
|
||||||
|
<h2 class="blog-index-item-title"><a href="/blog/markdown-test/">Markdown Formatting Test</a></h2>
|
||||||
|
<p class="blog-index-item-meta">September 11, 2026 <span aria-hidden="true">-</span> Test Author</p>
|
||||||
|
<div class="blog-index-tags"><span class="blog-tag">test</span><span class="blog-tag">formatting</span><span class="blog-tag">markdown</span></div>
|
||||||
|
</article>
|
||||||
|
<article class="blog-index-item">
|
||||||
|
<h2 class="blog-index-item-title"><a href="/blog/testddd/">test d d d d</a></h2>
|
||||||
|
<p class="blog-index-item-meta">May 10, 2026 <span aria-hidden="true">-</span> it me</p>
|
||||||
|
|
||||||
|
</article>
|
||||||
|
<article class="blog-index-item">
|
||||||
|
<h2 class="blog-index-item-title"><a href="/blog/welcome/">Welcome to My Blog</a></h2>
|
||||||
|
<p class="blog-index-item-meta">May 10, 2026 <span aria-hidden="true">-</span> Max Litruv Boonzaayer</p>
|
||||||
|
<div class="blog-index-tags"><span class="blog-tag">welcome</span><span class="blog-tag">meta</span><span class="blog-tag">introduction</span></div>
|
||||||
|
</article>
|
||||||
|
<article class="blog-index-item">
|
||||||
|
<h2 class="blog-index-item-title"><a href="/blog/node-graphs/">Building Interactive Node Graphs</a></h2>
|
||||||
|
<p class="blog-index-item-meta">April 15, 2026 <span aria-hidden="true">-</span> Max Litruv Boonzaayer</p>
|
||||||
|
<div class="blog-index-tags"><span class="blog-tag">javascript</span><span class="blog-tag">graph-systems</span><span class="blog-tag">tutorial</span></div>
|
||||||
|
</article></div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
110
website/blog/markdown-test/index.html
Normal file
110
website/blog/markdown-test/index.html
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Markdown Formatting Test - Blog</title>
|
||||||
|
<meta name="description" content="Lit.ruv.wtf blog posts.">
|
||||||
|
<link rel="canonical" href="https://lit.ruv.wtf/blog/markdown-test/">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/logos/32px.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="64x64" href="/logos/64px.png">
|
||||||
|
<link rel="stylesheet" href="/styles/main.css">
|
||||||
|
</head>
|
||||||
|
<body class="blog-page">
|
||||||
|
<nav class="quick-links" aria-label="Primary">
|
||||||
|
<a href="/blog.html" class="quick-link">blog</a>
|
||||||
|
<a href="/docs/" class="quick-link">docs</a>
|
||||||
|
<a href="https://github.com/litruv" target="_blank" rel="noopener" class="quick-link">github</a>
|
||||||
|
<a href="https://bsky.app/profile/lit.mates.dev" target="_blank" rel="noopener" class="quick-link">bluesky</a>
|
||||||
|
<a href="/materials" class="quick-link">materials</a>
|
||||||
|
</nav>
|
||||||
|
<main class="blog-shell">
|
||||||
|
<a class="blog-logo-link" href="/" aria-label="Back to main site">
|
||||||
|
<img src="/logos/LogoFull.svg" alt="lit.ruv.wtf" class="blog-logo" />
|
||||||
|
</a>
|
||||||
|
<article class="blog-card" aria-labelledby="blog-post-title">
|
||||||
|
<header class="blog-post-header">
|
||||||
|
<h1 id="blog-post-title" class="blog-post-title">Markdown Formatting Test</h1>
|
||||||
|
<p class="blog-post-meta"><span>September 11, 2026</span><span aria-hidden="true">-</span><span>Test Author</span></p>
|
||||||
|
<div class="blog-post-tags"><span class="blog-tag">test</span><span class="blog-tag">formatting</span><span class="blog-tag">markdown</span></div>
|
||||||
|
</header>
|
||||||
|
<section class="blog-post-content"><h1 class="md-h1">Heading 1 test 123</h1>
|
||||||
|
<h2 class="md-h2">Heading 2</h2>
|
||||||
|
<h3 class="md-h3">Heading 3</h3>
|
||||||
|
<h4 class="md-h4">Heading 4</h4>
|
||||||
|
<h5 class="md-h5">Heading 5</h5>
|
||||||
|
<h6 class="md-h6">Heading 6</h6>
|
||||||
|
<h2 class="md-h2">Text Formatting</h2>
|
||||||
|
<p class="md-p">This is <strong>bold text</strong> and this is <em>italic text</em> and this is <strong><em>bold italic text</em></strong>.</p>
|
||||||
|
<p class="md-p">Here's some <code class="md-code">inline code</code> in a sentence.</p>
|
||||||
|
<h2 class="md-h2">Links</h2>
|
||||||
|
<p class="md-p">Here's a <a class="md-link" href="https://example.com" rel="noopener noreferrer" target="_blank">regular link</a> and here's a <a class="md-link" href="https://youtube.com/watch?v=test" rel="noopener noreferrer" target="_blank">YouTube link</a>.</p>
|
||||||
|
<h2 class="md-h2">Lists</h2>
|
||||||
|
<p class="md-p">Unordered list:</p>
|
||||||
|
<ul class="md-ul"><li class="md-li">First item</li></ul>
|
||||||
|
<ul class="md-ul"><li class="md-li">Second item</li></ul>
|
||||||
|
<ul class="md-ul"><li class="md-li">Third item</li></ul>
|
||||||
|
<p class="md-p">Ordered list:</p>
|
||||||
|
<ol class="md-ol"><li class="md-li">First step</li><li class="md-li">Second step</li><li class="md-li">Third step</li></ol>
|
||||||
|
<h2 class="md-h2">Code Blocks</h2>
|
||||||
|
<p class="md-p">Standard JavaScript:</p>
|
||||||
|
<pre class="md-pre"><code class=" language-javascript">const hello = "world";
|
||||||
|
console.log(hello);
|
||||||
|
|
||||||
|
function test() {
|
||||||
|
return true;
|
||||||
|
}</code></pre>
|
||||||
|
<p class="md-p">Python with syntax highlighting:</p>
|
||||||
|
<pre class="md-pre"><code class=" language-python">def hello_world():
|
||||||
|
print("Hello, World!")
|
||||||
|
return True</code></pre>
|
||||||
|
<p class="md-p">Code with max height (200px):</p>
|
||||||
|
<pre class="md-pre"><code class=" language-javascript">// This is a long code block that will scroll
|
||||||
|
const data = [1, 2, 3, 4, 5];
|
||||||
|
|
||||||
|
function processData(arr) {
|
||||||
|
return arr.map(x => x * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(processData(data));
|
||||||
|
|
||||||
|
// Adding more lines to demonstrate scrolling
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
console.log(`Iteration ${i}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Even more content
|
||||||
|
const obj = {
|
||||||
|
name: "Test",
|
||||||
|
value: 42,
|
||||||
|
nested: {
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
};</code></pre>
|
||||||
|
<h2 class="md-h2">Blockquotes</h2>
|
||||||
|
<blockquote class="md-blockquote">This is a blockquote.<br />It can span multiple lines.<br />And continues here.</blockquote>
|
||||||
|
<h2 class="md-h2">Horizontal Rule</h2>
|
||||||
|
<hr class="md-hr" />
|
||||||
|
<h2 class="md-h2">Images</h2>
|
||||||
|
<figure class="md-figure"><img class="md-img" src="/data/blog/media/test.gif" alt="" style="width:100%;" /><figcaption class="md-figcaption">Caption Test</figcaption></figure>
|
||||||
|
<figure class="md-figure"><img class="md-img" src="/data/blog/media/paste-1778405550240-ddf8h.png" alt="" style="width:43%;" /><figcaption class="md-figcaption">Gobby</figcaption></figure>
|
||||||
|
<h2 class="md-h2">Mixed Content</h2>
|
||||||
|
<p class="md-p"><br /></p>
|
||||||
|
<p class="md-p">You can mix <strong>bold</strong>, <em>italic</em>, and <code class="md-code">code</code> in the same paragraph. Here's a <a class="md-link" href="https://example.com" rel="noopener noreferrer" target="_blank">link with</a> <strong><a class="md-link" href="https://example.com" rel="noopener noreferrer" target="_blank">bold</a></strong> <a class="md-link" href="https://example.com" rel="noopener noreferrer" target="_blank">text</a> too.</p>
|
||||||
|
<h3 class="md-h3">Nested Lists</h3>
|
||||||
|
<ul class="md-ul"><li class="md-li">Top level itemsss</li></ul>
|
||||||
|
<ul class="md-ul"><li class="md-li">Another top level</li></ul>
|
||||||
|
<ul class="md-ul"><li class="md-li">Nested item (if supported)</li></ul>
|
||||||
|
<ul class="md-ul"><li class="md-li">Another nested</li></ul>
|
||||||
|
<ol class="md-ol"><li class="md-li">Numbered item</li><li class="md-li">Another numbered</li></ol>
|
||||||
|
<ul class="md-ul"><li class="md-li">Mixed with bullets</li></ul>
|
||||||
|
<ul class="md-ul"><li class="md-li">More bullets</li></ul>
|
||||||
|
<h2 class="md-h2">Special Characters</h2>
|
||||||
|
<p class="md-p">Testing special chars: < > & " '</p>
|
||||||
|
<h2 class="md-h2">Long Paragraph</h2>
|
||||||
|
<p class="md-p">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
|
||||||
|
<p class="md-p">Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p></section>
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
54
website/blog/node-graphs/index.html
Normal file
54
website/blog/node-graphs/index.html
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Building Interactive Node Graphs - Blog</title>
|
||||||
|
<meta name="description" content="Lit.ruv.wtf blog posts.">
|
||||||
|
<link rel="canonical" href="https://lit.ruv.wtf/blog/node-graphs/">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/logos/32px.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="64x64" href="/logos/64px.png">
|
||||||
|
<link rel="stylesheet" href="/styles/main.css">
|
||||||
|
</head>
|
||||||
|
<body class="blog-page">
|
||||||
|
<nav class="quick-links" aria-label="Primary">
|
||||||
|
<a href="/blog.html" class="quick-link">blog</a>
|
||||||
|
<a href="/docs/" class="quick-link">docs</a>
|
||||||
|
<a href="https://github.com/litruv" target="_blank" rel="noopener" class="quick-link">github</a>
|
||||||
|
<a href="https://bsky.app/profile/lit.mates.dev" target="_blank" rel="noopener" class="quick-link">bluesky</a>
|
||||||
|
<a href="/materials" class="quick-link">materials</a>
|
||||||
|
</nav>
|
||||||
|
<main class="blog-shell">
|
||||||
|
<a class="blog-logo-link" href="/" aria-label="Back to main site">
|
||||||
|
<img src="/logos/LogoFull.svg" alt="lit.ruv.wtf" class="blog-logo" />
|
||||||
|
</a>
|
||||||
|
<article class="blog-card" aria-labelledby="blog-post-title">
|
||||||
|
<header class="blog-post-header">
|
||||||
|
<h1 id="blog-post-title" class="blog-post-title">Building Interactive Node Graphs</h1>
|
||||||
|
<p class="blog-post-meta"><span>April 15, 2026</span><span aria-hidden="true">-</span><span>Max Litruv Boonzaayer</span></p>
|
||||||
|
<div class="blog-post-tags"><span class="blog-tag">javascript</span><span class="blog-tag">graph-systems</span><span class="blog-tag">tutorial</span></div>
|
||||||
|
</header>
|
||||||
|
<section class="blog-post-content"><h1 class="md-h1">Building Interactive Node Graphs</h1>
|
||||||
|
<p class="md-p">In this post, I'll share some insights on building interactive node-based graph systems for the web.</p>
|
||||||
|
<h2 class="md-h2">Why Node Graphs?</h2>
|
||||||
|
<p class="md-p">Node graphs are powerful visual programming tools that make complex logic easier to understand and manipulate:</p>
|
||||||
|
<ul class="md-ul"><li class="md-li"><strong>Visual Clarity</strong>: See the flow of data and execution at a glance</li><li class="md-li"><strong>Modularity</strong>: Each node is a self-contained unit</li><li class="md-li"><strong>Flexibility</strong>: Easy to add new node types and behaviors</li></ul>
|
||||||
|
<h2 class="md-h2">The Architecture</h2>
|
||||||
|
<p class="md-p">The system is built on several key classes:</p>
|
||||||
|
<p class="md-p">### NodeBase<br />The abstract base class that all nodes inherit from. It defines the blueprint interface:</p>
|
||||||
|
<pre class="md-pre"><code class=" language-javascript">class NodeBase {
|
||||||
|
static NodeType = "";
|
||||||
|
static BlueprintPure_GetDefaultPins() { }
|
||||||
|
BlueprintNativeEvent_OnRender(article, graphNode, renderCtx) { }
|
||||||
|
async BlueprintCallable_Execute(ctx) { }
|
||||||
|
}</code></pre>
|
||||||
|
<p class="md-p">### Node Registry<br />Automatically registers node types and creates instances based on type strings.</p>
|
||||||
|
<p class="md-p">### Execution Context<br />Handles the graph traversal and execution flow when nodes are triggered.</p>
|
||||||
|
<h2 class="md-h2">Creating Custom Nodes</h2>
|
||||||
|
<p class="md-p">To create a new node type:</p>
|
||||||
|
<ol class="md-ol"><li class="md-li">Extend <code class="md-code">NodeBase</code></li><li class="md-li">Set a unique <code class="md-code">NodeType</code></li><li class="md-li">Define pins with <code class="md-code">BlueprintPure_GetDefaultPins()</code></li><li class="md-li">Implement rendering in <code class="md-code">BlueprintNativeEvent_OnRender()</code></li><li class="md-li">Implement logic in <code class="md-code">BlueprintCallable_Execute()</code></li></ol>
|
||||||
|
<p class="md-p">That's the blueprint system in a nutshell!</p></section>
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
38
website/blog/testddd/index.html
Normal file
38
website/blog/testddd/index.html
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>test d d d d - Blog</title>
|
||||||
|
<meta name="description" content="Lit.ruv.wtf blog posts.">
|
||||||
|
<link rel="canonical" href="https://lit.ruv.wtf/blog/testddd/">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/logos/32px.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="64x64" href="/logos/64px.png">
|
||||||
|
<link rel="stylesheet" href="/styles/main.css">
|
||||||
|
</head>
|
||||||
|
<body class="blog-page">
|
||||||
|
<nav class="quick-links" aria-label="Primary">
|
||||||
|
<a href="/blog.html" class="quick-link">blog</a>
|
||||||
|
<a href="/docs/" class="quick-link">docs</a>
|
||||||
|
<a href="https://github.com/litruv" target="_blank" rel="noopener" class="quick-link">github</a>
|
||||||
|
<a href="https://bsky.app/profile/lit.mates.dev" target="_blank" rel="noopener" class="quick-link">bluesky</a>
|
||||||
|
<a href="/materials" class="quick-link">materials</a>
|
||||||
|
</nav>
|
||||||
|
<main class="blog-shell">
|
||||||
|
<a class="blog-logo-link" href="/" aria-label="Back to main site">
|
||||||
|
<img src="/logos/LogoFull.svg" alt="lit.ruv.wtf" class="blog-logo" />
|
||||||
|
</a>
|
||||||
|
<article class="blog-card" aria-labelledby="blog-post-title">
|
||||||
|
<header class="blog-post-header">
|
||||||
|
<h1 id="blog-post-title" class="blog-post-title">test d d d d</h1>
|
||||||
|
<p class="blog-post-meta"><span>May 10, 2026</span><span aria-hidden="true">-</span><span>it me</span></p>
|
||||||
|
|
||||||
|
</header>
|
||||||
|
<section class="blog-post-content"><p class="md-p">asdffdsaasdffdsa</p>
|
||||||
|
<p class="md-p">| <br /> | <br /> | <br /> | <br /> |<br />| :----- | :----- | :----- | -----: |<br />| <br /> | <br /> | <br /> | <br /> |<br />| <br /> | <br /> | <br /> | <br /> |</p>
|
||||||
|
<p class="md-p"><br /></p>
|
||||||
|
<p class="md-p">this is a test post, should't publish i guess, but who's reading lmao</p></section>
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
54
website/blog/welcome/index.html
Normal file
54
website/blog/welcome/index.html
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Welcome to My Blog - Blog</title>
|
||||||
|
<meta name="description" content="Lit.ruv.wtf blog posts.">
|
||||||
|
<link rel="canonical" href="https://lit.ruv.wtf/blog/welcome/">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/logos/32px.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="64x64" href="/logos/64px.png">
|
||||||
|
<link rel="stylesheet" href="/styles/main.css">
|
||||||
|
</head>
|
||||||
|
<body class="blog-page">
|
||||||
|
<nav class="quick-links" aria-label="Primary">
|
||||||
|
<a href="/blog.html" class="quick-link">blog</a>
|
||||||
|
<a href="/docs/" class="quick-link">docs</a>
|
||||||
|
<a href="https://github.com/litruv" target="_blank" rel="noopener" class="quick-link">github</a>
|
||||||
|
<a href="https://bsky.app/profile/lit.mates.dev" target="_blank" rel="noopener" class="quick-link">bluesky</a>
|
||||||
|
<a href="/materials" class="quick-link">materials</a>
|
||||||
|
</nav>
|
||||||
|
<main class="blog-shell">
|
||||||
|
<a class="blog-logo-link" href="/" aria-label="Back to main site">
|
||||||
|
<img src="/logos/LogoFull.svg" alt="lit.ruv.wtf" class="blog-logo" />
|
||||||
|
</a>
|
||||||
|
<article class="blog-card" aria-labelledby="blog-post-title">
|
||||||
|
<header class="blog-post-header">
|
||||||
|
<h1 id="blog-post-title" class="blog-post-title">Welcome to My Blog</h1>
|
||||||
|
<p class="blog-post-meta"><span>May 10, 2026</span><span aria-hidden="true">-</span><span>Max Litruv Boonzaayer</span></p>
|
||||||
|
<div class="blog-post-tags"><span class="blog-tag">welcome</span><span class="blog-tag">meta</span><span class="blog-tag">introduction</span></div>
|
||||||
|
</header>
|
||||||
|
<section class="blog-post-content"><h1 class="md-h1">Welcome to My Blog</h1>
|
||||||
|
<p class="md-p">This is my first blog post! I'm excited to share my thoughts and experiences with you.</p>
|
||||||
|
<h2 class="md-h2">What This Blog Is About</h2>
|
||||||
|
<p class="md-p">This blog is built using a <strong>custom node-based graph system</strong> where each blog post is represented as a node in an interactive visual graph. Pretty cool, right?</p>
|
||||||
|
<h3 class="md-h3">Features</h3>
|
||||||
|
<ul class="md-ul"><li class="md-li">📝 Markdown support with YAML front matter</li></ul>
|
||||||
|
<ul class="md-ul"><li class="md-li">📅 Date-based organization</li></ul>
|
||||||
|
<ul class="md-ul"><li class="md-li">🏷️ Tag system for categorization</li></ul>
|
||||||
|
<ul class="md-ul"><li class="md-li">🎨 Interactive node-based visualization</li></ul>
|
||||||
|
<ul class="md-ul"><li class="md-li">📁 Media support in <code class="md-code">data/blog/media/</code></li></ul>
|
||||||
|
<h2 class="md-h2">Technical Details</h2>
|
||||||
|
<p class="md-p">The build system automatically:</p>
|
||||||
|
<ol class="md-ol"><li class="md-li">Scans the <code class="md-code">data/blog/</code> directory for <code class="md-code">.md</code> files</li><li class="md-li">Parses YAML front matter for metadata</li><li class="md-li">Generates a <code class="md-code">blogs.json</code> file</li><li class="md-li">Makes posts available to the BlogPostNode</li></ol>
|
||||||
|
<p class="md-p">You can reference images like this:<br /><img class="md-img md-img--inline" src="/data/blog/media/example.png" alt="Example" /></p>
|
||||||
|
<h2 class="md-h2">Code Examples</h2>
|
||||||
|
<p class="md-p">Here's some example code:</p>
|
||||||
|
<pre class="md-pre"><code class=" language-javascript">const greeting = "Hello, World!";
|
||||||
|
console.log(greeting);</code></pre>
|
||||||
|
<h2 class="md-h2">Conclusion</h2>
|
||||||
|
<p class="md-p">Stay tuned for more posts! This is just the beginning of an exciting journey.</p></section>
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -2,26 +2,34 @@
|
|||||||
{
|
{
|
||||||
"slug": "markdown-test",
|
"slug": "markdown-test",
|
||||||
"title": "Markdown Formatting Test",
|
"title": "Markdown Formatting Test",
|
||||||
"date": "2026-04-20",
|
"date": "2026-09-11",
|
||||||
"author": "Test Author",
|
"author": "Test Author",
|
||||||
"tags": [
|
"tags": [
|
||||||
"test",
|
"test",
|
||||||
"formatting",
|
"formatting",
|
||||||
"markdown"
|
"markdown"
|
||||||
],
|
],
|
||||||
"content": "\r\n# Heading 1\r\n\r\n## Heading 2\r\n\r\n### Heading 3\r\n\r\n#### Heading 4\r\n\r\n##### Heading 5\r\n\r\n###### Heading 6\r\n\r\n## Text Formatting\r\n\r\nThis is **bold text** and this is *italic text* and this is ***bold italic text***.\r\n\r\nHere's some `inline code` in a sentence.\r\n\r\n## Links\r\n\r\nHere's a [regular link](https://example.com) and here's a [YouTube link](https://youtube.com/watch?v=test).\r\n\r\n## Lists\r\n\r\nUnordered list:\r\n- First item\r\n- Second item\r\n- Third item\r\n\r\nOrdered list:\r\n1. First step\r\n2. Second step\r\n3. Third step\r\n\r\n## Code Blocks\r\n\r\nStandard JavaScript:\r\n```javascript\r\nconst hello = \"world\";\r\nconsole.log(hello);\r\n\r\nfunction test() {\r\n return true;\r\n}\r\n```\r\n\r\nPython with syntax highlighting:\r\n```python\r\ndef hello_world():\r\n print(\"Hello, World!\")\r\n return True\r\n```\r\n\r\nCode with max height (200px):\r\n```javascript {maxHeight: 200}\r\n// This is a long code block that will scroll\r\nconst data = [1, 2, 3, 4, 5];\r\n\r\nfunction processData(arr) {\r\n return arr.map(x => x * 2);\r\n}\r\n\r\nconsole.log(processData(data));\r\n\r\n// Adding more lines to demonstrate scrolling\r\nfor (let i = 0; i < 10; i++) {\r\n console.log(`Iteration ${i}`);\r\n}\r\n\r\n// Even more content\r\nconst obj = {\r\n name: \"Test\",\r\n value: 42,\r\n nested: {\r\n deep: true\r\n }\r\n};\r\n```\r\n\r\n## Blockquotes\r\n\r\n> This is a blockquote.\r\n> It can span multiple lines.\r\n> And continues here.\r\n\r\n## Horizontal Rule\r\n\r\n---\r\n\r\n## Images\r\n\r\n\r\n\r\n## Mixed Content\r\n\r\nYou can mix **bold**, *italic*, and `code` in the same paragraph. Here's a [link with **bold** text](https://example.com) too.\r\n\r\n### Nested Lists\r\n\r\n- Top level item\r\n- Another top level\r\n - Nested item (if supported)\r\n - Another nested\r\n\r\n1. Numbered item\r\n2. Another numbered\r\n - Mixed with bullets\r\n - More bullets\r\n\r\n## Special Characters\r\n\r\nTesting special chars: < > & \" ' \r\n\r\n## Long Paragraph\r\n\r\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\r\n\r\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\r\n"
|
"content": "# Heading 1 test 123\n\n## Heading 2\n\n### Heading 3\n\n#### Heading 4\n\n##### Heading 5\n\n###### Heading 6\n\n## Text Formatting\n\nThis is **bold text** and this is *italic text* and this is ***bold italic text***.\n\nHere's some `inline code` in a sentence.\n\n## Links\n\nHere's a [regular link](https://example.com) and here's a [YouTube link](https://youtube.com/watch?v=test).\n\n## Lists\n\nUnordered list:\n\n* First item\n\n* Second item\n\n* Third item\n\nOrdered list:\n\n1. First step\n2. Second step\n3. Third step\n\n## Code Blocks\n\nStandard JavaScript:\n\n```javascript\nconst hello = \"world\";\nconsole.log(hello);\n\nfunction test() {\n return true;\n}\n```\n\nPython with syntax highlighting:\n\n```python\ndef hello_world():\n print(\"Hello, World!\")\n return True\n```\n\nCode with max height (200px):\n\n```javascript\n// This is a long code block that will scroll\nconst data = [1, 2, 3, 4, 5];\n\nfunction processData(arr) {\n return arr.map(x => x * 2);\n}\n\nconsole.log(processData(data));\n\n// Adding more lines to demonstrate scrolling\nfor (let i = 0; i < 10; i++) {\n console.log(`Iteration ${i}`);\n}\n\n// Even more content\nconst obj = {\n name: \"Test\",\n value: 42,\n nested: {\n deep: true\n }\n};\n```\n\n## Blockquotes\n\n> This is a blockquote.\n> It can span multiple lines.\n> And continues here.\n\n## Horizontal Rule\n\n***\n\n## Images\n\n\n\n\n\n## Mixed Content\n\n<br />\n\nYou can mix **bold**, *italic*, and `code` in the same paragraph. Here's a [link with](https://example.com) **[bold](https://example.com)** [text](https://example.com) too.\n\n### Nested Lists\n\n* Top level itemsss\n\n* Another top level\n\n * Nested item (if supported)\n\n * Another nested\n\n1. Numbered item\n2. Another numbered\n\n * Mixed with bullets\n\n * More bullets\n\n## Special Characters\n\nTesting special chars: < > & \" '\n\n## Long Paragraph\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slug": "testddd",
|
||||||
|
"title": "test d d d d",
|
||||||
|
"date": "2026-05-10",
|
||||||
|
"author": "it me",
|
||||||
|
"tags": [],
|
||||||
|
"content": "asdffdsaasdffdsa\n\n| <br /> | <br /> | <br /> | <br /> |\n| :----- | :----- | :----- | -----: |\n| <br /> | <br /> | <br /> | <br /> |\n| <br /> | <br /> | <br /> | <br /> |\n\n<br />\n\nthis is a test post, should't publish i guess, but who's reading lmao\n"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"slug": "welcome",
|
"slug": "welcome",
|
||||||
"title": "Welcome to My Blog",
|
"title": "Welcome to My Blog",
|
||||||
"date": "2026-04-19",
|
"date": "2026-05-10",
|
||||||
"author": "Max Litruv Boonzaayer",
|
"author": "Max Litruv Boonzaayer",
|
||||||
"tags": [
|
"tags": [
|
||||||
"welcome",
|
"welcome",
|
||||||
"meta",
|
"meta",
|
||||||
"introduction"
|
"introduction"
|
||||||
],
|
],
|
||||||
"content": "\r\n# Welcome to My Blog\r\n\r\nThis is my first blog post! I'm excited to share my thoughts and experiences with you.\r\n\r\n## What This Blog Is About\r\n\r\nThis blog is built using a **custom node-based graph system** where each blog post is represented as a node in an interactive visual graph. Pretty cool, right?\r\n\r\n### Features\r\n\r\n- 📝 Markdown support with YAML front matter\r\n- 📅 Date-based organization\r\n- 🏷️ Tag system for categorization\r\n- 🎨 Interactive node-based visualization\r\n- 📁 Media support in `data/blog/media/`\r\n\r\n## Technical Details\r\n\r\nThe build system automatically:\r\n1. Scans the `data/blog/` directory for `.md` files\r\n2. Parses YAML front matter for metadata\r\n3. Generates a `blogs.json` file\r\n4. Makes posts available to the BlogPostNode\r\n\r\nYou can reference images like this:\r\n\r\n\r\n## Code Examples\r\n\r\nHere's some example code:\r\n\r\n```javascript\r\nconst greeting = \"Hello, World!\";\r\nconsole.log(greeting);\r\n```\r\n\r\n## Conclusion\r\n\r\nStay tuned for more posts! This is just the beginning of an exciting journey.\r\n"
|
"content": "# Welcome to My Blog\n\nThis is my first blog post! I'm excited to share my thoughts and experiences with you.\n\n## What This Blog Is About\n\nThis blog is built using a **custom node-based graph system** where each blog post is represented as a node in an interactive visual graph. Pretty cool, right?\n\n### Features\n\n* 📝 Markdown support with YAML front matter\n\n* 📅 Date-based organization\n\n* 🏷️ Tag system for categorization\n\n* 🎨 Interactive node-based visualization\n\n* 📁 Media support in `data/blog/media/`\n\n## Technical Details\n\nThe build system automatically:\n\n1. Scans the `data/blog/` directory for `.md` files\n2. Parses YAML front matter for metadata\n3. Generates a `blogs.json` file\n4. Makes posts available to the BlogPostNode\n\nYou can reference images like this:\n\n\n## Code Examples\n\nHere's some example code:\n\n```javascript\nconst greeting = \"Hello, World!\";\r\nconsole.log(greeting);\n```\n\n## Conclusion\n\nStay tuned for more posts! This is just the beginning of an exciting journey.\n"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"slug": "node-graphs",
|
"slug": "node-graphs",
|
||||||
|
|||||||
@@ -644,13 +644,40 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "blog_testddd",
|
||||||
|
"type": "blog_post",
|
||||||
|
"title": "test d d d d",
|
||||||
|
"blogSlug": "testddd",
|
||||||
|
"position": {
|
||||||
|
"x": 2200,
|
||||||
|
"y": 900
|
||||||
|
},
|
||||||
|
"width": 600,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"id": "exec_in",
|
||||||
|
"name": "Previous",
|
||||||
|
"direction": "input",
|
||||||
|
"kind": "exec"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"id": "exec_out",
|
||||||
|
"name": "Next",
|
||||||
|
"direction": "output",
|
||||||
|
"kind": "exec"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "blog_welcome",
|
"id": "blog_welcome",
|
||||||
"type": "blog_post",
|
"type": "blog_post",
|
||||||
"title": "Welcome to My Blog",
|
"title": "Welcome to My Blog",
|
||||||
"blogSlug": "welcome",
|
"blogSlug": "welcome",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 2200,
|
"x": 2900,
|
||||||
"y": 900
|
"y": 900
|
||||||
},
|
},
|
||||||
"width": 600,
|
"width": 600,
|
||||||
@@ -677,7 +704,7 @@
|
|||||||
"title": "Building Interactive Node Graphs",
|
"title": "Building Interactive Node Graphs",
|
||||||
"blogSlug": "node-graphs",
|
"blogSlug": "node-graphs",
|
||||||
"position": {
|
"position": {
|
||||||
"x": 2900,
|
"x": 3600,
|
||||||
"y": 900
|
"y": 900
|
||||||
},
|
},
|
||||||
"width": 600,
|
"width": 600,
|
||||||
@@ -928,13 +955,25 @@
|
|||||||
"pinId": "exec_out"
|
"pinId": "exec_out"
|
||||||
},
|
},
|
||||||
"to": {
|
"to": {
|
||||||
"nodeId": "blog_welcome",
|
"nodeId": "blog_testddd",
|
||||||
"pinId": "exec_in"
|
"pinId": "exec_in"
|
||||||
},
|
},
|
||||||
"kind": "exec"
|
"kind": "exec"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "blog_connection_1",
|
"id": "blog_connection_1",
|
||||||
|
"from": {
|
||||||
|
"nodeId": "blog_testddd",
|
||||||
|
"pinId": "exec_out"
|
||||||
|
},
|
||||||
|
"to": {
|
||||||
|
"nodeId": "blog_welcome",
|
||||||
|
"pinId": "exec_in"
|
||||||
|
},
|
||||||
|
"kind": "exec"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "blog_connection_2",
|
||||||
"from": {
|
"from": {
|
||||||
"nodeId": "blog_welcome",
|
"nodeId": "blog_welcome",
|
||||||
"pinId": "exec_out"
|
"pinId": "exec_out"
|
||||||
|
|||||||
@@ -50,6 +50,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<div id="workspaceCanvas" class="workspace-canvas">
|
<div id="workspaceCanvas" class="workspace-canvas">
|
||||||
<nav class="quick-links">
|
<nav class="quick-links">
|
||||||
|
<a href="/blog.html" class="quick-link">blog</a>
|
||||||
<a href="/docs/" class="quick-link">docs</a>
|
<a href="/docs/" class="quick-link">docs</a>
|
||||||
<a href="https://github.com/litruv" target="_blank" rel="noopener" class="quick-link">github</a>
|
<a href="https://github.com/litruv" target="_blank" rel="noopener" class="quick-link">github</a>
|
||||||
<a href="https://bsky.app/profile/lit.mates.dev" target="_blank" rel="noopener" class="quick-link">bluesky</a>
|
<a href="https://bsky.app/profile/lit.mates.dev" target="_blank" rel="noopener" class="quick-link">bluesky</a>
|
||||||
|
|||||||
@@ -586,6 +586,248 @@ svg.world-image {
|
|||||||
color: rgba(205, 214, 244, 0.9);
|
color: rgba(205, 214, 244, 0.9);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─── Static Blog Pages ─────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
body.blog-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
display: block;
|
||||||
|
background-image:
|
||||||
|
linear-gradient(var(--grid-color-strong) 2px, transparent 2px),
|
||||||
|
linear-gradient(90deg, var(--grid-color-strong) 2px, transparent 2px),
|
||||||
|
linear-gradient(var(--grid-color) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, var(--grid-color) 1px, transparent 1px);
|
||||||
|
background-size:
|
||||||
|
calc(var(--grid-size) * 5) calc(var(--grid-size) * 5),
|
||||||
|
calc(var(--grid-size) * 5) calc(var(--grid-size) * 5),
|
||||||
|
var(--grid-size) var(--grid-size),
|
||||||
|
var(--grid-size) var(--grid-size);
|
||||||
|
background-repeat: repeat;
|
||||||
|
background-position: 0 0, 0 0, 0 0, 0 0;
|
||||||
|
background-color: var(--bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-page .quick-links {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-shell {
|
||||||
|
min-height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 84px 16px 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-logo-link {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
opacity: 0.92;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-logo-link:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-logo {
|
||||||
|
width: min(520px, 76vw);
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-card {
|
||||||
|
width: min(900px, 100%);
|
||||||
|
background: rgba(30, 30, 46, 0.92);
|
||||||
|
border: 1px solid rgba(205, 214, 244, 0.14);
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 20px 70px rgba(0, 0, 0, 0.35);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
padding: 22px 24px 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-header {
|
||||||
|
border-bottom: 1px solid rgba(205, 214, 244, 0.12);
|
||||||
|
padding-bottom: 14px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-title {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--ctp-text);
|
||||||
|
font-size: clamp(1.35rem, 3vw, 2rem);
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-meta,
|
||||||
|
.blog-index-subtitle,
|
||||||
|
.blog-index-item-meta {
|
||||||
|
margin-top: 8px;
|
||||||
|
color: rgba(186, 194, 222, 0.82);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-tags,
|
||||||
|
.blog-index-tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid rgba(137, 180, 250, 0.35);
|
||||||
|
background: rgba(137, 180, 250, 0.12);
|
||||||
|
color: var(--ctp-sky);
|
||||||
|
padding: 3px 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-content {
|
||||||
|
color: var(--ctp-subtext1);
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.75;
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-content .md-h1,
|
||||||
|
.blog-post-content .md-h2,
|
||||||
|
.blog-post-content .md-h3,
|
||||||
|
.blog-post-content .md-h4,
|
||||||
|
.blog-post-content .md-h5,
|
||||||
|
.blog-post-content .md-h6 {
|
||||||
|
color: var(--ctp-text);
|
||||||
|
margin-top: 1.2em;
|
||||||
|
margin-bottom: 0.4em;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-content .md-h1 { font-size: 1.9rem; }
|
||||||
|
.blog-post-content .md-h2 { font-size: 1.55rem; }
|
||||||
|
.blog-post-content .md-h3 { font-size: 1.3rem; }
|
||||||
|
.blog-post-content .md-h4 { font-size: 1.1rem; }
|
||||||
|
|
||||||
|
.blog-post-content .md-p,
|
||||||
|
.blog-post-content .md-ul,
|
||||||
|
.blog-post-content .md-ol,
|
||||||
|
.blog-post-content .md-blockquote,
|
||||||
|
.blog-post-content .md-figure,
|
||||||
|
.blog-post-content .md-pre {
|
||||||
|
margin: 0.8em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-content .md-ul,
|
||||||
|
.blog-post-content .md-ol {
|
||||||
|
margin-left: 1.35em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-content .md-blockquote {
|
||||||
|
border-left: 3px solid rgba(250, 179, 135, 0.6);
|
||||||
|
padding: 0.25em 0.9em;
|
||||||
|
color: rgba(186, 194, 222, 0.86);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-content .md-link,
|
||||||
|
.blog-index-item-title a {
|
||||||
|
color: var(--ctp-sky);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-content .md-link:hover,
|
||||||
|
.blog-index-item-title a:hover {
|
||||||
|
color: var(--ctp-blue);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-content .md-code {
|
||||||
|
font-family: "Cascadia Code", "Fira Code", ui-monospace, monospace;
|
||||||
|
font-size: 0.9em;
|
||||||
|
background: rgba(49, 50, 68, 0.72);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.15em 0.4em;
|
||||||
|
color: var(--ctp-sky);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-content .md-pre {
|
||||||
|
background: rgba(17, 17, 27, 0.7);
|
||||||
|
border: 1px solid rgba(205, 214, 244, 0.12);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0.8em 0.95em;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-content .md-pre code {
|
||||||
|
font-family: "Cascadia Code", "Fira Code", ui-monospace, monospace;
|
||||||
|
color: var(--ctp-sky);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-content .md-img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-content .md-figcaption {
|
||||||
|
margin-top: 0.45em;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--ctp-subtext0);
|
||||||
|
font-size: 0.88rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-index-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-index-item {
|
||||||
|
background: rgba(24, 24, 37, 0.65);
|
||||||
|
border: 1px solid rgba(205, 214, 244, 0.12);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 14px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-index-item-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-empty {
|
||||||
|
color: rgba(186, 194, 222, 0.82);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 820px) {
|
||||||
|
.blog-page .quick-links {
|
||||||
|
position: static;
|
||||||
|
padding: 18px 16px 0;
|
||||||
|
margin-bottom: -54px;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-shell {
|
||||||
|
padding-top: 68px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-card {
|
||||||
|
padding: 18px 16px 22px;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ─── HUD ─────────────────────────────────────────────────────────────────────── */
|
/* ─── HUD ─────────────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
.hud {
|
.hud {
|
||||||
|
|||||||
Reference in New Issue
Block a user