This commit is contained in:
@@ -17,6 +17,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
initMediaFixes(document);
|
||||
initReadingProgress();
|
||||
hookSearchResults();
|
||||
initHomeDashboard();
|
||||
});
|
||||
|
||||
/* =========================================================
|
||||
@@ -93,14 +94,14 @@ function rearrangeDOM() {
|
||||
|
||||
function buildNoteToolbar(toolbar) {
|
||||
toolbar.innerHTML = `
|
||||
<button class="toolbar-btn" id="tb-back" title="Go back" aria-label="Go back">\u2190 back</button>
|
||||
<button class="toolbar-btn" id="tb-forward" title="Go forward" aria-label="Go forward">forward \u2192</button>
|
||||
<button class="toolbar-btn toolbar-btn-icon" id="tb-back" title="Go back" aria-label="Go back">\u2190</button>
|
||||
<button class="toolbar-btn toolbar-btn-icon" id="tb-forward" title="Go forward" aria-label="Go forward">\u2192</button>
|
||||
<div class="toolbar-sep"></div>
|
||||
<button class="toolbar-btn" id="tb-toc" title="Toggle table of contents">contents</button>
|
||||
<button class="toolbar-btn toolbar-btn-icon" id="tb-toc" title="Toggle table of contents" aria-label="Toggle table of contents">\u2261</button>
|
||||
<button class="toolbar-btn toolbar-btn-icon" id="tb-backlinks" title="Toggle backlinks panel" aria-label="Toggle backlinks panel">\u29C9</button>
|
||||
<div class="toolbar-spacer"></div>
|
||||
<span class="note-graph-pos" id="note-graph-pos"></span>
|
||||
<div class="toolbar-sep"></div>
|
||||
<button class="toolbar-btn" id="tb-backlinks" title="Toggle backlinks panel">backlinks</button>
|
||||
<button class="toolbar-btn toolbar-btn-icon" id="tb-close-all" title="Close all notes" aria-label="Close all notes">\u2715</button>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -108,19 +109,49 @@ function buildNoteToolbar(toolbar) {
|
||||
SIDEBAR RAIL
|
||||
========================================================= */
|
||||
|
||||
const RECENT_KEY = 'zg-recent-notes';
|
||||
const RECENT_MAX = 12;
|
||||
const FOLDER_STATE_KEY = 'zg-folder-state';
|
||||
|
||||
let sidebarDataCache = null;
|
||||
|
||||
async function fetchSidebarData() {
|
||||
if (sidebarDataCache) return sidebarDataCache;
|
||||
try {
|
||||
const res = await fetch(`/assets/sidebar-tree.json?v=${Date.now()}`, { cache: 'no-store' });
|
||||
if (res.ok) sidebarDataCache = await res.json();
|
||||
} catch (_) {}
|
||||
return sidebarDataCache || { mocs: [], tree: [] };
|
||||
}
|
||||
|
||||
function closeSidebar() {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
if (!sidebar) return;
|
||||
sidebar.classList.remove('expanded', 'mobile-open');
|
||||
sidebar.dataset.activePanel = '';
|
||||
sidebar.querySelectorAll('.rail-btn').forEach(b => b.classList.remove('active'));
|
||||
document.getElementById('sidebar-backdrop')?.classList.remove('visible');
|
||||
}
|
||||
|
||||
function initSidebarRail() {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
if (!sidebar) return;
|
||||
|
||||
const rail = el('div', { id: 'sidebar-rail', class: 'sidebar-rail' });
|
||||
const body = el('div', { id: 'sidebar-body', class: 'sidebar-body' });
|
||||
sidebar.append(rail, body);
|
||||
|
||||
const railBtns = [
|
||||
{ label: '\u2630', title: 'All notes', id: 'rail-tree', panel: 'tree' },
|
||||
{ label: '\u2315', title: 'Search', id: 'rail-search', action: 'search' },
|
||||
{ label: '\u2B21', title: 'Atlas', id: 'rail-atlas', panel: 'atlas' },
|
||||
{ label: '\u25F7', title: 'Recent', id: 'rail-recent', panel: 'recent' },
|
||||
{ label: '\u2630', title: 'Browse', id: 'rail-browse', panel: 'browse' },
|
||||
{ label: '\u2315', title: 'Search', id: 'rail-search', action: 'search' },
|
||||
];
|
||||
|
||||
railBtns.forEach(({ label, title, id, panel, action }) => {
|
||||
const btn = el('button', { class: 'rail-btn', id, title, 'aria-label': title });
|
||||
btn.textContent = label;
|
||||
sidebar.appendChild(btn);
|
||||
rail.appendChild(btn);
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
if (action === 'search') {
|
||||
@@ -131,13 +162,12 @@ function initSidebarRail() {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const isExpanded = sidebar.classList.contains('expanded');
|
||||
const samePanel = sidebar.dataset.activePanel === panel;
|
||||
|
||||
if (isExpanded && samePanel) {
|
||||
sidebar.classList.remove('expanded');
|
||||
sidebar.dataset.activePanel = '';
|
||||
btn.classList.remove('active');
|
||||
closeSidebar();
|
||||
} else {
|
||||
sidebar.classList.add('expanded');
|
||||
sidebar.dataset.activePanel = panel;
|
||||
@@ -148,57 +178,142 @@ function initSidebarRail() {
|
||||
});
|
||||
});
|
||||
|
||||
// Mobile backdrop
|
||||
let backdrop = document.getElementById('sidebar-backdrop');
|
||||
if (!backdrop) {
|
||||
backdrop = el('div', { id: 'sidebar-backdrop' });
|
||||
document.body.appendChild(backdrop);
|
||||
}
|
||||
backdrop.addEventListener('click', () => {
|
||||
sidebar.classList.remove('expanded', 'mobile-open');
|
||||
backdrop.classList.remove('visible');
|
||||
sidebar.querySelectorAll('.rail-btn').forEach(b => b.classList.remove('active'));
|
||||
});
|
||||
backdrop.addEventListener('click', closeSidebar);
|
||||
|
||||
initSidebarToggle();
|
||||
}
|
||||
|
||||
function showSidebarPanel(panel) {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
sidebar.querySelectorAll('.sidebar-panel').forEach(p => p.remove());
|
||||
const body = document.getElementById('sidebar-body');
|
||||
if (!body) return;
|
||||
body.innerHTML = '';
|
||||
|
||||
if (panel === 'tree') {
|
||||
const panelEl = el('div', { class: 'sidebar-panel' });
|
||||
panelEl.innerHTML = `
|
||||
<div class="sidebar-header">
|
||||
<span class="sidebar-header-label">all notes</span>
|
||||
<button class="sidebar-close-btn" aria-label="Close sidebar">\u00D7</button>
|
||||
</div>
|
||||
<div id="file-tree"></div>
|
||||
`;
|
||||
sidebar.appendChild(panelEl);
|
||||
const panelEl = el('div', { class: 'sidebar-panel', 'data-panel': panel });
|
||||
panelEl.innerHTML = `
|
||||
<div class="sidebar-header">
|
||||
<span class="sidebar-header-label">${panel}</span>
|
||||
<button class="sidebar-close-btn" aria-label="Close sidebar">\u00D7</button>
|
||||
</div>
|
||||
<div class="sidebar-panel-body" id="sidebar-panel-body"></div>
|
||||
`;
|
||||
body.appendChild(panelEl);
|
||||
|
||||
panelEl.querySelector('.sidebar-close-btn').addEventListener('click', () => {
|
||||
sidebar.classList.remove('expanded');
|
||||
sidebar.dataset.activePanel = '';
|
||||
sidebar.querySelectorAll('.rail-btn').forEach(b => b.classList.remove('active'));
|
||||
});
|
||||
panelEl.querySelector('.sidebar-close-btn').addEventListener('click', closeSidebar);
|
||||
|
||||
buildFileTree(panelEl.querySelector('#file-tree'));
|
||||
const container = panelEl.querySelector('#sidebar-panel-body');
|
||||
if (panel === 'atlas') buildAtlasPanel(container);
|
||||
else if (panel === 'recent') buildRecentPanel(container);
|
||||
else if (panel === 'browse') buildBrowsePanel(container);
|
||||
}
|
||||
|
||||
async function buildAtlasPanel(container) {
|
||||
const data = await fetchSidebarData();
|
||||
const mocs = data.mocs || [];
|
||||
|
||||
if (!mocs.length) {
|
||||
container.innerHTML = '<p class="sidebar-empty">No MOC hubs found.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
const grid = el('div', { class: 'moc-grid' });
|
||||
mocs.forEach(moc => {
|
||||
const card = el('button', {
|
||||
class: 'moc-card' + (moc.pinned ? ' moc-card-pinned' : ''),
|
||||
type: 'button',
|
||||
});
|
||||
card.innerHTML = `
|
||||
<span class="moc-card-title">${escHtml(moc.title)}</span>
|
||||
${moc.pinned ? '<span class="moc-card-badge">start</span>' : ''}
|
||||
`;
|
||||
card.addEventListener('click', () => {
|
||||
closeSidebar();
|
||||
openNote(normalisePathname(moc.url), moc.title);
|
||||
});
|
||||
grid.appendChild(card);
|
||||
});
|
||||
container.appendChild(grid);
|
||||
}
|
||||
|
||||
function getRecentNotes() {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(RECENT_KEY) || '[]');
|
||||
} catch (_) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function trackRecentNote(pathname, title) {
|
||||
if (!pathname) return;
|
||||
const entry = { pathname, title, visitedAt: Date.now() };
|
||||
let recent = getRecentNotes().filter(r => r.pathname !== pathname);
|
||||
recent.unshift(entry);
|
||||
localStorage.setItem(RECENT_KEY, JSON.stringify(recent.slice(0, RECENT_MAX)));
|
||||
const panel = document.querySelector('.sidebar-panel[data-panel="recent"] #sidebar-panel-body');
|
||||
if (panel) buildRecentPanel(panel);
|
||||
}
|
||||
|
||||
function formatRelativeTime(ts) {
|
||||
const diff = Date.now() - ts;
|
||||
const mins = Math.floor(diff / 60000);
|
||||
if (mins < 1) return 'just now';
|
||||
if (mins < 60) return `${mins}m ago`;
|
||||
const hrs = Math.floor(mins / 60);
|
||||
if (hrs < 24) return `${hrs}h ago`;
|
||||
const days = Math.floor(hrs / 24);
|
||||
if (days < 7) return `${days}d ago`;
|
||||
return new Date(ts).toLocaleDateString();
|
||||
}
|
||||
|
||||
function buildRecentPanel(container) {
|
||||
const recent = getRecentNotes();
|
||||
container.innerHTML = '';
|
||||
|
||||
if (!recent.length) {
|
||||
container.innerHTML = '<p class="sidebar-empty">No notes visited yet.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
const list = el('div', { class: 'recent-list' });
|
||||
recent.forEach(item => {
|
||||
const row = el('button', { class: 'recent-item', type: 'button' });
|
||||
row.innerHTML = `
|
||||
<span class="recent-item-title">${escHtml(item.title)}</span>
|
||||
<span class="recent-item-time">${formatRelativeTime(item.visitedAt)}</span>
|
||||
`;
|
||||
row.addEventListener('click', () => {
|
||||
closeSidebar();
|
||||
openNote(item.pathname, item.title);
|
||||
});
|
||||
list.appendChild(row);
|
||||
});
|
||||
container.appendChild(list);
|
||||
}
|
||||
|
||||
function buildBrowsePanel(container) {
|
||||
container.innerHTML = `
|
||||
<div class="tree-filter-wrap">
|
||||
<input type="search" class="tree-filter" placeholder="Filter notes\u2026" aria-label="Filter notes" />
|
||||
</div>
|
||||
<div id="file-tree"></div>
|
||||
`;
|
||||
|
||||
const filterInput = container.querySelector('.tree-filter');
|
||||
const treeRoot = container.querySelector('#file-tree');
|
||||
|
||||
filterInput.addEventListener('input', () => filterTree(treeRoot, filterInput.value.trim()));
|
||||
|
||||
buildFileTree(treeRoot);
|
||||
}
|
||||
|
||||
async function buildFileTree(container) {
|
||||
const currentPath = location.pathname;
|
||||
let treeData = null;
|
||||
|
||||
try {
|
||||
const res = await fetch(`/assets/sidebar-tree.json?v=${Date.now()}`, { cache: 'no-store' });
|
||||
if (res.ok) {
|
||||
const json = await res.json();
|
||||
if (Array.isArray(json.tree)) treeData = json.tree;
|
||||
}
|
||||
} catch (_) {}
|
||||
const data = await fetchSidebarData();
|
||||
let treeData = Array.isArray(data.tree) ? data.tree : null;
|
||||
|
||||
if (treeData) {
|
||||
renderTree(treeData, container, currentPath);
|
||||
@@ -207,45 +322,120 @@ async function buildFileTree(container) {
|
||||
|
||||
const groups = await buildGroupsFromSearchIndex();
|
||||
if (!groups?.length) {
|
||||
const empty = el('div', { class: 'tree-file' });
|
||||
empty.style.cssText = 'opacity:.4;font-size:.7rem;padding:8px 14px;';
|
||||
empty.textContent = 'no notes found';
|
||||
container.appendChild(empty);
|
||||
container.innerHTML = '<p class="sidebar-empty">No notes found.</p>';
|
||||
return;
|
||||
}
|
||||
const fallbackTree = groups.map(g => ({ label: g.label, children: [], files: g.files }));
|
||||
renderTree(fallbackTree, container, currentPath);
|
||||
}
|
||||
|
||||
function renderTree(nodes, container, currentPath) {
|
||||
function getFolderState() {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(FOLDER_STATE_KEY) || '{}');
|
||||
} catch (_) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function setFolderState(key, isOpen) {
|
||||
const state = getFolderState();
|
||||
state[key] = isOpen;
|
||||
localStorage.setItem(FOLDER_STATE_KEY, JSON.stringify(state));
|
||||
}
|
||||
|
||||
function countFilesInNode(node) {
|
||||
let count = (node.files || []).length;
|
||||
for (const child of node.children || []) {
|
||||
count += countFilesInNode(child);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
function nodeContainsPath(node, pathname, prefix = '') {
|
||||
const folderKey = prefix ? `${prefix}/${node.label}` : node.label;
|
||||
for (const f of node.files || []) {
|
||||
if (normalisePathname(f.url) === pathname) return true;
|
||||
}
|
||||
for (const child of node.children || []) {
|
||||
if (nodeContainsPath(child, pathname, folderKey)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function renderTree(nodes, container, currentPath, prefix = '') {
|
||||
nodes.forEach(node => {
|
||||
const folderEl = el('div', { class: 'tree-folder', 'data-folder': node.label });
|
||||
const icon = el('span', { class: 'tree-icon' });
|
||||
icon.textContent = '\u25BE ';
|
||||
const folderKey = prefix ? `${prefix}/${node.label}` : node.label;
|
||||
const fileCount = countFilesInNode(node);
|
||||
const containsActive = nodeContainsPath(node, currentPath, prefix);
|
||||
const folderState = getFolderState();
|
||||
const defaultOpen = containsActive || folderState[folderKey] === true;
|
||||
const isOpen = defaultOpen;
|
||||
|
||||
const folderEl = el('div', {
|
||||
class: 'tree-folder',
|
||||
'data-folder': node.label,
|
||||
'data-folder-key': folderKey,
|
||||
});
|
||||
const icon = el('span', { class: 'tree-icon' });
|
||||
icon.textContent = isOpen ? '\u25BE ' : '\u25B8 ';
|
||||
const labelSpan = el('span', { class: 'tree-folder-label' });
|
||||
labelSpan.textContent = node.label;
|
||||
folderEl.append(icon, labelSpan);
|
||||
const badge = el('span', { class: 'tree-folder-count' });
|
||||
badge.textContent = String(fileCount);
|
||||
folderEl.append(icon, labelSpan, badge);
|
||||
|
||||
const childrenWrap = el('div', { class: 'tree-children' });
|
||||
if (!isOpen) childrenWrap.style.display = 'none';
|
||||
|
||||
const searchTerms = [node.label.toLowerCase()];
|
||||
(node.files || []).forEach(f => {
|
||||
const pathname = normalisePathname(f.url);
|
||||
searchTerms.push(f.title.toLowerCase());
|
||||
childrenWrap.appendChild(makeFileItem({ title: f.title, pathname }, currentPath));
|
||||
});
|
||||
|
||||
if (node.children?.length) renderTree(node.children, childrenWrap, currentPath);
|
||||
if (node.children?.length) {
|
||||
renderTree(node.children, childrenWrap, currentPath, folderKey);
|
||||
}
|
||||
|
||||
folderEl.addEventListener('click', () => {
|
||||
const isOpen = childrenWrap.style.display !== 'none';
|
||||
childrenWrap.style.display = isOpen ? 'none' : '';
|
||||
icon.textContent = isOpen ? '\u25B8 ' : '\u25BE ';
|
||||
const nowOpen = childrenWrap.style.display === 'none';
|
||||
childrenWrap.style.display = nowOpen ? '' : 'none';
|
||||
icon.textContent = nowOpen ? '\u25BE ' : '\u25B8 ';
|
||||
setFolderState(folderKey, nowOpen);
|
||||
});
|
||||
|
||||
folderEl.dataset.searchText = searchTerms.join(' ');
|
||||
childrenWrap.dataset.searchText = searchTerms.join(' ');
|
||||
|
||||
container.appendChild(folderEl);
|
||||
container.appendChild(childrenWrap);
|
||||
});
|
||||
}
|
||||
|
||||
function filterTree(container, query) {
|
||||
const q = query.toLowerCase();
|
||||
container.querySelectorAll('.tree-folder, .tree-children, .tree-file').forEach(el => {
|
||||
if (!q) {
|
||||
el.classList.remove('tree-filtered-out');
|
||||
return;
|
||||
}
|
||||
const text = (el.dataset.searchText || el.textContent || '').toLowerCase();
|
||||
el.classList.toggle('tree-filtered-out', !text.includes(q));
|
||||
});
|
||||
|
||||
if (q) {
|
||||
container.querySelectorAll('.tree-children').forEach(wrap => {
|
||||
wrap.style.display = '';
|
||||
const folder = wrap.previousElementSibling;
|
||||
if (folder?.classList.contains('tree-folder')) {
|
||||
const icon = folder.querySelector('.tree-icon');
|
||||
if (icon) icon.textContent = '\u25BE ';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function buildGroupsFromSearchIndex() {
|
||||
try {
|
||||
const res = await fetch('/search-index.json');
|
||||
@@ -267,14 +457,17 @@ function makeFileItem({ title, pathname }, currentPath) {
|
||||
const fileEl = el('div', {
|
||||
class: 'tree-file' + (pathname === currentPath ? ' active' : ''),
|
||||
'data-pathname': pathname,
|
||||
'data-search-text': title.toLowerCase(),
|
||||
});
|
||||
const iconSpan = el('span', { class: 'tree-icon' });
|
||||
iconSpan.textContent = '\u00B7 ';
|
||||
const titleSpan = el('span', { class: 'tree-file-title' });
|
||||
titleSpan.textContent = title;
|
||||
fileEl.appendChild(iconSpan);
|
||||
fileEl.appendChild(document.createTextNode(title));
|
||||
fileEl.appendChild(titleSpan);
|
||||
fileEl.title = title;
|
||||
fileEl.addEventListener('click', () => {
|
||||
document.getElementById('sidebar')?.classList.remove('expanded');
|
||||
closeSidebar();
|
||||
openNote(pathname, title);
|
||||
});
|
||||
return fileEl;
|
||||
@@ -378,6 +571,7 @@ history.replaceState(
|
||||
document.getElementById('tb-forward')?.addEventListener('click', goForward);
|
||||
document.getElementById('tb-backlinks')?.addEventListener('click', toggleBacklinkPanel);
|
||||
document.getElementById('tb-toc')?.addEventListener('click', toggleTOC);
|
||||
document.getElementById('tb-close-all')?.addEventListener('click', closeAllNotes);
|
||||
|
||||
// Backlink panel close
|
||||
document.getElementById('bp-close')?.addEventListener('click', () => {
|
||||
@@ -548,6 +742,11 @@ function activateNote(id, skipBrowserWrite = true) {
|
||||
const area = document.getElementById('content-area');
|
||||
if (area) area.scrollTop = 0;
|
||||
|
||||
const current = noteHistory[historyIndex];
|
||||
if (current) {
|
||||
trackRecentNote(current.pathname, current.label);
|
||||
}
|
||||
|
||||
updateBreadcrumb();
|
||||
updateToolbar();
|
||||
|
||||
@@ -567,6 +766,18 @@ function goForward() {
|
||||
window.history.forward();
|
||||
}
|
||||
|
||||
function closeAllNotes() {
|
||||
if (noteHistory.length <= 1) return;
|
||||
const root = noteHistory[0];
|
||||
noteHistory.slice(1).forEach(n => {
|
||||
if (!n.isRoot) n.paneEl.remove();
|
||||
});
|
||||
noteHistory.splice(1);
|
||||
historyIndex = 0;
|
||||
activateNote(root.id, true);
|
||||
history.replaceState(makeHistoryState(root), '', historyUrlFor(root));
|
||||
}
|
||||
|
||||
function updateBreadcrumb() {
|
||||
const trail = document.getElementById('breadcrumb-trail');
|
||||
if (!trail) return;
|
||||
@@ -934,9 +1145,9 @@ function initSidebarToggle() {
|
||||
e.preventDefault();
|
||||
const nowExpanded = sidebar.classList.toggle('expanded');
|
||||
if (nowExpanded && !sidebar.dataset.activePanel) {
|
||||
sidebar.dataset.activePanel = 'tree';
|
||||
showSidebarPanel('tree');
|
||||
sidebar.querySelector('#rail-tree')?.classList.add('active');
|
||||
sidebar.dataset.activePanel = 'atlas';
|
||||
showSidebarPanel('atlas');
|
||||
sidebar.querySelector('#rail-atlas')?.classList.add('active');
|
||||
}
|
||||
if (!nowExpanded) {
|
||||
sidebar.dataset.activePanel = '';
|
||||
@@ -1285,6 +1496,51 @@ function normalisePathname(p) {
|
||||
// Lazy sidebar builder stub (built on demand)
|
||||
async function buildSidebar() {}
|
||||
|
||||
async function initHomeDashboard() {
|
||||
const pathname = normalisePathname(location.pathname);
|
||||
if (pathname !== '/' && !pathname.endsWith('/index.html')) return;
|
||||
|
||||
const content = document.querySelector('#content');
|
||||
if (!content || content.querySelector('.home-dashboard')) return;
|
||||
|
||||
const data = await fetchSidebarData();
|
||||
const mocs = data.mocs || [];
|
||||
if (!mocs.length) return;
|
||||
|
||||
const dash = el('div', { class: 'home-dashboard' });
|
||||
dash.innerHTML = `
|
||||
<header class="home-hero">
|
||||
<h2 class="home-hero-title">Zettelgarten</h2>
|
||||
<p class="home-hero-sub">A private library of notes, maps, and wandering thoughts.</p>
|
||||
</header>
|
||||
<div class="home-quick-links">
|
||||
<button type="button" class="home-quick-link" data-action="search">Search <kbd>/</kbd></button>
|
||||
<a class="home-quick-link" href="/all-files.html">All files</a>
|
||||
</div>
|
||||
<h3 class="home-section-label">Start here</h3>
|
||||
<div class="home-moc-grid"></div>
|
||||
`;
|
||||
|
||||
const grid = dash.querySelector('.home-moc-grid');
|
||||
mocs.forEach(moc => {
|
||||
const card = el('a', {
|
||||
class: 'moc-card home-moc-card' + (moc.pinned ? ' moc-card-pinned' : ''),
|
||||
href: moc.url,
|
||||
});
|
||||
card.innerHTML = `
|
||||
<span class="moc-card-title">${escHtml(moc.title)}</span>
|
||||
${moc.pinned ? '<span class="moc-card-badge">start</span>' : ''}
|
||||
`;
|
||||
grid.appendChild(card);
|
||||
});
|
||||
|
||||
dash.querySelector('[data-action="search"]')?.addEventListener('click', () => {
|
||||
window.openSearchModal?.();
|
||||
});
|
||||
|
||||
content.insertBefore(dash, content.firstChild);
|
||||
}
|
||||
|
||||
// Pane-out animation keyframe
|
||||
const _s = document.createElement('style');
|
||||
_s.textContent = `
|
||||
|
||||
@@ -88,14 +88,12 @@
|
||||
========================================================= */
|
||||
|
||||
@media (max-width: 900px) {
|
||||
#sidebar {
|
||||
width: 0;
|
||||
border-right: none;
|
||||
overflow: hidden;
|
||||
#sidebar:not(.expanded):not(.mobile-open) {
|
||||
width: var(--rail-w);
|
||||
}
|
||||
|
||||
.fullwidth {
|
||||
max-width: calc(100vw - 2rem);
|
||||
max-width: calc(100vw - var(--rail-w) - 2rem);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,22 +170,22 @@
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 80vw;
|
||||
max-width: 300px;
|
||||
width: min(92vw, var(--sidebar-w));
|
||||
height: 100dvh;
|
||||
z-index: 400;
|
||||
border-right: 1px solid var(--border-mid);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
overflow: hidden;
|
||||
transform: translateX(-100%);
|
||||
transition: transform var(--dur-mid) var(--ease-out),
|
||||
box-shadow var(--dur-mid) var(--ease-out);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
#sidebar.mobile-open {
|
||||
#sidebar.mobile-open,
|
||||
#sidebar.expanded {
|
||||
transform: translateX(0);
|
||||
box-shadow: 4px 0 32px rgba(0, 0, 0, 0.5);
|
||||
width: min(92vw, var(--sidebar-w)) !important;
|
||||
}
|
||||
|
||||
#sidebar-backdrop {
|
||||
@@ -218,12 +216,7 @@
|
||||
#content-area {
|
||||
overflow: visible;
|
||||
height: auto;
|
||||
min-height: calc(100dvh - var(--header-h) - var(--tab-h));
|
||||
background-attachment: scroll;
|
||||
}
|
||||
|
||||
#content-area::before {
|
||||
display: none;
|
||||
min-height: calc(100dvh - var(--header-h) - var(--toolbar-h));
|
||||
}
|
||||
|
||||
/* ---- Note content ---- */
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
ZETTELKASTEN GARDEN style.css
|
||||
========================================================= */
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,500;0,600;1,500&family=DM+Mono:wght@400;500&family=Inter:wght@400;500;600&family=Noto+Sans+Arabic:wght@400;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,500;0,600;1,500&family=DM+Mono:wght@400;500&family=Spectral:ital,wght@0,300;0,400;0,500;1,300;1,400&family=Noto+Sans+Arabic:wght@400;700&display=swap');
|
||||
|
||||
/* =========================================================
|
||||
TOKENS
|
||||
@@ -10,25 +10,28 @@
|
||||
|
||||
:root {
|
||||
--header-h: 48px;
|
||||
--toolbar-h: 36px;
|
||||
--rail-w: 52px;
|
||||
--panel-w: 260px;
|
||||
--content-max: none;
|
||||
--content-pad: clamp(1.5rem, 4vw, 4.5rem);
|
||||
--toolbar-h: 32px;
|
||||
--rail-w: 48px;
|
||||
--panel-w: 280px;
|
||||
--sidebar-w: calc(var(--rail-w) + var(--panel-w));
|
||||
--content-max: min(72rem, calc(100vw - var(--rail-w) - 2.5rem));
|
||||
--content-pad: clamp(1.25rem, 2.5vw, 2.25rem);
|
||||
|
||||
/* Dark academia: ink, vellum, forest green, oxblood, and aged brass */
|
||||
--bg: #11100c;
|
||||
--page-bg: #090907;
|
||||
--pane-bg: #15130f;
|
||||
--surface: #1e1a14;
|
||||
--surface-2: #29231a;
|
||||
/* Scriptorium: ink, vellum, oxblood, antique gold */
|
||||
--bg: #0f0e0b;
|
||||
--page-bg: #070605;
|
||||
--pane-bg: #0a0908;
|
||||
--reading-surface: #12100d;
|
||||
--surface: #1a1712;
|
||||
--surface-2: #242019;
|
||||
|
||||
--fg: #e8dfcd;
|
||||
--fg-dim: #b5a68f;
|
||||
--fg-faint: #766a58;
|
||||
--fg-ghost: #43382b;
|
||||
|
||||
--accent: #b68b45;
|
||||
--accent: #c4a35a;
|
||||
--accent-2: #6b2d3c;
|
||||
--accent-dim: color-mix(in oklab, var(--accent) 35%, transparent);
|
||||
--accent-sub: color-mix(in oklab, var(--accent) 10%, transparent);
|
||||
--accent-glow:color-mix(in oklab, var(--accent) 6%, transparent);
|
||||
@@ -38,13 +41,16 @@
|
||||
--heading-3: #a8604f;
|
||||
--link: #c7a763;
|
||||
--link-hover:#f1e2c2;
|
||||
--tag-bg: #251f16;
|
||||
--tag-bg: #1f1a14;
|
||||
--tag-fg: var(--accent);
|
||||
--border: #332c20;
|
||||
--border: #2e2820;
|
||||
--border-2: #4a3d29;
|
||||
--code-bg: #0c0c09;
|
||||
--border-mid: #3a3228;
|
||||
--chip-bg: var(--surface);
|
||||
--code-bg: #080807;
|
||||
|
||||
--font-body: 'Inter', 'Noto Sans Arabic', system-ui, sans-serif;
|
||||
--font-body: 'Spectral', 'Noto Sans Arabic', Georgia, serif;
|
||||
--font-ui: system-ui, -apple-system, sans-serif;
|
||||
--font-mono: 'DM Mono', 'Fira Mono', monospace;
|
||||
--font-serif: 'Cormorant Garamond', Georgia, serif;
|
||||
|
||||
@@ -58,24 +64,28 @@
|
||||
--r4:1rem; --r5:1.5rem; --r6:2rem; --r8:3rem;
|
||||
}
|
||||
|
||||
/* Light theme */
|
||||
/* Light theme — daylight reading room */
|
||||
[data-theme="light"] {
|
||||
--bg: #f3eee3;
|
||||
--page-bg: #e9e0cf;
|
||||
--pane-bg: #f7f1e6;
|
||||
--bg: #f5f0e6;
|
||||
--page-bg: #ebe4d6;
|
||||
--pane-bg: #f0ebe1;
|
||||
--reading-surface: #f7f1e6;
|
||||
--surface: #efe5d4;
|
||||
--surface-2: #e3d5be;
|
||||
--fg: #211c15;
|
||||
--fg-dim: #625744;
|
||||
--fg-faint: #998b72;
|
||||
--fg-ghost: #d2c3a8;
|
||||
--accent: #80602f;
|
||||
--fg: #1c1812;
|
||||
--fg-dim: #5c5040;
|
||||
--fg-faint: #8a7d68;
|
||||
--fg-ghost: #c8baa3;
|
||||
--accent: #8a6b32;
|
||||
--accent-2: #7e3a45;
|
||||
--border: #d4c4a7;
|
||||
--border-2: #b9a57f;
|
||||
--border-mid:#c4b08e;
|
||||
--chip-bg: #e8dcc8;
|
||||
--code-bg: #e8dcc8;
|
||||
--tag-bg: #dfd0b5;
|
||||
--link: #684f28;
|
||||
--heading: #211c15;
|
||||
--heading: #1c1812;
|
||||
--heading-2: #5b4929;
|
||||
--heading-3: #7e4438;
|
||||
}
|
||||
@@ -130,11 +140,32 @@ body, p, li, td, th, span, div {
|
||||
.banner-logo {
|
||||
height: 26px; width: 26px;
|
||||
border-radius: 50%; object-fit: cover;
|
||||
opacity: 0.85; flex-shrink: 0;
|
||||
opacity: 0.9; flex-shrink: 0;
|
||||
transition: opacity var(--dur-fast);
|
||||
}
|
||||
.banner-logo:hover { opacity: 1; }
|
||||
|
||||
.banner-brand {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
color: var(--heading);
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.banner-brand:hover {
|
||||
color: var(--accent);
|
||||
background: transparent;
|
||||
}
|
||||
.banner-title {
|
||||
font-family: var(--font-serif);
|
||||
font-size: 1.15rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* Breadcrumb trail */
|
||||
#breadcrumb-trail {
|
||||
display: flex;
|
||||
@@ -191,7 +222,7 @@ body, p, li, td, th, span, div {
|
||||
height: 30px;
|
||||
padding: 0 0.45rem 0 0.7rem;
|
||||
font-size: 0.78rem;
|
||||
font-family: var(--font-body);
|
||||
font-family: var(--font-ui);
|
||||
color: var(--fg-dim);
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
@@ -390,14 +421,13 @@ body, p, li, td, th, span, div {
|
||||
width: var(--rail-w);
|
||||
flex-shrink: 0;
|
||||
background:
|
||||
linear-gradient(90deg, color-mix(in oklab, #233327 18%, transparent), transparent 78%),
|
||||
linear-gradient(180deg, color-mix(in oklab, var(--accent-2) 8%, transparent), transparent 40%),
|
||||
linear-gradient(90deg, color-mix(in oklab, #1a2e1f 22%, transparent), transparent 90%),
|
||||
var(--bg);
|
||||
border-right: 1px solid var(--border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: var(--r3) 0;
|
||||
gap: var(--r2);
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
overflow: hidden;
|
||||
transition: width var(--dur-mid) var(--ease-out);
|
||||
z-index: 100;
|
||||
@@ -405,29 +435,60 @@ body, p, li, td, th, span, div {
|
||||
}
|
||||
|
||||
#sidebar.expanded {
|
||||
width: var(--panel-w);
|
||||
align-items: flex-start;
|
||||
overflow-y: auto;
|
||||
width: var(--sidebar-w);
|
||||
}
|
||||
|
||||
.sidebar-rail {
|
||||
width: var(--rail-w);
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: var(--r3) 0;
|
||||
gap: var(--r2);
|
||||
border-right: 1px solid color-mix(in oklab, var(--accent) 12%, var(--border));
|
||||
background: color-mix(in oklab, var(--surface) 40%, transparent);
|
||||
}
|
||||
|
||||
.sidebar-body {
|
||||
display: none;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#sidebar.expanded .sidebar-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Rail icon buttons */
|
||||
.rail-btn {
|
||||
width: 36px; height: 36px;
|
||||
border-radius: 8px;
|
||||
width: 34px; height: 34px;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--fg-faint);
|
||||
font-size: 1.05rem;
|
||||
font-size: 1rem;
|
||||
flex-shrink: 0;
|
||||
transition: color var(--dur-fast), background var(--dur-fast);
|
||||
transition: color var(--dur-fast), background var(--dur-fast), border-color var(--dur-fast), box-shadow var(--dur-fast);
|
||||
position: relative;
|
||||
}
|
||||
.rail-btn:hover { color: var(--fg); background: var(--accent-sub); }
|
||||
.rail-btn.active { color: var(--accent); background: var(--accent-glow); }
|
||||
.rail-btn:hover {
|
||||
color: var(--fg);
|
||||
background: var(--accent-sub);
|
||||
border-color: color-mix(in oklab, var(--accent) 20%, transparent);
|
||||
}
|
||||
.rail-btn.active {
|
||||
color: var(--accent);
|
||||
background: var(--accent-glow);
|
||||
border-color: var(--accent-dim);
|
||||
box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--accent) 15%, transparent);
|
||||
}
|
||||
|
||||
.rail-btn[title]:not(:disabled):hover::after {
|
||||
content: attr(title);
|
||||
@@ -447,8 +508,31 @@ body, p, li, td, th, span, div {
|
||||
z-index: 500;
|
||||
}
|
||||
|
||||
.sidebar-panel { display: none; width: 100%; flex-direction: column; }
|
||||
#sidebar.expanded .sidebar-panel { display: flex; }
|
||||
.sidebar-panel {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sidebar-panel-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding-bottom: 12px;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--border-2) transparent;
|
||||
}
|
||||
|
||||
.sidebar-empty {
|
||||
padding: 1.25rem 14px;
|
||||
color: var(--fg-faint);
|
||||
font-size: 0.78rem;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
display: flex;
|
||||
@@ -481,7 +565,133 @@ body, p, li, td, th, span, div {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding-bottom: 12px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.tree-filter-wrap {
|
||||
padding: 6px 10px 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tree-filter {
|
||||
width: 100%;
|
||||
padding: 5px 8px;
|
||||
font-size: 0.75rem;
|
||||
font-family: var(--font-ui);
|
||||
color: var(--fg);
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
outline: none;
|
||||
transition: border-color var(--dur-fast), box-shadow var(--dur-fast);
|
||||
}
|
||||
.tree-filter:focus {
|
||||
border-color: var(--accent-dim);
|
||||
box-shadow: 0 0 0 2px var(--accent-sub);
|
||||
}
|
||||
.tree-filter::placeholder { color: var(--fg-faint); }
|
||||
|
||||
.tree-filtered-out { display: none !important; }
|
||||
|
||||
/* MOC cards (Atlas + home) */
|
||||
.moc-grid,
|
||||
.home-moc-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 4px 8px 8px;
|
||||
}
|
||||
|
||||
.moc-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 8px 10px;
|
||||
text-align: left;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-left: 2px solid color-mix(in oklab, var(--accent) 50%, transparent);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
color: var(--fg);
|
||||
text-decoration: none;
|
||||
transition: transform var(--dur-fast), border-color var(--dur-fast), background var(--dur-fast), box-shadow var(--dur-fast);
|
||||
}
|
||||
.moc-card:hover {
|
||||
background: var(--surface-2);
|
||||
border-color: var(--accent-dim);
|
||||
border-left-color: var(--accent);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 16px color-mix(in oklab, var(--accent) 8%, transparent);
|
||||
}
|
||||
.moc-card-pinned {
|
||||
border-left-color: var(--accent);
|
||||
background: color-mix(in oklab, var(--accent) 5%, var(--surface));
|
||||
}
|
||||
.moc-card-title {
|
||||
font-family: var(--font-serif);
|
||||
font-size: 0.88rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.3;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.moc-card-badge {
|
||||
flex-shrink: 0;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.55rem;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--accent);
|
||||
padding: 2px 5px;
|
||||
border: 1px solid var(--accent-dim);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* Recent list */
|
||||
.recent-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
padding: 4px 6px;
|
||||
}
|
||||
|
||||
.recent-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 2px;
|
||||
width: 100%;
|
||||
padding: 7px 10px;
|
||||
text-align: left;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
color: var(--fg-dim);
|
||||
transition: background var(--dur-fast), color var(--dur-fast), border-color var(--dur-fast);
|
||||
}
|
||||
.recent-item:hover {
|
||||
background: var(--accent-sub);
|
||||
color: var(--fg);
|
||||
border-color: color-mix(in oklab, var(--accent) 15%, transparent);
|
||||
}
|
||||
.recent-item-title {
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.35;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
.recent-item-time {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.62rem;
|
||||
color: var(--fg-faint);
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
@@ -512,7 +722,7 @@ body, p, li, td, th, span, div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 10px 4px 12px;
|
||||
padding: 4px 8px 4px 10px;
|
||||
font-size: 0.68rem;
|
||||
cursor: pointer;
|
||||
color: var(--fg-faint);
|
||||
@@ -520,32 +730,43 @@ body, p, li, td, th, span, div {
|
||||
margin: 2px 4px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
user-select: none;
|
||||
font-weight: 500;
|
||||
font-family: var(--font-mono);
|
||||
letter-spacing: 0.06em;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
transition: background var(--dur-fast), color var(--dur-fast);
|
||||
}
|
||||
.tree-folder:hover { background: var(--accent-sub); color: var(--fg); }
|
||||
|
||||
.tree-folder-count {
|
||||
margin-left: auto;
|
||||
font-size: 0.58rem;
|
||||
color: var(--fg-ghost);
|
||||
flex-shrink: 0;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.tree-file {
|
||||
/* display: flex; */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 3px 10px 3px 12px;
|
||||
padding: 3px 8px 3px 10px;
|
||||
font-size: 0.76rem;
|
||||
cursor: pointer;
|
||||
color: var(--fg-dim);
|
||||
border-radius: 3px;
|
||||
margin: 1px 4px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
user-select: none;
|
||||
transition: background var(--dur-fast), color var(--dur-fast);
|
||||
}
|
||||
.tree-file-title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
min-width: 0;
|
||||
}
|
||||
.tree-file:hover { background: var(--accent-sub); color: var(--fg); }
|
||||
.tree-file.active {
|
||||
background: color-mix(in oklab, var(--accent) 14%, transparent);
|
||||
@@ -578,10 +799,10 @@ body, p, li, td, th, span, div {
|
||||
#note-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--r2);
|
||||
padding: 4px var(--r4);
|
||||
gap: var(--r1);
|
||||
padding: 2px var(--r3);
|
||||
height: var(--toolbar-h);
|
||||
background: color-mix(in oklab, var(--bg) 92%, #233327 8%);
|
||||
background: transparent;
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-shrink: 0;
|
||||
font-size: 0.75rem;
|
||||
@@ -591,6 +812,7 @@ body, p, li, td, th, span, div {
|
||||
.toolbar-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
@@ -603,6 +825,12 @@ body, p, li, td, th, span, div {
|
||||
transition: all var(--dur-fast);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.toolbar-btn-icon {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
padding: 0;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.toolbar-btn:hover:not(:disabled) {
|
||||
border-color: var(--border-2);
|
||||
color: var(--fg);
|
||||
@@ -623,10 +851,11 @@ body, p, li, td, th, span, div {
|
||||
.toolbar-spacer { flex: 1; }
|
||||
|
||||
.note-graph-pos {
|
||||
color: var(--fg-faint);
|
||||
font-size: 0.68rem;
|
||||
letter-spacing: 0.03em;
|
||||
max-width: 200px;
|
||||
color: var(--fg-ghost);
|
||||
font-size: 0.62rem;
|
||||
letter-spacing: 0.04em;
|
||||
font-style: italic;
|
||||
max-width: 180px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@@ -647,36 +876,45 @@ body, p, li, td, th, span, div {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
background-color: var(--pane-bg);
|
||||
background-image:
|
||||
linear-gradient(180deg, color-mix(in oklab, #233327 12%, transparent), transparent 22rem),
|
||||
linear-gradient(90deg, color-mix(in oklab, var(--accent) 7%, transparent) 0 1px, transparent 1px),
|
||||
repeating-linear-gradient(
|
||||
180deg,
|
||||
transparent, transparent 27px,
|
||||
color-mix(in oklab, var(--fg) 2%, transparent) 27px,
|
||||
color-mix(in oklab, var(--fg) 2%, transparent) 28px
|
||||
);
|
||||
background:
|
||||
radial-gradient(ellipse 80% 60% at 50% 0%, color-mix(in oklab, var(--accent) 4%, transparent), transparent 70%),
|
||||
var(--pane-bg);
|
||||
position: relative;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--border) transparent;
|
||||
background-attachment: local;
|
||||
}
|
||||
#content-area::-webkit-scrollbar { width: 5px; }
|
||||
#content-area::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
|
||||
|
||||
#content-area::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0; left: 52px; width: 1px; height: 100%;
|
||||
background: color-mix(in oklab, var(--accent) 8%, transparent);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
.tab-content {
|
||||
display: none;
|
||||
min-height: 100%;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: var(--r4) var(--r2) var(--r6);
|
||||
}
|
||||
#sidebar.expanded ~ #main .tab-content,
|
||||
body:has(#sidebar.expanded) .tab-content {
|
||||
--content-max: min(68rem, calc(100vw - var(--sidebar-w) - 2rem));
|
||||
}
|
||||
|
||||
.tab-content { display: none; min-height: 100%; position: relative; z-index: 1; }
|
||||
.tab-content.active { display: block; }
|
||||
|
||||
.tab-content > #content,
|
||||
.tab-content > .tab-content-inner {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
max-width: var(--content-max);
|
||||
padding: var(--r6) var(--content-pad) calc(var(--r8) * 1.5);
|
||||
background: var(--reading-surface);
|
||||
border: 1px solid var(--border);
|
||||
border-top: 1px solid color-mix(in oklab, var(--accent) 25%, var(--border));
|
||||
border-radius: 2px;
|
||||
box-shadow:
|
||||
0 1px 0 color-mix(in oklab, var(--accent) 8%, transparent),
|
||||
0 24px 64px color-mix(in oklab, #000 35%, transparent);
|
||||
}
|
||||
|
||||
/* =========================================================
|
||||
NOTE CONTENT
|
||||
========================================================= */
|
||||
@@ -685,7 +923,89 @@ body, p, li, td, th, span, div {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
max-width: var(--content-max);
|
||||
padding: var(--r6) var(--content-pad) calc(var(--r8) * 2.5);
|
||||
}
|
||||
|
||||
/* =========================================================
|
||||
HOME DASHBOARD
|
||||
========================================================= */
|
||||
|
||||
.home-dashboard {
|
||||
margin-bottom: var(--r8);
|
||||
padding-bottom: var(--r6);
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.home-hero-title {
|
||||
font-family: var(--font-serif);
|
||||
font-size: 2rem;
|
||||
font-weight: 500;
|
||||
color: var(--heading);
|
||||
margin: 0 0 var(--r2);
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.home-hero-sub {
|
||||
margin: 0 0 var(--r5);
|
||||
color: var(--fg-dim);
|
||||
font-size: 1rem;
|
||||
font-style: italic;
|
||||
max-width: 36ch;
|
||||
}
|
||||
|
||||
.home-quick-links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--r2);
|
||||
margin-bottom: var(--r6);
|
||||
}
|
||||
|
||||
.home-quick-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
padding: 5px 12px;
|
||||
font-size: 0.75rem;
|
||||
font-family: var(--font-mono);
|
||||
color: var(--fg-dim);
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: border-color var(--dur-fast), color var(--dur-fast), background var(--dur-fast);
|
||||
}
|
||||
.home-quick-link:hover {
|
||||
color: var(--accent);
|
||||
border-color: var(--accent-dim);
|
||||
background: var(--accent-glow);
|
||||
}
|
||||
.home-quick-link kbd {
|
||||
font-size: 0.65rem;
|
||||
padding: 1px 4px;
|
||||
border: 1px solid var(--border-2);
|
||||
border-radius: 3px;
|
||||
color: var(--fg-faint);
|
||||
}
|
||||
|
||||
.home-section-label {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.62rem;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
color: var(--fg-faint);
|
||||
margin: 0 0 var(--r3);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.home-moc-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
|
||||
gap: 8px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.home-moc-card {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* =========================================================
|
||||
@@ -1076,8 +1396,8 @@ li ul li::before { content: "\00B7"; font-weight: normal; color: var(--fg-faint)
|
||||
blockquote {
|
||||
margin: var(--r5) 0;
|
||||
padding: var(--r4) var(--r5);
|
||||
border-left: 2px solid var(--accent);
|
||||
background: var(--accent-glow);
|
||||
border-left: 3px solid var(--accent-2);
|
||||
background: color-mix(in oklab, var(--accent-2) 6%, transparent);
|
||||
border-radius: 0 6px 6px 0;
|
||||
color: var(--fg-dim);
|
||||
font-family: var(--font-serif);
|
||||
@@ -1383,15 +1703,16 @@ pre.src {
|
||||
========================================================= */
|
||||
|
||||
#postamble, footer {
|
||||
color: var(--fg-faint);
|
||||
color: var(--fg-ghost);
|
||||
padding: var(--r2) var(--r5);
|
||||
border-top: 1px solid var(--border);
|
||||
background: var(--bg);
|
||||
border-top: 1px solid color-mix(in oklab, var(--border) 60%, transparent);
|
||||
background: var(--page-bg);
|
||||
text-align: center;
|
||||
font-size: 0.68rem;
|
||||
font-size: 0.62rem;
|
||||
font-family: var(--font-mono);
|
||||
letter-spacing: 0.03em;
|
||||
flex-shrink: 0;
|
||||
opacity: 0.7;
|
||||
}
|
||||
#postamble a, footer a { color: var(--fg-faint); }
|
||||
#postamble a:hover, footer a:hover { color: var(--fg); }
|
||||
@@ -1433,7 +1754,7 @@ pre.src {
|
||||
|
||||
@media (max-width: 768px) {
|
||||
:root {
|
||||
--rail-w: 0px;
|
||||
--rail-w: 48px;
|
||||
--content-pad: 1rem;
|
||||
}
|
||||
#sidebar {
|
||||
@@ -1445,14 +1766,26 @@ pre.src {
|
||||
width: 0;
|
||||
}
|
||||
#sidebar.mobile-open,
|
||||
#sidebar.expanded { width: var(--panel-w) !important; }
|
||||
#sidebar.expanded { width: min(92vw, var(--sidebar-w)) !important; }
|
||||
|
||||
#backlink-panel.open { width: 200px; }
|
||||
#breadcrumb-trail { display: none; }
|
||||
|
||||
#note-toolbar { flex-wrap: wrap; height: auto; }
|
||||
#note-toolbar { flex-wrap: wrap; height: auto; min-height: var(--toolbar-h); }
|
||||
|
||||
/* Code blocks on mobile */
|
||||
.tab-content {
|
||||
padding: var(--r3) var(--r2) var(--r6);
|
||||
}
|
||||
|
||||
.tab-content > #content,
|
||||
.tab-content > .tab-content-inner {
|
||||
padding: var(--r4) var(--content-pad) var(--r6);
|
||||
box-shadow: 0 8px 32px color-mix(in oklab, #000 25%, transparent);
|
||||
}
|
||||
|
||||
.home-moc-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.org-src-container > pre,
|
||||
pre.src {
|
||||
font-size: 0.75rem;
|
||||
|
||||
Reference in New Issue
Block a user