website/src/styles/home/projects.css (9729B)
/* Projects: a wheel of cards (out in the left margin on wide screens, across the top on narrower ones) and the write-up and demo of the one picked on it (src/scripts/home/projects.js) */ .pj { /* The wheel's height, and the least a project's split takes: as tall as the tallest project (the 8-bit computer, about 721px), so picking another one doesn't move the page, unless the window is too short to show that much. */ --pj-h: min(724px, calc(100svh - var(--top-h) - var(--frame-h) - 72px)); } /* with room beside the content column, the wheel stands out in the margin */ @media (min-width: 1400px) { .pj { --pj-gap: 44px; display: grid; grid-template-columns: var(--wheel) minmax(0, 1fr); column-gap: var(--pj-gap); align-items: start; --wheel: min(236px, calc((100vw - var(--col)) / 2 + var(--gutter) - var(--pj-gap) - 20px)); margin-left: calc(-1 * (var(--wheel) + var(--pj-gap))); } } /* ── the wheel ─────────────────────────────────────────────────────── */ .pj-wheel { position: sticky; top: calc(var(--top-h) + 24px); height: var(--pj-h); /* a finger moving up or down scrolls the page, even over the wheel */ touch-action: pan-y; -webkit-user-select: none; user-select: none; } /* The cards and the spine fade out towards the top and bottom of the wheel. Their layer reaches out into the margin, where the arc goes, so the fade doesn't cut it off. */ .pj-spine, .pj-cards { position: absolute; top: 0; bottom: 0; left: -320px; width: calc(100% + 320px); -webkit-mask-image: linear-gradient(to bottom, transparent, #000 26%, #000 70%, transparent calc(100% - 28px)); mask-image: linear-gradient(to bottom, transparent, #000 26%, #000 70%, transparent calc(100% - 28px)); } .pj-spine { height: 100%; pointer-events: none; } .pj-spine path { fill: none; stroke: var(--rule); stroke-width: 1.25; } .pj-card { position: absolute; top: 0; left: 0; display: block; width: min(176px, calc(var(--wheel) - 34px)); padding: 0; border: 1px solid var(--rule); background: var(--bg); color: var(--dim); text-align: left; transform-origin: 50% 50%; will-change: transform; transition: border-color 150ms ease, color 150ms ease; } @media (hover: hover) { .pj-card:hover { border-color: var(--faint); } } .pj-card[aria-selected="true"] { border-color: var(--fg); color: var(--fg); } .is-dragging .pj-card { cursor: grabbing; } /* each card hangs off the spine by a stub with a via at its end, like a trace */ .pj-card::before, .pj-card::after { content: ""; position: absolute; pointer-events: none; } .pj-card::before { top: 50%; right: 100%; width: 14px; height: 1px; background: var(--rule); } .pj-card::after { top: calc(50% - 3px); right: calc(100% + 11px); width: 7px; height: 7px; border: 1px solid var(--faint); border-radius: 50%; background: var(--bg); } .pj-thumb { display: block; aspect-ratio: 160 / 96; padding: 7px; border-bottom: 1px solid var(--rule); } .pj-card[aria-selected="true"] .pj-thumb { border-bottom-color: var(--faint); } .pj-thumb svg { display: block; width: 100%; height: 100%; } .pj-label { display: block; padding: 5px 8px 6px; overflow: hidden; font-size: 11px; line-height: 1.35; text-overflow: ellipsis; white-space: nowrap; } .pj-label::before { content: ""; display: inline-block; width: 6px; height: 6px; margin-right: 1ch; background: var(--c, var(--fg)); vertical-align: 0.08em; } /* the picked card always stops at this pointer */ .pj-pointer { position: absolute; top: 0; left: 0; width: 0; height: 0; border-top: 5px solid transparent; border-bottom: 5px solid transparent; border-left: 7px solid var(--fg); pointer-events: none; } .pj-hint { position: absolute; right: 16px; bottom: 4px; margin: 0; font-size: 11px; color: var(--dim); } .pj-hint kbd { font-size: 11px; } /* ── the drawings on the cards ────────────────────────────────────── */ .th { font: 400 9.5px var(--mono); } .th-box, .th-line, .th-q { fill: none; stroke: var(--faint); stroke-width: 1; } .th-line { stroke: var(--rule); } .th-dash { stroke-dasharray: 3 3; } .th-on { fill: var(--c, var(--fg)); } .th-red { fill: var(--red); } .th-off { fill: var(--led-off); } .th-dim { fill: var(--faint); } .th-disc { fill: var(--fg); } .th-disc-o { fill: var(--bg); stroke: var(--fg); stroke-width: 1; } .th-seg, .th-seg-off { fill: none; stroke-width: 2.2; stroke-linecap: round; } .th-seg { stroke: var(--red); } .th-seg-off { stroke: var(--led-off); } .th-src { fill: var(--fg); } .th-op { fill: var(--dim); } .th-op.is-at { fill: var(--c, var(--fg)); } .th g g text.th-op { text-anchor: middle; } /* ── a project ─────────────────────────────────────────────────────── */ /* The write-up and the demo are two windows side by side, as after C-x 3: the same height, with a divider between them. The split is at least as tall as the wheel, so picking another project hardly changes the page's height. */ .pj-detail { container: pj-detail / inline-size; } /* the category, as a header line over each project in the list without JavaScript (on the wheel, the card shows it) */ .proj-cat { display: flex; align-items: center; margin: 0 0 12px; font-size: 12px; color: var(--dim); } .proj-cat::before { content: ""; flex: none; width: 7px; height: 7px; margin-right: 1ch; background: var(--c, var(--fg)); } .proj-cat::after { content: ""; flex: 1; height: 1px; margin-left: 1.5ch; background: var(--rule); } .proj-name { margin: 0 0 16px; font: 400 clamp(30px, 3.6vw, 44px)/1.06 var(--serif); } .proj-sum { max-width: 64ch; margin: 0 0 18px; font-size: 13px; color: var(--dim); } /* stack, links and source, labelled */ .proj-facts { margin: 0 0 26px; font-size: 13px; } .proj-facts div { display: flex; gap: 1.5ch; } .proj-facts dt { flex: none; width: 6ch; color: var(--dim); } .proj-facts dd { margin: 0; } .proj-facts a { margin-right: 2ch; } /* the write-up reads at the same size as About */ .proj-body > p, .proj-body li { max-width: 34em; font: 400 19px/1.55 var(--serif); } .proj-body > p { margin: 0 0 14px; } .proj-body ul { max-width: 36em; margin: 0 0 14px; padding-left: 1.2em; } .proj-body li { margin-bottom: 6px; } /* on phones, like About's text */ @media (max-width: 640px) { .proj-body > p, .proj-body li { font-size: 17px; } } /* the demo, under a header line of its own: *demo*, a rule, then a note on what it is. Without room beside the write-up, the demo comes under it, with space either side. */ .pj-demo { margin: 36px 0; font-size: 12px; } .pj-demo-head { display: flex; align-items: center; gap: 1.5ch; margin: 0 0 12px; } .pj-demo-head .rule { flex: 1; height: 1px; background: var(--rule); } .pj-demo-note { max-width: 64ch; margin: 0 0 18px; line-height: 1.5; color: var(--dim); } /* with room, the two windows sit side by side: the title, facts and write-up on the left, and the demo the whole height of the right */ @container pj-detail (min-width: 820px) { .pj-row { display: grid; grid-template-columns: 50% minmax(0, 1fr); grid-template-rows: auto 1fr; grid-template-areas: "head demo" "body demo"; min-height: var(--pj-h); } .pj-row > .pj-head { grid-area: head; padding-right: 32px; } .pj-row > .proj-body { grid-area: body; padding-right: 32px; } /* a touch smaller beside the demo, so the lines don't get too short */ .pj-row .proj-body > p, .pj-row .proj-body li { font-size: 18px; } .pj-row > .pj-demo { grid-area: demo; min-width: 0; margin: 0; padding-left: 32px; border-left: 1px solid var(--rule); } } /* without JavaScript there is no wheel, only the list */ .pj-list { margin: 0; padding: 0; list-style: none; } .pj-list .proj { padding: 32px 0 0; } .pj:not(.is-built) { display: block; margin-left: 0; } .pj:not(.is-built) .pj-wheel, .pj:not(.is-built) .pj-detail, .pj.is-built .pj-list { display: none; } /* ── narrower screens: the wheel lies across the top, leaving the whole column to the project ───────────────────────────────────────────── */ @media (max-width: 1399px) { .pj-wheel { position: relative; top: auto; height: 200px; margin: 0 calc(-1 * var(--gutter)) 36px; overflow: hidden; } .pj-card { width: 150px; } .pj-card::before { top: 100%; right: auto; left: 50%; width: 1px; height: 14px; } .pj-card::after { top: calc(100% + 11px); right: auto; left: calc(50% - 3px); } .pj-pointer { border-top: 7px solid var(--fg); border-right: 5px solid transparent; border-bottom: 0; border-left: 5px solid transparent; } /* lying flat, it fades at the sides */ .pj-spine, .pj-cards { left: 0; width: 100%; -webkit-mask-image: linear-gradient(to right, transparent, #000 22%, #000 78%, transparent); mask-image: linear-gradient(to right, transparent, #000 22%, #000 78%, transparent); } .pj-hint { right: auto; bottom: 0; left: var(--gutter); } }