Add AGENTS.md as the single source of truth (with CLAUDE.md and GEMINI.md as pointers), permissions for Claude Code, Codex and Gemini, the docs tree (architecture, decisions, TODO, permissions, specs, plans), PRODUCT.md and DESIGN.md, plus the design-phase record: the inspiration log, all mockup rounds with briefs and screenshots, and the owner's work history. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
278 lines
21 KiB
HTML
278 lines
21 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Lucas Winther: two chapters</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Instrument+Sans:wght@400;500;600&display=swap" rel="stylesheet">
|
||
<style>
|
||
:root{
|
||
--bg:#E2E5D9; --bg-2:#D7DBCC; --ink:#1E2A21; --muted:#5D695E; --line:#B9C0AE;
|
||
--verm:#CF4A2C; --pencil:#4A4F48;
|
||
--serif:"Instrument Serif",Georgia,serif; --sans:"Instrument Sans",system-ui,sans-serif;
|
||
}
|
||
*{box-sizing:border-box}
|
||
html{background:var(--bg);scroll-behavior:smooth}
|
||
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}}
|
||
body{margin:0;color:var(--ink);font-family:var(--sans);font-size:15px;line-height:1.5}
|
||
a{color:inherit;text-underline-offset:3px;text-decoration-thickness:1px}
|
||
a:hover{color:var(--verm)}
|
||
.ph{text-decoration:underline dotted var(--muted);text-underline-offset:3px;cursor:help}
|
||
.wrap{max-width:1200px;margin:0 auto;padding:0 32px}
|
||
.mock-note{position:fixed;top:10px;right:10px;z-index:9999;max-width:290px;background:#fff;color:#222;border:1px solid #aaa;padding:8px 30px 8px 10px;font:12px/1.4 system-ui,sans-serif}
|
||
.mock-note b{display:block}
|
||
.mock-note button{position:absolute;top:4px;right:6px;border:0;background:none;font-size:16px;cursor:pointer;color:#555}
|
||
|
||
header{display:grid;grid-template-columns:1.3fr 1fr;gap:48px;padding:48px 0 26px;align-items:end}
|
||
h1{font-family:var(--serif);font-weight:400;font-size:68px;line-height:.95;margin:0 0 14px;letter-spacing:-.01em}
|
||
.lede{font-size:18px;max-width:34em;margin:0;color:var(--ink)}
|
||
.lede em{font-family:var(--serif);font-size:21px}
|
||
.facts{margin:0;display:grid;grid-template-columns:auto 1fr;gap:6px 16px;border-top:1px solid var(--ink);padding-top:12px}
|
||
.facts dt{font-size:11px;text-transform:uppercase;letter-spacing:.09em;color:var(--muted);padding-top:3px}
|
||
.facts dd{margin:0}
|
||
.facts .links a{margin-right:12px}
|
||
|
||
/* minimap */
|
||
.mini{display:flex;align-items:flex-end;gap:3px;height:58px;border-bottom:1px solid var(--ink);margin-top:6px}
|
||
.mini a{flex:1;display:flex;flex-direction:column-reverse;align-items:stretch;gap:2px;text-decoration:none;height:100%;padding-bottom:4px;position:relative}
|
||
.mini a i{display:block;height:5px;background:var(--pencil);opacity:.55}
|
||
.mini a i.e{background:transparent;border:1px solid var(--pencil)}
|
||
.mini a i.ai{background:var(--verm);opacity:1}
|
||
.mini a span{position:absolute;bottom:-20px;left:0;font-size:11px;color:var(--muted)}
|
||
.mini a:hover i{opacity:1}
|
||
.mini a:hover span{color:var(--ink)}
|
||
.mini .gap{width:1px;background:var(--ink);margin:0 6px;align-self:stretch}
|
||
.mini-cap{display:flex;justify-content:space-between;font-size:11px;color:var(--muted);margin:26px 0 0}
|
||
|
||
/* lens / filter */
|
||
.lanes-head{position:sticky;top:0;z-index:6;background:var(--bg);display:grid;grid-template-columns:var(--cols);gap:0 18px;padding:12px 0 8px;border-bottom:1px solid var(--line);margin-top:28px}
|
||
.lanes-head h4{grid-row:2;margin:0;font-size:11px;text-transform:uppercase;letter-spacing:.09em;color:var(--muted);font-weight:500;align-self:end}
|
||
.lens{grid-column:1 / -1;grid-row:1;display:flex;flex-wrap:wrap;gap:6px 14px;align-items:center;justify-content:flex-end;font-size:12.5px;margin-bottom:6px}
|
||
.lens > span{color:var(--muted)}
|
||
.lens label{display:flex;align-items:center;gap:6px;cursor:pointer;user-select:none}
|
||
.lens input{position:absolute;opacity:0;pointer-events:none}
|
||
.lens .sw{width:26px;height:14px;border:1.5px solid var(--ink);display:inline-block}
|
||
.lens .sw.none{background:transparent}
|
||
.lens .sw.some{border-style:dashed}
|
||
.lens .sw.full{background:var(--verm);border-color:var(--verm)}
|
||
.lens input:not(:checked) + .sw{opacity:.3}
|
||
.lens input:not(:checked) ~ b{text-decoration:line-through;color:var(--muted);font-weight:400}
|
||
.lens input:focus-visible + .sw{outline:2px solid var(--verm);outline-offset:2px}
|
||
.lens b{font-weight:500}
|
||
|
||
:root{--cols:76px 150px 170px 1.8fr 1.25fr}
|
||
.tl{display:grid;grid-template-columns:var(--cols);gap:0 18px;position:relative}
|
||
.yr{grid-column:1;font-family:var(--serif);font-size:34px;line-height:1;padding:14px 0 18px;border-top:1px solid var(--line);color:var(--ink)}
|
||
.cell{padding:14px 0 18px;border-top:1px solid var(--line);display:flex;flex-direction:column;gap:6px;align-items:flex-start;min-width:0}
|
||
.cell.p{grid-column:4}.cell.w{grid-column:5}
|
||
.bar{padding:14px 0 18px;border-top:1px solid var(--line);position:relative;z-index:1}
|
||
.bar.ed{grid-column:2}.bar.wk{grid-column:3}
|
||
.bar .stripe{position:absolute;left:0;top:14px;bottom:10px;width:3px;background:var(--ink)}
|
||
.bar.now .stripe{background:linear-gradient(var(--verm),var(--verm)) top/3px 100% no-repeat}
|
||
.bar .in{padding-left:12px;cursor:default}
|
||
.bar b{display:block;font-weight:500;line-height:1.3}
|
||
.bar small{display:block;color:var(--muted);font-size:12.5px;margin-top:2px}
|
||
.bar p{margin:6px 0 0;font-size:13px;color:var(--muted);max-width:15em}
|
||
|
||
.chip{font:inherit;text-align:left;background:transparent;color:var(--ink);border:1.5px solid var(--ink);padding:4px 9px;cursor:pointer;max-width:100%;transition:background .15s,opacity .2s,color .15s;line-height:1.35}
|
||
.chip[data-ai="some"]{border-style:dashed}
|
||
.chip[data-ai="full"]{background:var(--verm);border-color:var(--verm);color:#FBF3EC}
|
||
.chip.post{border:0;border-left:1.5px solid var(--muted);padding:2px 0 2px 10px;font-family:var(--serif);font-size:17px}
|
||
.chip .k{font-family:var(--sans);font-size:11px;color:var(--muted);margin-left:6px;letter-spacing:.03em}
|
||
.chip:hover{background:var(--bg-2)}
|
||
.chip[data-ai="full"]:hover{background:#B83F24}
|
||
.chip[data-ai="full"] .k{color:rgba(251,243,236,.75)}
|
||
.chip.dim{opacity:.18}
|
||
.chip.rel,.bar.rel .in{outline:2px solid var(--verm);outline-offset:2px}
|
||
.detail{display:none;width:100%;border-left:1.5px solid var(--ink);margin:2px 0 6px;padding:8px 12px 10px;background:var(--bg-2);font-size:14px}
|
||
.detail.open{display:block}
|
||
.detail p{margin:0 0 6px}
|
||
.detail .meta{font-size:12px;color:var(--muted)}
|
||
.detail .ph-img{height:84px;margin:4px 0 8px;background:repeating-linear-gradient(45deg,transparent 0 6px,rgba(30,42,33,.08) 6px 7px);border:1px solid var(--line);font-size:11px;color:var(--muted);display:flex;align-items:flex-end;padding:4px 6px}
|
||
|
||
/* chapter divider */
|
||
.chapter{grid-column:1 / -1;display:grid;grid-template-columns:76px 1fr auto;gap:0 18px;align-items:end;padding:44px 0 18px;border-top:2px solid var(--ink)}
|
||
.chapter .num{font-family:var(--serif);font-size:15px;color:var(--muted);align-self:start;padding-top:6px}
|
||
.chapter h2{font-family:var(--serif);font-weight:400;font-size:44px;line-height:1;margin:0}
|
||
.chapter h2 i{color:var(--muted)}
|
||
.chapter p{margin:8px 0 0;max-width:40em;color:var(--muted)}
|
||
.chapter .aside{font-size:13px;text-align:right;max-width:240px}
|
||
.chapter .aside a{font-weight:500}
|
||
.chapter.one{margin-top:40px}
|
||
/* chapter I: pencil era */
|
||
.era-one{grid-column:1 / -1;display:grid;grid-template-columns:var(--cols);gap:0 18px;
|
||
background-image:repeating-linear-gradient(0deg,transparent 0 27px,rgba(74,79,72,.09) 27px 28px);}
|
||
.era-one .yr{color:var(--pencil);font-style:italic}
|
||
.era-one .chip{border-color:var(--pencil);color:var(--pencil);background:rgba(226,229,217,.85)}
|
||
.era-one .bar .stripe{background:var(--pencil)}
|
||
|
||
footer{display:grid;grid-template-columns:1fr 1fr 1fr;gap:28px;padding:36px 0 64px;margin-top:40px;border-top:2px solid var(--ink);font-size:13px;color:var(--muted)}
|
||
footer h5{margin:0 0 6px;font-size:11px;text-transform:uppercase;letter-spacing:.09em;color:var(--ink);font-weight:500}
|
||
footer p{margin:0 0 6px}
|
||
|
||
@media (max-width:860px){
|
||
header{grid-template-columns:1fr;gap:24px}
|
||
h1{font-size:52px}
|
||
.lanes-head h4{display:none}
|
||
.lanes-head{display:block}
|
||
.lens{grid-column:1 / -1;justify-content:flex-start}
|
||
.tl,.era-one{display:block}
|
||
.yr{padding-bottom:6px}
|
||
.cell,.bar{border-top:0;padding:4px 0 8px}
|
||
.bar .stripe{top:4px}
|
||
.cell:empty{display:none}
|
||
.chapter{display:block}
|
||
.chapter .aside{text-align:left;margin-top:10px}
|
||
footer{grid-template-columns:1fr}
|
||
.mini a span{display:none}
|
||
.mini a:nth-child(odd) span{display:block}
|
||
}
|
||
@media (max-width:480px){ .wrap{padding:0 16px} h1{font-size:44px} }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="mock-note"><button aria-label="Dismiss note" onclick="this.parentNode.remove()">×</button><b>tech-2 · Two chapters</b>Education, jobs, projects and writing on one year-by-year timeline, split into a by-hand chapter and an AI chapter. Hover anything to see what it connects to; click to open. Borrows lanes and varied shapes from sankar.work, yearly grouping from beige.zone.</div>
|
||
|
||
<div class="wrap">
|
||
<header>
|
||
<div>
|
||
<h1>Lucas Winther</h1>
|
||
<p class="lede">Computer scientist. I wrote software by hand for six years, then started building with AI as well. <em>This page holds both chapters</em>, on one line, so you can see where one ends and the next begins.</p>
|
||
</div>
|
||
<dl class="facts">
|
||
<dt>Now</dt><dd><span class="ph" title="placeholder">Software engineer, Nordlys Systems</span> <span class="ph" title="placeholder">(2023–)</span></dd>
|
||
<dt>Studied</dt><dd>BSc Computer Science, <span class="ph" title="placeholder">Aarhus University</span>, <span class="ph" title="placeholder">2022</span></dd>
|
||
<dt>Writes</dt><dd>AI tooling, devops, homelab at <a href="https://blog.lucaswinther.info">blog.lucaswinther.info</a></dd>
|
||
<dt>Contact</dt><dd class="links"><a href="mailto:lucas@lucaswinther.info">Email</a><a href="https://www.linkedin.com/in/lucas-winther">LinkedIn</a><a href="https://github.com/StereotypicalCat">GitHub</a><a href="#" title="placeholder: CV PDF">CV (PDF)</a></dd>
|
||
</dl>
|
||
</header>
|
||
|
||
<nav class="mini" id="mini" aria-label="Jump to year"></nav>
|
||
<div class="mini-cap"><span></span><span>Each mark is one thing made, studied or written. Vermillion: made with some or full AI help.</span></div>
|
||
|
||
<div class="lanes-head">
|
||
<h4>Year</h4><h4>Education</h4><h4>Work</h4><h4>Projects</h4><h4>Writing</h4>
|
||
<div class="lens" id="lens">
|
||
<span>Projects by AI involvement</span>
|
||
<label><input type="checkbox" value="none" checked><i class="sw none"></i><b>By hand</b></label>
|
||
<label><input type="checkbox" value="some" checked><i class="sw some"></i><b>Some help</b></label>
|
||
<label><input type="checkbox" value="full" checked><i class="sw full"></i><b>Fully AI</b></label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="tl" id="tl"></div>
|
||
|
||
<footer>
|
||
<div><h5>Chapter I, preserved</h5><p>The 2021 React portfolio is frozen at <a href="https://old.lucaswinther.info">old.lucaswinther.info</a>. Nothing there was made with AI.</p></div>
|
||
<div><h5>Elsewhere</h5><p><a href="https://blog.lucaswinther.info">Blog</a> · <a href="https://github.com/StereotypicalCat">GitHub</a> · <a href="https://www.linkedin.com/in/lucas-winther">LinkedIn</a> · <a href="mailto:lucas@lucaswinther.info">lucas@lucaswinther.info</a></p><p>Set in Instrument Serif and Instrument Sans.</p></div>
|
||
<div><h5>Placeholders</h5><p>Dotted underline = invented: employer and job titles, university, school, all years, blog post titles, CV link, “Homelab metrics” and “this site” projects.</p></div>
|
||
</footer>
|
||
</div>
|
||
|
||
<script>
|
||
const ED=[
|
||
{id:"htx",lane:"ed",from:2016,to:2018,t:"HTX, programming line",s:"2016–2018",p:"Upper secondary. Exam projects in VR and Python.",ph:1,rel:["censor","breakout"]},
|
||
{id:"bsc",lane:"ed",from:2019,to:2022,t:"BSc Computer Science",s:"Aarhus University · 2019–2022",p:"Distributed systems, HCI, a thesis on P2P archival.",ph:1,rel:["hyper","dash","vrc","r-thesis","r-dash","r-vrc","rowing"]},
|
||
{id:"stud",lane:"wk",from:2021,to:2022,t:"Student developer",s:"University IT · 2021–2022",p:"Small React and Node tools for teaching staff.",ph:1,rel:["dash"]},
|
||
{id:"job",lane:"wk",from:2023,to:2026,now:1,t:"Software engineer",s:"Nordlys Systems · 2023–now",p:"Platform team: CI, internal tooling, deployment automation.",ph:1,rel:["config","metrics","w-agent","w-repo"]}
|
||
];
|
||
const ITEMS=[
|
||
{id:"site",y:2026,lane:"p",t:"lucaswinther.info",k:"this site",ai:"some",ph:1,d:"Design and copy by hand; the timeline plumbing pair-written with AI.",m:"HTML, CSS, JS",rel:["w-agent"]},
|
||
{id:"metrics",y:2026,lane:"p",t:"Homelab metrics",k:"infra",ai:"some",ph:1,d:"Prometheus and Grafana for the homelab. Alert rules by hand, dashboards drafted by AI and rewritten.",m:"Prometheus, Grafana",rel:["config","w-k3s","job"]},
|
||
{id:"w-agent",y:2026,lane:"w",t:"What I let the agent write, and what I didn't",k:"AI",ph:1,d:"A line-by-line audit of a project built with an agent.",rel:["gacha","site"]},
|
||
{id:"w-k3s",y:2026,lane:"w",t:"Three mini PCs, one k3s cluster",k:"Homelab",ph:1,d:"Notes from moving the homelab to Kubernetes, and back out for some services.",rel:["metrics","config"]},
|
||
{id:"w-repo",y:2026,lane:"w",t:"Rebuilding the homelab from one git repo",k:"Devops",ph:1,d:"Disaster recovery, tested by actually wiping a machine.",rel:["config"]},
|
||
{id:"gacha",y:2025,lane:"p",t:"Event Tracker for Gacha Games",k:"web app",ai:"full",d:"Scrapes event announcements into one calendar. I wrote the spec and tests; an agent wrote the code.",m:"JavaScript, scraping · Live demo · GitHub",rel:["w-agent","w-spec"]},
|
||
{id:"config",y:2025,lane:"p",t:"Config",k:"infra",ai:"full",d:"Ansible and docker-compose that rebuild the homelab from bare metal. Generated with AI, decisions mine.",m:"Ansible, docker-compose · GitHub",rel:["w-ans","w-repo","metrics"]},
|
||
{id:"w-spec",y:2025,lane:"w",t:"Specs are prompts with manners",k:"AI",ph:1,d:"Why writing a good ticket and writing a good prompt are the same skill.",rel:["gacha"]},
|
||
{id:"w-ans",y:2025,lane:"w",t:"Ansible idempotency, the hard way",k:"Devops",ph:1,d:"The bug that restarted every container on every run.",rel:["config"]},
|
||
{id:"w-cop",y:2024,lane:"w",t:"A month of autocomplete: a diary",k:"AI",ph:1,d:"Daily notes from the first month with an AI pair programmer.",rel:[]},
|
||
{id:"w-prox",y:2023,lane:"w",t:"Moving the homelab to Proxmox",k:"Homelab",ph:1,d:"The first post on the new blog.",rel:["config"]},
|
||
{id:"hyper",y:2022,lane:"p",t:"HyperCache",k:"bachelor project",ai:"none",d:"Reputation systems for peer-to-peer archival, simulated in Node.js under churn and free-riding.",m:"Node.js, P2P",rel:["r-thesis","bsc"]},
|
||
{id:"r-thesis",y:2022,lane:"w",t:"Bachelor report: trust in P2P archival",k:"Thesis PDF",d:"The written half of HyperCache.",rel:["hyper","bsc"]},
|
||
{id:"dash",y:2021,lane:"p",t:"Student Dashboard",k:"HCI",ai:"none",d:"A React dashboard designed from two rounds of user studies.",m:"React · Report · Demo",rel:["r-dash","bsc","stud"]},
|
||
{id:"exp",y:2021,lane:"p",t:"Monthly Expenses Tracker",k:"web app",ai:"none",d:"Spending in DKK against a budget. Plain JS, all amounts stored in øre.",m:"JavaScript",rel:[]},
|
||
{id:"port",y:2021,lane:"p",t:"Portfolio (the old site)",k:"archive",ai:"none",d:"Create React App and Material UI. Frozen at old.lucaswinther.info.",m:"React, Material UI",rel:[]},
|
||
{id:"r-dash",y:2021,lane:"w",t:"Student Dashboard report",k:"Report PDF",d:"Method, studies and what we got wrong first.",rel:["dash","bsc"]},
|
||
{id:"rowing",y:2020,lane:"p",t:"Mixed-Reality Rowing Game",k:"hardware",ai:"none",d:"Two real oars with sensors drive a Unity boat race. Arduino firmware plus the game.",m:"Unity, Arduino, C#",rel:["bsc"]},
|
||
{id:"chess",y:2020,lane:"p",t:"Chessswap",k:"game",ai:"none",d:"Chess where the board swaps every few turns.",m:"React",rel:[]},
|
||
{id:"vrc",y:2019,lane:"p",t:"VR-Classroom",k:"prototype",ai:"none",d:"A shared VR classroom prototype with a Node.js sync server.",m:"Node.js · Report · Video",rel:["r-vrc","bsc"]},
|
||
{id:"lol",y:2019,lane:"p",t:"LoL Winrate Discord Bot",k:"bot",ai:"none",d:"Answers with champion win rates for the current patch.",m:"Node.js",rel:[]},
|
||
{id:"r-vrc",y:2019,lane:"w",t:"VR-Classroom report",k:"Report",d:"What remote teaching needs from presence.",rel:["vrc","bsc"]},
|
||
{id:"censor",y:2018,lane:"p",t:"Censor Simulator",k:"VR, exam project",ai:"none",d:"You play the examiner grading a nervous virtual student.",m:"Unity, C#",rel:["htx"]},
|
||
{id:"breakout",y:2017,lane:"p",t:"BreakoutPython",k:"exam project",ai:"none",d:"Breakout with levels and power-ups.",m:"Python",rel:["htx"]},
|
||
{id:"borlaug",y:2016,lane:"p",t:"Tribute to Norman Borlaug",k:"first website",ai:"none",d:"A freeCodeCamp tribute page. Where it started.",m:"HTML, CSS",rel:[]}
|
||
];
|
||
const YEARS=[];for(let y=2026;y>=2016;y--)YEARS.push(y);
|
||
const esc=s=>String(s).replace(/[&<>"]/g,c=>({"&":"&","<":"<",">":">",'"':"""}[c]));
|
||
const AIL={none:"By hand",some:"Some AI help",full:"Fully AI-made"};
|
||
|
||
function yearBlock(y,rowBase,rowsMap){
|
||
let h=`<div class="yr" id="y${y}" style="grid-row:${rowsMap[y]}">${y}</div>`;
|
||
ED.filter(e=>e.to===y || (e.now && y===2026)).forEach(e=>{
|
||
if(e.to!==y)return;
|
||
const start=rowsMap[e.to], end=rowsMap[Math.max(e.from,rowBase)]+1;
|
||
h+=`<div class="bar ${e.lane}${e.now?" now":""}" data-id="${e.id}" style="grid-row:${start} / ${end}"><i class="stripe"></i><div class="in" tabindex="0"><b>${e.ph?`<span class="ph" title="placeholder">${esc(e.t)}</span>`:esc(e.t)}</b><small>${esc(e.s)}</small><p>${esc(e.p)}</p></div></div>`;
|
||
});
|
||
["p","w"].forEach(l=>{
|
||
const its=ITEMS.filter(i=>i.y===y&&i.lane===l);
|
||
h+=`<div class="cell ${l}" style="grid-row:${rowsMap[y]}">`+its.map(i=>{
|
||
const title=i.ph?`<span class="ph" title="placeholder">${esc(i.t)}</span>`:esc(i.t);
|
||
return `<button class="chip${l==="w"?" post":""}" data-id="${i.id}" ${i.ai?`data-ai="${i.ai}"`:""} aria-expanded="false">${title}<span class="k">${esc(i.k)}</span></button>
|
||
<div class="detail" id="d-${i.id}">${l==="p"?`<div class="ph-img">placeholder: screenshot of ${esc(i.t)}</div>`:""}<p>${esc(i.d)}</p><div class="meta">${i.m?esc(i.m)+" · ":""}${i.ai?AIL[i.ai]:(i.y>=2023?`<a href="https://blog.lucaswinther.info">Read on the blog →</a>`:"PDF")}</div></div>`;
|
||
}).join("")+`</div>`;
|
||
});
|
||
return h;
|
||
}
|
||
function build(){
|
||
const two=[2026,2025,2024,2023], one=[2022,2021,2020,2019,2018,2017,2016];
|
||
const m2={},m1={};two.forEach((y,i)=>m2[y]=i+2);one.forEach((y,i)=>m1[y]=i+1);
|
||
let h=`<div class="chapter" style="border-top:0;padding-top:8px;grid-row:1"><span class="num">II</span><div><h2>With AI <i>2023 → now</i></h2><p>Work and side projects with AI in the loop. Every project says how much: by hand, some help, or fully AI-made with me as spec-writer and reviewer.</p></div><div class="aside">Rows run newest first. Hover a job or degree to see what came out of it.</div></div>`;
|
||
two.forEach(y=>h+=yearBlock(y,2023,m2));
|
||
h+=`<div class="chapter one" style="grid-row:6"><span class="num">I</span><div><h2>By hand <i>2016 – 2022</i></h2><p>School, university and side projects, all typed by me. No AI was involved in anything in this chapter.</p></div><div class="aside">This chapter is also preserved as it looked in 2021:<br><a href="https://old.lucaswinther.info">old.lucaswinther.info →</a></div></div>`;
|
||
h+=`<div class="era-one" style="grid-row:7">`;one.forEach(y=>h+=yearBlock(y,2016,m1));h+=`</div>`;
|
||
document.getElementById("tl").innerHTML=h;
|
||
|
||
// minimap
|
||
let mm="";
|
||
for(let y=2016;y<=2026;y++){
|
||
if(y===2023)mm+=`<i class="gap" title="Chapter I | Chapter II"></i>`;
|
||
const its=ITEMS.filter(i=>i.y===y);
|
||
const eds=ED.filter(e=>y>=e.from&&y<=e.to);
|
||
mm+=`<a href="#y${y}" title="${y}: ${its.length} items">`+eds.map(()=>`<i class="e"></i>`).join("")+its.map(i=>`<i class="${i.ai==="full"||i.ai==="some"?"ai":""}"></i>`).join("")+`<span>${y}</span></a>`;
|
||
}
|
||
document.getElementById("mini").innerHTML=mm;
|
||
}
|
||
build();
|
||
|
||
const tl=document.getElementById("tl");
|
||
tl.addEventListener("click",e=>{
|
||
const c=e.target.closest(".chip");if(!c)return;
|
||
const d=document.getElementById("d-"+c.dataset.id);
|
||
const open=d.classList.toggle("open");c.setAttribute("aria-expanded",open);
|
||
});
|
||
function relIds(id){
|
||
const all=[...ITEMS,...ED];const me=all.find(x=>x.id===id);if(!me)return[];
|
||
const s=new Set(me.rel||[]);all.forEach(x=>{if((x.rel||[]).includes(id))s.add(x.id);});return[...s];
|
||
}
|
||
function hi(id,on){
|
||
relIds(id).forEach(r=>{
|
||
document.querySelectorAll(`.chip[data-id="${r}"], .bar[data-id="${r}"]`).forEach(el=>el.classList.toggle("rel",on));
|
||
});
|
||
}
|
||
["mouseover","mouseout","focusin","focusout"].forEach(ev=>tl.addEventListener(ev,e=>{
|
||
const t=e.target.closest(".chip,.bar");if(!t)return;
|
||
if((ev==="mouseout"||ev==="mouseover")&&e.relatedTarget&&t.contains(e.relatedTarget))return;
|
||
hi(t.dataset.id,ev==="mouseover"||ev==="focusin");
|
||
}));
|
||
const lens=document.getElementById("lens");
|
||
lens.addEventListener("change",()=>{
|
||
const on=[...lens.querySelectorAll("input:checked")].map(i=>i.value);
|
||
document.querySelectorAll(".chip[data-ai]").forEach(c=>c.classList.toggle("dim",!on.includes(c.dataset.ai)));
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|