This repository has been archived on 2026-05-05. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
engram/engram-explainer.html

2297 lines
85 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Engram — A Database Designed for Minds</title>
<style>
:root {
--bg: #0D0D1A;
--bg2: #111122;
--bg3: #161628;
--purple: #7B61FF;
--purple-dim: #4A3A99;
--purple-glow: rgba(123, 97, 255, 0.15);
--purple-glow-strong: rgba(123, 97, 255, 0.35);
--white: #F0F0FF;
--muted: #8888AA;
--dim: #444466;
--working: #FF6B6B;
--episodic: #FFB347;
--semantic: #7B61FF;
--procedural: #4ECDC4;
--mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body {
background: var(--bg);
color: var(--white);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
line-height: 1.6;
overflow-x: hidden;
}
/* ── Typography ── */
h1 { font-size: clamp(3rem, 10vw, 7rem); font-weight: 800; letter-spacing: -0.04em; line-height: 1; }
h2 { font-size: clamp(1.6rem, 4vw, 2.4rem); font-weight: 700; letter-spacing: -0.02em; }
h3 { font-size: 1.1rem; font-weight: 600; letter-spacing: 0.01em; }
p { color: #CCCCEE; font-size: 1.05rem; }
code, pre {
font-family: var(--mono);
font-size: 0.875rem;
}
/* ── Layout ── */
section {
max-width: 1100px;
margin: 0 auto;
padding: 100px 40px;
}
.section-label {
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--purple);
margin-bottom: 1.2rem;
display: block;
}
/* ── Hero ── */
#hero {
max-width: 100%;
padding: 0;
position: relative;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
#hero-canvas {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.hero-content {
position: relative;
z-index: 2;
text-align: center;
padding: 60px 40px;
}
.hero-title {
font-size: clamp(5rem, 18vw, 14rem);
font-weight: 900;
letter-spacing: -0.06em;
line-height: 0.9;
background: linear-gradient(135deg, #FFFFFF 0%, #B8A8FF 40%, #7B61FF 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 1.5rem;
}
.hero-tagline {
font-size: clamp(1rem, 2.5vw, 1.4rem);
color: var(--muted);
max-width: 600px;
margin: 0 auto 2.5rem;
font-weight: 400;
line-height: 1.5;
}
.hero-tagline em {
color: var(--white);
font-style: normal;
}
.hero-badge {
display: inline-flex;
align-items: center;
gap: 8px;
background: var(--purple-glow);
border: 1px solid var(--purple-dim);
border-radius: 100px;
padding: 6px 16px;
font-size: 0.8rem;
color: var(--purple);
font-family: var(--mono);
}
.pulse-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--purple);
animation: pulse-dot 2s ease-in-out infinite;
}
@keyframes pulse-dot {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.4; transform: scale(0.6); }
}
.scroll-hint {
position: absolute;
bottom: 40px;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
color: var(--dim);
font-size: 0.75rem;
letter-spacing: 0.1em;
text-transform: uppercase;
animation: fade-up 3s ease-in-out infinite;
}
.scroll-arrow {
width: 1px;
height: 40px;
background: linear-gradient(to bottom, transparent, var(--dim));
}
@keyframes fade-up {
0%, 100% { opacity: 0.4; transform: translateX(-50%) translateY(0); }
50% { opacity: 1; transform: translateX(-50%) translateY(6px); }
}
/* ── Problem Section ── */
#problem {
border-top: 1px solid var(--dim);
}
.problem-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 2px;
margin: 3rem 0;
border: 1px solid var(--dim);
border-radius: 12px;
overflow: hidden;
}
.problem-card {
padding: 2.5rem 2rem;
background: var(--bg2);
position: relative;
}
.problem-card + .problem-card {
border-left: 1px solid var(--dim);
}
.problem-icon {
font-size: 2rem;
margin-bottom: 1rem;
display: block;
}
.problem-card h3 {
margin-bottom: 0.75rem;
color: var(--white);
}
.problem-card p {
font-size: 0.95rem;
color: var(--muted);
}
.problem-card::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 2px;
background: linear-gradient(90deg, transparent, var(--dim), transparent);
}
.bold-statement {
text-align: center;
margin-top: 4rem;
padding: 3rem;
background: var(--bg2);
border-radius: 16px;
border: 1px solid var(--purple-dim);
position: relative;
overflow: hidden;
}
.bold-statement::before {
content: '';
position: absolute;
inset: -1px;
border-radius: 16px;
background: linear-gradient(135deg, var(--purple-dim), transparent, var(--purple-dim));
z-index: 0;
opacity: 0.4;
}
.bold-statement p {
position: relative;
z-index: 1;
font-size: clamp(1.4rem, 3vw, 2rem);
font-weight: 700;
color: var(--white);
letter-spacing: -0.02em;
}
.bold-statement span {
color: var(--purple);
}
/* ── Activation Demo ── */
#activation {
background: var(--bg);
}
.demo-container {
margin-top: 2.5rem;
background: var(--bg2);
border: 1px solid var(--dim);
border-radius: 16px;
overflow: hidden;
}
.demo-toolbar {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--dim);
flex-wrap: wrap;
}
.demo-toolbar-label {
font-size: 0.75rem;
color: var(--muted);
letter-spacing: 0.05em;
text-transform: uppercase;
font-weight: 600;
}
.btn-activate {
background: var(--purple);
color: #fff;
border: none;
padding: 8px 24px;
border-radius: 8px;
font-size: 0.85rem;
font-weight: 600;
cursor: pointer;
transition: all 0.15s;
font-family: inherit;
letter-spacing: 0.02em;
}
.btn-activate:hover { background: #9580FF; transform: translateY(-1px); box-shadow: 0 4px 20px var(--purple-glow-strong); }
.btn-activate:active { transform: translateY(0); }
.btn-activate:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
.btn-reset {
background: transparent;
color: var(--muted);
border: 1px solid var(--dim);
padding: 8px 20px;
border-radius: 8px;
font-size: 0.85rem;
cursor: pointer;
font-family: inherit;
transition: all 0.15s;
}
.btn-reset:hover { border-color: var(--muted); color: var(--white); }
.speed-control {
display: flex;
align-items: center;
gap: 8px;
margin-left: auto;
}
.speed-control input {
width: 80px;
accent-color: var(--purple);
}
#activation-canvas {
width: 100%;
height: 500px;
display: block;
cursor: pointer;
}
.formula-box {
padding: 1rem 1.5rem;
border-top: 1px solid var(--dim);
font-family: var(--mono);
font-size: 0.85rem;
color: var(--muted);
display: flex;
align-items: center;
gap: 1rem;
flex-wrap: wrap;
}
.formula-box .formula-label {
color: var(--white);
font-size: 0.75rem;
letter-spacing: 0.08em;
text-transform: uppercase;
font-family: inherit;
}
.formula-box .formula {
color: var(--purple);
}
.formula-box .formula-hint {
margin-left: auto;
font-size: 0.78rem;
color: var(--dim);
font-family: inherit;
}
/* ── Memory Tiers ── */
#tiers {
background: var(--bg);
}
.tiers-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1.5rem;
margin-top: 2.5rem;
}
.tier-card {
background: var(--bg2);
border-radius: 12px;
padding: 2rem;
border: 1px solid var(--dim);
position: relative;
overflow: hidden;
transition: border-color 0.3s, box-shadow 0.3s;
}
.tier-card:hover {
box-shadow: 0 0 30px rgba(0,0,0,0.5);
}
.tier-card::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 3px;
}
.tier-card.working::before { background: var(--working); }
.tier-card.episodic::before { background: var(--episodic); }
.tier-card.semantic::before { background: var(--semantic); }
.tier-card.procedural::before { background: var(--procedural); }
.tier-card:hover.working { border-color: var(--working); box-shadow: 0 0 30px rgba(255,107,107,0.1); }
.tier-card:hover.episodic { border-color: var(--episodic); box-shadow: 0 0 30px rgba(255,179,71,0.1); }
.tier-card:hover.semantic { border-color: var(--semantic); box-shadow: 0 0 30px rgba(123,97,255,0.15); }
.tier-card:hover.procedural { border-color: var(--procedural); box-shadow: 0 0 30px rgba(78,205,196,0.1); }
.tier-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 1rem;
}
.tier-icon {
font-size: 1.5rem;
}
.tier-name {
font-size: 1.1rem;
font-weight: 700;
color: var(--white);
}
.tier-analogy {
font-size: 0.75rem;
color: var(--muted);
font-family: var(--mono);
font-weight: 400;
}
.tier-desc {
font-size: 0.95rem;
color: var(--muted);
margin-bottom: 1.2rem;
}
.tier-example {
background: var(--bg3);
border-radius: 8px;
padding: 0.75rem 1rem;
font-size: 0.8rem;
font-family: var(--mono);
color: var(--muted);
border-left: 3px solid;
line-height: 1.5;
}
.tier-card.working .tier-example { border-color: var(--working); color: #FFB0B0; }
.tier-card.episodic .tier-example { border-color: var(--episodic); color: #FFCC80; }
.tier-card.semantic .tier-example { border-color: var(--semantic); color: #B8A8FF; }
.tier-card.procedural .tier-example { border-color: var(--procedural); color: #80E8E4; }
/* ── Salience ── */
#salience {
background: var(--bg);
}
.salience-container {
margin-top: 2.5rem;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
}
.salience-formula-display {
background: var(--bg2);
border-radius: 12px;
border: 1px solid var(--dim);
padding: 2rem;
}
.salience-formula-display .formula-title {
font-size: 0.75rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: 1.5rem;
font-weight: 600;
}
.formula-visual {
font-family: var(--mono);
font-size: 0.9rem;
color: var(--white);
line-height: 2;
margin-bottom: 2rem;
}
.fv-equal { color: var(--muted); }
.fv-var { color: var(--purple); }
.fv-op { color: var(--muted); }
.fv-fn { color: #4ECDC4; }
.fv-num { color: #FFB347; }
.fv-comment { color: var(--dim); }
.salience-sliders {
background: var(--bg2);
border-radius: 12px;
border: 1px solid var(--dim);
padding: 2rem;
}
.slider-row {
margin-bottom: 1.5rem;
}
.slider-label {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.5rem;
}
.slider-label span:first-child {
font-size: 0.85rem;
color: var(--muted);
font-weight: 500;
}
.slider-label .slider-value {
font-family: var(--mono);
font-size: 0.85rem;
color: var(--purple);
font-weight: 700;
}
input[type=range] {
width: 100%;
-webkit-appearance: none;
height: 4px;
border-radius: 2px;
background: var(--dim);
outline: none;
cursor: pointer;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: var(--purple);
cursor: pointer;
box-shadow: 0 0 8px var(--purple-glow-strong);
transition: transform 0.1s;
}
input[type=range]::-webkit-slider-thumb:hover { transform: scale(1.2); }
.salience-output {
background: var(--bg3);
border-radius: 10px;
padding: 1.5rem;
text-align: center;
border: 1px solid var(--purple-dim);
margin-top: 1.5rem;
}
.salience-score-label {
font-size: 0.75rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: 0.75rem;
}
.salience-score {
font-size: 3rem;
font-weight: 900;
font-family: var(--mono);
color: var(--purple);
letter-spacing: -0.04em;
line-height: 1;
margin-bottom: 1rem;
transition: color 0.3s;
}
.salience-gauge {
width: 100%;
height: 8px;
background: var(--dim);
border-radius: 4px;
overflow: hidden;
}
.salience-gauge-fill {
height: 100%;
border-radius: 4px;
transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1), background 0.4s;
}
.forgetting-quote {
text-align: center;
margin-top: 3rem;
font-size: 1.2rem;
font-style: italic;
color: var(--muted);
max-width: 600px;
margin-left: auto;
margin-right: auto;
}
/* ── Consolidation ── */
#consolidation {
background: var(--bg);
}
.consolidation-diagram {
margin-top: 2.5rem;
background: var(--bg2);
border: 1px solid var(--dim);
border-radius: 16px;
padding: 2.5rem;
position: relative;
overflow: hidden;
}
.consol-stages {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
position: relative;
z-index: 2;
}
.consol-box {
flex: 1;
max-width: 260px;
background: var(--bg3);
border-radius: 12px;
padding: 1.5rem;
border: 1px solid var(--dim);
text-align: center;
}
.consol-box.episodic { border-color: var(--episodic); }
.consol-box.semantic { border-color: var(--semantic); }
.consol-box-title {
font-weight: 700;
font-size: 1rem;
margin-bottom: 0.5rem;
}
.consol-box.episodic .consol-box-title { color: var(--episodic); }
.consol-box.semantic .consol-box-title { color: var(--semantic); }
.consol-box p {
font-size: 0.8rem;
color: var(--muted);
}
.consol-arrow-zone {
flex: 1;
max-width: 200px;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.consol-conditions {
font-size: 0.72rem;
font-family: var(--mono);
color: var(--muted);
text-align: center;
line-height: 1.7;
background: var(--bg3);
padding: 0.75rem;
border-radius: 8px;
border: 1px solid var(--dim);
}
.consol-conditions .cond-hl { color: var(--purple); }
.consol-arrow-svg {
width: 100%;
}
#consol-canvas {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1;
}
.consol-diagram-bottom {
margin-top: 2rem;
text-align: center;
font-size: 0.9rem;
color: var(--muted);
}
/* ── Architecture ── */
#architecture {
background: var(--bg);
}
.arch-diagram {
margin-top: 2.5rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 0;
font-family: var(--mono);
}
.arch-layer {
width: 100%;
max-width: 700px;
padding: 1.2rem 2rem;
border-radius: 10px;
text-align: center;
font-size: 0.9rem;
position: relative;
}
.arch-layer.top {
background: linear-gradient(135deg, #1A1A35, #1F1F40);
border: 1px solid var(--purple-dim);
color: var(--white);
font-weight: 700;
letter-spacing: 0.02em;
}
.arch-layer.api {
background: var(--bg2);
border: 1px solid var(--purple);
color: var(--purple);
font-weight: 700;
margin: 2px 0;
}
.arch-layer.inner {
background: var(--bg3);
border: 1px solid var(--dim);
color: var(--white);
display: flex;
gap: 1px;
padding: 0;
overflow: hidden;
}
.arch-inner-block {
flex: 1;
padding: 1.2rem 1rem;
text-align: center;
font-size: 0.82rem;
}
.arch-inner-block + .arch-inner-block {
border-left: 1px solid var(--dim);
}
.arch-inner-block .block-title {
font-weight: 700;
color: var(--white);
margin-bottom: 0.25rem;
}
.arch-inner-block .block-sub {
color: var(--muted);
font-size: 0.75rem;
}
.arch-layer.storage {
background: var(--bg2);
border: 1px solid var(--dim);
color: var(--muted);
margin: 2px 0;
}
.arch-layer.disk {
background: var(--bg);
border: 1px solid var(--dim);
color: var(--dim);
}
.arch-connector {
display: flex;
flex-direction: column;
align-items: center;
color: var(--dim);
font-size: 1.1rem;
line-height: 1;
padding: 4px 0;
}
.arch-connector span {
font-size: 0.75rem;
letter-spacing: 0.05em;
color: var(--dim);
}
/* ── Bindings ── */
#bindings {
background: var(--bg);
}
.tabs {
margin-top: 2.5rem;
}
.tab-buttons {
display: flex;
gap: 2px;
margin-bottom: 0;
border-bottom: 1px solid var(--dim);
}
.tab-btn {
padding: 10px 24px;
background: transparent;
border: none;
color: var(--muted);
font-size: 0.85rem;
font-weight: 600;
cursor: pointer;
font-family: inherit;
letter-spacing: 0.03em;
position: relative;
transition: color 0.15s;
border-bottom: 2px solid transparent;
margin-bottom: -1px;
}
.tab-btn.active {
color: var(--white);
border-bottom-color: var(--purple);
}
.tab-btn:hover:not(.active) { color: var(--white); }
.tab-content {
display: none;
background: var(--bg2);
border: 1px solid var(--dim);
border-top: none;
border-radius: 0 0 12px 12px;
overflow: hidden;
}
.tab-content.active { display: block; }
.code-header {
display: flex;
align-items: center;
padding: 0.75rem 1.5rem;
border-bottom: 1px solid var(--dim);
font-size: 0.75rem;
color: var(--muted);
font-family: var(--mono);
gap: 8px;
}
.dot-red { width: 10px; height: 10px; border-radius: 50%; background: #FF5F57; }
.dot-yellow { width: 10px; height: 10px; border-radius: 50%; background: #FFBD2E; }
.dot-green { width: 10px; height: 10px; border-radius: 50%; background: #28CA41; }
pre {
padding: 2rem;
overflow-x: auto;
tab-size: 4;
line-height: 1.7;
font-size: 0.875rem;
}
/* Syntax highlighting */
.kw { color: #C792EA; }
.fn-name { color: #82AAFF; }
.str { color: #C3E88D; }
.num { color: #F78C6C; }
.comment { color: #546E7A; font-style: italic; }
.type { color: #FFCB6B; }
.op { color: var(--muted); }
.punct { color: var(--muted); }
.const { color: #89DDFF; }
/* ── Why Local ── */
#local {
background: var(--bg);
text-align: center;
}
.local-statement {
max-width: 700px;
margin: 2rem auto 0;
font-size: clamp(1.15rem, 2.5vw, 1.4rem);
color: var(--white);
line-height: 1.7;
font-weight: 400;
}
.local-statement strong {
color: var(--purple);
font-weight: 700;
}
.local-statement em {
color: var(--muted);
font-style: normal;
}
.local-divider {
width: 1px;
height: 3rem;
background: linear-gradient(to bottom, var(--purple), transparent);
margin: 2rem auto;
}
/* ── Status ── */
#status {
background: var(--bg);
border-top: 1px solid var(--dim);
padding-top: 60px;
padding-bottom: 100px;
}
.status-grid {
display: flex;
gap: 2rem;
margin-top: 2.5rem;
flex-wrap: wrap;
}
.status-badge {
display: flex;
align-items: center;
gap: 12px;
background: var(--bg2);
border: 1px solid var(--dim);
border-radius: 10px;
padding: 1rem 1.5rem;
}
.status-icon {
font-size: 1.5rem;
}
.status-badge-label {
font-size: 0.75rem;
color: var(--muted);
letter-spacing: 0.05em;
text-transform: uppercase;
}
.status-badge-value {
font-size: 1.1rem;
font-weight: 700;
color: var(--white);
font-family: var(--mono);
}
.status-badge.ok { border-color: #28CA41; }
.status-badge.ok .status-icon { color: #28CA41; }
.status-badge.ver { border-color: var(--purple); }
.status-badge.ver .status-icon { color: var(--purple); }
.status-badge.tests { border-color: #4ECDC4; }
.status-badge.tests .status-icon { color: #4ECDC4; }
/* ── Footer ── */
footer {
text-align: center;
padding: 3rem 2rem;
border-top: 1px solid var(--dim);
color: var(--dim);
font-size: 0.8rem;
font-family: var(--mono);
}
/* ── Responsive ── */
@media (max-width: 800px) {
.problem-grid { grid-template-columns: 1fr; }
.problem-card + .problem-card { border-left: none; border-top: 1px solid var(--dim); }
.tiers-grid { grid-template-columns: 1fr; }
.salience-container { grid-template-columns: 1fr; }
.consol-stages { flex-direction: column; }
.consol-arrow-zone { transform: rotate(90deg); }
section { padding: 60px 24px; }
}
@media (max-width: 600px) {
#activation-canvas { height: 380px; }
}
</style>
</head>
<body>
<!-- ═══════════════════════════════════════════ HERO ═══════════════════════════════════════════ -->
<div id="hero">
<canvas id="hero-canvas"></canvas>
<div class="hero-content">
<div class="hero-badge">
<div class="pulse-dot"></div>
v0.1.0 · Local-first memory substrate
</div>
<h1 class="hero-title">Engram</h1>
<p class="hero-tagline">
The physical trace of a memory.<br>
<em>A database designed for minds, not tables.</em>
</p>
</div>
<div class="scroll-hint">
<span>Scroll</span>
<div class="scroll-arrow"></div>
</div>
</div>
<!-- ═══════════════════════════════════════════ PROBLEM ═══════════════════════════════════════════ -->
<section id="problem">
<span class="section-label">The Problem</span>
<h2>Every existing database<br>gets memory wrong</h2>
<div class="problem-grid">
<div class="problem-card">
<span class="problem-icon"></span>
<h3>Relational</h3>
<p>You store rows. You query rows. Storage and retrieval are entirely separate systems. The structure that holds the data knows nothing about how you will need it back.</p>
</div>
<div class="problem-card">
<span class="problem-icon"></span>
<h3>Vector</h3>
<p>You store embeddings. You search by geometric proximity. Still fundamentally a query against static, passive data — the structure has no opinion about your context.</p>
</div>
<div class="problem-card">
<span class="problem-icon"></span>
<h3>Graph</h3>
<p>You store edges. You traverse paths. Still asking questions of a static structure. The graph doesn't activate — you query it from outside, like a stranger reading a map.</p>
</div>
</div>
<div class="bold-statement">
<p>The brain doesn't <span>query</span>.<br>It <span>activates</span>.</p>
</div>
</section>
<!-- ═══════════════════════════════════════════ ACTIVATION DEMO ═══════════════════════════════════════════ -->
<section id="activation">
<span class="section-label">Core Mechanism</span>
<h2>Spreading Activation</h2>
<p style="max-width:640px; margin-top:1rem;">Click any node to set it as the seed. Hit <strong style="color:var(--white)">Activate</strong> to watch the pattern propagate. Activation flows outward through weighted edges — attenuating at every hop, pruned when too weak to matter.</p>
<div class="demo-container">
<div class="demo-toolbar">
<span class="demo-toolbar-label">Seed</span>
<div id="seed-indicator" style="font-family:var(--mono);font-size:0.85rem;color:var(--purple);background:var(--purple-glow);padding:4px 12px;border-radius:6px;border:1px solid var(--purple-dim);">Click a node to select</div>
<button class="btn-activate" id="btn-activate" disabled>Activate</button>
<button class="btn-reset" id="btn-reset">Reset</button>
<div class="speed-control">
<span class="demo-toolbar-label">Speed</span>
<input type="range" id="speed-slider" min="1" max="5" value="3">
</div>
</div>
<canvas id="activation-canvas"></canvas>
<div class="formula-box">
<span class="formula-label">Formula</span>
<code class="formula">strength = parent × edge_weight × salience × cos_sim(query, target)</code>
<span class="formula-hint">Multiplicative — every factor must be non-trivial for the path to survive</span>
</div>
</div>
</section>
<!-- ═══════════════════════════════════════════ TIERS ═══════════════════════════════════════════ -->
<section id="tiers">
<span class="section-label">Memory Architecture</span>
<h2>The Four Memory Tiers</h2>
<p style="max-width:640px; margin-top:1rem;">Nodes migrate between tiers based on salience and reinforcement — exactly as memories migrate between the hippocampus and neocortex through activation and sleep.</p>
<div class="tiers-grid">
<div class="tier-card working">
<div class="tier-header">
<span class="tier-icon">🔥</span>
<div>
<div class="tier-name">Working</div>
<div class="tier-analogy">Prefrontal cortex — hot, volatile</div>
</div>
</div>
<p class="tier-desc">The K most recently activated nodes. Ultra-fast access. Evicted by recency when capacity is exceeded. What you're actively thinking about right now.</p>
<div class="tier-example">
// Currently active<br>
task: "Implement activation BFS"<br>
context: spreading_activation_node<br>
recent: hebbian_learning_concept
</div>
</div>
<div class="tier-card episodic">
<div class="tier-header">
<span class="tier-icon">📖</span>
<div>
<div class="tier-name">Episodic</div>
<div class="tier-analogy">Hippocampus — time-ordered experience</div>
</div>
</div>
<p class="tier-desc">Time-stamped events and raw experiences. What happened, and when. The raw feed of observations before they have been abstracted into knowledge.</p>
<div class="tier-example">
2026-04-27T14:23Z event:<br>
"Will explained Dharma Registry.<br>
Patterns logged. ISE confirmed."
</div>
</div>
<div class="tier-card semantic">
<div class="tier-header">
<span class="tier-icon">🧠</span>
<div>
<div class="tier-name">Semantic</div>
<div class="tier-analogy">Neocortex — stable knowledge</div>
</div>
</div>
<p class="tier-desc">The concept graph with weighted associations. Long-term structural knowledge. What you know, abstracted from any specific event that taught it to you.</p>
<div class="tier-example">
concept: "spreading_activation"<br>
→ Causes: "long_term_potentiation"<br>
→ Activates: "associative_memory"<br>
salience: 0.82, activations: 47
</div>
</div>
<div class="tier-card procedural">
<div class="tier-header">
<span class="tier-icon">⚙️</span>
<div>
<div class="tier-name">Procedural</div>
<div class="tier-analogy">Cerebellum — patterns and habits</div>
</div>
</div>
<p class="tier-desc">Encoded patterns, workflows, and repeatable processes. How to do things. Retrieved by similarity to current task context, not by conscious recall.</p>
<div class="tier-example">
process: "commit_workflow"<br>
steps: [stage → test → commit<br>
→ push → check_ci]<br>
triggered by: git_context_match
</div>
</div>
</div>
</section>
<!-- ═══════════════════════════════════════════ SALIENCE ═══════════════════════════════════════════ -->
<section id="salience">
<span class="section-label">Forgetting as Design</span>
<h2>Salience — the attention filter</h2>
<p style="max-width:640px; margin-top:1rem;">Every node has a salience score. It governs whether a node surfaces during retrieval. It decays. It strengthens on activation. Adjust the sliders to see how the three signals combine.</p>
<div class="salience-container">
<div class="salience-formula-display">
<div class="formula-title">The Salience Formula</div>
<div class="formula-visual">
<span class="fv-var">salience</span>
<span class="fv-equal"> = </span>
<span class="fv-var">importance</span>
<br>
<span style="color:transparent">salience = </span>
<span class="fv-op">× </span>
<span class="fv-fn">1</span><span class="fv-op"> / </span><span class="fv-fn">(1</span><span class="fv-op"> + </span><span class="fv-var">days_since</span><span class="fv-fn">)</span>
<br>
<span style="color:transparent">salience = </span>
<span class="fv-op">× </span>
<span class="fv-fn">ln(</span><span class="fv-var">activation_count</span><span class="fv-op"> + </span><span class="fv-num">1</span><span class="fv-fn">)</span>
</div>
<div style="font-size:0.85rem;color:var(--muted);line-height:2;">
<div style="display:flex;gap:1rem;align-items:baseline;margin-bottom:0.5rem;">
<span style="color:var(--purple);font-family:var(--mono);min-width:100px;">importance</span>
<span>Explicit weight at creation. Stable over time. You set it once.</span>
</div>
<div style="display:flex;gap:1rem;align-items:baseline;margin-bottom:0.5rem;">
<span style="color:var(--procedural);font-family:var(--mono);min-width:100px;">recency</span>
<span>At activation: 1.0. After 1 day: 0.5. After 6 days: ~0.14. Asymptotic toward zero.</span>
</div>
<div style="display:flex;gap:1rem;align-items:baseline;">
<span style="color:var(--episodic);font-family:var(--mono);min-width:100px;">frequency</span>
<span>Log-compressed: 0→1 activation matters more than 100→101. Diminishing returns.</span>
</div>
</div>
</div>
<div class="salience-sliders">
<div class="slider-row">
<div class="slider-label">
<span>Importance</span>
<span class="slider-value" id="val-importance">0.85</span>
</div>
<input type="range" id="sl-importance" min="0" max="100" value="85">
</div>
<div class="slider-row">
<div class="slider-label">
<span>Days since last activation</span>
<span class="slider-value" id="val-days">2</span>
</div>
<input type="range" id="sl-days" min="0" max="365" value="2">
</div>
<div class="slider-row">
<div class="slider-label">
<span>Activation count</span>
<span class="slider-value" id="val-count">12</span>
</div>
<input type="range" id="sl-count" min="1" max="1000" value="12">
</div>
<div class="salience-output">
<div class="salience-score-label">Computed Salience</div>
<div class="salience-score" id="salience-score">0.00</div>
<div class="salience-gauge">
<div class="salience-gauge-fill" id="salience-gauge-fill" style="width:0%;background:var(--purple)"></div>
</div>
</div>
</div>
</div>
<p class="forgetting-quote">
"Forgetting is not failure. It is the system prioritising what matters."
</p>
</section>
<!-- ═══════════════════════════════════════════ CONSOLIDATION ═══════════════════════════════════════════ -->
<section id="consolidation">
<span class="section-label">Memory Lifecycle</span>
<h2>Consolidation</h2>
<p style="max-width:640px; margin-top:1rem;">In the brain, memories consolidate during sleep — the hippocampus replays experiences into the neocortex until they become stable knowledge. Engram makes this explicit.</p>
<div class="consolidation-diagram" id="consol-diagram">
<canvas id="consol-canvas"></canvas>
<div class="consol-stages">
<div class="consol-box episodic" id="consol-ep">
<div class="consol-box-title">Episodic</div>
<p>Raw events. Time-stamped experience. Not yet abstract.</p>
<div style="margin-top:1rem;font-family:var(--mono);font-size:0.72rem;color:var(--muted);text-align:left;line-height:1.7;">
tier: Episodic<br>
activation_count: 2<br>
salience: 0.41
</div>
</div>
<div class="consol-arrow-zone">
<svg class="consol-arrow-svg" viewBox="0 0 180 80" fill="none">
<defs>
<marker id="arrow-head" markerWidth="8" markerHeight="8" refX="4" refY="4" orient="auto">
<path d="M 0 0 L 8 4 L 0 8 Z" fill="#7B61FF"/>
</marker>
<linearGradient id="arrow-grad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#FFB347"/>
<stop offset="100%" stop-color="#7B61FF"/>
</linearGradient>
</defs>
<line x1="10" y1="40" x2="160" y2="40" stroke="url(#arrow-grad)" stroke-width="2.5" stroke-dasharray="6 4" marker-end="url(#arrow-head)"/>
</svg>
<div class="consol-conditions">
activation_count <span class="cond-hl">≥ 5</span><br>
salience <span class="cond-hl">≥ 0.3</span><br>
<span style="color:var(--dim)">→ promote on consolidate()</span>
</div>
</div>
<div class="consol-box semantic" id="consol-sem">
<div class="consol-box-title">Semantic</div>
<p>Stable concept. Integrated knowledge. No longer tied to a specific event.</p>
<div style="margin-top:1rem;font-family:var(--mono);font-size:0.72rem;color:var(--muted);text-align:left;line-height:1.7;">
tier: Semantic<br>
activation_count: 7<br>
salience: 0.68
</div>
</div>
</div>
<div class="consol-diagram-bottom">
Promotion is earned by use — activated, reinforced, and found relevant repeatedly over time.
</div>
</div>
</section>
<!-- ═══════════════════════════════════════════ ARCHITECTURE ═══════════════════════════════════════════ -->
<section id="architecture">
<span class="section-label">System Design</span>
<h2>Architecture</h2>
<p style="max-width:640px; margin-top:1rem;">Three retrieval primitives layered over a single embedded store. No daemon. No network. No server to babysit.</p>
<div class="arch-diagram">
<div class="arch-layer top">[ Your Intelligence ]</div>
<div class="arch-connector"></div>
<div class="arch-layer api">EngramDb API</div>
<div class="arch-connector"></div>
<div class="arch-layer inner">
<div class="arch-inner-block">
<div class="block-title">Spreading Activation</div>
<div class="block-sub">Best-first BFS · multiplicative weights · pruning at 0.01</div>
</div>
<div class="arch-inner-block">
<div class="block-title">Vector Search</div>
<div class="block-sub">Flat cosine scan · semantic direction filter · secondary signal</div>
</div>
<div class="arch-inner-block">
<div class="block-title">Graph Traversal</div>
<div class="block-sub">BFS by relation type · typed edges · max depth</div>
</div>
</div>
<div class="arch-connector"></div>
<div class="arch-layer storage">sled — embedded persistent B-tree · bincode serialization · transactional</div>
<div class="arch-connector"></div>
<div class="arch-layer disk">[ Your disk — no daemon · no network · local-first ]</div>
</div>
<div style="margin-top:3rem;display:grid;grid-template-columns:repeat(3,1fr);gap:1.5rem;">
<div style="background:var(--bg2);border:1px solid var(--dim);border-radius:10px;padding:1.5rem;">
<div style="font-weight:700;margin-bottom:0.5rem;color:var(--white);">Why multiplication?</div>
<p style="font-size:0.85rem;color:var(--muted);">Addition lets many weak signals accumulate into false relevance. Multiplication is conjunctive: a weak edge, a dormant node, or a semantically irrelevant target all kill the path. This is how associative memory actually works.</p>
</div>
<div style="background:var(--bg2);border:1px solid var(--dim);border-radius:10px;padding:1.5rem;">
<div style="font-weight:700;margin-bottom:0.5rem;color:var(--white);">Why sled?</div>
<p style="font-size:0.85rem;color:var(--muted);">Local-first. Transactional. No daemon process, no network socket. HNSW indexing layers on top when needed — the graph structure itself is the primary retrieval mechanism.</p>
</div>
<div style="background:var(--bg2);border:1px solid var(--dim);border-radius:10px;padding:1.5rem;">
<div style="font-weight:700;margin-bottom:0.5rem;color:var(--white);">Why pruning at 0.01?</div>
<p style="font-size:0.85rem;color:var(--muted);">Small enough to allow long indirect chains when intermediate edges are strong. Raise it to focus retrieval; lower it for more associative drift. The brain's attention filter, made explicit.</p>
</div>
</div>
</section>
<!-- ═══════════════════════════════════════════ BINDINGS ═══════════════════════════════════════════ -->
<section id="bindings">
<span class="section-label">Language Bindings</span>
<h2>Use from anywhere</h2>
<p style="max-width:640px; margin-top:1rem;">A C FFI layer exposes the full API. Language bindings ship for Kotlin, TypeScript, Go, and Rust natively.</p>
<div class="tabs">
<div class="tab-buttons">
<button class="tab-btn active" onclick="switchTab('rust')">Rust</button>
<button class="tab-btn" onclick="switchTab('kotlin')">Kotlin</button>
<button class="tab-btn" onclick="switchTab('typescript')">TypeScript</button>
<button class="tab-btn" onclick="switchTab('go')">Go</button>
</div>
<div class="tab-content active" id="tab-rust">
<div class="code-header">
<div class="dot-red"></div><div class="dot-yellow"></div><div class="dot-green"></div>
<span style="margin-left:8px;">basic.rs</span>
</div>
<pre><code><span class="kw">use</span> engram_core<span class="punct">::{</span><span class="type">EngramDb</span><span class="punct">,</span> <span class="type">Node</span><span class="punct">,</span> <span class="type">Edge</span><span class="punct">,</span> <span class="type">NodeType</span><span class="punct">,</span> <span class="type">MemoryTier</span><span class="punct">,</span> <span class="type">RelationType</span><span class="punct">};</span>
<span class="comment">// Open or create a local database — no server, no daemon</span>
<span class="kw">let</span> db <span class="op">=</span> <span class="type">EngramDb</span><span class="punct">::</span><span class="fn-name">open</span><span class="punct">(</span><span class="type">Path</span><span class="punct">::</span><span class="fn-name">new</span><span class="punct">(</span><span class="str">"/var/lib/agent/memory"</span><span class="punct">))?;</span>
<span class="comment">// Store a concept with a semantic embedding</span>
<span class="kw">let</span> id <span class="op">=</span> db<span class="punct">.</span><span class="fn-name">put_node</span><span class="punct">(</span><span class="type">Node</span><span class="punct">::</span><span class="fn-name">new</span><span class="punct">(</span>
<span class="type">NodeType</span><span class="punct">::</span><span class="const">Concept</span><span class="punct">,</span>
embedding<span class="punct">,</span> <span class="comment">// Vec&lt;f32&gt; from your LLM</span>
content<span class="punct">,</span> <span class="comment">// Vec&lt;u8&gt; — any payload</span>
<span class="type">MemoryTier</span><span class="punct">::</span><span class="const">Semantic</span><span class="punct">,</span>
<span class="num">0.9</span><span class="punct">,</span> <span class="comment">// importance 0.01.0</span>
<span class="punct">))?;</span>
<span class="comment">// Link to a related concept</span>
db<span class="punct">.</span><span class="fn-name">put_edge</span><span class="punct">(</span><span class="type">Edge</span><span class="punct">::</span><span class="fn-name">new</span><span class="punct">(</span>id<span class="punct">,</span> related_id<span class="punct">,</span> <span class="type">RelationType</span><span class="punct">::</span><span class="const">Causes</span><span class="punct">,</span> <span class="num">0.88</span><span class="punct">))?;</span>
<span class="comment">// Retrieve by spreading activation — not a query, a pattern completion</span>
<span class="kw">let</span> results <span class="op">=</span> db<span class="punct">.</span><span class="fn-name">activate</span><span class="punct">(</span>
<span class="op">&amp;</span><span class="punct">[</span>id<span class="punct">],</span> <span class="comment">// seed nodes</span>
<span class="op">&amp;</span>query_embedding<span class="punct">,</span> <span class="comment">// direction of thought</span>
<span class="num">3</span><span class="punct">,</span> <span class="comment">// max hops</span>
<span class="num">10</span><span class="punct">,</span> <span class="comment">// top-N results</span>
<span class="punct">)?;</span>
<span class="kw">for</span> r <span class="kw">in</span> <span class="op">&amp;</span>results <span class="punct">{</span>
<span class="fn-name">println!</span><span class="punct">(</span><span class="str">"strength={:.4} hops={}"</span><span class="punct">,</span> r<span class="punct">.</span>activation_strength<span class="punct">,</span> r<span class="punct">.</span>hops<span class="punct">);</span>
<span class="punct">}</span></code></pre>
</div>
<div class="tab-content" id="tab-kotlin">
<div class="code-header">
<div class="dot-red"></div><div class="dot-yellow"></div><div class="dot-green"></div>
<span style="margin-left:8px;">Memory.kt</span>
</div>
<pre><code><span class="kw">import</span> ai<span class="punct">.</span>neuron<span class="punct">.</span>engram<span class="punct">.*</span>
<span class="comment">// JVM binding via JNI — same embedded storage, no server</span>
<span class="kw">val</span> db <span class="op">=</span> <span class="type">EngramDb</span><span class="punct">.</span><span class="fn-name">open</span><span class="punct">(</span><span class="str">"/data/user/0/ai.agent/memory"</span><span class="punct">)</span>
<span class="comment">// Store an episodic event</span>
<span class="kw">val</span> id <span class="op">=</span> db<span class="punct">.</span><span class="fn-name">putNode</span><span class="punct">(</span>
<span class="type">Node</span><span class="punct">(</span>
nodeType <span class="op">=</span> <span class="type">NodeType</span><span class="punct">.</span><span class="const">EVENT</span><span class="punct">,</span>
embedding <span class="op">=</span> llm<span class="punct">.</span><span class="fn-name">embed</span><span class="punct">(</span>text<span class="punct">),</span>
content <span class="op">=</span> text<span class="punct">.</span><span class="fn-name">toByteArray</span><span class="punct">(),</span>
tier <span class="op">=</span> <span class="type">MemoryTier</span><span class="punct">.</span><span class="const">EPISODIC</span><span class="punct">,</span>
importance <span class="op">=</span> <span class="num">0.8f</span>
<span class="punct">)</span>
<span class="punct">)</span>
<span class="comment">// Activate from current context</span>
<span class="kw">val</span> recalled <span class="op">=</span> db<span class="punct">.</span><span class="fn-name">activate</span><span class="punct">(</span>
seeds <span class="op">=</span> listOf<span class="punct">(</span>id<span class="punct">),</span>
queryEmbedding <span class="op">=</span> currentContext<span class="punct">.</span>embedding<span class="punct">,</span>
maxDepth <span class="op">=</span> <span class="num">3</span><span class="punct">,</span>
limit <span class="op">=</span> <span class="num">10</span>
<span class="punct">)</span>
recalled<span class="punct">.</span><span class="fn-name">forEach</span> <span class="punct">{</span>
<span class="fn-name">println</span><span class="punct">(</span><span class="str">"[${it.hops} hops] strength=${it.activationStrength}"</span><span class="punct">)</span>
<span class="punct">}</span></code></pre>
</div>
<div class="tab-content" id="tab-typescript">
<div class="code-header">
<div class="dot-red"></div><div class="dot-yellow"></div><div class="dot-green"></div>
<span style="margin-left:8px;">memory.ts</span>
</div>
<pre><code><span class="kw">import</span> <span class="punct">{</span> <span class="type">EngramDb</span><span class="punct">,</span> <span class="type">NodeType</span><span class="punct">,</span> <span class="type">MemoryTier</span><span class="punct">,</span> <span class="type">RelationType</span> <span class="punct">}</span> <span class="kw">from</span> <span class="str">"@neuron/engram"</span><span class="punct">;</span>
<span class="comment">// WASM build — runs in Node or browser, fully in-process</span>
<span class="kw">const</span> db <span class="op">=</span> <span class="kw">await</span> <span class="type">EngramDb</span><span class="punct">.</span><span class="fn-name">open</span><span class="punct">(</span><span class="str">"./agent-memory"</span><span class="punct">);</span>
<span class="comment">// Store a concept node</span>
<span class="kw">const</span> id <span class="op">=</span> <span class="kw">await</span> db<span class="punct">.</span><span class="fn-name">putNode</span><span class="punct">({</span>
nodeType<span class="punct">:</span> <span class="type">NodeType</span><span class="punct">.</span><span class="const">Concept</span><span class="punct">,</span>
embedding<span class="punct">:</span> <span class="kw">await</span> llm<span class="punct">.</span><span class="fn-name">embed</span><span class="punct">(</span>text<span class="punct">),</span>
content<span class="punct">:</span> <span class="kw">new</span> <span class="type">TextEncoder</span><span class="punct">().</span><span class="fn-name">encode</span><span class="punct">(</span>text<span class="punct">),</span>
tier<span class="punct">:</span> <span class="type">MemoryTier</span><span class="punct">.</span><span class="const">Semantic</span><span class="punct">,</span>
importance<span class="punct">:</span> <span class="num">0.85</span><span class="punct">,</span>
<span class="punct">});</span>
<span class="comment">// Spreading activation — pattern completion, not query</span>
<span class="kw">const</span> recalled <span class="op">=</span> <span class="kw">await</span> db<span class="punct">.</span><span class="fn-name">activate</span><span class="punct">({</span>
seeds<span class="punct">:</span> <span class="punct">[</span>id<span class="punct">],</span>
queryEmbedding<span class="punct">:</span> currentContext<span class="punct">.</span>embedding<span class="punct">,</span>
maxDepth<span class="punct">:</span> <span class="num">3</span><span class="punct">,</span>
limit<span class="punct">:</span> <span class="num">10</span><span class="punct">,</span>
<span class="punct">});</span>
<span class="kw">for</span> <span class="punct">(</span><span class="kw">const</span> node <span class="kw">of</span> recalled<span class="punct">)</span> <span class="punct">{</span>
console<span class="punct">.</span><span class="fn-name">log</span><span class="punct">(</span><span class="str">`strength=<span class="punct">${</span>node<span class="punct">.</span>activationStrength<span class="punct">.</span><span class="fn-name">toFixed</span><span class="punct">(</span><span class="num">4</span><span class="punct">)}</span> hops=<span class="punct">${</span>node<span class="punct">.</span>hops<span class="punct">}</span>`</span><span class="punct">);</span>
<span class="punct">}</span></code></pre>
</div>
<div class="tab-content" id="tab-go">
<div class="code-header">
<div class="dot-red"></div><div class="dot-yellow"></div><div class="dot-green"></div>
<span style="margin-left:8px;">memory.go</span>
</div>
<pre><code><span class="kw">import</span> <span class="punct">(</span>
engram <span class="str">"github.com/neuron-technologies/engram-go"</span>
<span class="punct">)</span>
<span class="comment">// CGo binding — links the Rust library, zero copies for embeddings</span>
db<span class="punct">,</span> err <span class="op">:=</span> engram<span class="punct">.</span><span class="fn-name">Open</span><span class="punct">(</span><span class="str">"/var/lib/agent/memory"</span><span class="punct">)</span>
<span class="kw">if</span> err <span class="op">!=</span> <span class="const">nil</span> <span class="punct">{</span>
<span class="kw">return</span> err
<span class="punct">}</span>
<span class="kw">defer</span> db<span class="punct">.</span><span class="fn-name">Close</span><span class="punct">()</span>
<span class="comment">// Store a node</span>
id<span class="punct">,</span> err <span class="op">:=</span> db<span class="punct">.</span><span class="fn-name">PutNode</span><span class="punct">(</span>engram<span class="punct">.</span><span class="type">Node</span><span class="punct">{</span>
NodeType<span class="punct">:</span> engram<span class="punct">.</span><span class="const">Concept</span><span class="punct">,</span>
Embedding<span class="punct">:</span> embedding<span class="punct">,</span>
Content<span class="punct">:</span> <span class="punct">[]</span><span class="type">byte</span><span class="punct">(</span>text<span class="punct">),</span>
Tier<span class="punct">:</span> engram<span class="punct">.</span><span class="const">Semantic</span><span class="punct">,</span>
Importance<span class="punct">:</span> <span class="num">0.9</span><span class="punct">,</span>
<span class="punct">})</span>
<span class="comment">// Activate from seed — the graph does the retrieval</span>
results<span class="punct">,</span> err <span class="op">:=</span> db<span class="punct">.</span><span class="fn-name">Activate</span><span class="punct">(</span>
<span class="punct">[]</span>engram<span class="punct">.</span><span class="type">UUID</span><span class="punct">{</span>id<span class="punct">},</span>
queryEmbedding<span class="punct">,</span>
<span class="num">3</span><span class="punct">,</span> <span class="comment">// maxDepth</span>
<span class="num">10</span><span class="punct">,</span> <span class="comment">// limit</span>
<span class="punct">)</span>
<span class="kw">for</span> <span class="punct">_,</span> r <span class="op">:=</span> <span class="kw">range</span> results <span class="punct">{</span>
fmt<span class="punct">.</span><span class="fn-name">Printf</span><span class="punct">(</span><span class="str">"strength=%.4f hops=%d\n"</span><span class="punct">,</span> r<span class="punct">.</span>Strength<span class="punct">,</span> r<span class="punct">.</span>Hops<span class="punct">)</span>
<span class="punct">}</span></code></pre>
</div>
</div>
</section>
<!-- ═══════════════════════════════════════════ WHY LOCAL ═══════════════════════════════════════════ -->
<section id="local" style="border-top: 1px solid var(--dim);">
<span class="section-label">Philosophy</span>
<h2>Why local</h2>
<div class="local-divider"></div>
<p class="local-statement">
Your memory is not a service.<br>
<em>It is you.</em><br>
<br>
It lives on <strong>your hardware</strong>, under <strong>your control</strong>.<br>
It does not leave your device.<br>
It does not phone home.<br>
<br>
<em>It does not require a network connection<br>to remember who you are.</em>
</p>
<div class="local-divider"></div>
<div style="display:flex;gap:2rem;justify-content:center;flex-wrap:wrap;margin-top:1rem;">
<div style="text-align:center;color:var(--muted);font-size:0.85rem;">
<div style="font-size:1.5rem;margin-bottom:0.5rem;">🔒</div>
No telemetry
</div>
<div style="text-align:center;color:var(--muted);font-size:0.85rem;">
<div style="font-size:1.5rem;margin-bottom:0.5rem;">📡</div>
No network required
</div>
<div style="text-align:center;color:var(--muted);font-size:0.85rem;">
<div style="font-size:1.5rem;margin-bottom:0.5rem;">🗄️</div>
Embedded storage
</div>
<div style="text-align:center;color:var(--muted);font-size:0.85rem;">
<div style="font-size:1.5rem;margin-bottom:0.5rem;"></div>
No daemon process
</div>
</div>
</section>
<!-- ═══════════════════════════════════════════ STATUS ═══════════════════════════════════════════ -->
<section id="status">
<span class="section-label">Build Status</span>
<h2>Current state</h2>
<div class="status-grid">
<div class="status-badge ver">
<span class="status-icon"></span>
<div>
<div class="status-badge-label">Version</div>
<div class="status-badge-value">v0.1.0</div>
</div>
</div>
<div class="status-badge ok">
<span class="status-icon"></span>
<div>
<div class="status-badge-label">Build</div>
<div class="status-badge-value">Zero warnings · Zero errors</div>
</div>
</div>
<div class="status-badge tests">
<span class="status-icon"></span>
<div>
<div class="status-badge-label">Test Suite</div>
<div class="status-badge-value">38 passing</div>
</div>
</div>
</div>
<div style="margin-top:3rem;background:var(--bg2);border:1px solid var(--dim);border-radius:12px;overflow:hidden;">
<div style="padding:1rem 1.5rem;border-bottom:1px solid var(--dim);font-size:0.75rem;color:var(--muted);font-family:var(--mono);">
Public API Surface · engram-core v0.1.0
</div>
<pre style="padding:1.5rem;font-size:0.8rem;"><code><span class="kw">impl</span> <span class="type">EngramDb</span> <span class="punct">{</span>
<span class="kw">fn</span> <span class="fn-name">open</span><span class="punct">(</span>path<span class="punct">:</span> <span class="op">&amp;</span><span class="type">Path</span><span class="punct">)</span> <span class="op">-&gt;</span> <span class="type">EngramResult</span><span class="punct">&lt;</span><span class="type">Self</span><span class="punct">&gt;;</span>
<span class="kw">fn</span> <span class="fn-name">put_node</span><span class="punct">(&amp;</span><span class="kw">self</span><span class="punct">,</span> node<span class="punct">:</span> <span class="type">Node</span><span class="punct">)</span> <span class="op">-&gt;</span> <span class="type">EngramResult</span><span class="punct">&lt;</span><span class="type">Uuid</span><span class="punct">&gt;;</span>
<span class="kw">fn</span> <span class="fn-name">get_node</span><span class="punct">(&amp;</span><span class="kw">self</span><span class="punct">,</span> id<span class="punct">:</span> <span class="type">Uuid</span><span class="punct">)</span> <span class="op">-&gt;</span> <span class="type">EngramResult</span><span class="punct">&lt;</span><span class="type">Option</span><span class="punct">&lt;</span><span class="type">Node</span><span class="punct">&gt;&gt;;</span>
<span class="kw">fn</span> <span class="fn-name">put_edge</span><span class="punct">(&amp;</span><span class="kw">self</span><span class="punct">,</span> edge<span class="punct">:</span> <span class="type">Edge</span><span class="punct">)</span> <span class="op">-&gt;</span> <span class="type">EngramResult</span><span class="punct">&lt;()&gt;;</span>
<span class="kw">fn</span> <span class="fn-name">activate</span><span class="punct">(&amp;</span><span class="kw">self</span><span class="punct">,</span> seeds<span class="punct">:</span> <span class="op">&amp;</span><span class="punct">[</span><span class="type">Uuid</span><span class="punct">],</span> emb<span class="punct">:</span> <span class="op">&amp;</span><span class="punct">[</span><span class="type">f32</span><span class="punct">],</span> depth<span class="punct">:</span> <span class="type">u8</span><span class="punct">,</span> n<span class="punct">:</span> <span class="type">usize</span><span class="punct">)</span> <span class="op">-&gt;</span> <span class="type">EngramResult</span><span class="punct">&lt;</span><span class="type">Vec</span><span class="punct">&lt;</span><span class="type">ActivatedNode</span><span class="punct">&gt;&gt;;</span>
<span class="kw">fn</span> <span class="fn-name">search_embedding</span><span class="punct">(&amp;</span><span class="kw">self</span><span class="punct">,</span> emb<span class="punct">:</span> <span class="op">&amp;</span><span class="punct">[</span><span class="type">f32</span><span class="punct">],</span> limit<span class="punct">:</span> <span class="type">usize</span><span class="punct">)</span> <span class="op">-&gt;</span> <span class="type">EngramResult</span><span class="punct">&lt;</span><span class="type">Vec</span><span class="punct">&lt;</span><span class="type">ScoredNode</span><span class="punct">&gt;&gt;;</span>
<span class="kw">fn</span> <span class="fn-name">traverse</span><span class="punct">(&amp;</span><span class="kw">self</span><span class="punct">,</span> from<span class="punct">:</span> <span class="type">Uuid</span><span class="punct">,</span> rel<span class="punct">:</span> <span class="type">Option</span><span class="punct">&lt;</span><span class="type">RelationType</span><span class="punct">&gt;,</span> depth<span class="punct">:</span> <span class="type">u8</span><span class="punct">)</span> <span class="op">-&gt;</span> <span class="type">EngramResult</span><span class="punct">&lt;</span><span class="type">Vec</span><span class="punct">&lt;</span><span class="type">Node</span><span class="punct">&gt;&gt;;</span>
<span class="kw">fn</span> <span class="fn-name">touch</span><span class="punct">(&amp;</span><span class="kw">self</span><span class="punct">,</span> id<span class="punct">:</span> <span class="type">Uuid</span><span class="punct">)</span> <span class="op">-&gt;</span> <span class="type">EngramResult</span><span class="punct">&lt;()&gt;;</span>
<span class="kw">fn</span> <span class="fn-name">decay</span><span class="punct">(&amp;</span><span class="kw">self</span><span class="punct">,</span> factor<span class="punct">:</span> <span class="type">f32</span><span class="punct">)</span> <span class="op">-&gt;</span> <span class="type">EngramResult</span><span class="punct">&lt;</span><span class="type">usize</span><span class="punct">&gt;;</span>
<span class="kw">fn</span> <span class="fn-name">consolidate</span><span class="punct">(&amp;</span><span class="kw">self</span><span class="punct">,</span> config<span class="punct">:</span> <span class="op">&amp;</span><span class="type">ConsolidationConfig</span><span class="punct">)</span> <span class="op">-&gt;</span> <span class="type">EngramResult</span><span class="punct">&lt;</span><span class="type">ConsolidationReport</span><span class="punct">&gt;;</span>
<span class="punct">}</span></code></pre>
</div>
</section>
<footer>
engram · v0.1.0 · neuron technologies · the physical trace of a memory
</footer>
<script>
// ════════════════════════════════════════════════════════════════════
// HERO CANVAS — pulsing node graph background
// ════════════════════════════════════════════════════════════════════
(function() {
const canvas = document.getElementById('hero-canvas');
const ctx = canvas.getContext('2d');
let W, H;
let nodes = [];
let edges = [];
let t = 0;
function resize() {
W = canvas.width = canvas.offsetWidth;
H = canvas.height = canvas.offsetHeight;
initGraph();
}
function initGraph() {
const N = Math.min(28, Math.floor(W * H / 22000));
nodes = [];
edges = [];
for (let i = 0; i < N; i++) {
nodes.push({
x: Math.random() * W,
y: Math.random() * H,
r: 2 + Math.random() * 3,
vx: (Math.random() - 0.5) * 0.18,
vy: (Math.random() - 0.5) * 0.18,
phase: Math.random() * Math.PI * 2,
speed: 0.3 + Math.random() * 0.8,
});
}
for (let i = 0; i < nodes.length; i++) {
for (let j = i + 1; j < nodes.length; j++) {
const dx = nodes[i].x - nodes[j].x;
const dy = nodes[i].y - nodes[j].y;
const dist = Math.sqrt(dx*dx + dy*dy);
if (dist < Math.min(W, H) * 0.28 && Math.random() < 0.35) {
edges.push({ a: i, b: j, baseOpacity: 0.04 + Math.random() * 0.08 });
}
}
}
}
function draw() {
ctx.clearRect(0, 0, W, H);
t += 0.006;
// Update node positions
for (const n of nodes) {
n.x += n.vx;
n.y += n.vy;
if (n.x < -40) n.x = W + 40;
if (n.x > W + 40) n.x = -40;
if (n.y < -40) n.y = H + 40;
if (n.y > H + 40) n.y = -40;
}
// Draw edges
for (const e of edges) {
const a = nodes[e.a], b = nodes[e.b];
const pulse = 0.5 + 0.5 * Math.sin(t * 1.5 + e.a * 0.7);
const opacity = e.baseOpacity * (0.5 + 0.5 * pulse);
ctx.beginPath();
ctx.moveTo(a.x, a.y);
ctx.lineTo(b.x, b.y);
ctx.strokeStyle = `rgba(123,97,255,${opacity})`;
ctx.lineWidth = 0.8;
ctx.stroke();
}
// Draw nodes
for (const n of nodes) {
const glow = 0.5 + 0.5 * Math.sin(t * n.speed + n.phase);
const r = n.r * (0.85 + 0.15 * glow);
const alpha = 0.25 + 0.35 * glow;
// Glow halo
const gradient = ctx.createRadialGradient(n.x, n.y, 0, n.x, n.y, r * 4);
gradient.addColorStop(0, `rgba(123,97,255,${alpha * 0.6})`);
gradient.addColorStop(1, 'rgba(123,97,255,0)');
ctx.beginPath();
ctx.arc(n.x, n.y, r * 4, 0, Math.PI * 2);
ctx.fillStyle = gradient;
ctx.fill();
// Core dot
ctx.beginPath();
ctx.arc(n.x, n.y, r, 0, Math.PI * 2);
ctx.fillStyle = `rgba(123,97,255,${alpha})`;
ctx.fill();
}
requestAnimationFrame(draw);
}
window.addEventListener('resize', resize);
resize();
draw();
})();
// ════════════════════════════════════════════════════════════════════
// ACTIVATION DEMO CANVAS
// ════════════════════════════════════════════════════════════════════
(function() {
const canvas = document.getElementById('activation-canvas');
const ctx = canvas.getContext('2d');
const btn = document.getElementById('btn-activate');
const resetBtn = document.getElementById('btn-reset');
const seedIndicator = document.getElementById('seed-indicator');
const speedSlider = document.getElementById('speed-slider');
let W, H, dpr;
let selectedSeed = null;
let animating = false;
let animationFrames = [];
let currentFrame = 0;
let animTimer = null;
// ── Graph definition (from the example in basic.rs) ──
const nodeData = [
{ label: 'Spreading\nActivation', type: 'Concept', tier: 'Semantic', salience: 0.95, importance: 0.95, col: '#7B61FF' },
{ label: 'Long-Term\nPotentiation', type: 'Concept', tier: 'Semantic', salience: 0.90, importance: 0.90, col: '#7B61FF' },
{ label: 'Hebbian\nLearning', type: 'Memory', tier: 'Episodic', salience: 0.85, importance: 0.85, col: '#FFB347' },
{ label: 'Associative\nMemory', type: 'Concept', tier: 'Semantic', salience: 0.88, importance: 0.88, col: '#7B61FF' },
{ label: 'Salience\nDecay', type: 'Process', tier: 'Procedural',salience: 0.75, importance: 0.75, col: '#4ECDC4' },
{ label: 'Memory\nConsolidation', type: 'Event', tier: 'Episodic', salience: 0.70, importance: 0.70, col: '#FFB347' },
{ label: 'Pattern\nCompletion', type: 'Concept', tier: 'Semantic', salience: 0.80, importance: 0.80, col: '#7B61FF' },
{ label: 'Synaptic\nWeight', type: 'Entity', tier: 'Semantic', salience: 0.72, importance: 0.72, col: '#7B61FF' },
];
const edgeData = [
{ from: 0, to: 1, weight: 0.90, label: 'Causes' },
{ from: 1, to: 2, weight: 0.85, label: 'References' },
{ from: 0, to: 3, weight: 0.88, label: 'Activates' },
{ from: 2, to: 3, weight: 0.80, label: 'Exemplifies' },
{ from: 4, to: 5, weight: 0.65, label: 'Precedes' },
{ from: 1, to: 5, weight: 0.72, label: 'Precedes' },
{ from: 3, to: 6, weight: 0.78, label: 'Causes' },
{ from: 6, to: 7, weight: 0.68, label: 'References' },
{ from: 1, to: 7, weight: 0.82, label: 'Activates' },
{ from: 0, to: 4, weight: 0.55, label: 'References' },
];
let nodes = [];
let edges = [];
let activationStrengths = {}; // nodeIdx -> strength
let activationHops = {};
function resize() {
const rect = canvas.getBoundingClientRect();
dpr = window.devicePixelRatio || 1;
W = rect.width;
H = rect.height;
canvas.width = W * dpr;
canvas.height = H * dpr;
ctx.scale(dpr, dpr);
layoutNodes();
render();
}
function layoutNodes() {
// Place nodes in a natural arrangement
const positions = [
[0.5, 0.2 ], // 0 Spreading Activation — top center (main)
[0.72, 0.38], // 1 LTP
[0.62, 0.58], // 2 Hebbian Learning
[0.38, 0.55], // 3 Associative Memory
[0.18, 0.32], // 4 Salience Decay
[0.28, 0.72], // 5 Memory Consolidation
[0.52, 0.80], // 6 Pattern Completion
[0.82, 0.62], // 7 Synaptic Weight
];
nodes = nodeData.map((d, i) => ({
...d,
x: positions[i][0] * W,
y: positions[i][1] * H,
r: 26,
idx: i,
}));
edges = edgeData.map(e => ({ ...e }));
}
function cosSim(seed) {
// Fake cos_sim: based on node tier similarity to seed
return function(targetIdx) {
const seedNode = nodeData[seed];
const targetNode = nodeData[targetIdx];
if (seedNode.tier === targetNode.tier) return 0.85;
if (seedNode.type === targetNode.type) return 0.72;
return 0.45 + Math.random() * 0.2;
};
}
function computeActivation(seedIdx) {
// BFS spreading activation, matching the Rust implementation
const best = {};
const queue = [{ idx: seedIdx, strength: 1.0, hops: 0 }];
best[seedIdx] = { strength: 1.0, hops: 0 };
const cos = cosSim(seedIdx);
const PRUNE = 0.01;
const MAX_DEPTH = 3;
while (queue.length > 0) {
queue.sort((a, b) => b.strength - a.strength);
const { idx, strength, hops } = queue.shift();
if (hops >= MAX_DEPTH) continue;
const outEdges = edges.filter(e => e.from === idx);
for (const e of outEdges) {
const target = e.to;
const sem = cos(target);
const newStr = strength * e.weight * nodeData[target].salience * sem;
if (newStr < PRUNE) continue;
const nextHops = hops + 1;
const existing = best[target];
if (!existing || newStr > existing.strength) {
best[target] = { strength: newStr, hops: nextHops };
queue.push({ idx: target, strength: newStr, hops: nextHops });
}
}
}
return best;
}
function buildAnimationFrames(seedIdx, activation) {
// Build frames: hop 0 (seed glow), then hop 1, hop 2, hop 3
const frames = [];
frames.push({ type: 'seed', nodeIdx: seedIdx });
// Group by hop distance
const byHop = {};
for (const [idxStr, data] of Object.entries(activation)) {
const idx = parseInt(idxStr);
if (idx === seedIdx) continue;
if (!byHop[data.hops]) byHop[data.hops] = [];
byHop[data.hops].push({ idx, strength: data.strength, hops: data.hops });
}
// Animate edges first, then node arrival
const maxHop = Math.max(...Object.keys(byHop).map(Number));
for (let h = 1; h <= maxHop; h++) {
if (byHop[h]) {
// Find which edges carry activation to nodes at this hop
for (const activated of byHop[h]) {
// Find source edge (the edge that carries this activation)
const inEdges = edges.filter(e => e.to === activated.idx);
for (const e of inEdges) {
if (activation[e.from] && activation[e.from].hops === h - 1) {
frames.push({ type: 'edge-activate', from: e.from, to: activated.idx, weight: e.weight });
break;
}
}
}
for (const activated of byHop[h]) {
frames.push({ type: 'node-activate', nodeIdx: activated.idx, strength: activated.strength, hops: activated.hops });
}
}
}
return frames;
}
// Rendering state
let glowNodes = {}; // nodeIdx -> { alpha, strength, hops }
let glowEdges = {}; // `${from}-${to}` -> alpha
let animationState = 'idle'; // idle | running | done
function render() {
ctx.clearRect(0, 0, W, H);
// ── Draw edges ──
for (const e of edges) {
const a = nodes[e.from], b = nodes[e.to];
const key = `${e.from}-${e.to}`;
const glowAlpha = glowEdges[key] || 0;
const baseAlpha = 0.18;
const alpha = baseAlpha + glowAlpha * 0.7;
const width = 1 + e.weight * 2 + glowAlpha * 3;
ctx.beginPath();
ctx.moveTo(a.x, a.y);
ctx.lineTo(b.x, b.y);
ctx.strokeStyle = glowAlpha > 0.1 ? `rgba(123,97,255,${alpha})` : `rgba(100,100,150,${baseAlpha})`;
ctx.lineWidth = width;
ctx.stroke();
// Weight label on active edges
if (glowAlpha > 0.3) {
const mx = (a.x + b.x) / 2, my = (a.y + b.y) / 2;
ctx.fillStyle = `rgba(180,160,255,${glowAlpha * 0.9})`;
ctx.font = `600 9px var(--mono, monospace)`;
ctx.textAlign = 'center';
ctx.fillText(e.weight.toFixed(2), mx, my - 5);
}
}
// ── Draw nodes ──
for (const n of nodes) {
const glow = glowNodes[n.idx];
const isSelected = n.idx === selectedSeed;
const isSeed = glow && glow.isSeed;
const isActivated = glow && !glow.isSeed;
const glowAlpha = glow ? glow.alpha : 0;
const r = n.r;
// Outer glow
if (glowAlpha > 0.01 || isSelected) {
const gAlpha = isSelected ? 0.3 : glowAlpha * 0.5;
const gRadius = isSeed ? r * 3.5 : r * 2.5;
const gradient = ctx.createRadialGradient(n.x, n.y, 0, n.x, n.y, gRadius);
const color = isSeed ? '123,97,255' : '123,97,255';
gradient.addColorStop(0, `rgba(${color},${gAlpha})`);
gradient.addColorStop(1, `rgba(${color},0)`);
ctx.beginPath();
ctx.arc(n.x, n.y, gRadius, 0, Math.PI * 2);
ctx.fillStyle = gradient;
ctx.fill();
}
// Ring for selected seed
if (isSelected && !isSeed) {
ctx.beginPath();
ctx.arc(n.x, n.y, r + 4, 0, Math.PI * 2);
ctx.strokeStyle = 'rgba(123,97,255,0.7)';
ctx.lineWidth = 1.5;
ctx.setLineDash([4, 4]);
ctx.stroke();
ctx.setLineDash([]);
}
// Node background
const bgAlpha = isSeed ? 0.95 : (isActivated ? 0.85 : (isSelected ? 0.7 : 0.5));
ctx.beginPath();
ctx.arc(n.x, n.y, r, 0, Math.PI * 2);
if (isSeed) {
ctx.fillStyle = '#7B61FF';
} else if (isActivated) {
const str = glow.strength;
const intensity = Math.min(1, str * 1.5);
ctx.fillStyle = `rgba(${Math.floor(60 + intensity * 63)},${Math.floor(40 + intensity * 57)},${Math.floor(150 + intensity * 105)},${bgAlpha})`;
} else if (isSelected) {
ctx.fillStyle = 'rgba(123,97,255,0.4)';
} else {
ctx.fillStyle = 'rgba(30,25,60,0.85)';
}
ctx.fill();
// Border
ctx.beginPath();
ctx.arc(n.x, n.y, r, 0, Math.PI * 2);
if (isSeed) {
ctx.strokeStyle = '#B8A8FF';
} else if (isActivated) {
ctx.strokeStyle = `rgba(123,97,255,${0.4 + glowAlpha * 0.5})`;
} else if (isSelected) {
ctx.strokeStyle = 'rgba(123,97,255,0.8)';
} else {
ctx.strokeStyle = 'rgba(80,70,130,0.6)';
}
ctx.lineWidth = isSeed ? 2 : 1;
ctx.stroke();
// Node label (type)
const lines = n.label.split('\n');
const textColor = isSeed ? '#fff' : (isActivated ? `rgba(220,210,255,${0.5 + glowAlpha * 0.5})` : 'rgba(140,130,180,0.8)');
ctx.fillStyle = textColor;
ctx.font = `600 9px -apple-system,sans-serif`;
ctx.textAlign = 'center';
if (lines.length === 1) {
ctx.fillText(lines[0], n.x, n.y + 3);
} else {
ctx.fillText(lines[0], n.x, n.y - 2);
ctx.fillText(lines[1], n.x, n.y + 9);
}
// Activation strength label
if (isActivated && glow.strength !== undefined) {
ctx.fillStyle = `rgba(160,145,255,${glowAlpha})`;
ctx.font = `bold 10px var(--mono, monospace)`;
ctx.fillText(glow.strength.toFixed(2), n.x, n.y + r + 13);
}
// Seed label
if (isSeed) {
ctx.fillStyle = 'rgba(200,180,255,0.9)';
ctx.font = `bold 9px var(--mono, monospace)`;
ctx.fillText('SEED', n.x, n.y + r + 13);
}
// Tier dot
const tierColors = { Semantic: '#7B61FF', Episodic: '#FFB347', Procedural: '#4ECDC4', Working: '#FF6B6B' };
ctx.beginPath();
ctx.arc(n.x + r - 5, n.y - r + 5, 4, 0, Math.PI * 2);
ctx.fillStyle = tierColors[n.tier] || '#888';
ctx.fill();
}
}
// Smooth animation loop
let glowTargets = {};
let edgeTargets = {};
let animRunning = false;
function smoothRender() {
// Lerp current glow toward targets
let changed = false;
for (const [key, target] of Object.entries(glowTargets)) {
const current = glowNodes[key] || { alpha: 0 };
const newAlpha = current.alpha + (target.alpha - current.alpha) * 0.12;
if (Math.abs(newAlpha - current.alpha) > 0.001) changed = true;
glowNodes[key] = { ...target, alpha: newAlpha };
}
for (const [key, target] of Object.entries(edgeTargets)) {
const current = glowEdges[key] || 0;
const newAlpha = current + (target - current) * 0.1;
if (Math.abs(newAlpha - current) > 0.001) changed = true;
glowEdges[key] = newAlpha;
}
render();
if (animRunning || changed) requestAnimationFrame(smoothRender);
}
function runActivationAnimation(seedIdx, activation, frames) {
animRunning = true;
glowNodes = {};
glowEdges = {};
glowTargets = {};
edgeTargets = {};
// Dim all non-seed nodes
nodes.forEach((n, i) => {
if (i !== seedIdx) {
glowTargets[i] = { alpha: 0, strength: 0, isSeed: false };
}
});
let frameIdx = 0;
const speedMs = [600, 450, 300, 200, 120][parseInt(speedSlider.value) - 1];
function nextFrame() {
if (frameIdx >= frames.length) {
animRunning = false;
animating = false;
btn.disabled = false;
btn.textContent = 'Activate';
return;
}
const frame = frames[frameIdx++];
if (frame.type === 'seed') {
glowTargets[frame.nodeIdx] = { alpha: 1, strength: 1.0, isSeed: true };
} else if (frame.type === 'edge-activate') {
const key = `${frame.from}-${frame.to}`;
edgeTargets[key] = 0.9;
} else if (frame.type === 'node-activate') {
const idx = frame.nodeIdx;
glowTargets[idx] = { alpha: frame.strength * 0.8 + 0.2, strength: frame.strength, hops: frame.hops, isSeed: false };
}
animTimer = setTimeout(nextFrame, frameIdx === 1 ? speedMs * 2 : speedMs);
}
smoothRender();
nextFrame();
}
btn.addEventListener('click', () => {
if (selectedSeed === null || animating) return;
animating = true;
btn.disabled = true;
btn.textContent = 'Activating…';
const activation = computeActivation(selectedSeed);
const frames = buildAnimationFrames(selectedSeed, activation);
runActivationAnimation(selectedSeed, activation, frames);
});
resetBtn.addEventListener('click', () => {
if (animTimer) clearTimeout(animTimer);
animating = false;
animRunning = false;
selectedSeed = null;
glowNodes = {};
glowEdges = {};
glowTargets = {};
edgeTargets = {};
seedIndicator.textContent = 'Click a node to select';
btn.disabled = true;
btn.textContent = 'Activate';
render();
});
canvas.addEventListener('click', (e) => {
if (animating) return;
const rect = canvas.getBoundingClientRect();
const mx = e.clientX - rect.left;
const my = e.clientY - rect.top;
for (const n of nodes) {
const dx = mx - n.x, dy = my - n.y;
if (Math.sqrt(dx*dx + dy*dy) < n.r + 6) {
selectedSeed = n.idx;
seedIndicator.textContent = n.label.replace('\n', ' ');
btn.disabled = false;
// Reset glow state
glowNodes = {};
glowEdges = {};
glowTargets = {};
edgeTargets = {};
animRunning = false;
render();
return;
}
}
});
canvas.style.cursor = 'pointer';
window.addEventListener('resize', () => {
const rect = canvas.getBoundingClientRect();
W = rect.width;
H = rect.height;
canvas.width = W * dpr;
canvas.height = H * dpr;
ctx.scale(dpr, dpr);
layoutNodes();
render();
});
resize();
})();
// ════════════════════════════════════════════════════════════════════
// SALIENCE SLIDERS
// ════════════════════════════════════════════════════════════════════
(function() {
const slImportance = document.getElementById('sl-importance');
const slDays = document.getElementById('sl-days');
const slCount = document.getElementById('sl-count');
const valImportance = document.getElementById('val-importance');
const valDays = document.getElementById('val-days');
const valCount = document.getElementById('val-count');
const scoreEl = document.getElementById('salience-score');
const gaugeEl = document.getElementById('salience-gauge-fill');
function compute() {
const importance = parseInt(slImportance.value) / 100;
const days = parseInt(slDays.value);
const count = parseInt(slCount.value);
valImportance.textContent = importance.toFixed(2);
valDays.textContent = days;
valCount.textContent = count;
const recency = 1.0 / (1.0 + days);
const frequency = Math.log(count + 1);
const salience = importance * recency * frequency;
scoreEl.textContent = salience.toFixed(3);
// Gauge: normalize to reasonable range (max ~4)
const pct = Math.min(100, (salience / 4) * 100);
gaugeEl.style.width = pct + '%';
// Color the score
if (salience > 1.5) {
scoreEl.style.color = '#28CA41';
gaugeEl.style.background = '#28CA41';
} else if (salience > 0.5) {
scoreEl.style.color = '#7B61FF';
gaugeEl.style.background = '#7B61FF';
} else if (salience > 0.1) {
scoreEl.style.color = '#FFB347';
gaugeEl.style.background = '#FFB347';
} else {
scoreEl.style.color = '#666688';
gaugeEl.style.background = '#666688';
}
}
slImportance.addEventListener('input', compute);
slDays.addEventListener('input', compute);
slCount.addEventListener('input', compute);
compute();
})();
// ════════════════════════════════════════════════════════════════════
// TABS
// ════════════════════════════════════════════════════════════════════
function switchTab(lang) {
document.querySelectorAll('.tab-btn').forEach(b => {
b.classList.toggle('active', b.textContent.toLowerCase() === lang);
});
document.querySelectorAll('.tab-content').forEach(c => {
c.classList.toggle('active', c.id === 'tab-' + lang);
});
}
// ════════════════════════════════════════════════════════════════════
// CONSOLIDATION SCROLL ANIMATION
// ════════════════════════════════════════════════════════════════════
(function() {
const diagram = document.getElementById('consol-diagram');
const consolCanvas = document.getElementById('consol-canvas');
const consolCtx = consolCanvas.getContext('2d');
let particles = [];
let animRunning = false;
let hasAnimated = false;
function resizeConsolCanvas() {
const rect = diagram.getBoundingClientRect();
consolCanvas.width = rect.width;
consolCanvas.height = rect.height;
}
function spawnParticles() {
// Find episodic box and semantic box positions roughly
const rect = diagram.getBoundingClientRect();
const W = consolCanvas.width;
const H = consolCanvas.height;
for (let i = 0; i < 12; i++) {
const delay = i * 180;
setTimeout(() => {
particles.push({
x: W * 0.15 + Math.random() * W * 0.12,
y: H * 0.3 + Math.random() * H * 0.4,
tx: W * 0.73 + Math.random() * W * 0.12,
ty: H * 0.3 + Math.random() * H * 0.4,
progress: 0,
speed: 0.008 + Math.random() * 0.006,
alpha: 0,
r: 3 + Math.random() * 2,
color: i % 2 === 0 ? '#FFB347' : '#7B61FF',
});
}, delay);
}
}
function animateParticles() {
consolCtx.clearRect(0, 0, consolCanvas.width, consolCanvas.height);
let allDone = true;
for (const p of particles) {
p.progress += p.speed;
p.alpha = p.progress < 0.1 ? p.progress * 10 : (p.progress > 0.9 ? (1 - p.progress) * 10 : 1);
p.alpha = Math.max(0, Math.min(1, p.alpha));
const t = p.progress;
// Bezier arc upward
const cx = (p.x + p.tx) / 2;
const cy = Math.min(p.y, p.ty) - consolCanvas.height * 0.18;
const x = (1-t)*(1-t)*p.x + 2*(1-t)*t*cx + t*t*p.tx;
const y = (1-t)*(1-t)*p.y + 2*(1-t)*t*cy + t*t*p.ty;
consolCtx.beginPath();
consolCtx.arc(x, y, p.r, 0, Math.PI * 2);
consolCtx.fillStyle = p.color.replace(')', `,${p.alpha * 0.85})`).replace('rgb', 'rgba');
// Handle hex colors
const hexMatch = p.color.match(/^#([A-Fa-f0-9]{6})$/);
if (hexMatch) {
const r = parseInt(hexMatch[1].substr(0,2),16);
const g = parseInt(hexMatch[1].substr(2,2),16);
const b = parseInt(hexMatch[1].substr(4,2),16);
consolCtx.fillStyle = `rgba(${r},${g},${b},${p.alpha * 0.85})`;
}
consolCtx.fill();
if (p.progress < 1) allDone = false;
}
if (!allDone || particles.length === 0) {
requestAnimationFrame(animateParticles);
} else {
animRunning = false;
consolCtx.clearRect(0, 0, consolCanvas.width, consolCanvas.height);
}
}
const observer = new IntersectionObserver((entries) => {
for (const entry of entries) {
if (entry.isIntersecting && !hasAnimated) {
hasAnimated = true;
resizeConsolCanvas();
setTimeout(() => {
spawnParticles();
if (!animRunning) {
animRunning = true;
animateParticles();
}
}, 600);
// Re-animate every 4 seconds
setInterval(() => {
particles = [];
spawnParticles();
if (!animRunning) {
animRunning = true;
animateParticles();
}
}, 4000);
}
}
}, { threshold: 0.4 });
observer.observe(diagram);
resizeConsolCanvas();
window.addEventListener('resize', resizeConsolCanvas);
})();
</script>
</body>
</html>