Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
9.5 kB · 231 lines
Astro
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232---// The app shell: file-tree sidebar + main pane (breadcrumbs, optional toolbar// slot, content). Light/dark follow the OS via prefers-color-scheme; Shiki emits// both theme palettes as CSS variables that the stylesheet swaps.import Breadcrumbs from '../components/Breadcrumbs.astro';import FileTree from '../components/FileTree.astro';import { buildTree, siteTitle } from '../lib/manifest';
interface Props { path?: string; view?: 'rendered' | 'raw' | 'tree';}const { path = '', view = 'rendered' } = Astro.props;// Sidebar links stay in the current file view; directory (tree) pages fall back to rendered.const treeView = view === 'raw' ? 'raw' : 'rendered';const tree = buildTree();---
<html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>{siteTitle} - owl</title> <link rel="icon" type="image/gif" href="/owl-cat.gif" /> <style is:global> :root { color-scheme: light dark; --owl-bg: #ffffff; --owl-fg: #1f2328; --owl-muted: #656d76; --owl-border: #d0d7de; --owl-accent: #0969da; --owl-code-bg: #f6f8fa; --owl-hl: #fff8c5; --owl-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace; } @media (prefers-color-scheme: dark) { :root { --owl-bg: #0d1117; --owl-fg: #e6edf3; --owl-muted: #8b949e; --owl-border: #30363d; --owl-accent: #4493f8; --owl-code-bg: #161b22; --owl-hl: #2e2a15; } }
* { box-sizing: border-box; } html, body { margin: 0; } body { display: flex; min-height: 100vh; font-family: system-ui, -apple-system, sans-serif; background: var(--owl-bg); color: var(--owl-fg); line-height: 1.5; } a { color: var(--owl-accent); text-decoration: none; } a:hover { text-decoration: underline; }
.owl-sidebar { width: 20rem; flex: 0 0 20rem; height: 100vh; position: sticky; top: 0; overflow: auto; border-right: 1px solid var(--owl-border); padding: 0.75rem; font-size: 0.85rem; } .owl-logo { display: flex; flex-direction: row; align-items: center; gap: 0.5rem; padding: 0.25rem 0.25rem 0.75rem; color: var(--owl-fg); } .owl-logo:hover { text-decoration: none; } .owl-logo-img { height: 2.25rem; width: auto; flex: 0 0 auto; border-radius: 0.35rem; } .owl-logo-text { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; } .owl-logo-name { font-weight: 700; font-size: 1.1rem; } .owl-logo-now { font-size: 0.72rem; font-weight: 400; color: var(--owl-muted); }
.owl-tree { list-style: none; margin: 0; padding: 0 0 0 0.6rem; } .owl-tree summary { cursor: pointer; padding: 0.1rem 0; color: var(--owl-fg); } .owl-tree summary::marker { color: var(--owl-muted); } .owl-tree-file a { display: block; padding: 0.1rem 0; color: var(--owl-fg); } .owl-tree-file.owl-current > a { color: var(--owl-accent); font-weight: 600; }
.owl-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; } .owl-header { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 0.6rem 1rem; border-bottom: 1px solid var(--owl-border); position: sticky; top: 0; background: var(--owl-bg); z-index: 1; } .owl-crumbs { font-size: 0.9rem; overflow-wrap: anywhere; } .owl-crumbs .sep { color: var(--owl-muted); margin: 0 0.35rem; } .owl-crumbs .cur { color: var(--owl-fg); font-weight: 600; }
.owl-copy-path { display: inline-flex; align-items: center; vertical-align: middle; margin-left: 0.4rem; padding: 0.15rem; line-height: 0; border: none; background: none; border-radius: 0.3rem; color: var(--owl-muted); cursor: pointer; } .owl-copy-path:hover { color: var(--owl-fg); background: var(--owl-code-bg); } .owl-copy-path .check-icon { display: none; } .owl-copy-path.owl-copied { color: var(--owl-accent); } .owl-copy-path.owl-copied .copy-icon { display: none; } .owl-copy-path.owl-copied .check-icon { display: inline; }
.owl-toggle { display: flex; gap: 0; border: 1px solid var(--owl-border); border-radius: 0.4rem; overflow: hidden; flex: none; } .owl-toggle a { padding: 0.25rem 0.7rem; color: var(--owl-fg); font-size: 0.85rem; } .owl-toggle a.active { background: var(--owl-accent); color: #fff; }
.owl-content { padding: 1rem; min-width: 0; }
/* Shiki dual theme (defaultColor:false): Shiki supplies token colors only; every code block — highlighted, plain, raw or rendered — shares one background so there is a single, consistent source-code look. */ .shiki { background-color: var(--owl-code-bg); color: var(--owl-fg); } .shiki span { color: var(--shiki-light); } @media (prefers-color-scheme: dark) { .shiki span { color: var(--shiki-dark); } } pre.shiki { margin: 0; padding: 0.75rem 1rem; border-radius: 0.4rem; overflow-x: auto; font-family: var(--owl-mono); font-size: 0.85rem; tab-size: 2; } .shiki .line { display: inline-block; width: 100%; } .owl-line-hl { background: var(--owl-hl); }
/* Line-number gutter (both views). Absolute numbers come from the data-line-no the highlighter stamps on each source line; attr-scoped so markdown-it code fences (not Shiki output) never get an empty gutter. */ .shiki .line[data-line-no]::before { content: attr(data-line-no); display: inline-block; box-sizing: content-box; /* keep the 3ch number width off the padding/border */ width: 3ch; padding-right: 1rem; margin-right: 1rem; border-right: 1px solid var(--owl-border); text-align: right; color: var(--owl-muted); user-select: none; }
/* The rendered view interleaves code blocks with comment boxes. */ .owl-rendered > .shiki { border-radius: 0; } .owl-rendered > .shiki:first-child { border-top-left-radius: 0.4rem; border-top-right-radius: 0.4rem; } .owl-rendered > .shiki:last-child { border-bottom-left-radius: 0.4rem; border-bottom-right-radius: 0.4rem; }
/* Prose lifted from comment runs. Neutral (no callout color): it reads as distinct from code by inverting the fill — code blocks are filled gray, a comment box is page-colored with a full border. */ .owl-comment { background: var(--owl-bg); border: 1px solid var(--owl-border); border-radius: 0.4rem; padding: 0.5rem 1rem; margin: 0.4rem 0; } .owl-comment > :first-child { margin-top: 0; } .owl-comment > :last-child { margin-bottom: 0; }
.owl-prose { max-width: 52rem; } .owl-prose pre { background: var(--owl-code-bg); padding: 0.75rem 1rem; border-radius: 0.4rem; overflow-x: auto; } .owl-prose code { font-family: var(--owl-mono); font-size: 0.9em; } .owl-prose :not(pre) > code { background: var(--owl-code-bg); padding: 0.1rem 0.3rem; border-radius: 0.3rem; } .owl-prose img { max-width: 100%; } .owl-readme { margin-top: 1.5rem; border-top: 1px solid var(--owl-border); padding-top: 1rem; }
.owl-listing ul { list-style: none; margin: 0 0 1rem; padding: 0; } .owl-listing li { padding: 0.15rem 0; } .owl-dir a { font-weight: 600; }
.owl-binary { color: var(--owl-muted); font-style: italic; padding: 1rem; }
@media (max-width: 720px) { body { flex-direction: column; } .owl-sidebar { width: 100%; flex-basis: auto; height: auto; position: static; max-height: 40vh; border-right: none; border-bottom: 1px solid var(--owl-border); } } </style> </head> <body> <aside class="owl-sidebar"> <a class="owl-logo" href="/"> <img class="owl-logo-img" src="/owl-cat.gif" alt="" width="165" height="173" /> <span class="owl-logo-text"> <span class="owl-logo-name">owl</span> <span class="owl-logo-now">now showing: {siteTitle}</span> </span> </a> <FileTree node={tree} current={path} view={treeView} /> </aside> <main class="owl-main"> <header class="owl-header"> <Breadcrumbs path={path} /> <slot name="toolbar" /> </header> <div class="owl-content"> <slot /> </div> </main> <script> // Highlight an #L12 or #L12-L20 line range from the URL hash (raw view). function applyHash(): void { document.querySelectorAll('.owl-line-hl').forEach((el) => el.classList.remove('owl-line-hl')); const m = location.hash.match(/^#L(\d+)(?:-L?(\d+))?$/); if (!m) return; const start = Number.parseInt(m[1], 10); const end = m[2] ? Number.parseInt(m[2], 10) : start; for (let n = start; n <= end; n++) { document.getElementById(`L${n}`)?.classList.add('owl-line-hl'); } document.getElementById(`L${start}`)?.scrollIntoView({ block: 'center' }); } addEventListener('hashchange', applyHash); applyHash(); </script> </body></html>