Skip to content

Instantly share code, notes, and snippets.

@shift
Created April 29, 2026 11:33
Show Gist options
  • Select an option

  • Save shift/8a3b89bd7e6e989dbed655cdd5ea7d45 to your computer and use it in GitHub Desktop.

Select an option

Save shift/8a3b89bd7e6e989dbed655cdd5ea7d45 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>agentic-repos - Agentic Developer Platform</title>
<style>
:root {
--bg: #020617;
--surface: #0f172a;
--surface2: #1e293b;
--border: #1e293b;
--text: #f8fafc;
--muted: #94a3b8;
--accent: #059669;
--accent2: #fb923c;
--green: #10b981;
--red: #ef4444;
--amber: #f59e0b;
--cyan: #06b6d4;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
background: var(--bg);
color: var(--text);
height: 100vh;
overflow: hidden;
user-select: none;
}
#progress-wrap {
position: fixed; bottom: 0; left: 0; right: 0; height: 4px;
background: var(--surface); z-index: 100;
}
#progress-bar {
height: 100%; background: linear-gradient(90deg, var(--accent), var(--accent2));
transition: width 0.3s linear; width: 0%;
}
#timer {
position: fixed; bottom: 12px; right: 24px;
font-family: 'Cascadia Code', 'Fira Code', monospace; font-size: 14px;
color: var(--muted); z-index: 100;
}
#slide-counter {
position: fixed; bottom: 12px; left: 24px;
font-family: 'Cascadia Code', 'Fira Code', monospace; font-size: 14px;
color: var(--muted); z-index: 100;
}
.controls-hint {
position: fixed; top: 16px; right: 24px;
font-size: 12px; color: var(--muted); z-index: 100;
font-family: 'Cascadia Code', 'Fira Code', monospace;
background: var(--surface); padding: 6px 12px; border-radius: 6px;
}
#slides {
width: 100%; height: 100vh;
display: flex; align-items: center; justify-content: center;
}
.slide {
display: none; width: 100%; height: 100%;
flex-direction: column; align-items: center; justify-content: center;
padding: 60px 80px;
animation: fadeIn 0.4s ease;
}
.slide.active { display: flex; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
h1 { font-size: 72px; font-weight: 900; letter-spacing: -2px; line-height: 1.1; margin-bottom: 24px; }
h2 { font-size: 48px; font-weight: 700; letter-spacing: -1px; margin-bottom: 16px; }
h3 { font-size: 24px; font-weight: 600; margin-bottom: 12px; }
.subtitle { font-size: 24px; color: var(--muted); font-weight: 300; margin-bottom: 32px; }
.body-text { font-size: 20px; line-height: 1.7; color: #a3a3a3; max-width: 800px; text-align: center; }
.mono { font-family: 'Cascadia Code', 'Fira Code', 'SF Mono', monospace; }
.badge {
display: inline-block; padding: 6px 16px; border-radius: 6px;
font-size: 14px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px;
margin-bottom: 24px;
}
.badge-blue { background: #05966920; color: #34d399; }
.badge-green { background: #05966920; color: #34d399; }
.badge-purple { background: #fb923c20; color: #fdba74; }
.badge-cyan { background: #05966920; color: #34d399; }
.badge-intel { background: #10b98120; color: #34d399; }
.badge-agentic { background: #a855f720; color: #c084fc; }
.badge-core { background: #fb923c20; color: #fdba74; }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; width: 100%; max-width: 1000px; }
.grid-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 24px; width: 100%; max-width: 1100px; }
.grid-4 { display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; gap: 20px; width: 100%; max-width: 1100px; }
.card {
background: var(--surface); border: 1px solid var(--border);
border-radius: 12px; padding: 24px;
}
.card-title { font-size: 16px; font-weight: 700; margin-bottom: 6px; }
.card-desc { font-size: 14px; color: var(--muted); line-height: 1.5; }
.card-highlight { border-color: var(--accent); }
.code-block {
background: var(--surface); border: 1px solid var(--border);
border-radius: 8px; padding: 20px 28px;
font-family: 'Cascadia Code', 'Fira Code', 'SF Mono', monospace; font-size: 14px;
text-align: left; line-height: 1.6; width: 100%; max-width: 750px;
color: #a3a3a3;
}
.prompt { color: var(--green); }
.comment { color: var(--muted); }
.cmd { color: var(--text); }
.flag { color: var(--accent); }
.string { color: var(--amber); }
.key { color: var(--cyan); }
.flow-row { display: flex; gap: 16px; align-items: center; justify-content: center; margin: 12px 0; }
.flow-box {
background: var(--surface); border: 1px solid var(--border);
border-radius: 8px; padding: 14px 24px; text-align: center;
font-size: 15px;
}
.flow-arrow { color: var(--muted); font-size: 24px; }
.flow-accent { border-color: var(--accent); background: #05966910; }
.flow-purple { border-color: var(--accent2); background: #fb923c10; }
.flow-cyan { border-color: var(--accent); background: #05966910; }
.flow-intel { border-color: #10b981; background: #10b98110; }
.flow-platform { border-color: #4f46e5; background: #4f46e510; }
.flow-agentic { border-color: #a855f7; background: #a855f710; }
.entity-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; width: 100%; max-width: 800px; }
.entity {
background: var(--surface); border: 1px solid var(--border); border-radius: 8px;
padding: 12px; text-align: center; font-size: 13px;
}
.entity-name { font-weight: 700; font-family: 'Cascadia Code', 'Fira Code', monospace; font-size: 12px; color: var(--accent2); }
.project-logo { width: 64px; height: 64px; }
.project-logo-lg { width: 100px; height: 100px; }
.project-logo-sm { width: 40px; height: 40px; }
.step { display: flex; gap: 16px; align-items: flex-start; margin-bottom: 16px; width: 100%; max-width: 800px; }
.step-num {
min-width: 36px; height: 36px; border-radius: 50%; background: var(--accent);
color: white; display: flex; align-items: center; justify-content: center;
font-weight: 700; font-size: 16px;
}
.step-content { flex: 1; }
.step-title { font-weight: 700; font-size: 16px; margin-bottom: 2px; }
.step-desc { font-size: 14px; color: var(--muted); }
.ref-tree {
font-family: 'Cascadia Code', 'Fira Code', monospace; font-size: 13px;
text-align: left; line-height: 1.8; color: #a3a3a3;
}
.ref-branch { color: var(--green); }
.ref-schema { color: var(--accent2); }
.ref-entity { color: var(--accent); }
.ref-data { color: var(--amber); }
</style>
</head>
<body>
<div id="progress-wrap"><div id="progress-bar"></div></div>
<div id="timer">0:00 / 5:00</div>
<div id="slide-counter">1 / 14</div>
<div id="slides">
<!-- SLIDE 1: Title -->
<div class="slide active" data-duration="18">
<img src="logo-agentic-repos.svg" class="project-logo-lg" alt="agentic-repos">
<span class="badge badge-cyan">c-base Hack n Tell · April 2026</span>
<h1>agentic-repos</h1>
<p class="subtitle">Agentic Developer Platform</p>
<p class="body-text">
A Git server that doubles as a memory layer for AI agents.<br>
Schemas in git refs drive the web UI. Agents think in tasks, reasoning, and ADRs — all stored as versioned data in the repository itself.
</p>
</div>
<!-- SLIDE 2: The Core Idea -->
<div class="slide" data-duration="16">
<span class="badge badge-cyan">The Core Idea</span>
<h2>Git repos are agent memory</h2>
<p class="body-text" style="margin-bottom: 32px;">
Every agent's task, decision, reasoning chain, and session is stored as a git ref. The same repository that holds your code also holds the agent's working memory — queryable, versioned, and branchable.
</p>
<div class="code-block" style="max-width: 600px;">
<span class="key">refs/engram/task/a1b2c3</span> → {title, status, priority}<br>
<span class="key">refs/engram/reasoning/d4e5f6</span> → {logic, confidence, conclusion}<br>
<span class="key">refs/engram/context/g7h8i9</span> → {finding, relevance, source}<br>
<span class="key">refs/engram/adr/0012</span> → {decision, context, consequences}<br>
<span class="key">refs/engram/session/j0k1l2</span> → {name, agent, started_at}
</div>
</div>
<!-- SLIDE 3: Architecture -->
<div class="slide" data-duration="14">
<span class="badge badge-cyan">Architecture</span>
<h2>Three layers, one binary</h2>
<div style="display: flex; flex-direction: column; gap: 8px; width: 100%; max-width: 800px; margin-top: 24px;">
<div class="flow-row">
<div class="flow-box flow-intel" style="flex:1;">
<img src="logo-engram.svg" class="project-logo-sm" style="margin-bottom:6px;" alt="engram">
<strong>engram</strong> — CLI/library memory layer<br>
<span style="font-size:12px;color:var(--muted);">task · reasoning · knowledge · ADR · session</span>
</div>
</div>
<div class="flow-arrow">↕ reads/writes git refs</div>
<div class="flow-row">
<div class="flow-box flow-accent" style="flex:1;">
<img src="logo-agentic-repos.svg" class="project-logo-sm" style="margin-bottom:6px;" alt="agentic-repos">
<strong>agentic-repos</strong> — Git server + schema engine<br>
<span style="font-size:12px;color:var(--muted);">HTTP · SSH · LFS · RBAC · RefSchema · SSE</span>
</div>
</div>
<div class="flow-arrow">↕ embeds + extends</div>
<div class="flow-row">
<div class="flow-box flow-platform" style="flex:1;">
<img src="logo-adp.svg" class="project-logo-sm" style="margin-bottom:6px;" alt="adp">
<strong>adp</strong> — Data-Driven UI + enterprise modules<br>
<span style="font-size:12px;color:var(--muted);">schema-driven views · SSE integration · compliance</span>
</div>
</div>
</div>
</div>
<!-- SLIDE 4: RefSchema -->
<div class="slide" data-duration="20">
<span class="badge badge-green">Schema-Driven UI</span>
<h2>One schema. Instant UI.</h2>
<p class="body-text" style="margin-bottom: 24px;">
A <span class="mono">RefSchema</span> at <span class="mono" style="color:var(--accent2)">refs/adp/schema/{id}</span> defines what data lives at a ref namespace, and <em>how the web UI renders it</em>. No frontend code needed.
</p>
<div class="code-block" style="max-width: 650px; font-size: 13px;">
<span class="comment">// Stored at refs/adp/schema/engram-task</span><br>
{<br>
&nbsp;&nbsp;<span class="key">"id"</span>: <span class="string">"engram-task"</span>,<br>
&nbsp;&nbsp;<span class="key">"namespace_pattern"</span>: <span class="string">"refs/engram/task/*"</span>,<br>
&nbsp;&nbsp;<span class="key">"schema"</span>: { <span class="comment">/* JSON Schema draft 2020-12 */</span> },<br>
&nbsp;&nbsp;<span class="key">"ui"</span>: {<br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="key">"view"</span>: <span class="string">"entity-list"</span>,<br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="key">"list_fields"</span>: [<span class="string">"title"</span>, <span class="string">"status"</span>, <span class="string">"priority"</span>],<br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="key">"fields"</span>: {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="key">"status"</span>: { <span class="key">"widget"</span>: <span class="string">"badge"</span>, <span class="key">"color_map"</span>: {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="string">"done"</span>: <span class="string">"green"</span>, <span class="string">"blocked"</span>: <span class="string">"red"</span> } }<br>
&nbsp;&nbsp;&nbsp;&nbsp;}<br>
&nbsp;&nbsp;}<br>
}
</div>
</div>
<!-- SLIDE 5: How it flows -->
<div class="slide" data-duration="16">
<span class="badge badge-cyan">The Flow</span>
<h2>Schemas → Refs → UI</h2>
<div style="display: flex; flex-direction: column; gap: 8px; width: 100%; max-width: 900px; margin-top: 16px;">
<div class="step">
<div class="step-num">1</div>
<div class="step-content">
<div class="step-title">Agent writes a ref</div>
<div class="step-desc"><span class="mono">engram task create --title "Fix auth"</span> → writes JSON blob to <span class="mono" style="color:var(--accent)">refs/engram/task/{uuid}</span></div>
</div>
</div>
<div class="step">
<div class="step-num">2</div>
<div class="step-content">
<div class="step-title">Schema resolves</div>
<div class="step-desc">The <span class="mono" style="color:var(--accent2)">engram-task</span> schema matches the ref via its <span class="mono">namespace_pattern</span><span class="mono">refs/engram/task/*</span></div>
</div>
</div>
<div class="step">
<div class="step-num">3</div>
<div class="step-content">
<div class="step-title">UI renders automatically</div>
<div class="step-desc">The web UI reads the schema's <span class="mono">ui.list_fields</span> and <span class="mono">ui.fields</span> to render a table with colored status badges — zero frontend code</div>
</div>
</div>
<div class="step">
<div class="step-num">4</div>
<div class="step-content">
<div class="step-title">SSE pushes updates</div>
<div class="step-desc">agentic-loop instances connect via SSE. When a ref changes, the UI updates in real-time</div>
</div>
</div>
</div>
</div>
<!-- SLIDE 6: Built-in schemas -->
<div class="slide" data-duration="12">
<span class="badge badge-intel">11 Built-in Schemas</span>
<h2>engram entities</h2>
<p class="body-text" style="margin-bottom: 20px;">
Bootstrap seeds the system repo with schemas for every engram entity type.
</p>
<div style="display:flex;justify-content:center;margin-bottom:16px;">
<img src="logo-engram.svg" class="project-logo" alt="engram">
</div>
<div class="entity-grid" style="--accent2: #10b981;">
<div class="entity"><div class="entity-name">engram-task</div>Status · Priority</div>
<div class="entity"><div class="entity-name">engram-session</div>Agent · Timeline</div>
<div class="entity"><div class="entity-name">engram-adr</div>Decisions</div>
<div class="entity"><div class="entity-name">engram-reasoning</div>Logic chains</div>
<div class="entity"><div class="entity-name">engram-context</div>Findings</div>
<div class="entity"><div class="entity-name">engram-knowledge</div>Facts · Rules</div>
<div class="entity"><div class="entity-name">engram-theory</div>Hypotheses</div>
<div class="entity"><div class="entity-name">engram-workflow</div>State machines</div>
<div class="entity"><div class="entity-name">engram-state-reflection</div>Self-review</div>
<div class="entity"><div class="entity-name">engram-doc-fragment</div>Documentation</div>
<div class="entity"><div class="entity-name">engram-compliance</div>Audit trails</div>
</div>
<p style="margin-top: 16px; color: var(--muted); font-size: 14px;">
Plus operational schemas: <span class="mono">webhook-config · ci-pipeline · team-member · deploy-target · review-checklist</span>
</p>
</div>
<!-- SLIDE 7: Any schema, any UI -->
<div class="slide" data-duration="14">
<span class="badge badge-green">Extensible</span>
<h2>Your schemas. Your UI.</h2>
<p class="body-text" style="margin-bottom: 24px;">
Not limited to engram. Any application can define schemas. The web UI adapts.
</p>
<div class="grid-3">
<div class="card card-highlight">
<div class="card-title" style="color: var(--accent);">Define</div>
<div class="card-desc">POST a JSON Schema to <span class="mono">refs/adp/schema/{id}</span>. Specify the namespace pattern and UI hints.</div>
</div>
<div class="card card-highlight">
<div class="card-title" style="color: var(--accent);">Write</div>
<div class="card-desc">Agents or humans write data to refs matching the namespace. Git handles versioning and branching.</div>
</div>
<div class="card card-highlight">
<div class="card-title" style="color: var(--accent);">Render</div>
<div class="card-desc">The web UI picks up the schema, reads matching refs, and renders list/detail views automatically.</div>
</div>
</div>
<p style="margin-top: 20px; color: var(--muted); font-size: 14px;">
Want a "PR review checklist" view? Define <span class="mono" style="color:var(--accent2)">review-checklist</span> schema → agents write to matching refs → UI appears.
</p>
</div>
<!-- SLIDE 8: agentic-loop SSE -->
<div class="slide" data-duration="14">
<span class="badge badge-agentic">Live Updates</span>
<h2>agentic-loop via SSE</h2>
<div style="display:flex;justify-content:center;margin-bottom:12px;">
<img src="logo-agentic-loop.svg" class="project-logo" alt="agentic-loop">
</div>
<div style="display: flex; flex-direction: column; gap: 8px; width: 100%; max-width: 850px; margin-top: 16px;">
<div class="flow-row">
<div class="flow-box flow-agentic" style="flex:1;">Agent pushes new entitiy refs</div>
<div class="flow-arrow"></div>
<div class="flow-box flow-agentic" style="flex:1;">Server registers<br>SSE connection</div>
<div class="flow-arrow"></div>
<div class="flow-box flow-agentic" style="flex:1;">Browser receives<br>real-time updates</div>
</div>
</div>
<p class="body-text" style="margin-top: 24px;">
agentic-loop instances register as service keys with type <span class="mono">"agentic-loop"</span>. They connect via SSE to receive events and push updates via Git. The web UI shows live task progress, reasoning chains, and session activity — no polling.
</p>
</div>
<!-- SLIDE 11: Why this matters -->
<div class="slide" data-duration="14">
<span class="badge badge-green">Why This Matters</span>
<h2>Agents need memory, not just chat</h2>
<div class="grid-2" style="margin-top: 24px;">
<div class="card card-highlight">
<div class="card-title" style="color: var(--accent);">🧠 Persistent memory</div>
<div class="card-desc">Tasks survive crashes. Reasoning chains are queryable. ADRs give future agents context. Sessions enable handoff.</div>
</div>
<div class="card card-highlight">
<div class="card-title" style="color: var(--accent);">📊 Observable agents</div>
<div class="card-desc">Every agent action is a git ref. Browse task trees, review reasoning, audit decisions — all in the web UI, all versioned.</div>
</div>
<div class="card card-highlight">
<div class="card-title" style="color: var(--accent);">🔀 Branchable context</div>
<div class="card-desc">Agent memory is git refs. Branch it, merge it, diff it. Two agents can work the same problem on different branches.</div>
</div>
<div class="card card-highlight">
<div class="card-title" style="color: var(--accent);">🔌 Plugin ecosystem</div>
<div class="card-desc">Schemas are data, not code. Anyone can define a new entity type and the UI adapts. No frontend changes needed.</div>
</div>
</div>
</div>
<!-- SLIDE 12: The Git Graph -->
<div class="slide" data-duration="14">
<span class="badge badge-cyan">How it stores</span>
<h2>Everything is git refs</h2>
<div style="display: flex; gap: 40px; margin-top: 16px; width: 100%; max-width: 1000px;">
<div style="flex: 1;">
<div class="ref-tree">
<span class="ref-branch">refs/</span><br>
&nbsp;&nbsp;<span class="ref-branch">heads/</span> main, develop<br>
&nbsp;&nbsp;<span class="ref-branch">tags/</span> v1.0.0<br>
&nbsp;&nbsp;<span class="ref-schema">adp/</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="ref-schema">schema/</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="ref-schema">engram-task</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="ref-schema">engram-adr</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="ref-schema">webhook-config</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="ref-schema">config/</span> access, lfs<br>
&nbsp;&nbsp;<span class="ref-entity">engram/</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="ref-entity">task/</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="ref-data">{uuid-1}</span> → {title, status}<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="ref-data">{uuid-2}</span> → {title, status}<br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="ref-entity">reasoning/</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="ref-data">{uuid-3}</span> → {logic}<br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="ref-entity">adr/</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="ref-data">0001</span> → {decision}<br>
&nbsp;&nbsp;<span class="ref-entity">webhooks/</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="ref-data">{id}</span> → {url, events}
</div>
</div>
<div style="flex: 1; display: flex; flex-direction: column; gap: 12px;">
<div class="card">
<div class="card-title" style="color: var(--accent2);">Schemas</div>
<div class="card-desc">Define structure + UI hints. Stored at <span class="mono">refs/adp/schema/*</span></div>
</div>
<div class="card">
<div class="card-title" style="color: var(--accent);">Entities</div>
<div class="card-desc">Data blobs matching a schema's namespace. Versioned, branchable, diffable.</div>
</div>
<div class="card">
<div class="card-title" style="color: var(--amber);">Regular refs</div>
<div class="card-desc">Branches and tags still work normally. Agent data is a parallel namespace.</div>
</div>
</div>
</div>
</div>
<!-- SLIDE 13: Come Hack -->
<div class="slide" data-duration="12">
<span class="badge badge-core">Come Hack On This</span>
<h2>Three repos. Open source.</h2>
<div class="grid-3" style="margin-top: 24px;">
<div class="card" style="border-color: #10b981;">
<img src="logo-engram.svg" class="project-logo-sm" style="margin-bottom:8px;" alt="engram">
<div class="card-title" style="color: #10b981;">engram (~1M loc)</div>
<div class="card-desc">Distributed AI memory layer.<br>CLI for tasks, reasoning, knowledge, ADRs, sessions.<br>Works with any git server.</div>
</div>
<div class="card" style="border-color: #059669;">
<img src="logo-agentic-repos.svg" class="project-logo-sm" style="margin-bottom:8px;" alt="agentic-repos">
<div class="card-title" style="color: #059669;">agentic-repos (~100K loc)</div>
<div class="card-desc">Git server core.<br>HTTP, SSH, LFS, RBAC, schema engine, SSE.<br>Pure Rust — gix + russh. No libgit2.</div>
</div>
<div class="card" style="border-color: #a855f7;">
<img src="logo-agentic-loop.svg" class="project-logo-sm" style="margin-bottom:8px;" alt="agentic-loop">
<div class="card-title" style="color: #a855f7;">agentic-loop (~16K loc)</div>
<div class="card-desc">Agentic developer which follows workflows with evals and guardrails.</div>
</div>
</div>
<p style="margin-top: 20px; color: var(--muted); font-size: 14px;">
Single binary. No database. No Node runtime. Just Rust + Object Storage (S3).
</p>
</div>
<!-- SLIDE 14: Thank You -->
<div class="slide" data-duration="0" data-pause="true">
<span class="badge badge-core" style="font-size: 16px; padding: 8px 20px;">🚀</span>
<div style="display:flex;justify-content:center;margin-bottom:20px;">
<img src="logo-agentic-repos.svg" class="project-logo-lg" alt="agentic-repos">
</div>
<h2>Thank you.</h2>
<p class="subtitle">Questions?</p>
<div class="code-block" style="max-width: 500px; text-align: center; font-size: 16px; color: var(--accent);">
github.com/vincents-ai - Agentic Projects
</div>
<div class="code-block" style="max-width: 500px; text-align: center; font-size: 16px; color: var(--accent);">
github.com/shift - Personal Github
</div>
<div class="code-block" style="max-width: 500px; text-align: center; font-size: 16px; color: var(--accent);">
linkedin.com/in/vincentp - Got a job I might be interested in? I'm looking.
</div>
<p style="margin-top: 24px; color: var(--muted); font-size: 15px;">
Built with ❤️ and Rust by Vincent (@shift) Palmer
</p>
</div>
</div>
<script>
const slides = document.querySelectorAll('.slide');
let current = 0;
let startTime = null;
let autoTimer = null;
const TOTAL_SECONDS = 300;
function showSlide(n) {
slides.forEach(s => s.classList.remove('active'));
slides[n].classList.add('active');
document.getElementById('slide-counter').textContent = `${n + 1} / ${slides.length}`;
const duration = parseInt(slides[n].dataset.duration) || 0;
const isPaused = slides[n].dataset.pause === 'true';
clearTimeout(autoTimer);
if (!isPaused && duration > 0) {
autoTimer = setTimeout(() => advance(), duration * 1000);
}
}
function advance() {
if (current < slides.length - 1) {
current++;
showSlide(current);
}
}
function retreat() {
if (current > 0) {
current--;
clearTimeout(autoTimer);
showSlide(current);
}
}
function updateTimer() {
if (!startTime) return;
const elapsed = Math.floor((Date.now() - startTime) / 1000);
const mins = Math.floor(elapsed / 60);
const secs = elapsed % 60;
const pct = Math.min(100, (elapsed / TOTAL_SECONDS) * 100);
document.getElementById('timer').textContent =
`${mins}:${secs.toString().padStart(2, '0')} / 5:00`;
document.getElementById('progress-bar').style.width = pct + '%';
if (elapsed >= TOTAL_SECONDS) {
document.getElementById('progress-bar').style.background =
'linear-gradient(90deg, var(--red), var(--amber))';
}
requestAnimationFrame(updateTimer);
}
let started = false;
function ensureStarted() {
if (!started) {
started = true;
startTime = Date.now();
updateTimer();
}
}
document.addEventListener('keydown', (e) => {
ensureStarted();
if (e.key === ' ' || e.key === 'ArrowRight' || e.key === 'Enter') {
e.preventDefault();
advance();
} else if (e.key === 'ArrowLeft' || e.key === 'Backspace') {
e.preventDefault();
retreat();
}
});
document.addEventListener('click', (e) => {
if (e.target.closest('.controls-hint')) return;
ensureStarted();
advance();
});
ensureStarted();
const firstDuration = parseInt(slides[0].dataset.duration) || 12;
autoTimer = setTimeout(() => advance(), firstDuration * 1000);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment