konoma 0.11.0

Full-screen preview-focused terminal file browser (macOS Apple Silicon)
---
// Animated hero terminal: konoma's core loop (full-screen Tree ⇄ full-screen Preview)
// drawn in HTML/CSS instead of a screenshot. Deliberately dark in both site themes —
// it depicts a terminal. Honors prefers-reduced-motion (static Preview frame).
//
// Layout notes (learned the hard way):
// - `not-content` opts the whole widget out of Starlight's markdown flow spacing,
//   which otherwise inflates every row with margins and breaks the fixed height.
// - The frame is CSS borders + flex rows, not ASCII box-drawing, so nothing can
//   misalign or overflow at any viewport width.
interface Props {
	lang?: 'en' | 'ja';
}
const { lang = 'en' } = Astro.props;
const t =
	lang === 'ja'
		? {
				aria: 'konoma のツリー画面とプレビュー画面が切り替わるデモ',
				toggleNote: '— Space でトグル、ファイルに書き戻る',
				linkNote: '← Tab でフォーカス・↵ で開く',
				today: '今日',
			}
		: {
				aria: 'Demo of konoma switching between its tree and preview screens',
				toggleNote: '— Space toggles, written back to the file',
				linkNote: '← Tab focuses, ↵ opens',
				today: 'today',
			};
const rows = [
	{ icon: '▸', name: 'src', mark: '', size: '--', date: '06-30 09:12' },
	{ icon: '', name: 'README.md', mark: 'M', size: '4.2 KB', date: `${t.today} 14:02`, sel: true },
	{ icon: '', name: 'app.rs', mark: 'M', size: '18.6 KB', date: `${t.today} 14:01` },
	{ icon: '', name: 'Cargo.toml', mark: '', size: '1.1 KB', date: '06-28 18:40' },
	{ icon: '', name: 'sample.pdf', mark: '', size: '312 KB', date: '06-27 11:05' },
	{ icon: '', name: 'data.csv', mark: '', size: '8.4 KB', date: '06-27 10:58' },
];
---

<div class="term not-content" role="img" aria-label={t.aria}>
	<div class="term-bar">
		<span class="dot"></span><span class="dot"></span><span class="dot"></span>
		<span class="term-title">konoma — ~/work/app</span>
		<span class="term-mode">TREE ⇄ PREVIEW</span>
	</div>
	<div class="screens">
		<div class="screen tree">
			<div class="thead"><span class="chip">TREE</span><span>sort: name ↑ · path: rel</span></div>
			<div class="frame">
				<span class="frame-label">app</span>
				{
					rows.map((r) => (
						<div class:list={['row', { sel: r.sel }]}>
							<span class="ric">{r.icon}</span>
							<span class:list={['rmark', { on: r.mark }]}>{r.mark || '·'}</span>
							<span class="rname">{r.name}</span>
							<span class="rsize">{r.size}</span>
							<span class="rdate">{r.date}</span>
							{r.sel && <span class="cursor" />}
						</div>
					))
				}
			</div>
			<div class="foot">j/k:move&ensp;↵:preview&ensp;/:filter&ensp;o:git&ensp;F:follow&ensp;?:help</div>
		</div>
		<div class="screen preview">
			<div class="thead"><span class="chip">PREVIEW</span><span>README.md</span></div>
			<div class="frame md">
				<div class="md-h">Release checklist</div>
				<div class="md-line"><span class="on"></span> bump version <span class="dim">{t.toggleNote}</span></div>
				<div class="md-line"><span class="on"></span> update changelog</div>
				<div class="md-line"><span class="dim"></span> tag &amp; publish</div>
				<div class="md-line md-gap">docs: <span class="md-link">CONFIGURATION.md</span> <span class="dim">{t.linkNote}</span></div>
			</div>
			<div class="foot">Tab:link&ensp;Space:toggle&ensp;/:search&ensp;R:raw&ensp;q:back</div>
		</div>
	</div>
</div>

<style>
	/* Fixed comp palette on purpose: a terminal window stays dark in light mode too. */
	.term {
		--t-ink: #0e1310;
		--t-pane: #141b16;
		--t-line: #253128;
		--t-moss: #3e9b63;
		--t-leaf: #a7d3a0;
		--t-paper: #e9efe6;
		--t-bark: #87927f;
		--t-amber: #d9a94e;
		margin: 2.5rem auto 0;
		max-width: 44rem;
		border: 1px solid var(--t-line);
		border-radius: 10px;
		background: var(--t-pane);
		overflow: hidden;
		box-shadow: 0 24px 60px rgba(0, 0, 0, 0.45);
		text-align: left;
		font-family: var(--konoma-mono, 'SF Mono', Menlo, Consolas, monospace);
		font-size: 0.82rem;
		line-height: 1.6;
	}
	.term-bar {
		display: flex;
		align-items: center;
		gap: 8px;
		padding: 10px 14px;
		border-bottom: 1px solid var(--t-line);
		font-size: 12px;
		color: var(--t-bark);
	}
	.dot {
		width: 10px;
		height: 10px;
		border-radius: 50%;
		background: var(--t-line);
		flex: none;
	}
	.term-title {
		margin-left: 10px;
	}
	.term-mode {
		margin-left: auto;
		color: var(--t-moss);
	}

	.screens {
		position: relative;
		height: 19.5rem;
		background: var(--t-ink);
	}
	.screen {
		position: absolute;
		inset: 0;
		display: flex;
		flex-direction: column;
		gap: 0.55rem;
		padding: 14px 18px 12px;
		opacity: 0;
		overflow: hidden;
	}
	.screen.tree {
		animation: showA 9s infinite;
	}
	.screen.preview {
		animation: showB 9s infinite;
	}
	@keyframes showA {
		0%,
		44% {
			opacity: 1;
		}
		50%,
		94% {
			opacity: 0;
		}
		100% {
			opacity: 1;
		}
	}
	@keyframes showB {
		0%,
		44% {
			opacity: 0;
		}
		50%,
		94% {
			opacity: 1;
		}
		100% {
			opacity: 0;
		}
	}
	@media (prefers-reduced-motion: reduce) {
		.screen.tree,
		.screen.preview {
			animation: none;
		}
		.screen.preview {
			opacity: 1;
		}
		.screen.tree {
			display: none;
		}
		.cursor {
			animation: none;
		}
	}

	.thead {
		display: flex;
		align-items: center;
		gap: 0.7rem;
		color: var(--t-bark);
		flex: none;
	}
	.chip {
		color: var(--t-ink);
		background: var(--t-leaf);
		padding: 0 7px;
		border-radius: 3px;
		font-weight: 600;
	}

	.frame {
		position: relative;
		border: 1px solid var(--t-line);
		border-radius: 4px;
		padding: 0.7rem 0.6rem 0.5rem;
		flex: none;
	}
	.frame-label {
		position: absolute;
		top: -0.75em;
		left: 0.8rem;
		background: var(--t-ink);
		padding: 0 0.4em;
		color: var(--t-bark);
	}
	.row {
		display: flex;
		align-items: baseline;
		gap: 0.6ch;
		padding: 0.05rem 0.5rem;
		border-radius: 3px;
		color: var(--t-paper);
	}
	.row.sel {
		background: rgba(62, 155, 99, 0.16);
	}
	.ric {
		width: 1.2ch;
		flex: none;
		color: var(--t-paper);
	}
	.rmark {
		width: 1.2ch;
		flex: none;
		color: transparent;
	}
	.rmark.on {
		color: var(--t-moss);
		font-weight: 600;
	}
	.rname {
		flex: 1;
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
	}
	.rsize,
	.rdate {
		color: var(--t-bark);
		font-variant-numeric: tabular-nums;
		white-space: nowrap;
	}
	.rsize {
		width: 7.5ch;
		text-align: right;
	}
	.rdate {
		width: 12ch;
		text-align: right;
	}
	.cursor {
		display: inline-block;
		width: 8px;
		height: 0.95rem;
		background: var(--t-amber);
		align-self: center;
		flex: none;
		animation: blink 1.2s steps(1) infinite;
	}
	@keyframes blink {
		50% {
			opacity: 0;
		}
	}

	.frame.md {
		padding: 0.8rem 1rem 0.9rem;
	}
	.md-h {
		color: var(--t-leaf);
		font-weight: 700;
		font-size: 1rem;
		border-bottom: 1px solid var(--t-line);
		padding-bottom: 0.35rem;
		margin-bottom: 0.55rem;
	}
	.md-line {
		color: var(--t-paper);
	}
	.md-line .on {
		color: var(--t-moss);
	}
	.md-gap {
		margin-top: 0.9rem;
	}
	.dim {
		color: var(--t-bark);
	}
	.md-link {
		background: var(--t-leaf);
		color: var(--t-ink);
		padding: 0 2px;
		font-weight: 600;
	}

	.foot {
		margin-top: auto;
		flex: none;
		color: var(--t-bark);
		font-size: 0.72rem;
		border-top: 1px solid var(--t-line);
		padding-top: 0.5rem;
		white-space: nowrap;
		overflow: hidden;
	}

	@media (max-width: 40rem) {
		.term {
			font-size: 0.72rem;
		}
		.rdate {
			display: none;
		}
		.screens {
			height: 18rem;
		}
	}
</style>