Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
65 kB · 1382 lines
HTML
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>bills · Aesthetic Computer</title> <meta name="robots" content="noindex, nofollow"> <link rel="icon" href="https://aesthetic.computer/icon/128x128/prompt.png" type="image/png" /> <link rel="stylesheet" href="https://aesthetic.computer/type/webfonts/berkeley-mono-variable.css"> <link rel="preload" href="https://aesthetic.computer/type/webfonts/ywft-processing-regular.woff2" as="font" type="font/woff2" crossorigin="anonymous"> <style> @font-face { font-family: 'YWFT Processing'; src: url('https://aesthetic.computer/type/webfonts/ywft-processing-regular.woff2') format('woff2'); font-weight: normal; font-display: swap; }
:root { --bg: #1a1a2e; --text: #e8e8e8; --dim: #888; --pink: #ff6b9d; --cyan: #4ecdc4; --green: #6bcb77; --gold: #ffd93d; --red: #ef4444; --purple: #a78bfa; --box-bg: rgba(255,255,255,0.03); --box-border: rgba(255,255,255,0.1); --shadow-soft: 0 2px 8px rgba(0,0,0,0.15); }
body.light-mode { --bg: #f5f5f5; --text: #1a1a2e; --dim: #666; --pink: rgb(205, 92, 155); --cyan: #0891b2; --green: #059669; --gold: #d97706; --red: #dc2626; --purple: #7c3aed; --box-bg: rgba(0,0,0,0.03); --box-border: rgba(0,0,0,0.12); --shadow-soft: 0 2px 8px rgba(0,0,0,0.08); }
* { margin: 0; padding: 0; box-sizing: border-box; } ::-webkit-scrollbar { display: none; }
body { background: var(--bg); color: var(--text); font-family: 'Berkeley Mono Variable', 'Menlo', monospace; font-size: 14px; line-height: 1.5; -webkit-text-size-adjust: none; user-select: none; -webkit-user-select: none; cursor: url('https://aesthetic.computer/aesthetic.computer/cursors/precise.svg') 12 12, auto; }
a, code, .prose, td, p { user-select: text; -webkit-user-select: text; }
.container { max-width: 720px; margin: 0 auto; padding: 1em 1.5em 1.5em; }
/* Header row */ .header-row { display: flex; align-items: baseline; justify-content: flex-start; gap: 1em; margin-bottom: 0.8em; }
.header-left { display: flex; align-items: baseline; gap: 0.6em; min-width: 0; }
h1 { font-family: 'YWFT Processing', 'Berkeley Mono Variable', monospace; font-size: 22px; font-weight: normal; color: var(--pink); cursor: pointer; transition: color 0.2s; margin: 0; white-space: nowrap; } h1:hover { color: purple; } h1:active { color: gray; }
.subtitle { font-size: 0.8em; color: var(--dim); white-space: nowrap; }
/* Per-update illustration banner */ .illy-banner { width: 100%; max-height: 200px; object-fit: cover; display: block; border: 1px solid var(--box-border); border-radius: 6px; margin-bottom: 1.2em; box-shadow: var(--shadow-soft); }
.sponsor-badge iframe { display: block; border: 1px solid var(--pink); border-radius: 8px; box-shadow: 0 0 8px rgba(255, 107, 157, 0.25); transition: box-shadow 0.2s, border-color 0.2s; } .sponsor-badge:hover iframe { box-shadow: 0 0 14px rgba(255, 107, 157, 0.45); border-color: #c44dff; }
h2 { font-size: 1.1em; font-weight: normal; color: var(--text); margin-top: 2em; margin-bottom: 0.8em; }
h3 { font-size: 0.8em; font-weight: normal; color: var(--dim); text-transform: uppercase; letter-spacing: 0.06em; margin-top: 1.5em; margin-bottom: 0.5em; }
/* Primary tabs */ .bill-tabs { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0; margin-bottom: 1.2em; border: 1px solid var(--box-border); background: var(--box-bg); overflow: hidden; }
.bill-tab { appearance: none; border: none; border-right: 1px solid var(--box-border); background: transparent; color: var(--dim); padding: 0.8em 0.6em; font-family: 'YWFT Processing', 'Berkeley Mono Variable', monospace; font-size: 0.95em; letter-spacing: 0.03em; cursor: pointer; transition: background 0.2s, color 0.2s; } .bill-tab:last-child { border-right: none; } .bill-tab:hover { background: var(--box-bg); color: var(--text); }
.bill-tab[data-view-target="income"] { color: var(--green); } .bill-tab[data-view-target="needs"] { color: var(--red); }
.bill-tab[data-view-target="income"].active { background: rgba(107, 203, 119, 0.14); color: var(--green); box-shadow: inset 0 -2px 0 0 var(--green); }
.bill-tab[data-view-target="needs"].active { background: rgba(239, 68, 68, 0.14); color: var(--red); box-shadow: inset 0 -2px 0 0 var(--red); }
.bill-section[hidden] { display: none !important; }
/* Give link */ .give-bumper { display: inline-block; padding: 0.5em 1em; border: 1px solid var(--pink); color: var(--pink); text-decoration: none; font-size: 13px; transition: background 0.2s, color 0.2s; } .give-bumper:hover { background: var(--pink); color: white; text-decoration: none; }
.needs-support { display: flex; align-items: center; gap: 0.6em; flex-wrap: wrap; margin-bottom: 1em; }
/* Alerts */ .alerts { margin-bottom: 1.5em; }
.alert { display: flex; align-items: flex-start; gap: 0.6em; padding: 0.7em 0.9em; background: var(--box-bg); border-left: 3px solid var(--red); margin-bottom: 0.4em; animation: slideIn 0.3s ease; } .alert-warn { border-left-color: var(--gold); } .alert-ok { border-left-color: var(--green); }
.alert-dot { width: 7px; height: 7px; border-radius: 50%; margin-top: 0.4em; flex-shrink: 0; animation: pulse 2s ease-in-out infinite; } .alert-dot.red { background: var(--red); } .alert-dot.gold { background: var(--gold); } .alert-dot.green { background: var(--green); }
.alert-text { font-size: 0.8em; line-height: 1.5; } .alert-text strong { color: var(--pink); }
/* Net flow hero */ .net-flow { display: grid; grid-template-columns: 1fr auto 1fr auto 1fr; align-items: center; gap: 0.5em; margin-bottom: 1.5em; padding: 1em; background: var(--box-bg); border: 1px solid var(--box-border); }
.flow-block { text-align: center; } .flow-label { font-size: 0.7em; color: var(--dim); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.2em; } .flow-value { font-size: 1.8em; font-weight: normal; font-variant-numeric: tabular-nums; } .flow-op { font-size: 1.4em; color: var(--dim); text-align: center; }
/* Stats grid */ .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 0.8em; margin-bottom: 1.5em; }
.stat-box { padding: 0.8em; background: var(--box-bg); border: 1px solid var(--box-border); }
.stat-label { font-size: 0.7em; color: var(--dim); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.2em; }
.stat-value { font-size: 1.8em; font-weight: normal; font-variant-numeric: tabular-nums; }
.stat-sub { font-size: 0.7em; color: var(--dim); margin-top: 0.2em; }
/* Loading shimmer */ .loading { color: var(--dim); animation: pulse 1.5s ease-in-out infinite; }
/* SVG Timeline */ .timeline-wrap { margin-bottom: 1.5em; background: var(--box-bg); border: 1px solid var(--box-border); padding: 1em 1em 0.5em; }
.timeline-wrap h3 { margin-top: 0; margin-bottom: 0.6em; }
.timeline-svg { width: 100%; display: block; }
/* Tables */ table { width: 100%; border-collapse: collapse; margin-bottom: 1em; font-size: 0.82em; }
th { text-align: left; padding: 0.45em 0.5em; border-bottom: 1px solid var(--box-border); color: var(--dim); font-weight: normal; font-size: 0.8em; text-transform: uppercase; letter-spacing: 0.04em; }
td { padding: 0.35em 0.5em; border-bottom: 1px solid var(--box-border); vertical-align: top; }
tr:last-child td { border-bottom: none; } tr:hover td { background: var(--box-bg); }
/* Status pills */ .pill { display: inline-block; padding: 0.1em 0.4em; font-size: 0.75em; text-transform: uppercase; letter-spacing: 0.04em; border: 1px solid; } .pill-red { color: var(--red); border-color: var(--red); background: rgba(239,68,68,0.08); } .pill-gold { color: var(--gold); border-color: var(--gold); background: rgba(255,217,61,0.08); } .pill-green { color: var(--green); border-color: var(--green); background: rgba(107,203,119,0.08); } .pill-cyan { color: var(--cyan); border-color: var(--cyan); background: rgba(78,205,196,0.08); } .pill-purple { color: var(--purple); border-color: var(--purple); background: rgba(167,139,250,0.08); }
body.light-mode .pill-red { background: rgba(220,38,38,0.06); } body.light-mode .pill-gold { background: rgba(217,119,6,0.06); } body.light-mode .pill-green { background: rgba(5,150,105,0.06); } body.light-mode .pill-cyan { background: rgba(8,145,178,0.06); } body.light-mode .pill-purple { background: rgba(124,58,237,0.06); }
.money { font-variant-numeric: tabular-nums; }
/* Service map (visual) */ .service-map { display: grid; grid-template-columns: repeat(auto-fill, minmax(95px, 1fr)); gap: 0.4em; margin-bottom: 1.5em; }
.svc { padding: 0.5em; background: var(--box-bg); border: 1px solid var(--box-border); text-align: center; font-size: 0.72em; transition: border-color 0.2s; } .svc:hover { border-color: var(--pink); }
.svc-name { font-size: 1.1em; margin-bottom: 0.15em; } .svc-cost { color: var(--dim); } .svc.down { border-color: var(--red); opacity: 0.6; } .svc.down .svc-name { text-decoration: line-through; color: var(--red); } .svc.ok { border-color: var(--green); } .svc.warn { border-color: var(--gold); } .svc.free { border-color: var(--cyan); opacity: 0.7; } .svc.income { border-color: var(--purple); }
/* Cards */ .card { background: var(--box-bg); border: 1px solid var(--box-border); padding: 0.8em; margin-bottom: 0.6em; } .card p { font-size: 0.82em; line-height: 1.8; } .card p strong { color: var(--pink); }
/* Current goal banner */ .goal-focus { margin-bottom: 1em; padding: 1em; border: 1px solid var(--pink); background: linear-gradient(135deg, rgba(255, 107, 157, 0.16), rgba(78, 205, 196, 0.12)); box-shadow: 0 0 0 1px rgba(255, 107, 157, 0.1) inset; } .goal-kicker { font-size: 0.7em; text-transform: uppercase; letter-spacing: 0.08em; color: var(--cyan); } .goal-title { margin-top: 0.25em; font-family: 'YWFT Processing', 'Berkeley Mono Variable', monospace; font-size: 1.35em; line-height: 1.2; color: var(--pink); } .goal-amount { margin-top: 0.18em; font-size: 2em; color: var(--green); font-variant-numeric: tabular-nums; } .goal-sub { margin-top: 0.35em; font-size: 0.84em; line-height: 1.6; } .goal-links { margin-top: 0.15em; font-size: 0.8em; color: var(--dim); } .goal-links a { color: var(--gold); text-decoration: none; border-bottom: 1px dotted currentColor; } .goal-links a:hover { text-decoration: underline; } .goal-split { margin-top: 0.8em; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0.45em; } .goal-item { padding: 0.55em; border: 1px solid var(--box-border); background: rgba(0, 0, 0, 0.15); } .goal-item-label { font-size: 0.68em; text-transform: uppercase; letter-spacing: 0.05em; color: var(--dim); } .goal-item-value { margin-top: 0.15em; font-size: 1.2em; font-variant-numeric: tabular-nums; } .goal-item-note { margin-top: 0.15em; font-size: 0.72em; color: var(--dim); } .goal-item-mac .goal-item-value { color: var(--pink); } .goal-item-framework .goal-item-value { color: var(--cyan); } .goal-item-buffer .goal-item-value { color: var(--gold); } .goal-item-claude .goal-item-value { color: var(--red); }
body.light-mode .goal-item { background: rgba(255, 255, 255, 0.55); }
/* Collapsible */ details { margin-bottom: 0.5em; }
summary { cursor: pointer; padding: 0.5em 0.7em; background: var(--box-bg); border: 1px solid var(--box-border); font-size: 0.82em; user-select: none; } summary:hover { border-color: var(--pink); } details[open] summary { border-bottom: none; }
.detail-body { padding: 0.7em; background: var(--box-bg); border: 1px solid var(--box-border); border-top: none; } .detail-body ol { padding-left: 1.2em; font-size: 0.82em; line-height: 2; }
/* Gives feed */ .gives-list { font-size: 0.82em; line-height: 1.8; } .give-entry { display: flex; justify-content: space-between; padding: 0.3em 0; border-bottom: 1px solid var(--box-border); } .give-entry:last-child { border-bottom: none; } .give-amount { color: var(--green); font-variant-numeric: tabular-nums; } .give-date { color: var(--dim); font-size: 0.85em; } .give-note { color: var(--dim); font-size: 0.85em; font-style: italic; } .give-recurring { color: var(--purple); font-size: 0.75em; }
/* Links */ a { color: var(--pink); text-decoration: none; } a:hover { color: purple; text-decoration: underline; }
code { background: var(--box-bg); border: 1px solid var(--box-border); padding: 0.1em 0.35em; font-size: 0.9em; }
/* Footer */ .footer { margin-top: 2em; padding-top: 1em; border-top: 1px solid var(--box-border); color: var(--dim); font-size: 0.7em; display: flex; justify-content: space-between; flex-wrap: wrap; gap: 0.5em; }
/* Section divider */ .section-divider { border: none; border-top: 1px solid var(--box-border); margin: 2em 0; }
/* Animations */ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } } @keyframes slideIn { from { opacity: 0; transform: translateX(-10px); } to { opacity: 1; transform: translateX(0); } }
/* Mobile */ @media (max-width: 600px) { .container { padding: 0.8em; } h1 { font-size: 18px; } .header-row { flex-wrap: wrap; gap: 0.5em; } .header-left { gap: 0.4em; } .subtitle { font-size: 0.7em; } .bill-tab { font-size: 0.82em; padding: 0.7em 0.5em; } .needs-support { align-items: flex-start; } .stats { grid-template-columns: 1fr 1fr; } .stat-value { font-size: 1.4em; } .net-flow { grid-template-columns: 1fr; gap: 0.3em; padding: 0.8em; } .flow-op { font-size: 1em; } .flow-value { font-size: 1.4em; } .goal-title { font-size: 1.1em; } .goal-amount { font-size: 1.55em; } .goal-split { grid-template-columns: 1fr; } table { font-size: 0.78em; } td, th { padding: 0.25em 0.35em; } .service-map { grid-template-columns: repeat(3, 1fr); } .hide-mobile { display: none; } }
@media (max-width: 380px) { .stats { grid-template-columns: 1fr; } .service-map { grid-template-columns: repeat(2, 1fr); } } </style></head><body> <div class="container">
<div class="header-row"> <div class="header-left"> <h1 id="title">bills</h1> <div class="subtitle">aesthetic.computer — cash flow</div> </div> </div>
<!-- Per-update illy (generated via bills/bin/gen-illy.mjs); self-removes if absent --> <img class="illy-banner" src="illy.jpg" alt="" onerror="this.remove()">
<div class="bill-tabs" role="tablist" aria-label="Bills sections"> <button class="bill-tab" type="button" data-view-target="income" role="tab" aria-selected="false">Income</button> <button class="bill-tab active" type="button" data-view-target="needs" role="tab" aria-selected="true">Needs</button> </div>
<section class="bill-section" data-view="needs"> <div class="needs-support"> <a class="give-bumper" href="https://give.aesthetic.computer">give.aesthetic.computer</a> <div class="sponsor-badge"> <iframe src="https://github.com/sponsors/whistlegraph/button" title="Sponsor whistlegraph" height="32" width="114" style="border: 0;"></iframe> </div> </div>
<div class="goal-focus"> <div class="goal-kicker">Status — June 2026</div> <div class="goal-title">the laptop crisis is over — thank you</div> <div class="goal-amount">~$356/mo to keep running</div> <p class="goal-sub">The emergency is past. A working MacBook Neo and restored Claude access came from the community. What's left is the ordinary monthly cost of keeping every aesthetic.computer service online — gives and keeps cover part of it, the rest comes out of pocket.</p> <p class="goal-links"><a href="https://give.aesthetic.computer">Help cover a month at give.aesthetic.computer</a></p> <div class="goal-split"> <div class="goal-item" style="opacity: 0.6;"> <div class="goal-item-label">macbook-neo</div> <div class="goal-item-value" style="color: var(--green);">✓</div> <div class="goal-item-note">Gift from Fía 💗</div> </div> <div class="goal-item" style="opacity: 0.6;"> <div class="goal-item-label">claude code max</div> <div class="goal-item-value" style="color: var(--green);">✓</div> <div class="goal-item-note">Paid — thanks Sage</div> </div> <div class="goal-item goal-item-framework"> <div class="goal-item-label">framework-12</div> <div class="goal-item-value">~$1,300</div> <div class="goal-item-note">Still on the wishlist (not urgent)</div> </div> <div class="goal-item goal-item-buffer"> <div class="goal-item-label">monthly nut</div> <div class="goal-item-value">~$356</div> <div class="goal-item-note">Servers + Claude + domains</div> </div> </div> </div>
<h3>what's covered now</h3> <div class="card"> <p><s><strong>MacBook Neo</strong> — a working daily laptop</s> — Fía got it for me 💗</p> <p><s><strong>Claude Code Max $100/mo</strong> — restored</s> — paid by Sage</p> <p><s><strong>DigitalOcean</strong> — all droplets active & paid</s> — account current</p> <p><s><strong>Netlify hosting risk</strong> — site at risk</s> — migrated off Netlify entirely (see below)</p> </div>
<h3>the big change since march: own the server</h3> <div class="card"> <p><strong>aesthetic.computer no longer runs on Netlify.</strong> In late March the whole site moved to a DigitalOcean droplet (<code>ac-lith</code>) running Caddy + an Express adapter that serves the same 125+ functions. No more per-function limits, no more "period ends, site goes dark" — just one VPS we control. Bandwidth went from 100GB to 4TB and the recurring Netlify bill ($19/mo + the threat of takedown) is gone.</p> </div>
<h3>machine state — updated jun 4, 2026</h3> <table> <thead><tr><th>machine</th><th>type</th><th>state</th><th class="hide-mobile">notes</th></tr></thead> <tbody> <tr><td>macbook-neo</td><td>laptop</td><td><span class="pill pill-green">primary</span></td><td class="hide-mobile">New daily driver — gift from Fía. Crisis resolved.</td></tr> <tr><td>jeffrey-windows</td><td>desktop tower</td><td><span class="pill pill-green">fine</span></td><td class="hide-mobile">Primary desktop tower is healthy.</td></tr> <tr><td>jeffrey-macbook (old)</td><td>laptop</td><td><span class="pill pill-red">degraded</span></td><td class="hide-mobile">Screen broke; retired in favor of the Neo.</td></tr> <tr><td>x1-nano-g2</td><td>laptop</td><td><span class="pill pill-red">unstable</span></td><td class="hide-mobile">Random shutdowns tied to NVMe issues.</td></tr> <tr><td>jas-fedora (x1 nano gen 1?)</td><td>laptop</td><td><span class="pill pill-red">offline</span></td><td class="hide-mobile">Broken shift key + smashed power port; out of commission.</td></tr> <tr><td>ac-lith / oven / silo / help / at / session / legacy</td><td>vps</td><td><span class="pill pill-purple">servers</span></td><td class="hide-mobile">DigitalOcean infrastructure hosts, not personal machines.</td></tr> </tbody> </table>
<h3>where your money goes right now</h3> <div class="card"> <p><strong>transparent breakdown:</strong> With the laptop and Claude covered, incoming support now goes straight to the monthly cost of keeping every service online. The recurring nut is roughly <strong>$356/mo</strong> before usage-based Anthropic API + Stripe fees.</p> </div> <table> <thead><tr><th>item</th><th>what</th><th>monthly</th><th class="hide-mobile">notes</th></tr></thead> <tbody> <tr><td>digitalocean</td><td>7 droplets + Redis + volume + Spaces</td><td class="money">~$248</td><td class="hide-mobile">All infra incl. the new lith host. Apr & May invoices both $248.40.</td></tr> <tr><td>claude code max</td><td>development subscription</td><td class="money">~$100</td><td class="hide-mobile">Ongoing; first month covered by Sage.</td></tr> <tr><td>domains (8)</td><td>renewals across registrars</td><td class="money">~$8</td><td class="hide-mobile">~$100/yr spread across 8 zones.</td></tr> <tr style="color: var(--pink);"><td>recurring monthly</td><td>fixed costs</td><td class="money">~$356</td><td class="hide-mobile">What incoming support is earmarked for.</td></tr> <tr><td>+ anthropic api</td><td>Claude Code usage</td><td class="money">usage</td><td class="hide-mobile">Variable, on top of the fixed nut.</td></tr> <tr><td>+ stripe / paypal</td><td>payment fees</td><td class="money">per-txn</td><td class="hide-mobile">2.9% + $0.30 / ~2.2% + $0.30.</td></tr> </tbody> </table>
<h3>completed</h3> <div class="card"> <p><s><strong>Get a working laptop</strong></s> — MacBook Neo, gift from Fía 💗</p> <p><s><strong>Restore Claude Code Max $100</strong></s> — paid by Sage</p> <p><s><strong>Pay DigitalOcean $412.34 backlog</strong></s> — done; account current</p> <p><s><strong>Escape the Netlify takedown risk</strong></s> — migrated to the ac-lith droplet</p> </div>
<h3>open items to verify</h3> <div class="card"> <p><strong>1.</strong> GitHub — confirm the ~$500 prior-months balance is settled — <a href="https://github.com/organizations/justanothersystem/billing/history">billing history</a></p> <p><strong>2.</strong> Shopify — store still closed; decide reactivate vs. remove the shop integration for good</p> <p><strong>3.</strong> GCP/Nanos — confirm no surprise VM spend — <a href="https://console.cloud.google.com">console.cloud.google.com</a></p> <p><strong>4.</strong> Anthropic API burn rate — <a href="https://console.anthropic.com">console.anthropic.com</a></p> <p><strong>5.</strong> Domain renewals — check 8 zones for upcoming expirations</p> </div>
</section>
<section class="bill-section" data-view="income">
<hr class="section-divider">
<!-- ═══════════════════════════════════════════════════ --> <!-- INCOME --> <!-- ═══════════════════════════════════════════════════ -->
<h2>income</h2>
<!-- Gives (Stripe) --> <h3>gives — stripe donations</h3> <div class="stats" id="gives-stats"> <div class="stat-box"> <div class="stat-label">this month</div> <div class="stat-value loading" id="gives-monthly" style="color: var(--green);">...</div> </div> <div class="stat-box"> <div class="stat-label">all time</div> <div class="stat-value loading" id="gives-total" style="color: var(--green);">...</div> </div> <div class="stat-box"> <div class="stat-label">subscribers</div> <div class="stat-value loading" id="gives-subs" style="color: var(--purple);">...</div> </div> <div class="stat-box"> <div class="stat-label">total gives</div> <div class="stat-value loading" id="gives-count" style="color: var(--cyan);">...</div> </div> </div>
<details> <summary>recent gives</summary> <div class="detail-body"> <div class="gives-list" id="gives-list"> <div class="loading" style="padding: 0.5em;">Loading from Stripe...</div> </div> </div> </details>
<!-- Keeps (Tezos) --> <h3>keeps — tezos nft sales</h3> <div class="stats" id="keeps-stats"> <div class="stat-box"> <div class="stat-label">total volume</div> <div class="stat-value loading" id="keeps-volume" style="color: var(--green);">...</div> </div> <div class="stat-box"> <div class="stat-label">fees earned</div> <div class="stat-value loading" id="keeps-fees" style="color: var(--green);">...</div> <div class="stat-sub" id="keeps-fees-sub">2.5 XTZ / mint</div> </div> <div class="stat-box"> <div class="stat-label">minted</div> <div class="stat-value loading" id="keeps-minted" style="color: var(--purple);">...</div> </div> <div class="stat-box"> <div class="stat-label">owners</div> <div class="stat-value loading" id="keeps-owners" style="color: var(--cyan);">...</div> </div> </div>
<details> <summary>keeps contract details</summary> <div class="detail-body"> <table> <tbody> <tr><td>contract</td><td><code id="keeps-contract">...</code></td></tr> <tr><td>network</td><td id="keeps-network">...</td></tr> <tr><td>profile/version</td><td id="keeps-profile">...</td></tr> <tr><td>config source</td><td id="keeps-source">...</td></tr> <tr><td>mint fee</td><td>2.5 XTZ</td></tr> <tr><td>royalties</td><td>10% (9% artist, 1% platform)</td></tr> <tr><td>marketplace</td><td><a id="keeps-objkt-link" href="#" target="_blank" rel="noopener">view on objkt.com</a></td></tr> <tr><td>explorer</td><td><a id="keeps-tzkt-link" href="#" target="_blank" rel="noopener">view on tzkt.io</a></td></tr> </tbody> </table> </div> </details>
</section>
<section class="bill-section" data-view="needs">
<hr class="section-divider">
<!-- ═══════════════════════════════════════════════════ --> <!-- COSTS --> <!-- ═══════════════════════════════════════════════════ -->
<h2>costs</h2>
<!-- Cost Totals --> <div class="stats"> <div class="stat-box"> <div class="stat-label">recurring monthly</div> <div class="stat-value" style="color: var(--gold);">~$356</div> <div class="stat-sub">DO $248 + Claude $100 + domains $8</div> </div> <div class="stat-box"> <div class="stat-label">digitalocean</div> <div class="stat-value" style="color: var(--gold);">~$248</div> <div class="stat-sub">Apr & May invoices: $248.40</div> </div> <div class="stat-box"> <div class="stat-label">jun month-to-date</div> <div class="stat-value" style="color: var(--green);">$26</div> <div class="stat-sub">DO usage as of Jun 4</div> </div> <div class="stat-box"> <div class="stat-label">to verify</div> <div class="stat-value" style="color: var(--red);">~$500</div> <div class="stat-sub">GitHub prior-months balance</div> </div> </div>
<!-- SVG Cost Timeline --> <div class="timeline-wrap"> <h3>digitalocean monthly invoices</h3> <svg class="timeline-svg" id="timeline" viewBox="0 0 680 200" preserveAspectRatio="xMidYMid meet"></svg> </div>
<!-- Service Map (visual) --> <h3>service map</h3> <div class="service-map"> <div class="svc income"><div class="svc-name">keeps</div><div class="svc-cost">tezos</div></div> <div class="svc income"><div class="svc-name">gives</div><div class="svc-cost">stripe</div></div> <div class="svc ok"><div class="svc-name">lith</div><div class="svc-cost">$24/mo — host</div></div> <div class="svc ok"><div class="svc-name">claude-max</div><div class="svc-cost">$100/mo</div></div> <div class="svc ok"><div class="svc-name">oven</div><div class="svc-cost">$96/mo</div></div> <div class="svc ok"><div class="svc-name">silo</div><div class="svc-cost">$24/mo</div></div> <div class="svc ok"><div class="svc-name">help</div><div class="svc-cost">$24/mo</div></div> <div class="svc ok"><div class="svc-name">at/pds</div><div class="svc-cost">$24/mo</div></div> <div class="svc ok"><div class="svc-name">session</div><div class="svc-cost">$6/mo</div></div> <div class="svc ok"><div class="svc-name">legacy</div><div class="svc-cost">$6/mo</div></div> <div class="svc ok"><div class="svc-name">redis</div><div class="svc-cost">$15/mo</div></div> <div class="svc ok"><div class="svc-name">volume</div><div class="svc-cost">$15/mo</div></div> <div class="svc ok"><div class="svc-name">spaces</div><div class="svc-cost">$5/mo</div></div> <div class="svc free"><div class="svc-name">cloudflare</div><div class="svc-cost">free</div></div> <div class="svc free"><div class="svc-name">auth0</div><div class="svc-cost">free</div></div> <div class="svc free"><div class="svc-name">firebase</div><div class="svc-cost">free</div></div> <div class="svc ok"><div class="svc-name">anthropic</div><div class="svc-cost">usage</div></div> <div class="svc down"><div class="svc-name">shopify</div><div class="svc-cost">closed</div></div> <div class="svc free"><div class="svc-name">jamsocket</div><div class="svc-cost">free</div></div> </div>
<!-- All Services --> <h3>all services</h3> <table> <thead><tr><th>status</th><th>service</th><th>monthly</th><th class="hide-mobile">notes</th></tr></thead> <tbody> <tr><td><span class="pill pill-green">active</span></td><td>DigitalOcean</td><td class="money">~$248</td><td class="hide-mobile">Paid — 7 droplets incl. the lith host + Redis + volume + Spaces</td></tr> <tr><td><span class="pill pill-green">active</span></td><td>Claude Code Max</td><td class="money">$100</td><td class="hide-mobile">Restored — first month covered by Sage</td></tr> <tr><td><span class="pill pill-green">active</span></td><td>Anthropic API</td><td class="money">Usage</td><td class="hide-mobile">Claude Code usage on top of Max</td></tr> <tr><td><span class="pill pill-green">active</span></td><td>MongoDB</td><td class="money">$0</td><td class="hide-mobile">Self-hosted on DO silo</td></tr> <tr><td><span class="pill pill-purple">retired</span></td><td>Netlify</td><td class="money">$0</td><td class="hide-mobile">Migrated off — site now runs on the ac-lith droplet</td></tr> <tr><td><span class="pill pill-red">closed</span></td><td>Shopify</td><td class="money">$0</td><td class="hide-mobile">shop.aesthetic.computer still closed — decide reactivate vs. remove</td></tr> <tr><td><span class="pill pill-gold">verify</span></td><td>GitHub</td><td class="money">~$500?</td><td class="hide-mobile">Confirm prior-months balance is settled</td></tr> <tr><td><span class="pill pill-cyan">free</span></td><td>Cloudflare</td><td class="money">$0</td><td class="hide-mobile">8 zones, Workers — now fronts the lith origin</td></tr> <tr><td><span class="pill pill-green">active</span></td><td>Firebase / GCP</td><td class="money">$0–50</td><td class="hide-mobile">Free auth + Nanos VMs (verify)</td></tr> <tr><td><span class="pill pill-cyan">free</span></td><td>Auth0</td><td class="money">$0</td><td class="hide-mobile">Free tier (7K users)</td></tr> <tr><td><span class="pill pill-green">active</span></td><td>Stripe</td><td class="money">Per-txn</td><td class="hide-mobile">2.9% + $0.30</td></tr> <tr><td><span class="pill pill-green">active</span></td><td>PayPal</td><td class="money">Per-txn</td><td class="hide-mobile">~2.2% + $0.30</td></tr> <tr><td><span class="pill pill-cyan">free</span></td><td>Jamsocket</td><td class="money">$0</td><td class="hide-mobile">Session orchestration</td></tr> <tr><td><span class="pill pill-green">active</span></td><td>Domains (8)</td><td class="money">~$8</td><td class="hide-mobile">~$100/yr across registrars</td></tr> </tbody> </table>
<!-- DigitalOcean --> <details> <summary>digitalocean — ~$248/mo (paid, active)</summary> <div class="detail-body"> <h3 style="margin-top: 0;">droplets</h3> <table> <thead><tr><th>name</th><th>ip</th><th class="hide-mobile">size</th><th>$/mo</th></tr></thead> <tbody> <tr><td>oven</td><td><code>137.184.237.166</code></td><td class="hide-mobile">s-8vcpu-16gb · 320GB</td><td class="money">~$96</td></tr> <tr><td>ac-lith <span class="pill pill-green">host</span></td><td><code>209.38.133.33</code></td><td class="hide-mobile">s-2vcpu-4gb · 80GB</td><td class="money">~$24</td></tr> <tr><td>help</td><td><code>146.190.150.173</code></td><td class="hide-mobile">s-2vcpu-4gb · 80GB</td><td class="money">~$24</td></tr> <tr><td>silo</td><td><code>64.23.151.169</code></td><td class="hide-mobile">s-2vcpu-4gb</td><td class="money">~$24</td></tr> <tr><td>at/pds</td><td><code>165.227.120.137</code></td><td class="hide-mobile">s-2vcpu-4gb — nyc3</td><td class="money">~$24</td></tr> <tr><td>session</td><td><code>157.245.134.225</code></td><td class="hide-mobile">s-1vcpu-1gb</td><td class="money">~$6</td></tr> <tr><td>legacy-2016</td><td><code>162.243.163.221</code></td><td class="hide-mobile">1GB — nyc1</td><td class="money">~$6</td></tr> </tbody> </table> <h3>other resources</h3> <table> <thead><tr><th>resource</th><th>detail</th><th>$/mo</th></tr></thead> <tbody> <tr><td>Managed Redis/Valkey</td><td>db-s-1vcpu-1gb — sfo3</td><td class="money">$15</td></tr> <tr><td>Block Volume</td><td>150GB — nyc1</td><td class="money">$15</td></tr> <tr><td>Spaces</td><td>250GB + bandwidth (10 buckets)</td><td class="money">$5</td></tr> <tr><td>Backups / snapshots + bandwidth</td><td>legacy weekly + overage</td><td class="money">~$9</td></tr> </tbody> </table> <p style="font-size: 0.78em; color: var(--dim); margin-top: 0.6em;">Droplet subtotal ~$204 + resources ~$44 ≈ $248/mo. Apr & May invoices both $248.40; Jun MTD $26 as of the 4th.</p> </div> </details>
<!-- Cloudflare --> <details> <summary>cloudflare domains (free)</summary> <div class="detail-body"> <table> <thead><tr><th>domain</th><th>registrar</th><th>plan</th></tr></thead> <tbody> <tr><td><a href="https://aesthetic.computer">aesthetic.computer</a></td><td>Tucows</td><td><span class="pill pill-cyan">free</span></td></tr> <tr><td><a href="https://kidlisp.com">kidlisp.com</a></td><td>Key-Systems</td><td><span class="pill pill-cyan">free</span></td></tr> <tr><td><a href="https://notepat.com">notepat.com</a></td><td>Key-Systems</td><td><span class="pill pill-cyan">free</span></td></tr> <tr><td><a href="https://sotce.net">sotce.net</a></td><td>1API</td><td><span class="pill pill-cyan">free</span></td></tr> <tr><td>prompt.ac</td><td>1API</td><td><span class="pill pill-cyan">free</span></td></tr> <tr><td><a href="https://jas.life">jas.life</a></td><td>1API</td><td><span class="pill pill-cyan">free</span></td></tr> <tr><td>justanothersystem.org</td><td>1API</td><td><span class="pill pill-cyan">free</span></td></tr> <tr><td>false.work</td><td>GoDaddy</td><td><span class="pill pill-cyan">free</span></td></tr> </tbody> </table> </div> </details>
<!-- Cost Reduction --> <details> <summary>cost reduction opportunities (save ~$60/mo)</summary> <div class="detail-body"> <table> <thead><tr><th>action</th><th>saves</th></tr></thead> <tbody> <tr><td>Kill legacy-2016 + 150GB volume + backup</td><td class="money" style="color: var(--green);">~$23</td></tr> <tr><td>Redis → Upstash free tier</td><td class="money" style="color: var(--green);">$15</td></tr> <tr><td>Right-size oven when not building</td><td class="money" style="color: var(--green);">varies</td></tr> <tr><td>Evaluate AT/PDS droplet</td><td class="money" style="color: var(--green);">~$24</td></tr> <tr><td>Consolidate domains</td><td class="money" style="color: var(--green);">$5–10</td></tr> <tr style="color: var(--green);"><td>realistic total</td><td class="money">~$60</td></tr> </tbody> </table> <p style="font-size: 0.78em; color: var(--dim); margin-top: 0.6em;">Note: oven was deliberately upgraded to 16GB/8vCPU ($96/mo) to run image/code/paper builds — downsizing it trades capacity for cost.</p> </div> </details>
</section>
<div class="footer"> <span id="footer-date">...</span> <span><a href="https://sitemap.aesthetic.computer">sitemap</a> · <a href="https://aesthetic.computer/docs">docs</a> · <a href="https://aesthetic.computer/list">list</a></span> </div>
</div>
<script> // Theme: follow system preference function applySystemTheme() { if (window.matchMedia('(prefers-color-scheme: light)').matches) { document.body.classList.add('light-mode'); document.body.classList.remove('dark-mode'); } else { document.body.classList.remove('light-mode'); document.body.classList.add('dark-mode'); } } applySystemTheme(); window.matchMedia('(prefers-color-scheme: light)') .addEventListener('change', applySystemTheme);
// h1 navigates to aesthetic.computer document.getElementById('title').onclick = () => { location.href = 'https://aesthetic.computer'; };
// Primary bills tabs const billTabs = Array.from(document.querySelectorAll('.bill-tab')); const billSections = Array.from(document.querySelectorAll('.bill-section'));
function setBillsView(view) { billTabs.forEach((tab) => { const active = tab.dataset.viewTarget === view; tab.classList.toggle('active', active); tab.setAttribute('aria-selected', active ? 'true' : 'false'); });
billSections.forEach((section) => { section.hidden = section.dataset.view !== view; });
try { localStorage.setItem('bills:view', view); } catch (_) {} }
billTabs.forEach((tab) => { tab.addEventListener('click', () => { setBillsView(tab.dataset.viewTarget); }); });
const savedBillsView = (() => { try { return localStorage.getItem('bills:view'); } catch (_) { return null; } })(); setBillsView(savedBillsView === 'income' || savedBillsView === 'needs' ? savedBillsView : 'needs');
// Footer date document.getElementById('footer-date').textContent = new Date().toISOString().split('T')[0];
// ═══════════════════════════════════════════════════════════ // LIVE DATA: Gives (Stripe) // ═══════════════════════════════════════════════════════════
const MONTHLY_COSTS_USD = 356;
let givesMonthlyUSD = 0; let givesTotalUSD = 0; let keepsIncomeUSD = 0;
function updateNetFlow() { const totalMonthlyIncome = givesMonthlyUSD + keepsIncomeUSD; const net = totalMonthlyIncome - MONTHLY_COSTS_USD; const flowIncomeEl = document.getElementById('flow-income'); const flowNetEl = document.getElementById('flow-net');
if (flowIncomeEl) { flowIncomeEl.textContent = '~$' + Math.round(totalMonthlyIncome); flowIncomeEl.classList.remove('loading'); }
if (flowNetEl) { flowNetEl.textContent = (net >= 0 ? '+$' : '-$') + Math.abs(Math.round(net)); flowNetEl.style.color = net >= 0 ? 'var(--green)' : 'var(--red)'; flowNetEl.classList.remove('loading'); } }
(async function loadGives() { try { const res = await fetch('https://aesthetic.computer/api/gives'); if (!res.ok) throw new Error('API error'); const data = await res.json();
givesMonthlyUSD = data.monthlyTotalUSD || 0; givesTotalUSD = data.totalUSD || 0;
document.getElementById('gives-monthly').textContent = '$' + givesMonthlyUSD; document.getElementById('gives-monthly').classList.remove('loading');
document.getElementById('gives-total').textContent = '$' + givesTotalUSD; document.getElementById('gives-total').classList.remove('loading');
document.getElementById('gives-subs').textContent = data.activeSubscribers || 0; document.getElementById('gives-subs').classList.remove('loading');
document.getElementById('gives-count').textContent = data.count || 0; document.getElementById('gives-count').classList.remove('loading');
// Render recent gives list const listEl = document.getElementById('gives-list'); if (data.gives && data.gives.length > 0) { listEl.innerHTML = data.gives.slice(0, 20).map(g => { const date = new Date(g.createdAt).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); const recurring = g.isRecurring ? '<span class="give-recurring">monthly</span>' : ''; const note = g.note ? `<span class="give-note">"${g.note}"</span>` : ''; return `<div class="give-entry"> <span><span class="give-amount">${g.amountDisplay}</span> ${recurring} ${note}</span> <span class="give-date">${date}</span> </div>`; }).join(''); } else { listEl.innerHTML = '<div style="color: var(--dim); padding: 0.5em;">No gives yet.</div>'; }
updateNetFlow(); } catch (e) { console.error('Failed to load gives:', e); document.getElementById('gives-monthly').textContent = '—'; document.getElementById('gives-total').textContent = '—'; document.getElementById('gives-subs').textContent = '—'; document.getElementById('gives-count').textContent = '—'; document.getElementById('gives-list').innerHTML = '<div style="color: var(--dim); padding: 0.5em;">Could not load gives data.</div>'; updateNetFlow(); } })();
// ═══════════════════════════════════════════════════════════ // LIVE DATA: Keeps (Tezos via TzKT API) // ═══════════════════════════════════════════════════════════
const DEFAULT_KEEPS_CONTRACT = 'KT1Q1irsjSZ7EfUN4qHzAB2t7xLBPsAWYwBB'; const KEEPS_CONFIG_URL = '/api/keeps-config?network=mainnet'; const KEEPS_CONFIG_FALLBACK_URL = 'https://aesthetic.computer/api/keeps-config?network=mainnet'; const TZKT_API = 'https://api.tzkt.io'; const KEEPS_FEE_XTZ = 2.5;
(async function loadKeeps() { try { const keepsContractEl = document.getElementById('keeps-contract'); const keepsNetworkEl = document.getElementById('keeps-network'); const keepsProfileEl = document.getElementById('keeps-profile'); const keepsSourceEl = document.getElementById('keeps-source'); const keepsObjktLinkEl = document.getElementById('keeps-objkt-link'); const keepsTzktLinkEl = document.getElementById('keeps-tzkt-link');
let keepsContract = DEFAULT_KEEPS_CONTRACT; let keepsNetwork = 'mainnet'; let keepsProfile = 'unknown'; let keepsVersion = ''; let keepsSource = 'fallback'; let objktUrl = 'https://objkt.com/collections/' + keepsContract; let tzktUrl = 'https://tzkt.io/' + keepsContract;
try { let configRes = await fetch(KEEPS_CONFIG_URL); if (!configRes.ok) configRes = await fetch(KEEPS_CONFIG_FALLBACK_URL); if (configRes.ok) { const config = await configRes.json(); if (config?.contractAddress) keepsContract = config.contractAddress; if (config?.network) keepsNetwork = config.network; if (config?.profile) keepsProfile = config.profile; if (config?.version) keepsVersion = config.version; if (config?.source) keepsSource = config.source; if (config?.objktCollectionUrl) objktUrl = config.objktCollectionUrl; if (config?.tzktContractUrl) tzktUrl = config.tzktContractUrl; } } catch (e) { // fall back to default contract }
objktUrl = objktUrl.includes('/collection/') || objktUrl.includes('/collections/') ? objktUrl : ('https://objkt.com/collections/' + keepsContract); tzktUrl = tzktUrl || ('https://tzkt.io/' + keepsContract);
if (keepsContractEl) keepsContractEl.textContent = keepsContract; if (keepsNetworkEl) keepsNetworkEl.textContent = `Tezos ${keepsNetwork}`; if (keepsProfileEl) keepsProfileEl.textContent = keepsVersion ? `${keepsProfile} · ${keepsVersion}` : keepsProfile; if (keepsSourceEl) keepsSourceEl.textContent = keepsSource; if (keepsObjktLinkEl) keepsObjktLinkEl.href = objktUrl; if (keepsTzktLinkEl) keepsTzktLinkEl.href = tzktUrl;
// Fetch contract storage for token count const storageRes = await fetch(TZKT_API + '/v1/contracts/' + keepsContract + '/storage'); const storage = storageRes.ok ? await storageRes.json() : null;
// Fetch token holders const holdersRes = await fetch(TZKT_API + '/v1/tokens?contract=' + keepsContract + '&limit=1000'); const tokens = holdersRes.ok ? await holdersRes.json() : [];
// Count unique owners from token holders const ownersRes = await fetch(TZKT_API + '/v1/tokens/balances?token.contract=' + keepsContract + '&balance.gt=0&limit=1000'); const balances = ownersRes.ok ? await ownersRes.json() : []; const uniqueOwners = new Set(balances.map(b => b.account?.address)).size;
// Get mint count from storage const mintCount = storage?.next_token_id ? parseInt(storage.next_token_id) : tokens.length;
// Calculate fees earned (mint count * fee) const feesXTZ = mintCount * KEEPS_FEE_XTZ;
// Fetch XTZ/USD price for conversion let xtzPrice = 0.38; // fallback try { const priceRes = await fetch(TZKT_API + '/v1/head'); if (priceRes.ok) { const head = await priceRes.json(); if (head.quoteUsd) xtzPrice = head.quoteUsd; } } catch (e) { /* use fallback */ }
// Fetch total transfer volume const transfersRes = await fetch(TZKT_API + '/v1/operations/transactions?target=' + keepsContract + '&entrypoint=keep&limit=1000&select=amount'); let totalVolumeXTZ = feesXTZ; // At minimum, minting fees if (transfersRes.ok) { const amounts = await transfersRes.json(); totalVolumeXTZ = amounts.reduce((sum, a) => sum + (a / 1000000), 0); }
// Monthly estimate from mints in the last 30 days let monthlyMints = 0; try { const start30d = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(); const mintTransfersRes = await fetch( TZKT_API + '/v1/tokens/transfers?token.contract=' + keepsContract + '×tamp.ge=' + encodeURIComponent(start30d) + '&limit=1000' ); if (mintTransfersRes.ok) { const transfers = await mintTransfersRes.json(); monthlyMints = transfers.filter(t => t.from == null).length; } } catch (e) { /* keep fallback below */ }
const monthlyFeesXTZ = monthlyMints * KEEPS_FEE_XTZ; keepsIncomeUSD = Math.round(monthlyFeesXTZ * xtzPrice); if (monthlyMints > 0) { const keepsFeesSub = document.getElementById('keeps-fees-sub'); if (keepsFeesSub) keepsFeesSub.textContent = `2.5 XTZ / mint · ${monthlyMints} mints in last 30d`; }
document.getElementById('keeps-volume').textContent = Math.round(totalVolumeXTZ) + ' XTZ'; document.getElementById('keeps-volume').classList.remove('loading');
document.getElementById('keeps-fees').textContent = feesXTZ.toFixed(1) + ' XTZ'; document.getElementById('keeps-fees').classList.remove('loading');
document.getElementById('keeps-minted').textContent = mintCount; document.getElementById('keeps-minted').classList.remove('loading');
document.getElementById('keeps-owners').textContent = uniqueOwners; document.getElementById('keeps-owners').classList.remove('loading');
updateNetFlow(); } catch (e) { console.error('Failed to load keeps:', e); document.getElementById('keeps-volume').textContent = '—'; document.getElementById('keeps-fees').textContent = '—'; document.getElementById('keeps-minted').textContent = '—'; document.getElementById('keeps-owners').textContent = '—'; updateNetFlow(); } })();
// ═══════════════════════════════════════════════════════════ // SVG Cost Timeline // ═══════════════════════════════════════════════════════════
(function() { // DigitalOcean monthly invoices (actual) const data = [ { month: 'Oct 25', cost: 71.30 }, { month: 'Nov 25', cost: 154.74 }, { month: 'Dec 25', cost: 182.40 }, { month: 'Jan 26', cost: 110.40 }, { month: 'Feb 26', cost: 132.36 }, { month: 'Mar 26', cost: 189.12 }, { month: 'Apr 26', cost: 248.40 }, { month: 'May 26', cost: 248.40 }, { month: 'Jun 26', cost: 26.37, partial: true }, ];
const svg = document.getElementById('timeline'); const ns = 'http://www.w3.org/2000/svg'; const W = 680, H = 200; const pad = { top: 20, right: 20, bottom: 35, left: 55 }; const chartW = W - pad.left - pad.right; const chartH = H - pad.top - pad.bottom; const maxCost = Math.max(...data.map(d => d.cost)) * 1.15;
const style = getComputedStyle(document.documentElement); const pink = style.getPropertyValue('--pink').trim() || '#ff6b9d'; const dim = style.getPropertyValue('--dim').trim() || '#888'; const red = style.getPropertyValue('--red').trim() || '#ef4444'; const boxBorder = style.getPropertyValue('--box-border').trim() || 'rgba(255,255,255,0.1)'; const text = style.getPropertyValue('--text').trim() || '#e8e8e8';
function el(tag, attrs) { const e = document.createElementNS(ns, tag); for (const [k, v] of Object.entries(attrs || {})) e.setAttribute(k, v); return e; }
function x(i) { return pad.left + (i / (data.length - 1)) * chartW; } function y(v) { return pad.top + chartH - (v / maxCost) * chartH; }
// Grid lines const gridSteps = [50, 100, 150, 200, 250]; for (const v of gridSteps) { if (v > maxCost) continue; const yy = y(v); svg.appendChild(el('line', { x1: pad.left, y1: yy, x2: W - pad.right, y2: yy, stroke: boxBorder, 'stroke-width': 0.5 })); const label = el('text', { x: pad.left - 8, y: yy + 3, fill: dim, 'font-size': '9', 'text-anchor': 'end', 'font-family': "'Berkeley Mono Variable', monospace" }); label.textContent = '$' + v; svg.appendChild(label); }
// Area fill let areaPath = `M ${x(0)} ${y(0)}`; for (let i = 0; i < data.length; i++) areaPath += ` L ${x(i)} ${y(data[i].cost)}`; areaPath += ` L ${x(data.length - 1)} ${y(0)} Z`;
const defs = el('defs'); const grad = el('linearGradient', { id: 'areaGrad', x1: '0', y1: '0', x2: '0', y2: '1' }); const stop1 = el('stop', { offset: '0%', 'stop-color': pink, 'stop-opacity': '0.25' }); const stop2 = el('stop', { offset: '100%', 'stop-color': pink, 'stop-opacity': '0.02' }); grad.appendChild(stop1); grad.appendChild(stop2); defs.appendChild(grad); svg.appendChild(defs);
svg.appendChild(el('path', { d: areaPath, fill: 'url(#areaGrad)' }));
// Line let linePath = `M ${x(0)} ${y(data[0].cost)}`; for (let i = 1; i < data.length; i++) linePath += ` L ${x(i)} ${y(data[i].cost)}`; svg.appendChild(el('path', { d: linePath, fill: 'none', stroke: pink, 'stroke-width': 2, 'stroke-linejoin': 'round' }));
// Data points and labels for (let i = 0; i < data.length; i++) { const cx = x(i), cy = y(data[i].cost);
// Dot const dotColor = data[i].partial ? red : pink; svg.appendChild(el('circle', { cx, cy, r: 4, fill: dotColor, stroke: 'var(--bg)', 'stroke-width': 2 }));
// Cost label above dot const costLabel = el('text', { x: cx, y: cy - 10, fill: data[i].partial ? red : text, 'font-size': '10', 'text-anchor': 'middle', 'font-family': "'Berkeley Mono Variable', monospace" }); costLabel.textContent = '$' + data[i].cost.toFixed(0); svg.appendChild(costLabel);
// Month label below axis const monthLabel = el('text', { x: cx, y: H - 8, fill: dim, 'font-size': '9', 'text-anchor': 'middle', 'font-family': "'Berkeley Mono Variable', monospace" }); monthLabel.textContent = data[i].month; svg.appendChild(monthLabel); }
// Baseline annotation — pre-lith run rate const baseY = y(132); svg.appendChild(el('line', { x1: pad.left, y1: baseY, x2: W - pad.right, y2: baseY, stroke: dim, 'stroke-width': 0.8, 'stroke-dasharray': '4,3' })); const baseLabel = el('text', { x: W - pad.right + 2, y: baseY + 3, fill: dim, 'font-size': '8', 'text-anchor': 'start', 'font-family': "'Berkeley Mono Variable', monospace" }); baseLabel.textContent = 'pre-lith'; svg.appendChild(baseLabel); })(); </script></body></html>