website/src/components/home/Projects.astro (2886B)
--- // Projects: a wheel of cards, out in the margin on wide screens, and the // project picked on it, written up with its demo. Each project is a file in // src/content/projects; src/scripts/home/projects.js turns the wheel, and a // project's demo is src/scripts/demos/<demo>.js. Without JavaScript the // projects are a plain list. import { getCollection, render } from "astro:content"; import Buffer from "../frame/Buffer.astro"; import ProjectThumb from "./ProjectThumb.astro"; import "../../styles/home/projects.css"; import "../../styles/demos/demo.css"; const CATS: Record<string, string> = { hw: "hardware", sw: "software", math: "maths", sec: "security" }; const projects = await Promise.all( (await getCollection("projects")) .sort((a, b) => a.data.order - b.data.order) .map(async (p) => ({ ...p.data, Body: (await render(p)).Content })), ); --- <Buffer id="projects" file="~/projects/" mode="Dired" metaAttrs={{ "data-pj": "meta" }}> <h2 class="h2">Projects</h2> <div class="pj"> <div class="pj-wheel"> <svg class="pj-spine" aria-hidden="true"><path /></svg> <div class="pj-cards" role="tablist" aria-label="Projects" aria-orientation="vertical"> {projects.map((p, i) => ( <button type="button" class="pj-card" id={`pj-${p.name}`} role="tab" data-cat={p.cat} aria-selected={i === 0 ? "true" : "false"} aria-controls="project-detail" tabindex={i === 0 ? 0 : -1}> <span class="pj-thumb"><ProjectThumb demo={p.demo} /></span> <span class="pj-label">{p.name}</span> {/* the category, which the card shows only as the colour of its swatch */} <span class="sr-only">, {CATS[p.cat] ?? p.cat}</span> </button> ))} </div> <p class="pj-hint" aria-hidden="true">drag or <kbd>↑</kbd> <kbd>↓</kbd></p> </div> <article class="pj-detail" id="project-detail" role="tabpanel"></article> <ol class="pj-list"> {projects.map((p) => ( <li class="proj" data-name={p.name} data-cat={p.cat} data-demo={p.demo} data-demo-note={p.demoNote}> <p class="proj-cat">{CATS[p.cat] ?? p.cat}</p> <h3 class="proj-name">{p.title}</h3> <p class="proj-sum">{p.summary}</p> <dl class="proj-facts"> {p.stack && <div><dt>stack</dt><dd>{p.stack}</dd></div>} {p.links.length > 0 && <div><dt>links</dt><dd>{p.links.map((l) => <a href={l.href}>{l.text}</a>)}</dd></div>} {p.note && <div><dt>source</dt><dd>{p.note}</dd></div>} </dl> <div class="proj-body"><p.Body /></div> </li> ))} </ol> </div> </Buffer> <script> import { initProjects } from "../../scripts/home/projects.js"; // the 8-bit computer's M-x commands; its project mounts the board as a demo import "../../scripts/demos/computer/commands.js"; initProjects(); </script>