mirror of
https://github.com/kbenestad/mdcms.git
synced 2026-06-18 07:24:31 +00:00
Merge pull request #21 from kbenestad/development
Some checks failed
/ mirror (push) Has been cancelled
Some checks failed
/ mirror (push) Has been cancelled
Merge main into development — sync all theme system changes
This commit is contained in:
commit
8295cbca2c
28 changed files with 2278 additions and 25 deletions
10
CLAUDE.md
10
CLAUDE.md
|
|
@ -8,13 +8,17 @@ Every merge into `main` is a release. Before committing any change to `mdcms.py`
|
|||
|
||||
## Branching convention
|
||||
|
||||
Only two branches exist in this repository: **`main`** and **`development`**. No other branches should be created or left alive.
|
||||
|
||||
- **`main`** is the release branch. Every merge to `main` is a release. Never commit work-in-progress directly to `main`.
|
||||
- **`development`** is the default branch for all development, including all Claude-driven work. Create it from `main` if it doesn't exist. Do not create a new branch per conversation.
|
||||
- **Phased branches** (`claude/<feature>`) are allowed when a large feature needs staged review, but the final merge target is always `main` via `development`.
|
||||
- **`development`** is the default branch for all development, including all Claude-driven work. Always commit to `development` — never create a new branch per conversation or feature.
|
||||
- **Documentation only** (`CLAUDE.md`, `docs/`) — may be pushed directly to `main`.
|
||||
- **If a non-canonical branch is created** (e.g. for a large staged feature), it must be deleted immediately after it is merged. The repo returns to `main` + `development` only.
|
||||
|
||||
In practice: check out `development`, do the work, push to `development`, PR `development` → `main` when ready to release.
|
||||
|
||||
**When a branch isn't visible locally:** always run `git fetch origin <branch-name>` before concluding a branch doesn't exist. Never create a new branch if the user names one — fetch it from the remote first.
|
||||
|
||||
## Unreleased changelog
|
||||
|
||||
`docs/unreleased.md` is a living document that tracks every fix or feature on `development` that has not yet been merged to `main`. Keep it current: whenever a change lands on `development`, add or update an entry in `unreleased.md` in the same commit (or a follow-up commit to `development`). When a batch of changes is merged to `main` and released, clear the entries that were released and leave the file in place for the next round of work.
|
||||
|
|
@ -97,7 +101,7 @@ Single-module Python script. Logical layers in order:
|
|||
5. **Category system** — `identify_variant()` splits `.md` paths into `(base, category_code)`. A suffix is only treated as a category code if it appears in the declared code list.
|
||||
6. **Scanner** (`scan_and_categorize`) — walks a directory, skips drafts, returns records with the first 5000 chars of body for search indexing. Paths are relative to `site_root`.
|
||||
7. **Nav/search generators** — `generate_nav_yml()` emits a fixed-format YAML subset. `generate_search_json()` emits a JSON array. `merge_sections()` preserves existing section metadata on rebuild.
|
||||
8. **Core build** (`run_build`) — orchestrates the full build: version check → config read → scan → merge → write nav.yml and search.json.
|
||||
8. **Core build** (`run_build`) — orchestrates the full build: version check → config read → scan → merge → write nav.yml and search.json → patch `<title>` in `index.html` with `sitename` → generate PWA files if enabled. The `<title>` patch ensures crawlers and link-preview scrapers (WhatsApp, Slack, etc.) see the correct site name in the static HTML before any JavaScript runs.
|
||||
9. **Template download** (`download_template`) — fetches `app/` from GitHub via the Contents API using `urllib` + `certifi` for SSL. Recursively downloads files and directories.
|
||||
10. **CLI commands** (`register`, `delete`, `view`, `build`) — implemented with `click`. Entry point: `main()` → `cli()`.
|
||||
|
||||
|
|
|
|||
428
app/index.html
428
app/index.html
|
|
@ -21,7 +21,7 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>MD-CMS</title>
|
||||
<title></title>
|
||||
<meta name="description" content="">
|
||||
<link rel="icon" href="assets/images/favicon.png">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
|
|
@ -917,6 +917,146 @@ body {
|
|||
}
|
||||
.post-load-more:hover { background: var(--nav-hover-bg); }
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
TAG SYSTEM: TABS
|
||||
═══════════════════════════════════════════ */
|
||||
.mdcms-tabs { margin: 1.25rem 0; }
|
||||
|
||||
/* Underline variant */
|
||||
.mdcms-tabs-underline .mdcms-tabs-strip {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 18px;
|
||||
border-bottom: 1px solid var(--mdcms-strip-border, color-mix(in srgb, var(--font-colour) 12%, transparent));
|
||||
}
|
||||
.mdcms-tabs-underline .mdcms-tab-btn {
|
||||
padding: 8px 2px;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -1px;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
font-weight: 500;
|
||||
color: var(--font-colour-muted);
|
||||
line-height: inherit;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
.mdcms-tabs-underline .mdcms-tab-btn:hover { color: var(--font-colour); }
|
||||
.mdcms-tabs-underline .mdcms-tab-btn[aria-selected="true"] {
|
||||
font-weight: 600;
|
||||
color: var(--font-colour);
|
||||
border-bottom-color: var(--accent);
|
||||
}
|
||||
|
||||
/* Filled variant */
|
||||
.mdcms-tabs-filled .mdcms-tabs-strip { display: flex; flex-wrap: wrap; gap: 4px; }
|
||||
.mdcms-tabs-filled .mdcms-tab-btn {
|
||||
padding: 6px 11px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--mdcms-filled-border, rgba(var(--accent-rgb), 0.30));
|
||||
background: var(--mdcms-filled-bg, rgba(var(--accent-rgb), 0.10));
|
||||
color: var(--mdcms-filled-fg-muted, var(--font-colour-muted));
|
||||
cursor: pointer;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
font-weight: 400;
|
||||
line-height: inherit;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
.mdcms-tabs-filled .mdcms-tab-btn:hover { color: var(--mdcms-filled-fg, var(--font-colour)); }
|
||||
.mdcms-tabs-filled .mdcms-tab-btn[aria-selected="true"] {
|
||||
background: var(--mdcms-bg, var(--bg-main));
|
||||
border-color: rgba(var(--accent-rgb), 0.55);
|
||||
color: var(--accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Shared panel */
|
||||
.mdcms-tabs-panel { padding-top: 1rem; }
|
||||
.mdcms-tabs-panel[hidden] { display: none; }
|
||||
.mdcms-tabs-panel > *:first-child { margin-top: 0; }
|
||||
.mdcms-tabs-panel > *:last-child { margin-bottom: 0; }
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
TAG SYSTEM: ACCORDIONS
|
||||
═══════════════════════════════════════════ */
|
||||
.mdcms-accordion { margin: 1.25rem 0; }
|
||||
.mdcms-accordion-btn {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
text-align: left;
|
||||
line-height: inherit;
|
||||
}
|
||||
.mdcms-accordion-chevron {
|
||||
display: inline-flex;
|
||||
flex-shrink: 0;
|
||||
width: 1.1em;
|
||||
height: 1.1em;
|
||||
transition: transform 0.2s ease;
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
.mdcms-accordion-item[data-open="false"] .mdcms-accordion-chevron { transform: rotate(-90deg); }
|
||||
.mdcms-accordion-body[hidden] { display: none; }
|
||||
.mdcms-accordion-body > *:first-child { margin-top: 0; }
|
||||
.mdcms-accordion-body > *:last-child { margin-bottom: 0; }
|
||||
|
||||
/* Underline variant */
|
||||
.mdcms-accordion-underline .mdcms-accordion-item { margin-bottom: 6px; }
|
||||
.mdcms-accordion-underline .mdcms-accordion-btn {
|
||||
padding: 8px 2px 9px;
|
||||
border-bottom: 2px solid var(--mdcms-bar, var(--accent));
|
||||
font-weight: 600;
|
||||
font-size: 0.75rem;
|
||||
color: var(--font-colour);
|
||||
}
|
||||
.mdcms-accordion-underline .mdcms-accordion-chevron { color: var(--mdcms-bar, var(--accent)); }
|
||||
.mdcms-accordion-underline .mdcms-accordion-body {
|
||||
border-left: 1px solid var(--mdcms-bar, var(--accent));
|
||||
border-right: 1px solid var(--mdcms-bar, var(--accent));
|
||||
border-bottom: 1px solid var(--mdcms-bar, var(--accent));
|
||||
border-radius: 0 0 3px 3px;
|
||||
padding: 8px 10px 9px;
|
||||
color: var(--font-colour-muted);
|
||||
}
|
||||
|
||||
/* Filled variant — closed */
|
||||
.mdcms-accordion-filled .mdcms-accordion-item { margin-bottom: 6px; }
|
||||
.mdcms-accordion-filled .mdcms-accordion-item[data-open="true"] { margin-bottom: 8px; }
|
||||
.mdcms-accordion-filled .mdcms-accordion-btn {
|
||||
padding: 8px 11px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--mdcms-filled-border, rgba(var(--accent-rgb), 0.30));
|
||||
background: var(--mdcms-filled-bg, rgba(var(--accent-rgb), 0.10));
|
||||
color: var(--mdcms-filled-fg, var(--font-colour));
|
||||
}
|
||||
.mdcms-accordion-filled .mdcms-accordion-chevron { color: var(--mdcms-filled-fg, var(--font-colour)); }
|
||||
|
||||
/* Filled variant — open: item becomes the outer frame */
|
||||
.mdcms-accordion-filled .mdcms-accordion-item[data-open="true"] {
|
||||
border: 1px solid var(--mdcms-bar, var(--accent));
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.mdcms-accordion-filled .mdcms-accordion-item[data-open="true"] > .mdcms-accordion-btn {
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
.mdcms-accordion-filled .mdcms-accordion-body {
|
||||
background: var(--mdcms-bg, var(--bg-main));
|
||||
padding: 8px 11px 9px;
|
||||
color: var(--font-colour-muted);
|
||||
}
|
||||
|
||||
@media print {
|
||||
.sidebar, .topbar, .scroll-top, .hamburger,
|
||||
.mobile-header, .theme-toggle, .search-container { display: none !important; }
|
||||
|
|
@ -952,7 +1092,7 @@ body {
|
|||
|
||||
// Category state (phase 3)
|
||||
let categoriesUse = false;
|
||||
let categoriesList = []; // [{code, name, direction, message, notfoundmessage, pagenotfoundmessage, font, ...}]
|
||||
let categoriesList = []; // [{code, name, direction, message, notfoundmessage, pagenotfoundmessage, visibilityifnocontent, font, ...}]
|
||||
let categoriesByCode = {}; // code → category object
|
||||
let defaultCategoryCode = null;
|
||||
let activeCategory = null; // current code
|
||||
|
|
@ -1135,11 +1275,19 @@ body {
|
|||
if (b) b.remove();
|
||||
}
|
||||
|
||||
function _isMdResponse(r) {
|
||||
// Reject HTML responses — servers with SPA routing (e.g. Cloudflare Pages with
|
||||
// "/* /index.html 200") return index.html with 200 for missing files, which would
|
||||
// be mistaken for a found markdown file.
|
||||
const ct = r.headers.get('content-type') || '';
|
||||
return !ct.startsWith('text/html');
|
||||
}
|
||||
|
||||
async function fetchPageFile(conceptualFile) {
|
||||
// conceptualFile like "pages/foo.md". Returns { ok, text, resolvedFile } or { ok: false }.
|
||||
if (!categoriesUse) {
|
||||
const r = await fetch(conceptualFile);
|
||||
if (r.ok) return { ok: true, text: await r.text(), resolvedFile: conceptualFile };
|
||||
if (r.ok && _isMdResponse(r)) return { ok: true, text: await r.text(), resolvedFile: conceptualFile };
|
||||
return { ok: false };
|
||||
}
|
||||
const base = conceptualFile.replace(/\.md$/, '');
|
||||
|
|
@ -1169,7 +1317,7 @@ body {
|
|||
if (seen.has(url)) continue;
|
||||
seen.add(url);
|
||||
const r = await fetch(url);
|
||||
if (r.ok) return { ok: true, text: await r.text(), resolvedFile: url };
|
||||
if (r.ok && _isMdResponse(r)) return { ok: true, text: await r.text(), resolvedFile: url };
|
||||
}
|
||||
return { ok: false };
|
||||
}
|
||||
|
|
@ -1202,13 +1350,15 @@ body {
|
|||
// - Home page: always show (per config.homepage or default 'pages/home.md')
|
||||
// - Variant exists for active category: show
|
||||
// - Active category has notfoundmessage: show (renderer falls back to default language)
|
||||
// - Active category has visibilityifnocontent: visible: show (renderer shows pagenotfoundmessage)
|
||||
// - Otherwise: hide
|
||||
if (!categoriesUse) return true;
|
||||
if (page.file === defaultPage()) return true;
|
||||
if (page.uncategorized) return true;
|
||||
const variants = page.variants || [];
|
||||
if (variants.includes(activeCategory)) return true;
|
||||
const cat = categoriesByCode[activeCategory];
|
||||
return !!(cat && cat.notfoundmessage);
|
||||
return !!(cat && (cat.notfoundmessage || cat.visibilityifnocontent === 'visible'));
|
||||
}
|
||||
|
||||
// ─── Theme ────────────────────────────────────────────────
|
||||
|
|
@ -1226,6 +1376,7 @@ body {
|
|||
btn.appendChild(iconEl(isDark ? 'light_mode' : 'dark_mode'));
|
||||
btn.appendChild(el('span', { textContent: isDark ? 'Light mode' : 'Dark mode' }));
|
||||
}
|
||||
computeDerivedTokens();
|
||||
}
|
||||
|
||||
function getInitialTheme() {
|
||||
|
|
@ -1457,6 +1608,10 @@ body {
|
|||
const fenceType = codeLang === 'mdcms' ? '' : codeLang.slice('mdcms '.length).trim();
|
||||
const fullText = fenceType ? (fenceType + '\n' + (codeText || '')) : (codeText || '');
|
||||
const tag = parseMdcmsTag(fullText);
|
||||
// For tab/accordion blocks, preserve the raw fence body to avoid trim() breaking YAML indentation.
|
||||
if (/^tab(-underline|-filled)?$|^accordion(-underline|-filled)?$/.test(tag.tagName)) {
|
||||
tag.rawBody = codeText || '';
|
||||
}
|
||||
const encoded = JSON.stringify(tag).replace(/&/g, '&').replace(/"/g, '"');
|
||||
return '<div class="mdcms-tag" data-config="' + encoded + '"></div>';
|
||||
}
|
||||
|
|
@ -1607,7 +1762,7 @@ function fmtDatetime(dtStr) {
|
|||
|
||||
// Category filter
|
||||
if (categoriesUse && activeCategory) {
|
||||
posts = posts.filter(function(e) { return e.category === activeCategory; });
|
||||
posts = posts.filter(function(e) { return !e.category || e.category === activeCategory; });
|
||||
}
|
||||
|
||||
// Field filter
|
||||
|
|
@ -1946,6 +2101,95 @@ function fmtDatetime(dtStr) {
|
|||
return 'rgba(' + r + ',' + g + ',' + b + ',' + alpha + ')';
|
||||
}
|
||||
|
||||
function parseColorToHex(val) {
|
||||
if (!val) return null;
|
||||
val = val.trim();
|
||||
if (val.startsWith('#')) {
|
||||
if (val.length === 4) return '#' + val[1]+val[1]+val[2]+val[2]+val[3]+val[3];
|
||||
return val.toLowerCase();
|
||||
}
|
||||
var m = val.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/);
|
||||
if (m) return '#' + [m[1],m[2],m[3]].map(function(n) { return parseInt(n).toString(16).padStart(2,'0'); }).join('');
|
||||
return null;
|
||||
}
|
||||
|
||||
function relativeLuminance(hex) {
|
||||
hex = hex.replace('#','');
|
||||
if (hex.length === 3) hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
|
||||
var r = parseInt(hex.substr(0,2),16)/255;
|
||||
var g = parseInt(hex.substr(2,2),16)/255;
|
||||
var b = parseInt(hex.substr(4,2),16)/255;
|
||||
function lin(c) { return c <= 0.04045 ? c/12.92 : Math.pow((c+0.055)/1.055, 2.4); }
|
||||
return 0.2126*lin(r) + 0.7152*lin(g) + 0.0722*lin(b);
|
||||
}
|
||||
|
||||
function hexToHsl(hex) {
|
||||
hex = hex.replace('#','');
|
||||
if (hex.length === 3) hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
|
||||
var r = parseInt(hex.substr(0,2),16)/255;
|
||||
var g = parseInt(hex.substr(2,2),16)/255;
|
||||
var b = parseInt(hex.substr(4,2),16)/255;
|
||||
var max = Math.max(r,g,b), min = Math.min(r,g,b);
|
||||
var h = 0, s = 0, l = (max+min)/2;
|
||||
if (max !== min) {
|
||||
var d = max - min;
|
||||
s = l > 0.5 ? d/(2-max-min) : d/(max+min);
|
||||
switch(max) {
|
||||
case r: h = ((g-b)/d + (g<b?6:0))/6; break;
|
||||
case g: h = ((b-r)/d + 2)/6; break;
|
||||
case b: h = ((r-g)/d + 4)/6; break;
|
||||
}
|
||||
}
|
||||
return [h, s, l];
|
||||
}
|
||||
|
||||
// HSL chroma: S × (1-|2L-1|) — gives perceptually meaningful colorfulness
|
||||
// unlike raw HSL S which is artificially high near white/black.
|
||||
function hslChroma(hex) {
|
||||
var hsl = hexToHsl(hex);
|
||||
return hsl[1] * (1 - Math.abs(2 * hsl[2] - 1));
|
||||
}
|
||||
|
||||
function computeDerivedTokens() {
|
||||
var cs = getComputedStyle(document.documentElement);
|
||||
var bgHex = parseColorToHex(cs.getPropertyValue('--bg-main').trim());
|
||||
var navHex = parseColorToHex(cs.getPropertyValue('--bg-nav').trim());
|
||||
var textHex = parseColorToHex(cs.getPropertyValue('--font-colour').trim());
|
||||
var mutedHex = parseColorToHex(cs.getPropertyValue('--font-colour-muted').trim());
|
||||
var accentHex = parseColorToHex(cs.getPropertyValue('--accent').trim());
|
||||
|
||||
if (!bgHex || !navHex || !textHex || !mutedHex || !accentHex) return;
|
||||
|
||||
var bgL = relativeLuminance(bgHex);
|
||||
var navL = relativeLuminance(navHex);
|
||||
var navC = hslChroma(navHex);
|
||||
var bgC = hslChroma(bgHex);
|
||||
var isDark = document.documentElement.getAttribute('data-theme') === 'dark';
|
||||
|
||||
var navIsAccent = Math.abs(bgL - navL) > 0.22 || (navC > 0.35 && Math.abs(navC - bgC) > 0.25);
|
||||
var navIsInverted = Math.abs(bgL - navL) > 0.35;
|
||||
|
||||
var navText = navIsInverted ? (navL < 0.5 ? '#F2EFE8' : '#161412') : textHex;
|
||||
var navTextMuted = navIsInverted ? hexToRgba(navText, 0.6) : mutedHex;
|
||||
|
||||
var filledBg = navIsAccent ? navHex : hexToRgba(accentHex, 0.10);
|
||||
var filledBorder = navIsAccent ? hexToRgba(navText, 0.18) : hexToRgba(accentHex, 0.30);
|
||||
var filledFg = navIsAccent ? navText : textHex;
|
||||
var filledFgMuted = navIsAccent ? navTextMuted : mutedHex;
|
||||
var barColor = navIsAccent ? navHex : accentHex;
|
||||
var stripAlpha = isDark ? 0.14 : 0.10;
|
||||
|
||||
var root = document.documentElement;
|
||||
root.style.setProperty('--mdcms-bg', bgHex);
|
||||
root.style.setProperty('--mdcms-accent', accentHex);
|
||||
root.style.setProperty('--mdcms-filled-bg', filledBg);
|
||||
root.style.setProperty('--mdcms-filled-border', filledBorder);
|
||||
root.style.setProperty('--mdcms-filled-fg', filledFg);
|
||||
root.style.setProperty('--mdcms-filled-fg-muted',filledFgMuted);
|
||||
root.style.setProperty('--mdcms-bar', barColor);
|
||||
root.style.setProperty('--mdcms-strip-border', hexToRgba(textHex, stripAlpha));
|
||||
}
|
||||
|
||||
function renderTocTag(container) {
|
||||
const byCode = {};
|
||||
navSections.forEach(s => { byCode[s.code] = s; });
|
||||
|
|
@ -2002,6 +2246,143 @@ function fmtDatetime(dtStr) {
|
|||
container.replaceWith(div);
|
||||
}
|
||||
|
||||
function renderTabsTag(container, cfg) {
|
||||
var variant = cfg.tagName === 'tab' ? 'tab-underline' : cfg.tagName;
|
||||
var isFilled = variant === 'tab-filled';
|
||||
var varClass = isFilled ? 'filled' : 'underline';
|
||||
|
||||
var items = [];
|
||||
try {
|
||||
// Use rawBody (pre-trim YAML) when available; fall back to reconstructed form.
|
||||
var rawYaml = cfg.rawBody !== undefined ? cfg.rawBody : ('items:\n' + (cfg.body || ''));
|
||||
var parsed = jsyaml.load(rawYaml);
|
||||
items = (parsed && parsed.items) || [];
|
||||
} catch (e) {
|
||||
container.textContent = 'Error parsing tab items.';
|
||||
return;
|
||||
}
|
||||
if (!items.length) { container.textContent = 'No tab items.'; return; }
|
||||
|
||||
var selectedIdx = items.findIndex(function(it) { return it && it.default === 'selected'; });
|
||||
if (selectedIdx < 0) selectedIdx = 0;
|
||||
|
||||
var wrapper = el('div', { className: 'mdcms-tabs mdcms-tabs-' + varClass });
|
||||
var strip = el('div', { className: 'mdcms-tabs-strip', role: 'tablist' });
|
||||
var panels = [];
|
||||
|
||||
items.forEach(function(item, i) {
|
||||
if (!item) return;
|
||||
var isSelected = i === selectedIdx;
|
||||
|
||||
var btn = el('button', {
|
||||
className: 'mdcms-tab-btn',
|
||||
role: 'tab',
|
||||
type: 'button',
|
||||
'aria-selected': String(isSelected)
|
||||
});
|
||||
var titleStyle = item['title-style'] || '';
|
||||
var lvlMatch = titleStyle.match(/^(#{1,6})$/);
|
||||
var titleSpan;
|
||||
if (lvlMatch) {
|
||||
titleSpan = el('span', { role: 'heading', 'aria-level': String(lvlMatch[1].length) });
|
||||
titleSpan.textContent = item.title || '';
|
||||
} else {
|
||||
titleSpan = el('span', { textContent: item.title || '' });
|
||||
}
|
||||
btn.appendChild(titleSpan);
|
||||
strip.appendChild(btn);
|
||||
|
||||
var panel = el('div', { className: 'mdcms-tabs-panel', role: 'tabpanel' });
|
||||
panel.innerHTML = renderMarkdown(String(item.content || ''));
|
||||
if (!isSelected) panel.setAttribute('hidden', '');
|
||||
panels.push(panel);
|
||||
|
||||
btn.addEventListener('click', (function(idx) {
|
||||
return function() {
|
||||
strip.querySelectorAll('.mdcms-tab-btn').forEach(function(b, j) {
|
||||
b.setAttribute('aria-selected', String(j === idx));
|
||||
if (j === idx) panels[j].removeAttribute('hidden');
|
||||
else panels[j].setAttribute('hidden', '');
|
||||
});
|
||||
};
|
||||
})(i));
|
||||
});
|
||||
|
||||
wrapper.appendChild(strip);
|
||||
panels.forEach(function(p) { wrapper.appendChild(p); });
|
||||
container.replaceWith(wrapper);
|
||||
}
|
||||
|
||||
function renderAccordionTag(container, cfg) {
|
||||
var variant = cfg.tagName === 'accordion' ? 'accordion-underline' : cfg.tagName;
|
||||
var isFilled = variant === 'accordion-filled';
|
||||
var varClass = isFilled ? 'filled' : 'underline';
|
||||
|
||||
var items = [];
|
||||
try {
|
||||
var rawYaml = cfg.rawBody !== undefined ? cfg.rawBody : ('items:\n' + (cfg.body || ''));
|
||||
var parsed = jsyaml.load(rawYaml);
|
||||
items = (parsed && parsed.items) || [];
|
||||
} catch (e) {
|
||||
container.textContent = 'Error parsing accordion items.';
|
||||
return;
|
||||
}
|
||||
if (!items.length) { container.textContent = 'No accordion items.'; return; }
|
||||
|
||||
var CHEVRON_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>';
|
||||
|
||||
var wrapper = el('div', { className: 'mdcms-accordion mdcms-accordion-' + varClass });
|
||||
|
||||
items.forEach(function(item) {
|
||||
if (!item) return;
|
||||
var isOpen = item.default === 'open';
|
||||
|
||||
var itemEl = el('div', { className: 'mdcms-accordion-item' });
|
||||
itemEl.setAttribute('data-open', String(isOpen));
|
||||
|
||||
var btn = el('button', {
|
||||
className: 'mdcms-accordion-btn',
|
||||
type: 'button',
|
||||
'aria-expanded': String(isOpen)
|
||||
});
|
||||
|
||||
var titleStyle = item['title-style'] || '';
|
||||
var lvlMatch = titleStyle.match(/^(#{1,6})$/);
|
||||
var titleEl = el('span', { className: 'mdcms-accordion-title' });
|
||||
if (lvlMatch) {
|
||||
var heading = el('span', { role: 'heading', 'aria-level': String(lvlMatch[1].length) });
|
||||
heading.textContent = item.title || '';
|
||||
titleEl.appendChild(heading);
|
||||
} else {
|
||||
titleEl.textContent = item.title || '';
|
||||
}
|
||||
btn.appendChild(titleEl);
|
||||
|
||||
var chevron = el('span', { className: 'mdcms-accordion-chevron' });
|
||||
chevron.innerHTML = CHEVRON_SVG;
|
||||
btn.appendChild(chevron);
|
||||
|
||||
var body = el('div', { className: 'mdcms-accordion-body' });
|
||||
body.innerHTML = renderMarkdown(String(item.content || ''));
|
||||
if (!isOpen) body.setAttribute('hidden', '');
|
||||
|
||||
btn.addEventListener('click', function() {
|
||||
var open = itemEl.getAttribute('data-open') === 'true';
|
||||
var next = !open;
|
||||
itemEl.setAttribute('data-open', String(next));
|
||||
btn.setAttribute('aria-expanded', String(next));
|
||||
if (next) body.removeAttribute('hidden');
|
||||
else body.setAttribute('hidden', '');
|
||||
});
|
||||
|
||||
itemEl.appendChild(btn);
|
||||
itemEl.appendChild(body);
|
||||
wrapper.appendChild(itemEl);
|
||||
});
|
||||
|
||||
container.replaceWith(wrapper);
|
||||
}
|
||||
|
||||
function hydrateMdcmsTags() {
|
||||
document.querySelectorAll('.mdcms-tag').forEach(function(tagEl) {
|
||||
try {
|
||||
|
|
@ -2010,6 +2391,10 @@ function fmtDatetime(dtStr) {
|
|||
renderCalloutTag(tagEl, cfg);
|
||||
} else if (cfg.tagName === 'toc') {
|
||||
renderTocTag(tagEl);
|
||||
} else if (/^tab(-underline|-filled)?$/.test(cfg.tagName)) {
|
||||
renderTabsTag(tagEl, cfg);
|
||||
} else if (/^accordion(-underline|-filled)?$/.test(cfg.tagName)) {
|
||||
renderAccordionTag(tagEl, cfg);
|
||||
} else {
|
||||
renderPostTag(tagEl, cfg);
|
||||
}
|
||||
|
|
@ -2243,16 +2628,17 @@ function fmtDatetime(dtStr) {
|
|||
function visibleCategoryCodesForCurrentPage() {
|
||||
// Which categories should appear in the dropdown:
|
||||
// - the variant exists for this page, OR
|
||||
// - the category has a notfoundmessage
|
||||
// - the category has a notfoundmessage (fallback to default content), OR
|
||||
// - the category has visibilityifnocontent: visible (shows pagenotfoundmessage instead)
|
||||
// - always include the active category so user can see what they're on
|
||||
const out = new Set();
|
||||
const page = currentPage
|
||||
? navData.find(p => p.file === currentPage)
|
||||
: null;
|
||||
categoriesList.forEach(cat => {
|
||||
const hasVariant = !page || !page.variants || page.variants.includes(cat.code);
|
||||
const hasMsg = !!cat.notfoundmessage;
|
||||
if (hasVariant || hasMsg || cat.code === activeCategory) out.add(cat.code);
|
||||
const hasVariant = !page || page.uncategorized || !(page.variants && page.variants.length) || page.variants.includes(cat.code);
|
||||
const alwaysVisible = !!cat.notfoundmessage || cat.visibilityifnocontent === 'visible';
|
||||
if (hasVariant || alwaysVisible || cat.code === activeCategory) out.add(cat.code);
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
|
@ -2277,7 +2663,7 @@ function fmtDatetime(dtStr) {
|
|||
'data-code': cat.code
|
||||
});
|
||||
option.appendChild(document.createTextNode(primary));
|
||||
const hasVariant = !page || !page.variants || page.variants.includes(cat.code);
|
||||
const hasVariant = !page || page.uncategorized || !(page.variants && page.variants.length) || page.variants.includes(cat.code);
|
||||
if (!hasVariant && cat.notfoundmessage) {
|
||||
option.appendChild(el('span', { className: 'secondary', textContent: cat.notfoundmessage }));
|
||||
} else if (secondary) {
|
||||
|
|
@ -2642,6 +3028,24 @@ function fmtDatetime(dtStr) {
|
|||
const contentEl = document.getElementById('pageContent');
|
||||
highlightNav(file);
|
||||
|
||||
// If the active category is "hidden" (no notfoundmessage, not visibilityifnocontent:visible)
|
||||
// and this page has no variant for it, silently switch to the default category instead of
|
||||
// showing an error.
|
||||
if (categoriesUse && activeCategory !== defaultCategoryCode && file !== defaultPage()) {
|
||||
const cat = categoriesByCode[activeCategory];
|
||||
const isHidden = cat && !cat.notfoundmessage && cat.visibilityifnocontent !== 'visible';
|
||||
if (isHidden) {
|
||||
const pageEntry = navData.find(p => p.file === file);
|
||||
const hasVariant = !pageEntry || pageEntry.uncategorized
|
||||
|| !(pageEntry.variants && pageEntry.variants.length)
|
||||
|| pageEntry.variants.includes(activeCategory);
|
||||
if (!hasVariant) {
|
||||
setActiveCategory(defaultCategoryCode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build a clean URL: keep origin + path, set ?cat only when non-default, set hash to conceptual file
|
||||
const u = new URL(window.location);
|
||||
if (categoriesUse && activeCategory && activeCategory !== defaultCategoryCode) {
|
||||
|
|
@ -2656,7 +3060,7 @@ function fmtDatetime(dtStr) {
|
|||
|
||||
const result = await fetchPageFile(file);
|
||||
if (!result.ok) {
|
||||
const offlineMsg = localStorage.getItem('mdcms-offline');
|
||||
const offlineMsg = !navigator.onLine && localStorage.getItem('mdcms-offline');
|
||||
const bodyMsg = offlineMsg
|
||||
? `<p>${offlineMsg}</p>`
|
||||
: `<p>${pageNotFoundMessage()}</p>`;
|
||||
|
|
|
|||
|
|
@ -16,3 +16,7 @@ pages:
|
|||
- file: pages/docs.md
|
||||
title: Docs
|
||||
sort: 300
|
||||
|
||||
- file: pages/tabs-accordions.md
|
||||
title: Tabs & Accordions
|
||||
sort: 400
|
||||
|
|
|
|||
78
app/pages/tabs-accordions.md
Normal file
78
app/pages/tabs-accordions.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
---
|
||||
title: Tabs & Accordions
|
||||
sort: 400
|
||||
---
|
||||
|
||||
# Tabs & Accordions
|
||||
|
||||
## Tab — Underline variant
|
||||
|
||||
```mdcms tab-underline
|
||||
items:
|
||||
- title: Install
|
||||
default: selected
|
||||
content: |
|
||||
Install with `npm i mdcms` or `pnpm add mdcms`.
|
||||
- title: Configure
|
||||
content: |
|
||||
Drop a `mdcms.config.yaml` next to your content folder.
|
||||
- title: Deploy
|
||||
content: |
|
||||
Any static host. The build emits plain HTML.
|
||||
```
|
||||
|
||||
## Tab — Filled variant
|
||||
|
||||
```mdcms tab-filled
|
||||
items:
|
||||
- title: Overview
|
||||
default: selected
|
||||
content: |
|
||||
MD-CMS is a markdown-based static site system with no build step.
|
||||
- title: Features
|
||||
content: |
|
||||
- Sidebar navigation with sections
|
||||
- Full-text search via Fuse.js
|
||||
- PWA support with offline caching
|
||||
- Dark / light theme toggle
|
||||
- title: Architecture
|
||||
content: |
|
||||
Two parts: `mdcms.py` (CLI) and `app/index.html` (browser renderer).
|
||||
```
|
||||
|
||||
## Accordion — Underline variant
|
||||
|
||||
```mdcms accordion-underline
|
||||
items:
|
||||
- title: What is MD-CMS?
|
||||
default: open
|
||||
content: |
|
||||
MD-CMS is a single-file browser renderer that reads markdown, config,
|
||||
and nav at runtime entirely client-side. No build pipeline, no compilation.
|
||||
- title: How do I install it?
|
||||
content: |
|
||||
Run `pip install mdcms` or download a binary from the GitHub releases page.
|
||||
- title: Does it work offline?
|
||||
content: |
|
||||
Yes — run `mdcms fetch-deps` to bundle all vendor assets locally, then
|
||||
enable `pwa: yes` in `config.yml` for full offline support.
|
||||
```
|
||||
|
||||
## Accordion — Filled variant
|
||||
|
||||
```mdcms accordion-filled
|
||||
items:
|
||||
- title: Can I use custom themes?
|
||||
default: open
|
||||
content: |
|
||||
Yes. Create a `theme.yml` file and point to it with `theme: theme.yml` in
|
||||
your `config.yml`. The theme controls colours, fonts, and layout.
|
||||
- title: What markdown features are supported?
|
||||
content: |
|
||||
GFM (GitHub Flavored Markdown): tables, task lists, fenced code blocks,
|
||||
strikethrough, and autolinks. Syntax highlighting via highlight.js.
|
||||
- title: Can I nest categories?
|
||||
content: |
|
||||
Categories are flat (no nesting), but nav sections support a `parent:`
|
||||
key for two-level sidebar grouping.
|
||||
```
|
||||
|
|
@ -34,5 +34,17 @@
|
|||
"modified": "",
|
||||
"language": "en",
|
||||
"body": "# Phase 7 — PWA Test\n\nThis page verifies the service worker and manifest generated by `mdcms build` when `pwa: yes` is set in `config.yml`.\n\n## Test procedure\n\n1. Run `python3 mdcms.py build --path app/` — confirm `manifest.json` and `service-worker.js` appear in `app/`\n2. Load `http://localhost:8800` — service worker registers on first load\n3. Navigate to the **About** and **Docs** pages so they are fetched and cached\n4. Stop the HTTP server (`Ctrl+C` in its terminal)\n5. Reload — site should load fully from the service worker cache\n6. Navigate between pages — all should work offline\n7. Check that a page not yet visited shows the offline message\n\n## What to look for\n\n- `manifest.json` and `service-worker.js` exist after build\n- DevTools → Application → Service Workers: status **activated and running**\n- DevTools → Application → Cache Storage: cache named `mdcms-xxxxxxxx` with all files listed\n- Site loads fully with server stopped\n- Offline message (`config.yml: offline-message`) appears for uncached pages\n"
|
||||
},
|
||||
{
|
||||
"file": "pages/tabs-accordions.md",
|
||||
"title": "Tabs & Accordions",
|
||||
"section-id": null,
|
||||
"keywords": "",
|
||||
"description": "",
|
||||
"author": null,
|
||||
"created": "",
|
||||
"modified": "",
|
||||
"language": "en",
|
||||
"body": "# Tabs & Accordions\n\n## Tab — Underline variant\n\n```mdcms tab-underline\nitems:\n - title: Install\n default: selected\n content: |\n Install with `npm i mdcms` or `pnpm add mdcms`.\n - title: Configure\n content: |\n Drop a `mdcms.config.yaml` next to your content folder.\n - title: Deploy\n content: |\n Any static host. The build emits plain HTML.\n```\n\n## Tab — Filled variant\n\n```mdcms tab-filled\nitems:\n - title: Overview\n default: selected\n content: |\n MD-CMS is a markdown-based static site system with no build step.\n - title: Features\n content: |\n - Sidebar navigation with sections\n - Full-text search via Fuse.js\n - PWA support with offline caching\n - Dark / light theme toggle\n - title: Architecture\n content: |\n Two parts: `mdcms.py` (CLI) and `app/index.html` (browser renderer).\n```\n\n## Accordion — Underline variant\n\n```mdcms accordion-underline\nitems:\n - title: What is MD-CMS?\n default: open\n content: |\n MD-CMS is a single-file browser renderer that reads markdown, config,\n and nav at runtime entirely client-side. No build pipeline, no compilation.\n - title: How do I install it?\n content: |\n Run `pip install mdcms` or download a binary from the GitHub releases page.\n - title: Does it work offline?\n content: |\n Yes — run `mdcms fetch-deps` to bundle all vendor assets locally, then\n enable `pwa: yes` in `config.yml` for full offline support.\n```\n\n## Accordion — Filled variant\n\n```mdcms accordion-filled\nitems:\n - title: Can I use custom themes?\n default: open\n content: |\n Yes. Create a `theme.yml` file and point to it with `theme: theme.yml` in\n your `config.yml`. The theme controls colours, fonts, and layout.\n - title: What markdown features are supported?\n content: |\n GFM (GitHub Flavored Markdown): tables, task lists, fenced code blocks,\n strikethrough, and autolinks. Syntax highlighting via highlight.js.\n - title: Can I nest categories?\n content: |\n Categories are flat (no nesting), but nav sections support a `parent:`\n key for two-level sidebar grouping.\n```\n"
|
||||
}
|
||||
]
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
// mdcms service worker — generated by mdcms build
|
||||
const CACHE_NAME = 'mdcms-eb384247';
|
||||
const CACHE_NAME = 'mdcms-a1862733';
|
||||
const PRECACHE_URLS = [
|
||||
"index.html",
|
||||
"config.yml",
|
||||
|
|
@ -9,20 +9,29 @@ const PRECACHE_URLS = [
|
|||
"pages/about.md",
|
||||
"pages/docs.md",
|
||||
"pages/home.md",
|
||||
"pages/tabs-accordions.md",
|
||||
"posts/.gitkeep",
|
||||
"assets/fonts/.gitkeep",
|
||||
"assets/icons/.gitkeep",
|
||||
"assets/icons/add.svg",
|
||||
"assets/icons/arrow_drop_down.svg",
|
||||
"assets/icons/arrow_right.svg",
|
||||
"assets/icons/collapse_content.svg",
|
||||
"assets/icons/dangerous.svg",
|
||||
"assets/icons/dark_mode.svg",
|
||||
"assets/icons/error.svg",
|
||||
"assets/icons/exclamation.svg",
|
||||
"assets/icons/expand_content.svg",
|
||||
"assets/icons/history.svg",
|
||||
"assets/icons/info.svg",
|
||||
"assets/icons/keyboard_arrow_down.svg",
|
||||
"assets/icons/keyboard_arrow_right.svg",
|
||||
"assets/icons/keyboard_double_arrow_down.svg",
|
||||
"assets/icons/keyboard_double_arrow_right.svg",
|
||||
"assets/icons/language.svg",
|
||||
"assets/icons/light_mode.svg",
|
||||
"assets/icons/menu.svg",
|
||||
"assets/icons/minimize.svg",
|
||||
"assets/icons/mobile_arrow_down.svg",
|
||||
"assets/icons/report.svg",
|
||||
"assets/icons/search.svg",
|
||||
|
|
|
|||
35
docs/knownbugs.md
Normal file
35
docs/knownbugs.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# Known bugs
|
||||
|
||||
Bugs that have been identified but not yet fixed. Fixed bugs are moved to the release notes.
|
||||
|
||||
---
|
||||
|
||||
## Fixed in development (not yet released)
|
||||
|
||||
### Category-variant pages fail to load on servers with SPA routing
|
||||
|
||||
**Symptom:** On Cloudflare Pages (and any other server configured to serve `index.html` with HTTP 200 for missing paths), clicking a nav item whose page only exists as a category-variant file (e.g. `page.current.md`, no plain `page.md`) showed garbled content — the raw HTML of `index.html` rendered as markdown, with the site's `<title>` text visible in the content area.
|
||||
|
||||
**Root cause:** `fetchPageFile` tried the base filename (`pages/page.md`) first. Servers with SPA routing return this with HTTP 200 (serving `index.html`), so `r.ok` was true and the function returned without trying the actual variant file (`pages/page.current.md`).
|
||||
|
||||
**Fix:** `fetchPageFile` now checks the `Content-Type` response header and skips any response with `text/html`, continuing to the next candidate URL.
|
||||
|
||||
---
|
||||
|
||||
### Stale service worker not removed when `pwa: no`
|
||||
|
||||
**Symptom:** After changing a site from `pwa: yes` to `pwa: no` and rebuilding, the old service worker remained active in browsers that had previously visited the site. Cached responses from the old build continued to be served.
|
||||
|
||||
**Root cause:** `mdcms build` stopped generating PWA files when `pwa: no`, but `index.html` unconditionally registers `service-worker.js` on every page load. With no new SW to replace it, the old worker stayed installed indefinitely.
|
||||
|
||||
**Fix:** `mdcms build` now writes a self-unregistering stub `service-worker.js` when `pwa: no`. On the visitor's next visit, the browser installs the stub which immediately calls `self.registration.unregister()`, evicting the stale worker. `manifest.json` is also deleted if present.
|
||||
|
||||
---
|
||||
|
||||
### `config.yml` YAML parse errors were silently swallowed
|
||||
|
||||
**Symptom:** A malformed `config.yml` (e.g. a stray tab character, which YAML forbids as a token starter) caused `read_config` to catch the `YAMLError` and return an empty dict. The build would proceed with no config — categories disabled, no default category code — producing a `nav.yml` that omitted `variants` fields and listed category variant files (e.g. `page.current.md`) as plain pages. Pages with category variants would not appear in the sidebar.
|
||||
|
||||
**Root cause:** `read_config` caught `(OSError, yaml.YAMLError)` in a single block and silently returned `{}` on any error.
|
||||
|
||||
**Fix:** `read_config` now raises `click.ClickException` on both `OSError` and `yaml.YAMLError`, aborting the build with a descriptive error message instead of continuing with an empty config.
|
||||
|
|
@ -151,16 +151,46 @@ categories-use: yes # Enable the category system. Default: no.
|
|||
|
||||
default-category: # The category used when no ?cat= parameter is in the URL.
|
||||
code: en # Short code. Used in filenames (page.en.md) and URL params.
|
||||
name: English # Display name shown in the category selector.
|
||||
name: English # Display name shown in the category dropdown list.
|
||||
message: English # Label shown on the selector bar (trigger button). Falls back to name.
|
||||
name-latin: English # Secondary label shown in the dropdown alongside name. Use when name
|
||||
# is in a non-Latin script (e.g. Arabic, Devanagari) to aid recognition.
|
||||
# Omit if name is already Latin or identical to name.
|
||||
direction: ltr # Text direction. ltr or rtl. Default: ltr.
|
||||
# rtl flips the nav position and content text direction.
|
||||
notfoundmessage: "Not available in this language"
|
||||
# Short note shown in the dropdown when no variant exists for the
|
||||
# current page. Also enables fallback: the renderer will fall back to
|
||||
# the default-category content instead of hiding the page.
|
||||
# Omit to hide the category from the dropdown when no variant exists.
|
||||
visibilityifnocontent: hidden # hidden (default) or visible.
|
||||
# hidden: category disappears from the selector when no variant exists
|
||||
# for the current page (unless notfoundmessage is also set).
|
||||
# visible: category stays in the selector regardless. When the user
|
||||
# navigates to a page with no variant, pagenotfoundmessage is shown
|
||||
# in the content area. No fallback to default-category content.
|
||||
pagenotfoundmessage: "This page is not yet available in English."
|
||||
# Message shown in the content area when a page cannot be fetched for
|
||||
# this category. Overrides the top-level pagenotfoundmessage.
|
||||
font: NotoNastaliqUrdu-Regular.ttf
|
||||
# Font filename inside assets/fonts/. Loaded on demand when this
|
||||
# category is activated. Useful for scripts that need a specific font.
|
||||
line-height: 2.8 # Line height override for this category. Useful for scripts like
|
||||
# Nastaliq that need extra vertical space. Restores to theme default
|
||||
# when switching away.
|
||||
|
||||
categories: # Additional categories.
|
||||
categories: # Additional categories. Each entry supports the same keys as
|
||||
# default-category above.
|
||||
- code: nb
|
||||
name: Norsk
|
||||
direction: ltr
|
||||
- code: ar
|
||||
name: عربي
|
||||
direction: rtl # RTL flips nav position and content text direction.
|
||||
name-latin: Arabic
|
||||
direction: rtl
|
||||
notfoundmessage: "غير متاح"
|
||||
font: NotoNastaliqUrdu-Regular.ttf
|
||||
line-height: 2.8
|
||||
|
||||
categories-sectionnames: same # How section names are shown per category.
|
||||
# same: all categories share one section name (defaultname in nav.yml).
|
||||
|
|
@ -170,6 +200,21 @@ categories-selecticon: globe # Icon shown in the category selector bar. SVG na
|
|||
categories-selecttext: "Language" # Label shown next to the icon in the category selector bar.
|
||||
```
|
||||
|
||||
### Per-category keys summary
|
||||
|
||||
| Key | Required | Description |
|
||||
|---|---|---|
|
||||
| `code` | Yes | Short identifier used in filenames (`page.nb.md`) and the `?cat=` URL param. |
|
||||
| `name` | Yes | Display name shown in the dropdown list. |
|
||||
| `message` | No | Label shown on the selector trigger button. Falls back to `name`. |
|
||||
| `name-latin` | No | Secondary label in the dropdown, shown alongside `name` when `name` uses a non-Latin script. |
|
||||
| `direction` | No | `ltr` or `rtl`. Default: `ltr`. RTL flips nav and content direction. |
|
||||
| `notfoundmessage` | No | Short note shown in the dropdown when no variant exists for the current page. Also enables fallback to default-category content. |
|
||||
| `visibilityifnocontent` | No | `hidden` (default) or `visible`. `visible` keeps the category in the selector when no variant exists; navigating to it shows `pagenotfoundmessage` with no fallback to default content. |
|
||||
| `pagenotfoundmessage` | No | Message shown in the content area when a page cannot be fetched for this category. Overrides the top-level `pagenotfoundmessage`. |
|
||||
| `font` | No | Font filename from `assets/fonts/`. Loaded on demand when this category is activated. |
|
||||
| `line-height` | No | Body line height override for this category. Restores to theme default when switching away. |
|
||||
|
||||
---
|
||||
|
||||
## Reusable callout messages
|
||||
|
|
@ -228,6 +273,30 @@ offline-message:
|
|||
nb: "Du er frakoblet. Koble til og last inn på nytt."
|
||||
|
||||
language: en
|
||||
pagenotfoundmessage: "Please select a page to continue."
|
||||
|
||||
categories-use: yes
|
||||
default-category:
|
||||
code: en
|
||||
name: English
|
||||
direction: ltr
|
||||
categories:
|
||||
- code: nb
|
||||
name: Norsk
|
||||
direction: ltr
|
||||
visibilityifnocontent: visible
|
||||
pagenotfoundmessage: "Denne siden er ikke tilgjengelig på norsk ennå."
|
||||
- code: ar
|
||||
name: عربي
|
||||
name-latin: Arabic
|
||||
direction: rtl
|
||||
notfoundmessage: "غير متاح"
|
||||
pagenotfoundmessage: "هذه الصفحة غير متاحة."
|
||||
font: NotoNastaliqUrdu-Regular.ttf
|
||||
line-height: 2.8
|
||||
categories-sectionnames: same
|
||||
categories-selecticon: globe
|
||||
categories-selecttext: "Language"
|
||||
|
||||
callouts:
|
||||
aitranslation:
|
||||
|
|
|
|||
|
|
@ -167,6 +167,92 @@ paginate: yes # Pagination mode:
|
|||
|
||||
---
|
||||
|
||||
### Tabs — `tab-underline`, `tab-filled`, `tab`
|
||||
|
||||
A horizontal tab strip with a single visible content panel. The active tab is set with `default: selected`; if no item carries that value the first item is selected automatically.
|
||||
|
||||
| Tag name | Appearance |
|
||||
|---|---|
|
||||
| `tab-underline` | Labels in a row; active tab marked with a 2 px underline in the accent colour. |
|
||||
| `tab` | Alias for `tab-underline`. |
|
||||
| `tab-filled` | Each label is a chip with a filled background; active chip inverts to the page background with an accent border. |
|
||||
|
||||
The body of the block is YAML. It must start with `items:` followed by a list of item objects.
|
||||
|
||||
````markdown
|
||||
```mdcms tab-underline
|
||||
items:
|
||||
- title: npm
|
||||
default: selected
|
||||
content: |
|
||||
```bash
|
||||
npm install mdcms
|
||||
```
|
||||
- title: pnpm
|
||||
content: |
|
||||
```bash
|
||||
pnpm add mdcms
|
||||
```
|
||||
- title: yarn
|
||||
content: |
|
||||
```bash
|
||||
yarn add mdcms
|
||||
```
|
||||
```
|
||||
````
|
||||
|
||||
**Per-item keys:**
|
||||
|
||||
| Key | Required | Notes |
|
||||
|---|---|---|
|
||||
| `title` | yes | Label on the tab button. Plain text only. |
|
||||
| `content` | yes | Tab panel body. Full Markdown, use `\|` for multi-line. |
|
||||
| `default` | no | `selected` — open on load. If no item is `selected`, the first item is used. |
|
||||
| `title-style` | no | Heading level for screen readers. One of `"#"` … `"######"` or `""` (default). Does not affect visual size. |
|
||||
|
||||
---
|
||||
|
||||
### Accordions — `accordion-underline`, `accordion-filled`, `accordion`
|
||||
|
||||
Stacked collapsible items. Each item has a clickable header and a body that expands below it. Any number of items can be open simultaneously.
|
||||
|
||||
| Tag name | Appearance |
|
||||
|---|---|
|
||||
| `accordion-underline` | Header separated from the content by a 2 px bar in the accent or nav colour; open content has a matching 1 px border on three sides. |
|
||||
| `accordion` | Alias for `accordion-underline`. |
|
||||
| `accordion-filled` | Closed header is a filled chip; when open the item becomes a single bordered card with the header fill at the top and the page background below. |
|
||||
|
||||
````markdown
|
||||
```mdcms accordion
|
||||
items:
|
||||
- title: What is MD-CMS?
|
||||
default: open
|
||||
content: |
|
||||
A single-file browser renderer. No build pipeline, no compilation,
|
||||
no server required.
|
||||
- title: How do I install it?
|
||||
content: |
|
||||
Run `pip install mdcms` or download a binary from the GitHub releases page.
|
||||
- title: Does it work offline?
|
||||
content: |
|
||||
Yes — run `mdcms fetch-deps` to bundle vendor assets locally, then enable
|
||||
`pwa: yes` in `config.yml` for full offline support.
|
||||
```
|
||||
````
|
||||
|
||||
**Per-item keys:**
|
||||
|
||||
| Key | Required | Notes |
|
||||
|---|---|---|
|
||||
| `title` | yes | Header label. Plain text only. |
|
||||
| `content` | yes | Body shown when expanded. Full Markdown, use `\|` for multi-line. |
|
||||
| `default` | no | `open` — expanded on load. `closed` or omitted — collapsed. Multiple items may be `open`. |
|
||||
| `title-style` | no | Heading level for screen readers. One of `"#"` … `"######"` or `""` (default). Does not affect visual size. |
|
||||
|
||||
**How the colour adapts to themes:** The bar/border colour and the chip fill are derived automatically from the active theme. On themes where the sidebar background is visually distinct from the page (dark nav on a light page, or a coloured nav), the components use the nav colour as their fill. On subtle themes where sidebar and page backgrounds are near-identical, the accent colour is used instead. No per-theme config is needed.
|
||||
|
||||
---
|
||||
|
||||
## Markdown features
|
||||
|
||||
Standard CommonMark plus GFM (GitHub-flavoured) extensions:
|
||||
|
|
|
|||
204
docs/unreleased.md
Normal file
204
docs/unreleased.md
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
# Unreleased changes
|
||||
|
||||
Changes merged into `development` that have not yet been released to `main`.
|
||||
|
||||
---
|
||||
|
||||
## Tabs & Accordions (`app/index.html`)
|
||||
|
||||
Four new `mdcms` fenced-block types for rich content layout. All four variants read from the active theme automatically — no new config keys, no per-theme overrides needed.
|
||||
|
||||
### Block types
|
||||
|
||||
| Language tag | Alias for | Renders as |
|
||||
|---|---|---|
|
||||
| `tab-underline` | — | Tab strip, active tab marked with underline |
|
||||
| `tab` | `tab-underline` | (same) |
|
||||
| `tab-filled` | — | Tab strip, tabs as filled chips |
|
||||
| `accordion-underline` | — | Stacked accordion, header underline style |
|
||||
| `accordion` | `accordion-underline` | (same) |
|
||||
| `accordion-filled` | — | Stacked accordion, filled card style |
|
||||
|
||||
### Authoring syntax
|
||||
|
||||
Open a fenced block with the language tag `mdcms <type>`. The body is YAML with a single top-level key `items:`, whose value is a list of item objects.
|
||||
|
||||
~~~markdown
|
||||
```mdcms tab-underline
|
||||
items:
|
||||
- title: Install
|
||||
default: selected
|
||||
content: |
|
||||
Install with `npm i mdcms` or `pnpm add mdcms`.
|
||||
- title: Configure
|
||||
content: |
|
||||
Drop a `mdcms.config.yaml` next to your content folder.
|
||||
- title: Deploy
|
||||
content: |
|
||||
Any static host. The build emits plain HTML.
|
||||
```
|
||||
~~~
|
||||
|
||||
### Per-item keys
|
||||
|
||||
| Key | Required | Type | Notes |
|
||||
|---|---|---|---|
|
||||
| `title` | yes | plain string | Label shown on the tab button or accordion header. Plain text only — no Markdown. |
|
||||
| `content` | yes | Markdown block | Body content. Use the YAML literal block scalar (`\|`) for multi-line Markdown. Rendered with the same pipeline as the surrounding page (GFM, syntax highlighting, internal links). |
|
||||
| `default` | no | string | **Tabs:** `selected` marks the tab that is open on load; if no item has `selected`, the first item is used. `notselected` (or omitting the key) leaves the tab inactive. Exactly one tab should be `selected`. **Accordions:** `open` makes the item expanded on load; `closed` (or omitting) leaves it collapsed. Any number of accordion items may be `open`. |
|
||||
| `title-style` | no | string | Heading level for screen readers and external TOC tools. One of `"#"`, `"##"`, `"###"`, `"####"`, `"#####"`, `"######"`, or `""` (default). Visual size is always fixed by the component — this only changes the underlying ARIA role and level. Use a value when you want the item to be picked up as a heading by assistive technology. |
|
||||
|
||||
### Examples
|
||||
|
||||
**Tabs — underline (default)**
|
||||
|
||||
~~~markdown
|
||||
```mdcms tab
|
||||
items:
|
||||
- title: npm
|
||||
default: selected
|
||||
content: |
|
||||
```bash
|
||||
npm install mdcms
|
||||
```
|
||||
- title: pnpm
|
||||
content: |
|
||||
```bash
|
||||
pnpm add mdcms
|
||||
```
|
||||
- title: yarn
|
||||
content: |
|
||||
```bash
|
||||
yarn add mdcms
|
||||
```
|
||||
```
|
||||
~~~
|
||||
|
||||
**Tabs — filled chips**
|
||||
|
||||
~~~markdown
|
||||
```mdcms tab-filled
|
||||
items:
|
||||
- title: Overview
|
||||
default: selected
|
||||
content: |
|
||||
MD-CMS is a markdown-based static site system with no build step.
|
||||
- title: Features
|
||||
content: |
|
||||
- Sidebar navigation
|
||||
- Full-text search
|
||||
- PWA + offline support
|
||||
- Dark / light theme
|
||||
```
|
||||
~~~
|
||||
|
||||
**Accordion — underline (default)**
|
||||
|
||||
~~~markdown
|
||||
```mdcms accordion
|
||||
items:
|
||||
- title: What is MD-CMS?
|
||||
default: open
|
||||
content: |
|
||||
A single-file browser renderer. No build pipeline, no compilation,
|
||||
no server required.
|
||||
- title: How do I install it?
|
||||
content: |
|
||||
Run `pip install mdcms` or download a binary from the GitHub releases page.
|
||||
- title: Does it work offline?
|
||||
content: |
|
||||
Yes — run `mdcms fetch-deps` to bundle vendor assets locally, then enable
|
||||
`pwa: yes` in `config.yml` for full offline support.
|
||||
```
|
||||
~~~
|
||||
|
||||
**Accordion — filled cards**
|
||||
|
||||
~~~markdown
|
||||
```mdcms accordion-filled
|
||||
items:
|
||||
- title: Can I use custom themes?
|
||||
default: open
|
||||
content: |
|
||||
Yes. Create a `theme.yml` and reference it with `theme: theme.yml` in
|
||||
`config.yml`. The theme controls colours, fonts, and layout.
|
||||
- title: title-style example
|
||||
title-style: "##"
|
||||
content: |
|
||||
This header is announced as an `<h2>` to screen readers, even though
|
||||
its visual size is set by the accordion component.
|
||||
```
|
||||
~~~
|
||||
|
||||
### How the appearance adapts to themes
|
||||
|
||||
The components derive their fill colours and bar/border colours from the active theme at runtime. No new keys in `config.yml` or `theme.yml` are needed.
|
||||
|
||||
**Bold themes** (nav background is visually distinct from the page — e.g. a dark sidebar on a light page, or a coloured nav like red or navy): filled tabs and accordion headers use the nav background colour as their fill; the bar/border uses the nav colour. This makes the components look like an extension of the sidebar chrome.
|
||||
|
||||
**Subtle themes** (nav background is almost identical to the page — e.g. both near-white or both near-dark): filled tabs use a light tint of the accent colour; the bar and border use the accent colour directly. This keeps the components visible without a strong nav background to borrow from.
|
||||
|
||||
The switch between bold and subtle is automatic. The algorithm uses HSL chroma (`S × (1−|2L−1|)`) rather than raw HSL saturation, which would give false "bold" readings for near-white or near-black nav backgrounds.
|
||||
|
||||
---
|
||||
|
||||
## `mdcms build` patches `<title>` with sitename
|
||||
|
||||
`mdcms build` now rewrites the `<title>` tag in `index.html` with the value of `sitename` from `config.yml`. Previously the tag was hardcoded (`MD-CMS`) in older templates, or blank in the starter template, so link previews in WhatsApp, Slack, and other crawlers that read static HTML showed the wrong name.
|
||||
|
||||
---
|
||||
|
||||
## Untranslated posts now visible in all categories
|
||||
|
||||
**Status:** On `development`, pending release.
|
||||
|
||||
### What was broken
|
||||
|
||||
When the category system is enabled, a post file without a category suffix (e.g. `posts/my-post.md`) was silently assigned to the default category only. Switching to any other category caused those posts to disappear from the nav and from `posts-*` tag listings — even though no translated version existed. If you wrote posts without a language suffix, they simply vanished the moment a visitor switched category.
|
||||
|
||||
Pages without a category suffix are unaffected: they continue to be assigned to the default category, which is the correct behaviour for pages.
|
||||
|
||||
### What it does now
|
||||
|
||||
Posts without a category suffix are treated as uncategorised — meaning they appear in every category. A post called `my-post.md` now shows up regardless of which category is active. A post called `my-post.en.md` still appears only in the `en` category as before.
|
||||
|
||||
Mixed situations work as expected: if you have both `my-post.md` and `my-post.nb.md`, the Norwegian variant is shown when the `nb` category is active, and the bare `my-post.md` is shown for every other category.
|
||||
|
||||
### What changes in the build output
|
||||
|
||||
After rebuilding a site with `mdcms build`, affected post entries in `nav.yml` gain an `uncategorized: true` field:
|
||||
|
||||
```yaml
|
||||
- file: posts/my-post.md
|
||||
title: My Post
|
||||
sort: 100
|
||||
uncategorized: true
|
||||
```
|
||||
|
||||
In `search.json`, these entries carry `"category": null` instead of the default category code. This is what tells the renderer to include them universally.
|
||||
|
||||
A rebuild is required for existing sites to pick up the change.
|
||||
|
||||
---
|
||||
|
||||
## Fix: category-variant pages fail to load on servers with SPA routing (e.g. Cloudflare Pages)
|
||||
|
||||
When a site uses category-suffixed page files (e.g. `page.current.md`) and is hosted on a server configured with SPA fallback routing (serving `index.html` with HTTP 200 for any unknown path), the renderer's `fetchPageFile` mistook the HTML fallback for a found markdown file. It returned `index.html` content instead of falling through to try the `.current.md` variant. The page rendered the raw HTML of `index.html` as markdown, showing the `<title>` text (`sitename`) in the content area.
|
||||
|
||||
`fetchPageFile` now checks the `Content-Type` response header and rejects any response with `text/html`, continuing to the next candidate URL instead.
|
||||
|
||||
---
|
||||
|
||||
## Fix: stale service worker not removed when `pwa: no`
|
||||
|
||||
`index.html` unconditionally registers `service-worker.js` on every page load. When a site switched from `pwa: yes` to `pwa: no`, `mdcms build` stopped generating a new service worker, but the old one remained active in browsers that had visited the site before. The stale worker continued to serve cached responses from the old build.
|
||||
|
||||
`mdcms build` now writes a self-unregistering `service-worker.js` when `pwa: no`. On the visitor's next page load, the browser installs this stub worker, which immediately unregisters itself and evicts any previously cached content. `manifest.json` is also removed if present.
|
||||
|
||||
---
|
||||
|
||||
## Fix: `config.yml` YAML parse errors now abort the build with a clear message
|
||||
|
||||
A malformed `config.yml` (e.g. a stray tab character, which YAML forbids as a token starter) previously caused `read_config` to silently return an empty dict. The build would proceed with no config — categories disabled, no default category code — producing a broken `nav.yml` with wrong filenames and missing `variants` fields, so category-variant pages would not appear in the sidebar.
|
||||
|
||||
`read_config` now raises `ClickException` on both `OSError` and `yaml.YAMLError`, aborting the build with a descriptive error message instead of continuing silently with an empty config.
|
||||
71
mdcms.py
71
mdcms.py
|
|
@ -113,9 +113,12 @@ def read_config(site_path: Path) -> dict:
|
|||
return {}
|
||||
try:
|
||||
text = config_file.read_text(encoding="utf-8")
|
||||
except OSError as e:
|
||||
raise click.ClickException(f"Could not read config.yml: {e}")
|
||||
try:
|
||||
return yaml.safe_load(text) or {}
|
||||
except (OSError, yaml.YAMLError):
|
||||
return {}
|
||||
except yaml.YAMLError as e:
|
||||
raise click.ClickException(f"config.yml is not valid YAML: {e}")
|
||||
|
||||
|
||||
def get_category_info(cfg: dict) -> dict:
|
||||
|
|
@ -265,11 +268,19 @@ def build_page_nav(
|
|||
"sort": sort,
|
||||
}
|
||||
if categories_use:
|
||||
is_post = file.startswith("posts/")
|
||||
covered = {}
|
||||
has_uncategorized = False
|
||||
for code, record in variants.items():
|
||||
key = code if code is not None else default_code
|
||||
if key:
|
||||
covered[key] = record.get("title", "")
|
||||
if code is None:
|
||||
if is_post:
|
||||
has_uncategorized = True
|
||||
elif default_code:
|
||||
covered[default_code] = record.get("title", "")
|
||||
else:
|
||||
covered[code] = record.get("title", "")
|
||||
if has_uncategorized:
|
||||
entry["uncategorized"] = True
|
||||
entry["variants"] = sorted(covered.keys())
|
||||
entry["titles"] = covered
|
||||
out.append(entry)
|
||||
|
|
@ -313,6 +324,8 @@ def generate_nav_yml(sections: list, pages: list, categories_use: bool = False)
|
|||
if p.get("section-id"):
|
||||
lines.append(f" section-id: {p['section-id']}")
|
||||
lines.append(f" sort: {p.get('sort', 100)}")
|
||||
if categories_use and p.get("uncategorized"):
|
||||
lines.append(" uncategorized: true")
|
||||
if categories_use and p.get("variants"):
|
||||
lines.append(f" variants: [{', '.join(p['variants'])}]")
|
||||
if categories_use and p.get("titles"):
|
||||
|
|
@ -345,7 +358,13 @@ def generate_search_json(
|
|||
}
|
||||
if categories_use:
|
||||
code = r.get("code")
|
||||
entry["category"] = code if code is not None else default_code
|
||||
is_post = r.get("file", "").startswith("posts/")
|
||||
if code is not None:
|
||||
entry["category"] = code
|
||||
elif is_post:
|
||||
entry["category"] = None # null = show in all categories
|
||||
else:
|
||||
entry["category"] = default_code
|
||||
out.append(entry)
|
||||
return json.dumps(out, indent=2, ensure_ascii=False)
|
||||
|
||||
|
|
@ -412,6 +431,19 @@ def validate_assets(site_path: Path, cfg: dict) -> list:
|
|||
|
||||
# ─── Core build logic ─────────────────────────────────────────
|
||||
|
||||
_TITLE_RE = re.compile(r"<title>[^<]*</title>")
|
||||
|
||||
|
||||
def _patch_html_title(site_path: Path, sitename: str) -> None:
|
||||
index = site_path / "index.html"
|
||||
if not index.exists():
|
||||
return
|
||||
html = index.read_text(encoding="utf-8")
|
||||
new_html = _TITLE_RE.sub(f"<title>{sitename}</title>", html, count=1)
|
||||
if new_html != html:
|
||||
index.write_text(new_html, encoding="utf-8")
|
||||
|
||||
|
||||
def run_build(site_path: Path):
|
||||
"""Scan pages/ and posts/, write nav.yml and search.json. Raises ClickException on failure."""
|
||||
if not site_path.is_dir():
|
||||
|
|
@ -491,9 +523,13 @@ def run_build(site_path: Path):
|
|||
)
|
||||
click.echo(f" Wrote search.json ({len(live_pages) + len(post_records)} entries)")
|
||||
|
||||
_patch_html_title(site_path, cfg.get("sitename", ""))
|
||||
|
||||
pwa_enabled = str(cfg.get("pwa", "no")).lower() in ("yes", "true")
|
||||
if pwa_enabled:
|
||||
generate_pwa(site_path, cfg)
|
||||
else:
|
||||
cleanup_pwa(site_path)
|
||||
|
||||
asset_warnings = validate_assets(site_path, cfg)
|
||||
for w in asset_warnings:
|
||||
|
|
@ -509,6 +545,29 @@ def run_build(site_path: Path):
|
|||
|
||||
# ─── PWA generation ───────────────────────────────────────────
|
||||
|
||||
def cleanup_pwa(site_path: Path):
|
||||
"""When pwa: no, write a self-unregistering service worker and remove manifest.json.
|
||||
|
||||
Browsers keep the previously installed service worker active until a new one is
|
||||
installed. Writing a stub that immediately unregisters itself ensures any stale
|
||||
caching worker is evicted on the next visit after a pwa: yes → pwa: no change.
|
||||
"""
|
||||
sw = site_path / "service-worker.js"
|
||||
sw.write_text(
|
||||
"// mdcms: PWA disabled — unregisters any previously installed service worker.\n"
|
||||
"self.addEventListener('install', () => self.skipWaiting());\n"
|
||||
"self.addEventListener('activate', event => {\n"
|
||||
" event.waitUntil(self.registration.unregister());\n"
|
||||
"});\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
manifest = site_path / "manifest.json"
|
||||
if manifest.exists():
|
||||
manifest.unlink()
|
||||
click.echo(" Removed manifest.json (pwa: no)")
|
||||
click.echo(" Wrote service-worker.js (self-unregistering stub, pwa: no)")
|
||||
|
||||
|
||||
def generate_pwa(site_path: Path, cfg: dict):
|
||||
"""Generate manifest.json and service-worker.js when pwa: yes."""
|
||||
pwa_name = cfg.get("pwa-name", cfg.get("sitename", "MD-CMS Site"))
|
||||
|
|
|
|||
1
themes/Operating Systems/.gitkeep
Normal file
1
themes/Operating Systems/.gitkeep
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
76
themes/Operating Systems/os-adwaita.yaml
Normal file
76
themes/Operating Systems/os-adwaita.yaml
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
# mdcms v0.4 | DO NOT REMOVE THIS COMMENT
|
||||
# mdcms theme — os-adwaita
|
||||
# GNOME / Adwaita feel. Warm near-white paper, soft window-bg chrome,
|
||||
# the familiar Adwaita blue accent. Cantarell typeface throughout.
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Colours — based on published libadwaita tokens
|
||||
# light: window_bg_color #fafafa, view_bg #ffffff, accent #1c71d8 (blue 4),
|
||||
# fg ~rgba(0,0,0,0.8), dim ~rgba(0,0,0,0.55)
|
||||
# dark: window_bg_color #242424, view_bg #1e1e1e, accent #78aeed (blue 1)
|
||||
# ──────────────────────────────────
|
||||
light:
|
||||
accent: "#1C71D8"
|
||||
background: "#FFFFFF"
|
||||
nav-background: "#FAFAFA"
|
||||
text: "#202020"
|
||||
text-muted: "#5E5C64"
|
||||
|
||||
dark:
|
||||
accent: "#78AEED"
|
||||
background: "#1E1E1E"
|
||||
nav-background: "#242424"
|
||||
text: "#FFFFFF"
|
||||
text-muted: "#C0BFBC"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Semantic colours — Adwaita "named colors": green-3, yellow-5, red-3
|
||||
# ──────────────────────────────────
|
||||
colours-semantic:
|
||||
info: "#1C71D8"
|
||||
warning: "#E5A50A"
|
||||
success: "#26A269"
|
||||
error: "#C01C28"
|
||||
|
||||
colours-semantic-dark:
|
||||
info: "#78AEED"
|
||||
warning: "#F8E45C"
|
||||
success: "#57E389"
|
||||
error: "#F66151"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Callout defaults
|
||||
# ──────────────────────────────────
|
||||
callouts:
|
||||
info:
|
||||
icon: info
|
||||
primary-colour: "#1C71D8"
|
||||
background-colour: "#1C71D8"
|
||||
warning:
|
||||
icon: warning
|
||||
primary-colour: "#E5A50A"
|
||||
background-colour: "#E5A50A"
|
||||
success:
|
||||
icon: success
|
||||
primary-colour: "#26A269"
|
||||
background-colour: "#26A269"
|
||||
error:
|
||||
icon: error
|
||||
primary-colour: "#C01C28"
|
||||
background-colour: "#C01C28"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Typography
|
||||
# Cantarell is GNOME's UI typeface — humanist sans, slightly tall x-height.
|
||||
# Available on Google Fonts.
|
||||
# ──────────────────────────────────
|
||||
font-body: "google:Cantarell:400"
|
||||
font-heading: "google:Cantarell:700"
|
||||
font-size: 1.00
|
||||
line-height: 1.65
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Layout
|
||||
# ──────────────────────────────────
|
||||
main-width: 78em
|
||||
nav-width: 20em
|
||||
82
themes/Operating Systems/os-aero.yaml
Normal file
82
themes/Operating Systems/os-aero.yaml
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
# mdcms v0.4 | DO NOT REMOVE THIS COMMENT
|
||||
# mdcms theme — os-aero
|
||||
# Windows Vista / 7 "Aero Glass" feel. Pale glass-tinted blue chrome,
|
||||
# translucent sidebar vibes, bright sky-blue accent. The desktop your
|
||||
# laptop sweated to render in 2009.
|
||||
#
|
||||
# Colours approximated from the default Aero theme palette:
|
||||
# accent (taskbar / button glow) #1A78D4
|
||||
# glass tint #B8D6F0 (frosted blue)
|
||||
# window face #F0F4F9
|
||||
# text #1B1B1B
|
||||
# Aero Dark / "Aero Black" variant uses the same accent over near-black.
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Colours
|
||||
# ──────────────────────────────────
|
||||
light:
|
||||
accent: "#1A78D4"
|
||||
background: "#F4F9FE"
|
||||
nav-background: "#B8D6F0"
|
||||
text: "#1B1B1B"
|
||||
text-muted: "#525E6E"
|
||||
|
||||
dark:
|
||||
accent: "#4FC3F7"
|
||||
background: "#0F1A2A"
|
||||
nav-background: "#1A2A40"
|
||||
text: "#EAF2FC"
|
||||
text-muted: "#8FA8C4"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Semantic colours — Vista/7 standard hues
|
||||
# ──────────────────────────────────
|
||||
colours-semantic:
|
||||
info: "#1A78D4"
|
||||
warning: "#E59400"
|
||||
success: "#1E8C3F"
|
||||
error: "#C42B1C"
|
||||
|
||||
colours-semantic-dark:
|
||||
info: "#4FC3F7"
|
||||
warning: "#FFC74A"
|
||||
success: "#7AD18F"
|
||||
error: "#FF7A7A"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Callout defaults
|
||||
# ──────────────────────────────────
|
||||
callouts:
|
||||
info:
|
||||
icon: info
|
||||
primary-colour: "#1A78D4"
|
||||
background-colour: "#1A78D4"
|
||||
warning:
|
||||
icon: warning
|
||||
primary-colour: "#E59400"
|
||||
background-colour: "#E59400"
|
||||
success:
|
||||
icon: success
|
||||
primary-colour: "#1E8C3F"
|
||||
background-colour: "#1E8C3F"
|
||||
error:
|
||||
icon: error
|
||||
primary-colour: "#C42B1C"
|
||||
background-colour: "#C42B1C"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Typography
|
||||
# Inter portable default. Preferred: Segoe UI (Vista/7 default — first
|
||||
# Microsoft OS to ship it). Open Segoe-metric-compatible alternative:
|
||||
# "Selawik". Drop your TTFs in /fonts and swap font-body / font-heading.
|
||||
# ──────────────────────────────────
|
||||
font-body: "bunny:Inter:400"
|
||||
font-heading: "bunny:Inter:600"
|
||||
font-size: 1.00
|
||||
line-height: 1.55
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Layout
|
||||
# ──────────────────────────────────
|
||||
main-width: 78em
|
||||
nav-width: 20em
|
||||
82
themes/Operating Systems/os-amiga.yaml
Normal file
82
themes/Operating Systems/os-amiga.yaml
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
# mdcms v0.4 | DO NOT REMOVE THIS COMMENT
|
||||
# mdcms theme — os-amiga
|
||||
# Workbench 1.3 revival. Iconic blue/orange/white/black on Workbench grey.
|
||||
# Pixel-screen energy. The most idiosyncratic theme in the set.
|
||||
#
|
||||
# Original Workbench 1.x palette (4 colours, hardware-fixed):
|
||||
# #0055AA blue (window chrome, background)
|
||||
# #FFFFFF white
|
||||
# #000000 black
|
||||
# #FF8800 orange (highlights)
|
||||
# 2.x onward added the warm grey #AAAAAA.
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Colours
|
||||
# ──────────────────────────────────
|
||||
light:
|
||||
accent: "#FF8800"
|
||||
background: "#FFFFFF"
|
||||
nav-background: "#0055AA"
|
||||
text: "#000000"
|
||||
text-muted: "#555555"
|
||||
|
||||
dark:
|
||||
accent: "#FF8800"
|
||||
background: "#0055AA"
|
||||
nav-background: "#003D7A"
|
||||
text: "#FFFFFF"
|
||||
text-muted: "#AAC4E0"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Semantic colours
|
||||
# ──────────────────────────────────
|
||||
colours-semantic:
|
||||
info: "#0055AA"
|
||||
warning: "#FF8800"
|
||||
success: "#00AA55"
|
||||
error: "#CC0000"
|
||||
|
||||
colours-semantic-dark:
|
||||
info: "#7FB2E0"
|
||||
warning: "#FFB04A"
|
||||
success: "#7FD9A4"
|
||||
error: "#FF6B6B"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Callout defaults
|
||||
# ──────────────────────────────────
|
||||
callouts:
|
||||
info:
|
||||
icon: info
|
||||
primary-colour: "#0055AA"
|
||||
background-colour: "#0055AA"
|
||||
warning:
|
||||
icon: warning
|
||||
primary-colour: "#FF8800"
|
||||
background-colour: "#FF8800"
|
||||
success:
|
||||
icon: success
|
||||
primary-colour: "#00AA55"
|
||||
background-colour: "#00AA55"
|
||||
error:
|
||||
icon: error
|
||||
primary-colour: "#CC0000"
|
||||
background-colour: "#CC0000"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Typography
|
||||
# VT323 portable default for the pixel-screen feel.
|
||||
# Preferred for true Workbench-fidelity: "Topaz" or "Topaz New" (free
|
||||
# pixel font replicas of the Amiga system font, widely available as TTF).
|
||||
# For a more readable modern take, swap to "bunny:IBM Plex Mono:400".
|
||||
# ──────────────────────────────────
|
||||
font-body: "google:VT323:400"
|
||||
font-heading: "google:VT323:400"
|
||||
font-size: 1.15
|
||||
line-height: 1.45
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Layout
|
||||
# ──────────────────────────────────
|
||||
main-width: 76em
|
||||
nav-width: 20em
|
||||
80
themes/Operating Systems/os-beos.yaml
Normal file
80
themes/Operating Systems/os-beos.yaml
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
# mdcms v0.4 | DO NOT REMOVE THIS COMMENT
|
||||
# mdcms theme — os-beos
|
||||
# BeOS / Haiku revival. Iconic yellow window tab, cream paper,
|
||||
# navy text. The friendly weird desktop of 1996 that won't quit.
|
||||
#
|
||||
# Colours from Haiku's default "Beige" palette:
|
||||
# panel background #DCDCDC
|
||||
# document-tab yellow #FFCB00
|
||||
# text #000000
|
||||
# accent (link/button) #336699 navy
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Colours
|
||||
# ──────────────────────────────────
|
||||
light:
|
||||
accent: "#336699"
|
||||
background: "#F8F8E8"
|
||||
nav-background: "#FFCB00"
|
||||
text: "#000000"
|
||||
text-muted: "#4A4A3E"
|
||||
|
||||
dark:
|
||||
accent: "#FFCB00"
|
||||
background: "#1A1A14"
|
||||
nav-background: "#2A2515"
|
||||
text: "#F8F8E8"
|
||||
text-muted: "#A89E70"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Semantic colours
|
||||
# ──────────────────────────────────
|
||||
colours-semantic:
|
||||
info: "#336699"
|
||||
warning: "#CC7700"
|
||||
success: "#3F8F3F"
|
||||
error: "#B22222"
|
||||
|
||||
colours-semantic-dark:
|
||||
info: "#7FB2E0"
|
||||
warning: "#FFCB00"
|
||||
success: "#86C58B"
|
||||
error: "#E07A7A"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Callout defaults
|
||||
# ──────────────────────────────────
|
||||
callouts:
|
||||
info:
|
||||
icon: info
|
||||
primary-colour: "#336699"
|
||||
background-colour: "#336699"
|
||||
warning:
|
||||
icon: warning
|
||||
primary-colour: "#CC7700"
|
||||
background-colour: "#CC7700"
|
||||
success:
|
||||
icon: success
|
||||
primary-colour: "#3F8F3F"
|
||||
background-colour: "#3F8F3F"
|
||||
error:
|
||||
icon: error
|
||||
primary-colour: "#B22222"
|
||||
background-colour: "#B22222"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Typography
|
||||
# Noto Sans is the portable default and Haiku's actual UI font.
|
||||
# Preferred (BeOS original): Swis721 BT / "Be Sans" (proprietary, paid).
|
||||
# DejaVu Sans is a very close free alternative.
|
||||
# ──────────────────────────────────
|
||||
font-body: "bunny:Noto Sans:400"
|
||||
font-heading: "bunny:Noto Sans:700"
|
||||
font-size: 1.00
|
||||
line-height: 1.55
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Layout
|
||||
# ──────────────────────────────────
|
||||
main-width: 78em
|
||||
nav-width: 20em
|
||||
86
themes/Operating Systems/os-breeze.yaml
Normal file
86
themes/Operating Systems/os-breeze.yaml
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# mdcms v0.4 | DO NOT REMOVE THIS COMMENT
|
||||
# mdcms theme — os-breeze
|
||||
# KDE Plasma / Breeze feel. Cool neutral chrome, the famous Plasma blue
|
||||
# accent. Breeze is intentionally subtle — light, low-saturation, with
|
||||
# just a touch of cool grey.
|
||||
#
|
||||
# Colours from KDE's Breeze stylesheet (qss + colour scheme):
|
||||
# accent (Highlight) #3DAEE9
|
||||
# view-background-color #FCFCFC
|
||||
# window-background-color #EFF0F1
|
||||
# foreground (Text) #232629
|
||||
# foreground-inactive #7F8C8D
|
||||
# Breeze Dark:
|
||||
# view-background-color #1B1E20
|
||||
# window-background-color #232629
|
||||
# foreground #FCFCFC
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Colours
|
||||
# ──────────────────────────────────
|
||||
light:
|
||||
accent: "#3DAEE9"
|
||||
background: "#FCFCFC"
|
||||
nav-background: "#EFF0F1"
|
||||
text: "#232629"
|
||||
text-muted: "#7F8C8D"
|
||||
|
||||
dark:
|
||||
accent: "#3DAEE9"
|
||||
background: "#1B1E20"
|
||||
nav-background: "#232629"
|
||||
text: "#FCFCFC"
|
||||
text-muted: "#A1A9B1"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Semantic colours — Breeze "positive / neutral / negative" tones
|
||||
# ──────────────────────────────────
|
||||
colours-semantic:
|
||||
info: "#3DAEE9"
|
||||
warning: "#F67400"
|
||||
success: "#27AE60"
|
||||
error: "#DA4453"
|
||||
|
||||
colours-semantic-dark:
|
||||
info: "#61C1F0"
|
||||
warning: "#F8A04A"
|
||||
success: "#56C883"
|
||||
error: "#ED7077"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Callout defaults
|
||||
# ──────────────────────────────────
|
||||
callouts:
|
||||
info:
|
||||
icon: info
|
||||
primary-colour: "#3DAEE9"
|
||||
background-colour: "#3DAEE9"
|
||||
warning:
|
||||
icon: warning
|
||||
primary-colour: "#F67400"
|
||||
background-colour: "#F67400"
|
||||
success:
|
||||
icon: success
|
||||
primary-colour: "#27AE60"
|
||||
background-colour: "#27AE60"
|
||||
error:
|
||||
icon: error
|
||||
primary-colour: "#DA4453"
|
||||
background-colour: "#DA4453"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Typography
|
||||
# Noto Sans is the portable default and KDE's current UI font.
|
||||
# Preferred (classic Plasma 4 era): "Oxygen Sans" — open SIL-licensed,
|
||||
# available on Google Fonts as "Oxygen".
|
||||
# ──────────────────────────────────
|
||||
font-body: "bunny:Noto Sans:400"
|
||||
font-heading: "bunny:Noto Sans:600"
|
||||
font-size: 1.00
|
||||
line-height: 1.6
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Layout
|
||||
# ──────────────────────────────────
|
||||
main-width: 78em
|
||||
nav-width: 20em
|
||||
85
themes/Operating Systems/os-chromeos.yaml
Normal file
85
themes/Operating Systems/os-chromeos.yaml
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
# mdcms v0.4 | DO NOT REMOVE THIS COMMENT
|
||||
# mdcms theme — os-chromeos
|
||||
# ChromeOS feel. Bright white shelf, soft Google-grey surface,
|
||||
# Google Blue accent. Material-rooted but its own dialect.
|
||||
#
|
||||
# Colours from Google's public ChromeOS / Material reference:
|
||||
# Google Blue 600 #1A73E8 (accent light)
|
||||
# Google Blue 200 #8AB4F8 (accent dark)
|
||||
# Surface #FFFFFF / #202124
|
||||
# Surface variant #F1F3F4 / #292A2D
|
||||
# On-surface #202124 / #E8EAED
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Colours
|
||||
# ──────────────────────────────────
|
||||
light:
|
||||
accent: "#1A73E8"
|
||||
background: "#FFFFFF"
|
||||
nav-background: "#F1F3F4"
|
||||
text: "#202124"
|
||||
text-muted: "#5F6368"
|
||||
|
||||
dark:
|
||||
accent: "#8AB4F8"
|
||||
background: "#202124"
|
||||
nav-background: "#292A2D"
|
||||
text: "#E8EAED"
|
||||
text-muted: "#9AA0A6"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Semantic colours — Google standard hues
|
||||
# Green 700 #1E8E3E / Green 300 #81C995
|
||||
# Yellow 700 #F29900 / Yellow 300 #FDD663
|
||||
# Red 600 #D93025 / Red 300 #F28B82
|
||||
# ──────────────────────────────────
|
||||
colours-semantic:
|
||||
info: "#1A73E8"
|
||||
warning: "#F29900"
|
||||
success: "#1E8E3E"
|
||||
error: "#D93025"
|
||||
|
||||
colours-semantic-dark:
|
||||
info: "#8AB4F8"
|
||||
warning: "#FDD663"
|
||||
success: "#81C995"
|
||||
error: "#F28B82"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Callout defaults
|
||||
# ──────────────────────────────────
|
||||
callouts:
|
||||
info:
|
||||
icon: info
|
||||
primary-colour: "#1A73E8"
|
||||
background-colour: "#1A73E8"
|
||||
warning:
|
||||
icon: warning
|
||||
primary-colour: "#F29900"
|
||||
background-colour: "#F29900"
|
||||
success:
|
||||
icon: success
|
||||
primary-colour: "#1E8E3E"
|
||||
background-colour: "#1E8E3E"
|
||||
error:
|
||||
icon: error
|
||||
primary-colour: "#D93025"
|
||||
background-colour: "#D93025"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Typography
|
||||
# Roboto is the portable default and ChromeOS's body font.
|
||||
# Preferred for headings: "Google Sans" (proprietary, restricted).
|
||||
# Open near-equivalent for Google Sans display: "Product Sans"
|
||||
# — also restricted; use Roboto for both and you'll be fine.
|
||||
# ──────────────────────────────────
|
||||
font-body: "bunny:Roboto:400"
|
||||
font-heading: "bunny:Roboto:500"
|
||||
font-size: 1.00
|
||||
line-height: 1.55
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Layout
|
||||
# ──────────────────────────────────
|
||||
main-width: 78em
|
||||
nav-width: 20em
|
||||
72
themes/Operating Systems/os-cupertino-graphite.yaml
Normal file
72
themes/Operating Systems/os-cupertino-graphite.yaml
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
# mdcms v0.4 | DO NOT REMOVE THIS COMMENT
|
||||
# mdcms theme — os-cupertino-graphite
|
||||
# Mac desktop, Graphite accent variant — for people who switch the system
|
||||
# tint to "Graphite" because they're serious. Pure neutral chrome.
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Colours
|
||||
# ──────────────────────────────────
|
||||
light:
|
||||
accent: "#6E6E73"
|
||||
background: "#FFFFFF"
|
||||
nav-background: "#F2F2F7"
|
||||
text: "#1D1D1F"
|
||||
text-muted: "#6E6E73"
|
||||
|
||||
dark:
|
||||
accent: "#98989D"
|
||||
background: "#1E1E1E"
|
||||
nav-background: "#2C2C2E"
|
||||
text: "#F5F5F7"
|
||||
text-muted: "#98989D"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Semantic colours
|
||||
# ──────────────────────────────────
|
||||
colours-semantic:
|
||||
info: "#6E6E73"
|
||||
warning: "#FF9500"
|
||||
success: "#34C759"
|
||||
error: "#FF3B30"
|
||||
|
||||
colours-semantic-dark:
|
||||
info: "#98989D"
|
||||
warning: "#FF9F0A"
|
||||
success: "#30D158"
|
||||
error: "#FF453A"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Callout defaults
|
||||
# ──────────────────────────────────
|
||||
callouts:
|
||||
info:
|
||||
icon: info
|
||||
primary-colour: "#6E6E73"
|
||||
background-colour: "#6E6E73"
|
||||
warning:
|
||||
icon: warning
|
||||
primary-colour: "#FF9500"
|
||||
background-colour: "#FF9500"
|
||||
success:
|
||||
icon: success
|
||||
primary-colour: "#34C759"
|
||||
background-colour: "#34C759"
|
||||
error:
|
||||
icon: error
|
||||
primary-colour: "#FF3B30"
|
||||
background-colour: "#FF3B30"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Typography
|
||||
# Inter portable default. Preferred: SF Pro Text / SF Pro Display.
|
||||
# ──────────────────────────────────
|
||||
font-body: "bunny:Inter:400"
|
||||
font-heading: "bunny:Inter:600"
|
||||
font-size: 1.00
|
||||
line-height: 1.55
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Layout
|
||||
# ──────────────────────────────────
|
||||
main-width: 78em
|
||||
nav-width: 20em
|
||||
85
themes/Operating Systems/os-cupertino.yaml
Normal file
85
themes/Operating Systems/os-cupertino.yaml
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
# mdcms v0.4 | DO NOT REMOVE THIS COMMENT
|
||||
# mdcms theme — os-cupertino
|
||||
# Mac desktop feel. Bright white paper, light-platinum sidebar,
|
||||
# vivid system blue accent. Dark mode goes near-black with brighter blue.
|
||||
#
|
||||
# Colours from Apple's publicly-documented system colour palette
|
||||
# (developer.apple.com → Human Interface Guidelines → Color):
|
||||
# systemBlue light #007AFF dark #0A84FF
|
||||
# secondarySystemBackground (light) #F2F2F7
|
||||
# systemBackground (dark) #000000
|
||||
# secondarySystemBackground (dark) #1C1C1E
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Colours
|
||||
# ──────────────────────────────────
|
||||
light:
|
||||
accent: "#007AFF"
|
||||
background: "#FFFFFF"
|
||||
nav-background: "#F2F2F7"
|
||||
text: "#1D1D1F"
|
||||
text-muted: "#6E6E73"
|
||||
|
||||
dark:
|
||||
accent: "#0A84FF"
|
||||
background: "#1E1E1E"
|
||||
nav-background: "#2C2C2E"
|
||||
text: "#F5F5F7"
|
||||
text-muted: "#98989D"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Semantic colours — Apple system colours (light / dark)
|
||||
# green #34C759 / #30D158
|
||||
# orange #FF9500 / #FF9F0A
|
||||
# red #FF3B30 / #FF453A
|
||||
# ──────────────────────────────────
|
||||
colours-semantic:
|
||||
info: "#007AFF"
|
||||
warning: "#FF9500"
|
||||
success: "#34C759"
|
||||
error: "#FF3B30"
|
||||
|
||||
colours-semantic-dark:
|
||||
info: "#0A84FF"
|
||||
warning: "#FF9F0A"
|
||||
success: "#30D158"
|
||||
error: "#FF453A"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Callout defaults
|
||||
# ──────────────────────────────────
|
||||
callouts:
|
||||
info:
|
||||
icon: info
|
||||
primary-colour: "#007AFF"
|
||||
background-colour: "#007AFF"
|
||||
warning:
|
||||
icon: warning
|
||||
primary-colour: "#FF9500"
|
||||
background-colour: "#FF9500"
|
||||
success:
|
||||
icon: success
|
||||
primary-colour: "#34C759"
|
||||
background-colour: "#34C759"
|
||||
error:
|
||||
icon: error
|
||||
primary-colour: "#FF3B30"
|
||||
background-colour: "#FF3B30"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Typography
|
||||
# Inter is the portable default — close metrics to SF.
|
||||
# Preferred on Apple platforms: SF Pro Text (body), SF Pro Display (headings).
|
||||
# Drop your own TTFs in /fonts and change font-body / font-heading to
|
||||
# "local:SF Pro Text:400" etc.
|
||||
# ──────────────────────────────────
|
||||
font-body: "bunny:Inter:400"
|
||||
font-heading: "bunny:Inter:600"
|
||||
font-size: 1.00
|
||||
line-height: 1.55
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Layout
|
||||
# ──────────────────────────────────
|
||||
main-width: 78em
|
||||
nav-width: 20em
|
||||
81
themes/Operating Systems/os-elementary.yaml
Normal file
81
themes/Operating Systems/os-elementary.yaml
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
# mdcms v0.4 | DO NOT REMOVE THIS COMMENT
|
||||
# mdcms theme — os-elementary
|
||||
# elementary OS feel. Calm paper, "Slate" silver chrome,
|
||||
# Blueberry-blue accent. Inter is their actual UI typeface (Inter Variable).
|
||||
#
|
||||
# Colours from elementary's published Stylesheet (Granite/Pantheon):
|
||||
# Blueberry 500 #3689E6 (accent)
|
||||
# Slate 100 #F4F4F4 / Slate 700 #333333
|
||||
# Strawberry/Lime/Banana/Cherry are the named semantic palette.
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Colours
|
||||
# ──────────────────────────────────
|
||||
light:
|
||||
accent: "#3689E6"
|
||||
background: "#FAFAFA"
|
||||
nav-background: "#F4F4F4"
|
||||
text: "#333333"
|
||||
text-muted: "#7E8087"
|
||||
|
||||
dark:
|
||||
accent: "#64BAFF"
|
||||
background: "#1A1A1A"
|
||||
nav-background: "#262626"
|
||||
text: "#FFFFFF"
|
||||
text-muted: "#A6A6A6"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Semantic colours — elementary named palette
|
||||
# Lime 500 #68B723 success
|
||||
# Banana 500 #F9C440 warning
|
||||
# Strawberry 500 #C6262E error
|
||||
# Blueberry 500 #3689E6 info
|
||||
# ──────────────────────────────────
|
||||
colours-semantic:
|
||||
info: "#3689E6"
|
||||
warning: "#F9C440"
|
||||
success: "#68B723"
|
||||
error: "#C6262E"
|
||||
|
||||
colours-semantic-dark:
|
||||
info: "#64BAFF"
|
||||
warning: "#FFD66B"
|
||||
success: "#9BDB4D"
|
||||
error: "#E14852"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Callout defaults
|
||||
# ──────────────────────────────────
|
||||
callouts:
|
||||
info:
|
||||
icon: info
|
||||
primary-colour: "#3689E6"
|
||||
background-colour: "#3689E6"
|
||||
warning:
|
||||
icon: warning
|
||||
primary-colour: "#F9C440"
|
||||
background-colour: "#F9C440"
|
||||
success:
|
||||
icon: success
|
||||
primary-colour: "#68B723"
|
||||
background-colour: "#68B723"
|
||||
error:
|
||||
icon: error
|
||||
primary-colour: "#C6262E"
|
||||
background-colour: "#C6262E"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Typography
|
||||
# Inter is elementary's actual UI typeface — used as-is.
|
||||
# ──────────────────────────────────
|
||||
font-body: "bunny:Inter:400"
|
||||
font-heading: "bunny:Inter:600"
|
||||
font-size: 1.00
|
||||
line-height: 1.55
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Layout
|
||||
# ──────────────────────────────────
|
||||
main-width: 78em
|
||||
nav-width: 20em
|
||||
71
themes/Operating Systems/os-fluent-dark.yaml
Normal file
71
themes/Operating Systems/os-fluent-dark.yaml
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# mdcms v0.4 | DO NOT REMOVE THIS COMMENT
|
||||
# mdcms theme — os-fluent-dark
|
||||
# Windows 11 dark mica. Same accent system, dark-first defaults.
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Colours
|
||||
# ──────────────────────────────────
|
||||
light:
|
||||
accent: "#005FB8"
|
||||
background: "#202020"
|
||||
nav-background: "#2C2C2C"
|
||||
text: "#FFFFFF"
|
||||
text-muted: "#C7C7C7"
|
||||
|
||||
dark:
|
||||
accent: "#60CDFF"
|
||||
background: "#1A1A1A"
|
||||
nav-background: "#202020"
|
||||
text: "#FFFFFF"
|
||||
text-muted: "#C7C7C7"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Semantic colours
|
||||
# ──────────────────────────────────
|
||||
colours-semantic:
|
||||
info: "#60CDFF"
|
||||
warning: "#FCE100"
|
||||
success: "#6CCB5F"
|
||||
error: "#FF99A4"
|
||||
|
||||
colours-semantic-dark:
|
||||
info: "#60CDFF"
|
||||
warning: "#FCE100"
|
||||
success: "#6CCB5F"
|
||||
error: "#FF99A4"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Callout defaults
|
||||
# ──────────────────────────────────
|
||||
callouts:
|
||||
info:
|
||||
icon: info
|
||||
primary-colour: "#60CDFF"
|
||||
background-colour: "#60CDFF"
|
||||
warning:
|
||||
icon: warning
|
||||
primary-colour: "#FCE100"
|
||||
background-colour: "#FCE100"
|
||||
success:
|
||||
icon: success
|
||||
primary-colour: "#6CCB5F"
|
||||
background-colour: "#6CCB5F"
|
||||
error:
|
||||
icon: error
|
||||
primary-colour: "#FF99A4"
|
||||
background-colour: "#FF99A4"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Typography
|
||||
# Inter portable default. Preferred: Segoe UI Variable / Selawik.
|
||||
# ──────────────────────────────────
|
||||
font-body: "bunny:Inter:400"
|
||||
font-heading: "bunny:Inter:600"
|
||||
font-size: 1.00
|
||||
line-height: 1.55
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Layout
|
||||
# ──────────────────────────────────
|
||||
main-width: 78em
|
||||
nav-width: 20em
|
||||
81
themes/Operating Systems/os-fluent.yaml
Normal file
81
themes/Operating Systems/os-fluent.yaml
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
# mdcms v0.4 | DO NOT REMOVE THIS COMMENT
|
||||
# mdcms theme — os-fluent
|
||||
# Windows 11 / Fluent feel. Cool near-white "Mica" paper, light gray sidebar,
|
||||
# Windows accent blue. Dark mode uses the dark mica neutrals.
|
||||
#
|
||||
# Colours from Microsoft's public Fluent 2 design tokens:
|
||||
# accent (light): #005FB8 accent (dark): #60CDFF
|
||||
# neutralBackground1 light #F9F9F9 / sidebar #F3F3F3
|
||||
# neutralBackground1 dark #202020 / sidebar #2C2C2C
|
||||
# neutralForeground1 light #1A1A1A / dark #FFFFFF
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Colours
|
||||
# ──────────────────────────────────
|
||||
light:
|
||||
accent: "#005FB8"
|
||||
background: "#F9F9F9"
|
||||
nav-background: "#F3F3F3"
|
||||
text: "#1A1A1A"
|
||||
text-muted: "#5C5C5C"
|
||||
|
||||
dark:
|
||||
accent: "#60CDFF"
|
||||
background: "#202020"
|
||||
nav-background: "#2C2C2C"
|
||||
text: "#FFFFFF"
|
||||
text-muted: "#C7C7C7"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Semantic colours — Fluent persona / shared colours
|
||||
# ──────────────────────────────────
|
||||
colours-semantic:
|
||||
info: "#005FB8"
|
||||
warning: "#9D5D00"
|
||||
success: "#107C10"
|
||||
error: "#C42B1C"
|
||||
|
||||
colours-semantic-dark:
|
||||
info: "#60CDFF"
|
||||
warning: "#FCE100"
|
||||
success: "#6CCB5F"
|
||||
error: "#FF99A4"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Callout defaults
|
||||
# ──────────────────────────────────
|
||||
callouts:
|
||||
info:
|
||||
icon: info
|
||||
primary-colour: "#005FB8"
|
||||
background-colour: "#005FB8"
|
||||
warning:
|
||||
icon: warning
|
||||
primary-colour: "#9D5D00"
|
||||
background-colour: "#9D5D00"
|
||||
success:
|
||||
icon: success
|
||||
primary-colour: "#107C10"
|
||||
background-colour: "#107C10"
|
||||
error:
|
||||
icon: error
|
||||
primary-colour: "#C42B1C"
|
||||
background-colour: "#C42B1C"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Typography
|
||||
# Inter portable default. Preferred on Windows: Segoe UI Variable Text
|
||||
# (body) / Segoe UI Variable Display (headings). Open alternatives:
|
||||
# "Selawik" or "Selawik Semilight" (Microsoft's Segoe-metric-compatible
|
||||
# release). Drop TTFs in /fonts and swap to "local:Segoe UI Variable:400".
|
||||
# ──────────────────────────────────
|
||||
font-body: "bunny:Inter:400"
|
||||
font-heading: "bunny:Inter:600"
|
||||
font-size: 1.00
|
||||
line-height: 1.55
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Layout
|
||||
# ──────────────────────────────────
|
||||
main-width: 78em
|
||||
nav-width: 20em
|
||||
80
themes/Operating Systems/os-ios.yaml
Normal file
80
themes/Operating Systems/os-ios.yaml
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
# mdcms v0.4 | DO NOT REMOVE THIS COMMENT
|
||||
# mdcms theme — os-ios
|
||||
# iPhone feel. Crisp white paper, grouped-table grey sidebar,
|
||||
# iOS system blue. Dark mode goes true-black like the OLED dark mode.
|
||||
#
|
||||
# Colours from Apple's iOS system colour palette:
|
||||
# systemBlue light #007AFF / dark #0A84FF
|
||||
# systemBackground light #FFFFFF / dark #000000
|
||||
# secondarySystemBackground light #F2F2F7 / dark #1C1C1E
|
||||
# label light #000000 / dark #FFFFFF
|
||||
# secondaryLabel light #3C3C43 60% / dark #EBEBF5 60%
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Colours
|
||||
# ──────────────────────────────────
|
||||
light:
|
||||
accent: "#007AFF"
|
||||
background: "#FFFFFF"
|
||||
nav-background: "#F2F2F7"
|
||||
text: "#000000"
|
||||
text-muted: "#8E8E93"
|
||||
|
||||
dark:
|
||||
accent: "#0A84FF"
|
||||
background: "#000000"
|
||||
nav-background: "#1C1C1E"
|
||||
text: "#FFFFFF"
|
||||
text-muted: "#8E8E93"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Semantic colours — iOS system colours
|
||||
# ──────────────────────────────────
|
||||
colours-semantic:
|
||||
info: "#007AFF"
|
||||
warning: "#FF9500"
|
||||
success: "#34C759"
|
||||
error: "#FF3B30"
|
||||
|
||||
colours-semantic-dark:
|
||||
info: "#0A84FF"
|
||||
warning: "#FF9F0A"
|
||||
success: "#30D158"
|
||||
error: "#FF453A"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Callout defaults
|
||||
# ──────────────────────────────────
|
||||
callouts:
|
||||
info:
|
||||
icon: info
|
||||
primary-colour: "#007AFF"
|
||||
background-colour: "#007AFF"
|
||||
warning:
|
||||
icon: warning
|
||||
primary-colour: "#FF9500"
|
||||
background-colour: "#FF9500"
|
||||
success:
|
||||
icon: success
|
||||
primary-colour: "#34C759"
|
||||
background-colour: "#34C759"
|
||||
error:
|
||||
icon: error
|
||||
primary-colour: "#FF3B30"
|
||||
background-colour: "#FF3B30"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Typography
|
||||
# Inter portable default. Preferred on iOS: SF Pro Text (body),
|
||||
# SF Pro Display (headings ≥20pt), SF Pro Rounded for friendly UI.
|
||||
# ──────────────────────────────────
|
||||
font-body: "bunny:Inter:400"
|
||||
font-heading: "bunny:Inter:600"
|
||||
font-size: 1.00
|
||||
line-height: 1.5
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Layout
|
||||
# ──────────────────────────────────
|
||||
main-width: 78em
|
||||
nav-width: 20em
|
||||
80
themes/Operating Systems/os-material-you.yaml
Normal file
80
themes/Operating Systems/os-material-you.yaml
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
# mdcms v0.4 | DO NOT REMOVE THIS COMMENT
|
||||
# mdcms theme — os-material-you
|
||||
# Android / Material 3 feel. Tonal-palette neutrals built on the M3
|
||||
# baseline purple (#6750A4). Pale lavender paper, soft surface chrome.
|
||||
#
|
||||
# Colours from the Material 3 baseline scheme:
|
||||
# primary light #6750A4 / dark #D0BCFF
|
||||
# surface light #FEF7FF / dark #141218
|
||||
# surface-container-low light #F7F2FA / dark #1D1B20
|
||||
# on-surface light #1D1B20 / dark #E6E0E9
|
||||
# on-surface-variant light #49454F / dark #CAC4D0
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Colours
|
||||
# ──────────────────────────────────
|
||||
light:
|
||||
accent: "#6750A4"
|
||||
background: "#FEF7FF"
|
||||
nav-background: "#F7F2FA"
|
||||
text: "#1D1B20"
|
||||
text-muted: "#49454F"
|
||||
|
||||
dark:
|
||||
accent: "#D0BCFF"
|
||||
background: "#141218"
|
||||
nav-background: "#1D1B20"
|
||||
text: "#E6E0E9"
|
||||
text-muted: "#CAC4D0"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Semantic colours — M3 baseline error + standard tertiary/green/yellow
|
||||
# ──────────────────────────────────
|
||||
colours-semantic:
|
||||
info: "#6750A4"
|
||||
warning: "#9A6700"
|
||||
success: "#386A20"
|
||||
error: "#B3261E"
|
||||
|
||||
colours-semantic-dark:
|
||||
info: "#D0BCFF"
|
||||
warning: "#EFBE6E"
|
||||
success: "#A6D388"
|
||||
error: "#F2B8B5"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Callout defaults
|
||||
# ──────────────────────────────────
|
||||
callouts:
|
||||
info:
|
||||
icon: info
|
||||
primary-colour: "#6750A4"
|
||||
background-colour: "#6750A4"
|
||||
warning:
|
||||
icon: warning
|
||||
primary-colour: "#9A6700"
|
||||
background-colour: "#9A6700"
|
||||
success:
|
||||
icon: success
|
||||
primary-colour: "#386A20"
|
||||
background-colour: "#386A20"
|
||||
error:
|
||||
icon: error
|
||||
primary-colour: "#B3261E"
|
||||
background-colour: "#B3261E"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Typography
|
||||
# Roboto is the portable default and the Material default.
|
||||
# Preferred: Roboto Flex (variable) or Google Sans for headings.
|
||||
# ──────────────────────────────────
|
||||
font-body: "bunny:Roboto:400"
|
||||
font-heading: "bunny:Roboto:500"
|
||||
font-size: 1.00
|
||||
line-height: 1.55
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Layout
|
||||
# ──────────────────────────────────
|
||||
main-width: 78em
|
||||
nav-width: 20em
|
||||
84
themes/Operating Systems/os-nextstep.yaml
Normal file
84
themes/Operating Systems/os-nextstep.yaml
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
# mdcms v0.4 | DO NOT REMOVE THIS COMMENT
|
||||
# mdcms theme — os-nextstep
|
||||
# NeXTSTEP / OPENSTEP revival. Cool 50% greys everywhere, jet-black
|
||||
# title chrome, and the famous NeXT magenta as the accent. Heavy,
|
||||
# considered, very 1989 Cube energy.
|
||||
#
|
||||
# Colours approximated from NeXTSTEP's 2-bit greyscale + colour passes:
|
||||
# #555555 dark window chrome (title bars, scrollbar wells)
|
||||
# #AAAAAA panel face (50% grey)
|
||||
# #DDDDDD highlight
|
||||
# #000000 ink
|
||||
# #C72A86 NeXT magenta (used in logo + accents)
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Colours
|
||||
# ──────────────────────────────────
|
||||
light:
|
||||
accent: "#C72A86"
|
||||
background: "#DDDDDD"
|
||||
nav-background: "#555555"
|
||||
text: "#000000"
|
||||
text-muted: "#5A5A5A"
|
||||
|
||||
dark:
|
||||
accent: "#E579B5"
|
||||
background: "#1A1A1A"
|
||||
nav-background: "#000000"
|
||||
text: "#DDDDDD"
|
||||
text-muted: "#A0A0A0"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Semantic colours — kept restrained, in keeping with the grey-on-grey
|
||||
# NeXTSTEP discipline.
|
||||
# ──────────────────────────────────
|
||||
colours-semantic:
|
||||
info: "#3A6FA5"
|
||||
warning: "#A06A00"
|
||||
success: "#3E7A3E"
|
||||
error: "#A02828"
|
||||
|
||||
colours-semantic-dark:
|
||||
info: "#88AED9"
|
||||
warning: "#D9B36B"
|
||||
success: "#86C58B"
|
||||
error: "#E07A7A"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Callout defaults
|
||||
# ──────────────────────────────────
|
||||
callouts:
|
||||
info:
|
||||
icon: info
|
||||
primary-colour: "#3A6FA5"
|
||||
background-colour: "#3A6FA5"
|
||||
warning:
|
||||
icon: warning
|
||||
primary-colour: "#A06A00"
|
||||
background-colour: "#A06A00"
|
||||
success:
|
||||
icon: success
|
||||
primary-colour: "#3E7A3E"
|
||||
background-colour: "#3E7A3E"
|
||||
error:
|
||||
icon: error
|
||||
primary-colour: "#A02828"
|
||||
background-colour: "#A02828"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Typography
|
||||
# Inter portable default. NeXTSTEP used Helvetica system-wide.
|
||||
# Preferred: "Helvetica Neue" (Apple system) — falls back to Inter.
|
||||
# For the more brutalist OPENSTEP feel try "bunny:Helvetica:400"
|
||||
# if you have it installed locally.
|
||||
# ──────────────────────────────────
|
||||
font-body: "bunny:Inter:400"
|
||||
font-heading: "bunny:Inter:700"
|
||||
font-size: 1.00
|
||||
line-height: 1.55
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Layout
|
||||
# ──────────────────────────────────
|
||||
main-width: 76em
|
||||
nav-width: 20em
|
||||
79
themes/Operating Systems/os-pop.yaml
Normal file
79
themes/Operating Systems/os-pop.yaml
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# mdcms v0.4 | DO NOT REMOVE THIS COMMENT
|
||||
# mdcms theme — os-pop
|
||||
# Pop!_OS feel. Warm Cosmic-grey chrome, signature Pop orange accent.
|
||||
# System76's GTK-rooted desktop with its own distinctive warmth.
|
||||
#
|
||||
# Colours from System76's public Pop palette:
|
||||
# Pop Orange #FAA41A (primary accent)
|
||||
# Cosmic Light bg #F2F2F2 / surface #FAFAFA
|
||||
# Cosmic Dark bg #2D2D2D / surface #232323
|
||||
# Text light #181818 / dark #F2F2F2
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Colours
|
||||
# ──────────────────────────────────
|
||||
light:
|
||||
accent: "#FAA41A"
|
||||
background: "#FAFAFA"
|
||||
nav-background: "#F2F2F2"
|
||||
text: "#181818"
|
||||
text-muted: "#5C5C5C"
|
||||
|
||||
dark:
|
||||
accent: "#FAA41A"
|
||||
background: "#232323"
|
||||
nav-background: "#2D2D2D"
|
||||
text: "#F2F2F2"
|
||||
text-muted: "#A8A8A8"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Semantic colours — Pop palette greens/yellows/reds with the warm cast
|
||||
# ──────────────────────────────────
|
||||
colours-semantic:
|
||||
info: "#1B6091"
|
||||
warning: "#FAA41A"
|
||||
success: "#73C48F"
|
||||
error: "#F15D22"
|
||||
|
||||
colours-semantic-dark:
|
||||
info: "#88B8DC"
|
||||
warning: "#FFC664"
|
||||
success: "#9BD7AF"
|
||||
error: "#FF8A5C"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Callout defaults
|
||||
# ──────────────────────────────────
|
||||
callouts:
|
||||
info:
|
||||
icon: info
|
||||
primary-colour: "#1B6091"
|
||||
background-colour: "#1B6091"
|
||||
warning:
|
||||
icon: warning
|
||||
primary-colour: "#FAA41A"
|
||||
background-colour: "#FAA41A"
|
||||
success:
|
||||
icon: success
|
||||
primary-colour: "#73C48F"
|
||||
background-colour: "#73C48F"
|
||||
error:
|
||||
icon: error
|
||||
primary-colour: "#F15D22"
|
||||
background-colour: "#F15D22"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Typography
|
||||
# Fira Sans is the portable default and Pop!_OS's actual UI font.
|
||||
# Available on Google Fonts. Pop also ships Fira Mono for code.
|
||||
# ──────────────────────────────────
|
||||
font-body: "bunny:Fira Sans:400"
|
||||
font-heading: "bunny:Fira Sans:600"
|
||||
font-size: 1.00
|
||||
line-height: 1.6
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Layout
|
||||
# ──────────────────────────────────
|
||||
main-width: 78em
|
||||
nav-width: 20em
|
||||
84
themes/Operating Systems/os-system-7.yaml
Normal file
84
themes/Operating Systems/os-system-7.yaml
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
# mdcms v0.4 | DO NOT REMOVE THIS COMMENT
|
||||
# mdcms theme — os-system-7
|
||||
# Classic Macintosh System 7 / early Mac OS. Black on white with the
|
||||
# Platinum-grey window chrome that arrived around System 7.5. Very flat,
|
||||
# very calm, very monochrome.
|
||||
#
|
||||
# Colours:
|
||||
# #FFFFFF paper (white)
|
||||
# #DDDDDD Platinum window chrome
|
||||
# #000000 ink (1-bit Mac heritage)
|
||||
# #B0B0B0 shadow grey
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Colours
|
||||
# ──────────────────────────────────
|
||||
light:
|
||||
accent: "#000000"
|
||||
background: "#FFFFFF"
|
||||
nav-background: "#DDDDDD"
|
||||
text: "#000000"
|
||||
text-muted: "#555555"
|
||||
|
||||
dark:
|
||||
accent: "#FFFFFF"
|
||||
background: "#1A1A1A"
|
||||
nav-background: "#262626"
|
||||
text: "#FFFFFF"
|
||||
text-muted: "#A0A0A0"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Semantic colours — restrained, since System 7 was a 1-bit interface
|
||||
# until colour Macs. Kept muted and "drawn-in-MacPaint".
|
||||
# ──────────────────────────────────
|
||||
colours-semantic:
|
||||
info: "#000000"
|
||||
warning: "#7A5A00"
|
||||
success: "#1F5A1F"
|
||||
error: "#8B0000"
|
||||
|
||||
colours-semantic-dark:
|
||||
info: "#FFFFFF"
|
||||
warning: "#E5C36B"
|
||||
success: "#7FB87F"
|
||||
error: "#E08585"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Callout defaults
|
||||
# ──────────────────────────────────
|
||||
callouts:
|
||||
info:
|
||||
icon: info
|
||||
primary-colour: "#000000"
|
||||
background-colour: "#000000"
|
||||
warning:
|
||||
icon: warning
|
||||
primary-colour: "#7A5A00"
|
||||
background-colour: "#7A5A00"
|
||||
success:
|
||||
icon: success
|
||||
primary-colour: "#1F5A1F"
|
||||
background-colour: "#1F5A1F"
|
||||
error:
|
||||
icon: error
|
||||
primary-colour: "#8B0000"
|
||||
background-colour: "#8B0000"
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Typography
|
||||
# Inter portable default. Preferred for true System 7 fidelity:
|
||||
# "ChicagoFLF" (free Chicago revival, body) for headings
|
||||
# "Geneva" (system) for body — or any free Geneva-alike like
|
||||
# "Charcoal CY" or "ArkPixel".
|
||||
# For an authentic 1-bit look, try a pixel font like "VT323".
|
||||
# ──────────────────────────────────
|
||||
font-body: "bunny:Inter:400"
|
||||
font-heading: "bunny:Inter:700"
|
||||
font-size: 1.00
|
||||
line-height: 1.6
|
||||
|
||||
# ──────────────────────────────────
|
||||
# Layout
|
||||
# ──────────────────────────────────
|
||||
main-width: 76em
|
||||
nav-width: 20em
|
||||
Loading…
Reference in a new issue