chore: bootstrap lean sysadmin-chronicles repo
Import the runnable game code, content, docs, scripts, and repo guidance while leaving local agent state, dependency installs, build output, and backup copies out of the published tree.
This commit is contained in:
+317
@@ -0,0 +1,317 @@
|
||||
/* Sage — knowledge base app */
|
||||
|
||||
let allArticles = [];
|
||||
let navIndex = null;
|
||||
let currentArticleId = null;
|
||||
|
||||
async function loadData() {
|
||||
const [indexRes, ...articleRes] = await Promise.all([
|
||||
fetch('/sage/api/_index.json'),
|
||||
...ARTICLE_IDS.map(id => fetch(`/sage/api/${id}.json`))
|
||||
]);
|
||||
navIndex = await indexRes.json();
|
||||
allArticles = await Promise.all(articleRes.map(r => r.json()));
|
||||
}
|
||||
|
||||
const ARTICLE_IDS = [
|
||||
'ssh-keys', 'ssh-access-controls', 'nginx-config',
|
||||
'disk-logs', 'file-permissions', 'cron-jobs',
|
||||
'time-sync', 'package-management'
|
||||
];
|
||||
|
||||
const CATEGORY_LABELS = {
|
||||
access: 'Access & Authentication',
|
||||
web: 'Web Services',
|
||||
storage: 'Storage & Logs',
|
||||
sysadmin: 'System Administration',
|
||||
packages: 'Package Management'
|
||||
};
|
||||
|
||||
// ── Sidebar ───────────────────────────────────────────────────────────────────
|
||||
|
||||
function buildNav() {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
sidebar.innerHTML = '';
|
||||
|
||||
// Home link
|
||||
const homeWrap = document.createElement('div');
|
||||
homeWrap.className = 'nav-section';
|
||||
const homeLink = document.createElement('a');
|
||||
homeLink.className = 'nav-link';
|
||||
homeLink.textContent = '⌂ Home';
|
||||
homeLink.dataset.home = '1';
|
||||
homeLink.onclick = (e) => { e.preventDefault(); showHome(); };
|
||||
homeWrap.appendChild(homeLink);
|
||||
sidebar.appendChild(homeWrap);
|
||||
|
||||
navIndex.categories.forEach(cat => {
|
||||
const section = document.createElement('div');
|
||||
section.className = 'nav-section';
|
||||
|
||||
const label = document.createElement('div');
|
||||
label.className = 'nav-category';
|
||||
label.textContent = cat.label;
|
||||
section.appendChild(label);
|
||||
|
||||
cat.articles.forEach(id => {
|
||||
const article = allArticles.find(a => a.id === id);
|
||||
if (!article) return;
|
||||
const link = document.createElement('a');
|
||||
link.className = 'nav-link';
|
||||
link.textContent = article.title;
|
||||
link.dataset.articleId = id;
|
||||
link.onclick = (e) => { e.preventDefault(); showArticle(id); };
|
||||
section.appendChild(link);
|
||||
});
|
||||
|
||||
sidebar.appendChild(section);
|
||||
});
|
||||
}
|
||||
|
||||
function setActiveNav(articleId) {
|
||||
document.querySelectorAll('.nav-link').forEach(el => {
|
||||
el.classList.toggle('active',
|
||||
articleId ? el.dataset.articleId === articleId : !!el.dataset.home
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// ── Home ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
function showHome() {
|
||||
currentArticleId = null;
|
||||
clearSearch();
|
||||
setActiveNav(null);
|
||||
const main = document.getElementById('main');
|
||||
main.classList.remove('hidden');
|
||||
document.getElementById('search-results').classList.remove('visible');
|
||||
|
||||
main.innerHTML = '';
|
||||
const home = document.createElement('div');
|
||||
home.id = 'home-page';
|
||||
|
||||
const h1 = document.createElement('h1');
|
||||
h1.textContent = 'Sage — Internal Knowledge Base';
|
||||
const subtitle = document.createElement('p');
|
||||
subtitle.className = 'home-subtitle';
|
||||
subtitle.textContent = 'Runbooks, reference guides, and procedures for Axiom Works infrastructure.';
|
||||
|
||||
home.appendChild(h1);
|
||||
home.appendChild(subtitle);
|
||||
|
||||
const grid = document.createElement('div');
|
||||
grid.className = 'home-grid';
|
||||
|
||||
navIndex.categories.forEach(cat => {
|
||||
const catLabel = document.createElement('div');
|
||||
catLabel.className = 'home-category-label';
|
||||
catLabel.textContent = cat.label;
|
||||
home.appendChild(catLabel);
|
||||
|
||||
const catGrid = document.createElement('div');
|
||||
catGrid.className = 'home-grid';
|
||||
|
||||
cat.articles.forEach(id => {
|
||||
const article = allArticles.find(a => a.id === id);
|
||||
if (!article) return;
|
||||
const card = document.createElement('div');
|
||||
card.className = 'home-card';
|
||||
card.innerHTML = `
|
||||
<div class="home-card-title">${esc(article.title)}</div>
|
||||
<div class="home-card-summary">${esc(article.summary)}</div>
|
||||
`;
|
||||
card.onclick = () => showArticle(id);
|
||||
catGrid.appendChild(card);
|
||||
});
|
||||
|
||||
home.appendChild(catGrid);
|
||||
});
|
||||
|
||||
main.appendChild(home);
|
||||
main.scrollTop = 0;
|
||||
}
|
||||
|
||||
// ── Article ───────────────────────────────────────────────────────────────────
|
||||
|
||||
function showArticle(id) {
|
||||
const article = allArticles.find(a => a.id === id);
|
||||
if (!article) return;
|
||||
currentArticleId = id;
|
||||
clearSearch();
|
||||
setActiveNav(id);
|
||||
|
||||
const main = document.getElementById('main');
|
||||
main.classList.remove('hidden');
|
||||
document.getElementById('search-results').classList.remove('visible');
|
||||
|
||||
const catLabel = CATEGORY_LABELS[article.category] ?? article.category;
|
||||
|
||||
let html = `
|
||||
<h1 class="article-title">${esc(article.title)}</h1>
|
||||
<div class="article-meta">
|
||||
<span class="article-category-badge">${esc(catLabel)}</span>
|
||||
<span class="article-updated">${esc(article.updated)}</span>
|
||||
</div>
|
||||
<p class="article-summary">${esc(article.summary)}</p>
|
||||
`;
|
||||
|
||||
article.sections.forEach(section => {
|
||||
html += `<div class="section">`;
|
||||
if (section.heading) {
|
||||
html += `<h2>${esc(section.heading)}</h2>`;
|
||||
}
|
||||
if (section.body) {
|
||||
html += `<div class="section-body">${section.body}</div>`;
|
||||
}
|
||||
if (section.code) {
|
||||
html += `<pre><code>${esc(section.code)}</code></pre>`;
|
||||
}
|
||||
html += `</div>`;
|
||||
});
|
||||
|
||||
if (article.tags?.length) {
|
||||
html += `<div class="article-tags">${article.tags.map(t => `<span class="tag">${esc(t)}</span>`).join('')}</div>`;
|
||||
}
|
||||
|
||||
main.innerHTML = html;
|
||||
main.scrollTop = 0;
|
||||
}
|
||||
|
||||
// ── Search ────────────────────────────────────────────────────────────────────
|
||||
|
||||
let searchTimer = null;
|
||||
|
||||
function onSearchInput(e) {
|
||||
const q = e.target.value.trim();
|
||||
clearTimeout(searchTimer);
|
||||
if (!q) { clearSearch(); return; }
|
||||
searchTimer = setTimeout(() => runSearch(q), 120);
|
||||
}
|
||||
|
||||
function clearSearch() {
|
||||
document.getElementById('search').value = '';
|
||||
document.getElementById('search-results').classList.remove('visible');
|
||||
const main = document.getElementById('main');
|
||||
main.classList.remove('hidden');
|
||||
}
|
||||
|
||||
function runSearch(q) {
|
||||
const terms = q.toLowerCase().split(/\s+/).filter(Boolean);
|
||||
const results = [];
|
||||
|
||||
for (const article of allArticles) {
|
||||
const haystack = [
|
||||
article.title,
|
||||
article.summary,
|
||||
...(article.tags ?? []),
|
||||
...article.sections.flatMap(s => [s.heading ?? '', textFromHtml(s.body ?? ''), s.code ?? ''])
|
||||
].join(' ').toLowerCase();
|
||||
|
||||
const score = terms.filter(t => haystack.includes(t)).length;
|
||||
if (score === 0) continue;
|
||||
|
||||
// Find a snippet with the first matching term
|
||||
let snippet = article.summary;
|
||||
const firstTerm = terms[0];
|
||||
for (const section of article.sections) {
|
||||
const text = textFromHtml(section.body ?? '') + ' ' + (section.code ?? '');
|
||||
const idx = text.toLowerCase().indexOf(firstTerm);
|
||||
if (idx !== -1) {
|
||||
const start = Math.max(0, idx - 60);
|
||||
const end = Math.min(text.length, idx + 120);
|
||||
snippet = (start > 0 ? '…' : '') + text.slice(start, end) + (end < text.length ? '…' : '');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
results.push({ article, score, snippet });
|
||||
}
|
||||
|
||||
results.sort((a, b) => b.score - a.score);
|
||||
|
||||
const container = document.getElementById('search-results');
|
||||
container.classList.add('visible');
|
||||
document.getElementById('main').classList.add('hidden');
|
||||
|
||||
if (results.length === 0) {
|
||||
container.innerHTML = `<div class="no-results">No articles matched "<strong>${esc(q)}</strong>".</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = results.map(({ article, snippet }) => {
|
||||
const highlighted = highlightTerms(esc(snippet), terms);
|
||||
return `
|
||||
<div class="search-result" data-id="${esc(article.id)}">
|
||||
<div class="search-result-title">${esc(article.title)}</div>
|
||||
<div class="search-result-snippet">${highlighted}</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
container.querySelectorAll('.search-result').forEach(el => {
|
||||
el.onclick = () => showArticle(el.dataset.id);
|
||||
});
|
||||
}
|
||||
|
||||
function highlightTerms(text, terms) {
|
||||
let out = text;
|
||||
terms.forEach(term => {
|
||||
const re = new RegExp(`(${escapeRegex(term)})`, 'gi');
|
||||
out = out.replace(re, '<mark>$1</mark>');
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
// ── Utilities ─────────────────────────────────────────────────────────────────
|
||||
|
||||
function esc(str) {
|
||||
return String(str ?? '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function textFromHtml(html) {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = html;
|
||||
return div.textContent ?? '';
|
||||
}
|
||||
|
||||
function escapeRegex(str) {
|
||||
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
|
||||
// ── Init ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
async function init() {
|
||||
const main = document.getElementById('main');
|
||||
main.innerHTML = '<p style="color:var(--text-muted);padding:20px">Loading…</p>';
|
||||
|
||||
try {
|
||||
await loadData();
|
||||
} catch (err) {
|
||||
main.innerHTML = `<p style="color:var(--text-muted);padding:20px">Failed to load knowledge base: ${esc(err.message)}</p>`;
|
||||
return;
|
||||
}
|
||||
|
||||
buildNav();
|
||||
showHome();
|
||||
|
||||
document.getElementById('search').addEventListener('input', onSearchInput);
|
||||
|
||||
// Keyboard shortcut: / to focus search
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key === '/' && document.activeElement !== document.getElementById('search')) {
|
||||
e.preventDefault();
|
||||
document.getElementById('search').focus();
|
||||
}
|
||||
if (e.key === 'Escape') {
|
||||
clearSearch();
|
||||
if (currentArticleId) setActiveNav(currentArticleId);
|
||||
else showHome();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Sage — Axiom Works KB</title>
|
||||
<link rel="stylesheet" href="/sage/style.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header id="header">
|
||||
<img class="header-logo" src="/sage/logo.png" alt="Axiom Works">
|
||||
<span class="wordmark">sage</span>
|
||||
<span class="tagline">Axiom Works Internal Knowledge Base</span>
|
||||
<div id="search-wrap">
|
||||
<span id="search-icon">⌕</span>
|
||||
<input id="search" type="search" placeholder="Search docs… ( / )" autocomplete="off" spellcheck="false" />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div id="layout">
|
||||
<nav id="sidebar"></nav>
|
||||
|
||||
<div id="search-results"></div>
|
||||
<main id="main"></main>
|
||||
</div>
|
||||
|
||||
<script src="/sage/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 844 KiB |
+416
@@ -0,0 +1,416 @@
|
||||
:root {
|
||||
--bg: #0d1117;
|
||||
--bg-sidebar: #161b22;
|
||||
--bg-content: #0d1117;
|
||||
--bg-code: #161b22;
|
||||
--bg-hover: #1f2937;
|
||||
--border: #30363d;
|
||||
--text: #c9d1d9;
|
||||
--text-muted: #6e7681;
|
||||
--text-heading: #e6edf3;
|
||||
--text-code: #79c0ff;
|
||||
--accent: #388bfd;
|
||||
--accent-soft: rgba(56, 139, 253, 0.12);
|
||||
--accent-active: rgba(56, 139, 253, 0.2);
|
||||
--tag-bg: #21262d;
|
||||
--tag-text: #8b949e;
|
||||
--good: #3fb950;
|
||||
--font-sans: -apple-system, "Segoe UI", system-ui, sans-serif;
|
||||
--font-mono: "JetBrains Mono", "Fira Code", "Cascadia Code", "Consolas", monospace;
|
||||
}
|
||||
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
html, body { height: 100%; }
|
||||
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: var(--text);
|
||||
background: var(--bg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* ── Header ────────────────────────────────────────── */
|
||||
#header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 0 20px;
|
||||
height: 52px;
|
||||
background: var(--bg-sidebar);
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-shrink: 0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
#header .header-logo {
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
border-radius: 5px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
#header .wordmark {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--accent);
|
||||
letter-spacing: -0.5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#header .tagline {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
border-left: 1px solid var(--border);
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
#search-wrap {
|
||||
margin-left: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#search {
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
color: var(--text);
|
||||
font-family: var(--font-sans);
|
||||
font-size: 13px;
|
||||
padding: 6px 12px 6px 32px;
|
||||
width: 260px;
|
||||
outline: none;
|
||||
transition: border-color 0.15s, width 0.2s;
|
||||
}
|
||||
|
||||
#search::placeholder { color: var(--text-muted); }
|
||||
|
||||
#search:focus {
|
||||
border-color: var(--accent);
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
#search-icon {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* ── Layout ─────────────────────────────────────────── */
|
||||
#layout {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* ── Sidebar ─────────────────────────────────────────── */
|
||||
#sidebar {
|
||||
width: 220px;
|
||||
flex-shrink: 0;
|
||||
background: var(--bg-sidebar);
|
||||
border-right: 1px solid var(--border);
|
||||
overflow-y: auto;
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.nav-section { margin-bottom: 8px; }
|
||||
|
||||
.nav-category {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
padding: 6px 16px 4px;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
display: block;
|
||||
padding: 5px 16px;
|
||||
font-size: 13px;
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
border-left: 2px solid transparent;
|
||||
transition: background 0.1s, border-color 0.1s;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-heading);
|
||||
}
|
||||
|
||||
.nav-link.active {
|
||||
background: var(--accent-active);
|
||||
border-left-color: var(--accent);
|
||||
color: var(--text-heading);
|
||||
}
|
||||
|
||||
/* ── Main content ─────────────────────────────────────── */
|
||||
#main {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 36px 48px;
|
||||
max-width: 860px;
|
||||
}
|
||||
|
||||
/* ── Search results ──────────────────────────────────── */
|
||||
#search-results {
|
||||
display: none;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 24px 48px;
|
||||
}
|
||||
|
||||
#search-results.visible { display: block; }
|
||||
#main.hidden { display: none; }
|
||||
|
||||
.search-result {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 16px 20px;
|
||||
margin-bottom: 12px;
|
||||
cursor: pointer;
|
||||
transition: background 0.1s, border-color 0.1s;
|
||||
}
|
||||
|
||||
.search-result:hover {
|
||||
background: var(--bg-hover);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.search-result-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--text-heading);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.search-result-snippet {
|
||||
font-size: 13px;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.search-result-snippet mark {
|
||||
background: var(--accent-soft);
|
||||
color: var(--text);
|
||||
border-radius: 2px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.no-results {
|
||||
color: var(--text-muted);
|
||||
font-size: 14px;
|
||||
padding: 24px 0;
|
||||
}
|
||||
|
||||
/* ── Article typography ──────────────────────────────── */
|
||||
.article-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 24px;
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.article-updated::before { content: "Updated "; }
|
||||
|
||||
.article-category-badge {
|
||||
background: var(--tag-bg);
|
||||
color: var(--tag-text);
|
||||
border-radius: 20px;
|
||||
padding: 2px 10px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
h1.article-title {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: var(--text-heading);
|
||||
line-height: 1.3;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.article-summary {
|
||||
font-size: 14px;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 32px;
|
||||
padding-bottom: 24px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.article-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
background: var(--tag-bg);
|
||||
color: var(--tag-text);
|
||||
border-radius: 4px;
|
||||
padding: 2px 8px;
|
||||
font-size: 11px;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.section { margin-bottom: 32px; }
|
||||
|
||||
.section h2 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-heading);
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.section p { margin-bottom: 10px; }
|
||||
.section p:last-child { margin-bottom: 0; }
|
||||
|
||||
.section ul, .section ol {
|
||||
padding-left: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.section li { margin-bottom: 4px; }
|
||||
|
||||
.section code {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12.5px;
|
||||
color: var(--text-code);
|
||||
background: var(--bg-code);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
padding: 1px 5px;
|
||||
}
|
||||
|
||||
.section pre {
|
||||
background: var(--bg-code);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 16px;
|
||||
overflow-x: auto;
|
||||
margin: 12px 0;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12.5px;
|
||||
line-height: 1.7;
|
||||
color: var(--text-code);
|
||||
}
|
||||
|
||||
.section pre code {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.section table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 12px 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.section th {
|
||||
background: var(--bg-sidebar);
|
||||
color: var(--text-muted);
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.section td {
|
||||
padding: 7px 12px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.section tr:nth-child(even) td { background: rgba(255,255,255,0.02); }
|
||||
|
||||
/* ── Home page ───────────────────────────────────────── */
|
||||
#home-page h1 {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: var(--text-heading);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
#home-page .home-subtitle {
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 32px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.home-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.home-card {
|
||||
background: var(--bg-sidebar);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 16px;
|
||||
cursor: pointer;
|
||||
transition: background 0.1s, border-color 0.1s;
|
||||
}
|
||||
|
||||
.home-card:hover {
|
||||
background: var(--bg-hover);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.home-card-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--text-heading);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.home-card-summary {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.home-category-label {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--text-muted);
|
||||
margin: 24px 0 10px;
|
||||
}
|
||||
|
||||
.home-category-label:first-of-type { margin-top: 0; }
|
||||
|
||||
/* ── Scrollbars ──────────────────────────────────────── */
|
||||
::-webkit-scrollbar { width: 8px; height: 8px; }
|
||||
::-webkit-scrollbar-track { background: transparent; }
|
||||
::-webkit-scrollbar-thumb { background: #30363d; border-radius: 4px; }
|
||||
::-webkit-scrollbar-thumb:hover { background: #484f58; }
|
||||
|
||||
/* ── Responsive ──────────────────────────────────────── */
|
||||
@media (max-width: 700px) {
|
||||
#sidebar { display: none; }
|
||||
#main, #search-results { padding: 20px 20px; }
|
||||
#search { width: 180px; }
|
||||
#search:focus { width: 200px; }
|
||||
#header .tagline { display: none; }
|
||||
}
|
||||
Reference in New Issue
Block a user