<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Richard Lemon]]></title><description><![CDATA[Frontend dev writing about CSS, Make.com, biohacking, and building in public]]></description><link>https://richardlemon.com/</link><image><url>https://richardlemon.com/favicon.png</url><title>Richard Lemon</title><link>https://richardlemon.com/</link></image><generator>Ghost 6.42</generator><lastBuildDate>Tue, 09 Jun 2026 10:27:18 GMT</lastBuildDate><atom:link href="https://richardlemon.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Using CSS Variables To Theme Biohacking Dashboards Without Losing Your Mind]]></title><description><![CDATA[How I use CSS variables to keep my HRV, sleep, and training dashboards visually consistent, even when the tools do everything in their power to look different.]]></description><link>https://richardlemon.com/css-variables-theming-biohacking-tools/</link><guid isPermaLink="false">6a1a44d9950ef5a96b76fda7</guid><category><![CDATA[CSS]]></category><category><![CDATA[Biohacking]]></category><dc:creator><![CDATA[Richard Lemon]]></dc:creator><pubDate>Mon, 08 Jun 2026 19:09:52 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1732046801426-f32529468176?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDV8fGJpb3xlbnwwfHx8fDE3ODA1OTk1NzJ8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[
<!--kg-card-begin: html-->
<aside class="toc-container">
    <div class="toc-sidebar">
        <h4>On this page</h4>
        <div class="toc"></div>
    </div>
</aside>
<!--kg-card-end: html-->
<h2 id="biohacking-dashboards-are-ugly-by-default">Biohacking dashboards are ugly by default</h2><img src="https://images.unsplash.com/photo-1732046801426-f32529468176?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDV8fGJpb3xlbnwwfHx8fDE3ODA1OTk1NzJ8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="Using CSS Variables To Theme Biohacking Dashboards Without Losing Your Mind"><p>I track too many things. HRV, sleep, kettlebell sessions, batting drills, glucose on and off, sometimes reaction time. Every tool ships with its own opinionated UI and zero interest in playing nicely with others.</p><p>I hate context switching visually. Dark mode here, clinical white there, neon gradients in the next tab. It fragments my focus. If I am looking at my nervous system, I want one visual language. Not six.</p><p>So I stopped waiting for integrations and themes that never ship. I started forcing every biohacking tool into the same look using one boring but powerful feature: CSS variables.</p><h2 id="my-constraint-one-theme-many-weird-tools">My constraint: one theme, many weird tools</h2><p>Here is the practical setup I am working with:</p><ul><li>Garmin and Oura exports piped into a custom dashboard.</li><li>A small web app I built for tracking baseball drills and swing volume.</li><li>A Notion-like notes tool where I keep protocols and experiments.</li><li>Random vendor dashboards in iframes. Because life is pain.</li></ul><p>I wanted all of them to feel like one system. Same colors. Same typography. Same sense of hierarchy. If I glance at HRV or at kettlebell volume, my brain should not need a full layout parse.</p><p>CSS variables are the glue. I define the design system once, then keep bending UIs around it until they submit.</p><h2 id="the-core-a-tiny-design-token-layer-in-css">The core: a tiny design token layer in CSS</h2><p>I treat CSS variables as the public API of my theme. Colors, spacing, fonts, radii, shadows. If it affects how the interface feels, it becomes a variable.</p><p>This is the baseline I use across my biohacking tools:</p><pre><code>:root {
  /* core palette */
  --color-bg: #04060a;
  --color-bg-elevated: #0b1018;
  --color-surface: #121927;

  --color-accent: #4fd1c5;        /* &quot;recovered&quot; / good */
  --color-accent-soft: #234e52;

  --color-danger: #f56565;        /* &quot;overreached&quot; / bad */
  --color-warning: #ecc94b;

  --color-text: #f7fafc;
  --color-text-muted: #a0aec0;
  --color-border: #2d3748;

  /* typography */
  --font-sans: system-ui, -apple-system, BlinkMacSystemFont,
               &quot;SF Pro Text&quot;, &quot;Segoe UI&quot;, sans-serif;
  --font-mono: &quot;JetBrains Mono&quot;, ui-monospace, SFMono-Regular, monospace;

  /* spacing scale */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;

  /* radii */
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 16px;

  /* shadows */
  --shadow-soft: 0 10px 30px rgba(0, 0, 0, 0.45);
}
</code></pre><p>This file is <code>theme.css</code>. Every project imports it. I do not care if it is a Next app, a static dashboard, or something injected via a browser extension.</p><p>The exact values are opinionated. Dark, slightly cinematic, a hint of lab equipment. The details matter, because that mood is the mental context for my biohacking work.</p><h2 id="mapping-physiology-to-color-and-motion">Mapping physiology to color and motion</h2><p>I hate random color choices. If you stare at these dashboards daily, your brain starts building associations whether you like it or not. I would rather decide those intentionally.</p><p>For my biohacking stack, I use a very simple mapping:</p><ul><li><strong>Cool greens / teals</strong> for recovered, parasympathetic leaning states.</li><li><strong>Ambers</strong> for &#x201C;pay attention&#x201D; but not panic.</li><li><strong>Warm reds</strong> for &#x201C;do less or you will regret it.&#x201D;</li></ul><p>The CSS side of that looks like this:</p><pre><code>:root {
  --state-good: var(--color-accent);
  --state-good-soft: var(--color-accent-soft);
  --state-warning: var(--color-warning);
  --state-bad: var(--color-danger);
}

.badge-good {
  color: var(--state-good);
  background: color-mix(in srgb, var(--state-good-soft) 70%, transparent);
}

.badge-warning {
  color: var(--state-warning);
}

.badge-bad {
  color: var(--state-bad);
}
</code></pre><p>Now when I wire up a new panel for HRV, sleep debt, or training load, I do not think about exact hex values. I just choose the state variable.</p><p>HRV green, sleep debt amber, too many heavy swings deep red. Same palette everywhere. My brain learns it fast, which is the entire point.</p><h2 id="a-theme-object-that-frameworks-can-understand">A theme object that frameworks can understand</h2><p>Variables in CSS are nice. Variables in JS are sometimes unavoidable. I want both to agree.</p><p>My compromise is to define the tokens in CSS and then mirror them into a tiny JS theme object. I do not maintain two separate systems. I read from CSS:</p><pre><code>const cssVar = (name) =&gt;
  getComputedStyle(document.documentElement)
    .getPropertyValue(name)
    .trim();

export const theme = {
  bg: cssVar(&quot;--color-bg&quot;),
  accent: cssVar(&quot;--color-accent&quot;),
  danger: cssVar(&quot;--color-danger&quot;),
};
</code></pre><p>Now, if I feed values into Vega-Lite charts or some random canvas visualization, I am still using the same colors as the CSS buttons and cards.</p><p>I think this is underrated. Most health dashboards look like ten designers disagreed over a period of three years. Keeping everything tied to the CSS variables makes it feel like one tool instead of a playlist of vendor branding.</p><h2 id="switching-day-and-night-without-a-mess">Switching day and night without a mess</h2><p>Biohacking is inherently tied to light. Circadian rhythm, blue blockers, light intensity. So my UI respects that. I do not want a retina-burning chart at 23:00.</p><p>I only run two themes: <code>dark</code> and <code>night</code>. Yes, both are dark. One is just darker and softer. I do not maintain a light theme. I think that is pointless for this context.</p><p>The CSS is simple:</p><pre><code>:root[data-theme=&quot;dark&quot;] {
  --color-bg: #04060a;
  --color-bg-elevated: #0b1018;
  --color-surface: #121927;
  --color-text: #f7fafc;
  --color-text-muted: #a0aec0;
}

:root[data-theme=&quot;night&quot;] {
  --color-bg: #010208;
  --color-bg-elevated: #05060b;
  --color-surface: #0a0c14;
  --color-text: #e2e8f0;
  --color-text-muted: #718096;
}
</code></pre><p>Theme switching is just an attribute toggle:</p><pre><code>const setTheme = (mode) =&gt; {
  document.documentElement.setAttribute(&quot;data-theme&quot;, mode);
  localStorage.setItem(&quot;theme&quot;, mode);
};
</code></pre><p>All the charts, tables, inputs, and cards update automatically because they never used literal colors. Only variables.</p><p>This is the core benefit. I can keep adjusting how &#x201C;night mode&#x201D; feels without touching a single component. I just massage the variables until my eyes are happy.</p><h2 id="hijacking-external-dashboards-with-a-browser-extension">Hijacking external dashboards with a browser extension</h2><p>So far I have only talked about stuff I own. Custom apps, small dashboards, things I deploy myself. The more interesting bit is external tools that I cannot control.</p><p>Here is how I force them to behave.</p><h3 id="step-1-global-theme-injector">Step 1: Global theme injector</h3><p>I run a user-style extension in the browser (Stylus works fine). It injects a very small stylesheet into matching domains. That stylesheet defines my variables and overrides just enough of the UI to make it feel like my system.</p><p>Example for a hypothetical HRV platform:</p><pre><code>@-moz-document domain(&quot;hrv-vendor.example&quot;) {
  :root {
    --color-bg: #04060a !important;
    --color-surface: #121927 !important;
    --color-text: #f7fafc !important;
    --color-accent: #4fd1c5 !important;
  }

  body {
    background: var(--color-bg) !important;
    color: var(--color-text) !important;
    font-family: var(--font-sans, system-ui) !important;
  }

  .btn-primary,
  button.primary {
    background: var(--color-accent) !important;
    border-radius: var(--radius-md, 8px) !important;
    border: none !important;
  }
}
</code></pre><p>Is it a bit brute force? Yes. Does it work? Also yes.</p><p>The idea is not to re-skin their product fully. I only care about aligning the background, text, and accent colors with my variables. That is enough for my brain to treat it as part of the same ecosystem.</p><h3 id="step-2-dealing-with-iframes">Step 2: Dealing with iframes</h3><p>Iframes are annoying. The host page can be perfectly themed while the embedded analytics widget still screams corporate teal on white.</p><p>If the iframe is on the same origin, it is easy. I just inject the same <code>theme.css</code> file into the iframe document. Done.</p><p>If it is cross origin, I have two options:</p><ul><li>Live with it and only control the chrome around it.</li><li>Proxy it through my own domain and strip or override its CSS.</li></ul><p>For personal tooling, I sometimes go for the proxy. A tiny Next or Cloudflare Worker that fetches the vendor page, rewrites the CSS to variables, and serves it under my domain.</p><p>It is hacky and fragile, but for one or two critical dashboards, the trade-off is worth it. My rule is simple. If I look at it daily, it deserves to speak my visual language.</p><h2 id="making-charts-obey-the-theme">Making charts obey the theme</h2><p>Charts are where most dashboards fall apart visually. They come pre-styled with awful contrasts and gradients that feel like a marketing deck.</p><p>I standardize chart theming around the same variables and keep the configuration close to the CSS.</p><p>For example, in a simple D3 or Chart.js setup:</p><pre><code>const getTheme = () =&gt; ({
  bg: cssVar(&quot;--color-bg&quot;),
  surface: cssVar(&quot;--color-surface&quot;),
  text: cssVar(&quot;--color-text&quot;),
  muted: cssVar(&quot;--color-text-muted&quot;),
  good: cssVar(&quot;--state-good&quot;),
  warning: cssVar(&quot;--state-warning&quot;),
  bad: cssVar(&quot;--state-bad&quot;),
});

const t = getTheme();

const hrvSeriesColor = (value) =&gt; {
  if (value &gt;= 80) return t.good;
  if (value &gt;= 60) return t.warning;
  return t.bad;
};
</code></pre><p>Now if I decide that &#x201C;good&#x201D; should be less neon and more muted, I touch one variable and the charts, tags, and state badges all shift together.</p><p>This is the main reason I like CSS variables for biohacking tools. I change my mind often. One night of bad sleep and I think the entire palette feels wrong. With variables, that is a 10 minute fix, not a refactor.</p><h2 id="connecting-ui-intensity-to-physiological-load">Connecting UI intensity to physiological load</h2><p>One thing I started playing with recently: UI intensity that matches how stressed my system is. Not just colors but spacing and density.</p><p>A calm body gets a calm UI. A stressed body gets a louder one.</p><p>I expose a variable for &#x201C;tension&#x201D; and hook it into subtle bits of the interface:</p><pre><code>:root {
  --tension: 0; /* 0 = calm, 1 = high tension */
}

.card {
  box-shadow:
    0 0 0 calc(1px + var(--tension) * 1px) rgba(255, 255, 255, 0.03),
    0 12px 30px rgba(0, 0, 0, 0.4 + var(--tension) * 0.2);
}

.hrv-line {
  stroke-width: calc(1.5px + var(--tension) * 0.5px);
}
</code></pre><p>Then in JS I do something naive first:</p><pre><code>const tensionFromHrv = (rmssd) =&gt; {
  if (!rmssd) return 0.5;
  if (rmssd &gt;= 80) return 0.1;
  if (rmssd &lt;= 40) return 1;
  return 0.5;
};

const applyTension = (value) =&gt; {
  document.documentElement.style.setProperty(
    &quot;--tension&quot;,
    String(value)
  );
};
</code></pre><p>This is subtle, but I feel it. On days where HRV is low or load is high, the dashboard feels slightly &#x201C;sharper.&#x201D; More contrast. Slightly thicker lines. It nudges my behavior.</p><p>Is this overkill? Maybe. But this is where CSS variables start feeling like a nervous system for the UI. You hook the state of the body into the state of the interface without touching every component manually.</p><h2 id="practical-advice-if-you-want-to-copy-this">Practical advice if you want to copy this</h2><p>If you want your own unified biohacking UI, here is the order I would actually do it in.</p><p><strong>First</strong>, write down three words for how you want the interface to feel. Mine: <em>calm, clinical, focused</em>. That will drive your color and spacing choices more than any palette generator.</p><p><strong>Second</strong>, build one small <code>theme.css</code> file. Backgrounds, text, accents, spacing, radii, shadows. Nothing fancy. Ship it.</p><p><strong>Third</strong>, refactor one tool at a time to only use variables. No hex codes in components. No stray <code>rgba</code> inlined in chart configs.</p><p><strong>Fourth</strong>, add a browser extension stylesheet for the external dashboards you use most often. Fix just the background, text, and primary buttons. Do not try to be their design team.</p><p><strong>Finally</strong>, once you live with it for a week, start experimenting with state-connected variables like <code>--tension</code> or separate <code>dark</code> and <code>night</code> modes.</p><p>The tooling is trivial. The value comes from using CSS variables as a contract. Your body state changes. Your tools change. The contract stays the same.</p><p>That is how you keep a messy stack of biohacking apps feeling like one instrument instead of a drawer of mismatched gadgets.</p>]]></content:encoded></item><item><title><![CDATA[Sovereign AI For My Smart Home Lab: Biohacking Without Giving Big Tech My Vitals]]></title><description><![CDATA[I wired my biohacking devices into a sovereign, self-hosted AI that lives on my network and actually runs my protocols. No cloud, no API key dependency, just a bossy local model running my day.]]></description><link>https://richardlemon.com/sovereign-ai-smart-home-biohacking/</link><guid isPermaLink="false">6a18f35bdce51298215d4226</guid><category><![CDATA[Biohacking]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Richard Lemon]]></dc:creator><pubDate>Sun, 07 Jun 2026 19:09:10 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1513584684374-8bab748fbf90?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDJ8fHNtYXJ0JTIwaG9tZXxlbnwwfHx8fDE3ODA1NjQyMzB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[
<!--kg-card-begin: html-->
<aside class="toc-container">
    <div class="toc-sidebar">
        <h4>On this page</h4>
        <div class="toc"></div>
    </div>
</aside>
<!--kg-card-end: html-->
<h2 id="why-i-stopped-letting-big-tech-babysit-my-biology">Why I Stopped Letting Big Tech Babysit My Biology</h2><img src="https://images.unsplash.com/photo-1513584684374-8bab748fbf90?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDJ8fHNtYXJ0JTIwaG9tZXxlbnwwfHx8fDE3ODA1NjQyMzB8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="Sovereign AI For My Smart Home Lab: Biohacking Without Giving Big Tech My Vitals"><p>I like data. I like knobs, levers, sliders. I like seeing my HRV respond to a protocol and then wiring that into code.</p><p>What I do not like is my health data living on random servers, under vague privacy policies, with &#x201C;AI insights&#x201D; that look like a horoscope in gym clothes.</p><p>So I built a small, sovereign AI layer for my home biohacking setup. It lives on my hardware. It talks to my devices. It decides when to nudge, when to back off, and it never phones home.</p><p>This is not some theoretical &#x201C;web3 + wellness&#x201D; pitch. It is a very opinionated, very hacky, very real system running in my house in the Netherlands.</p><h2 id="my-smart-home-biohacking-stack-before-ai-got-involved">My Smart Home Biohacking Stack, Before AI Got Involved</h2><p>Before I added AI, the setup looked like this:</p><ul><li>Wearables: Oura Ring, Garmin watch, sometimes a Polar chest strap for honest HR data.</li><li>Environment: Hue lights, smart plugs on red light and PEMF devices, a CO&#x2082; sensor, temperature sensors, cheap smart plugs on fans and a space heater.</li><li>Inputs: Standing desk, under-desk treadmill, a basic indoor air quality sensor, and a very boring but solid mechanical timer for blue light blockers as a backup.</li><li>Software glue: Home Assistant on a small Intel NUC, Node-RED for the ugly flows that would embarrass my younger self, a bunch of MQTT topics yelling at each other.</li></ul><p>It worked, sort of. I had automations like:</p><ul><li>Turn office lights warm at 21:30.</li><li>If CO&#x2082; &gt; 900 ppm, turn on fan and notify my phone.</li><li>If it is after 22:00, cut power to standing desk to force myself to stop &#x201C;just one more thing.&#x201D;</li></ul><p>Nice, but dumb. Completely deterministic. No context about my day, my sleep, my training load, or whether I was traveling.</p><h2 id="why-i-wanted-a-sovereign-ai-layer">Why I Wanted a Sovereign AI Layer</h2><p>Adding a cloud LLM was easy. Call an API, send some metrics, ask &#x201C;what should I do.&#x201D; I tried it. It felt wrong.</p><p>Three reasons I abandoned that idea fast:</p><ul><li><strong>Data sovereignty</strong>. I do not want to ship sleep, HRV, stress, and location data to any third party, no matter how nicely they talk about encryption.</li><li><strong>Fragility</strong>. If your core automation depends on a model endpoint and that endpoint changes pricing, limits, or quality, your protocols break overnight.</li><li><strong>Latency and reliability</strong>. I want sub-second decisions. If my CO&#x2082; spikes or my HR jumps during sleep, I prefer a local machine to react, not a SaaS platform that decided to reboot.</li></ul><p>So I decided the AI brain had to live at home. Fully local if possible. Or at least as local as my threat model demands.</p><h2 id="what-%E2%80%9Csovereign-ai%E2%80%9D-means-in-my-house">What &#x201C;Sovereign AI&#x201D; Means In My House</h2><p>My working definition is simple.</p><p>Sovereign AI means:</p><ul><li>The model runs on hardware I control, on a network I control.</li><li>It can act on the environment without asking a cloud function for permission.</li><li>I can inspect its instructions, training prompts, and logs.</li><li>If every external connection dies, it still works.</li></ul><p>I do not pretend I am running a perfectly audited, nation-state-proof setup. This is not that. This is a reasonably private, practical system that makes my house feel like a personal lab instead of a consumer IoT showroom.</p><h2 id="the-architecture-from-sensors-to-ai-to-devices">The Architecture, From Sensors To AI To Devices</h2><p>Here is what the current setup looks like.</p><ul><li><strong>Hardware</strong>: Intel NUC with 32GB RAM, small Nvidia GPU. On the same VLAN as my smart home gear.</li><li><strong>Home Automation</strong>: Home Assistant + MQTT broker.</li><li><strong>AI Runtime</strong>: Local LLM via Ollama, plus a small embedding model for &#x201C;memories.&#x201D;</li><li><strong>Brain</strong>: A Python service that pulls context from Home Assistant, calls the local model, and returns decisions as structured JSON.</li><li><strong>Device control</strong>: Home Assistant automations that listen for the AI&#x2019;s decisions and actually flip switches, change light scenes, or send me a message.</li></ul><p>I do not point the model directly at my devices. That feels reckless. It speaks a very limited language instead, something like:</p><pre><code>{
  &quot;actions&quot;: [
    {&quot;type&quot;: &quot;set_light_scene&quot;, &quot;target&quot;: &quot;office&quot;, &quot;scene&quot;: &quot;evening_focus&quot;},
    {&quot;type&quot;: &quot;toggle_device&quot;, &quot;target&quot;: &quot;red_light_panel&quot;, &quot;state&quot;: &quot;on&quot;, &quot;duration&quot;: 900},
    {&quot;type&quot;: &quot;notify&quot;, &quot;channel&quot;: &quot;mobile&quot;, &quot;message&quot;: &quot;Walk 5 minutes. HRV is tanking.&quot;}
  ]
}
</code></pre><p>Then Home Assistant translates those into real automations. If the AI says something that does not match the schema, it is ignored and logged.</p><h2 id="the-daily-loop-how-the-ai-runs-my-protocols">The Daily Loop: How The AI Runs My Protocols</h2><p>The core of this system is a loop that runs every few minutes and some specific event-based triggers. Here is the basic flow.</p><ol><li>Home Assistant aggregates my latest metrics: last night&#x2019;s sleep score, HRV, RHR, current HR, step count, CO&#x2082; levels, time of day, calendar events, and whether I am in the office or not.</li><li>The Python service pulls that context. It compresses raw numbers into a short, opinionated summary: &#x201C;Slept 6h15, HRV -18% vs baseline, high stress yesterday, no morning light yet, 2 calendar calls before noon.&#x201D;</li><li>It feeds this summary plus a very explicit system prompt to the local model. The prompt encodes my rules and my personal philosophy. Things like &#x201C;avoid aggressive protocols after bad sleep&#x201D; or &#x201C;prioritize light and movement over supplements.&#x201D;</li><li>The model returns a JSON list of actions. No paragraphs. No coaching speech.</li><li>Home Assistant executes the safe subset of those actions and logs what happened.</li></ol><p>That is the skeleton. The interesting bit is what you tell the model it is allowed to care about.</p><h2 id="encoding-my-biohacking-philosophy-into-the-ai">Encoding My Biohacking Philosophy Into The AI</h2><p>I am not trying to build a doctor replacement. I am building a pushy lab assistant that respects my constraints.</p><p>My system prompt reads something like this, heavily summarized:</p><ul><li>Never recommend anything medical. You only orchestrate environment and nudges.</li><li>Respect recovery. If sleep is poor and HRV is low, reduce intensity of any protocol.</li><li>Prioritize natural inputs first: light, movement, temperature, air quality.</li><li>Do not spam. Max 3 proactive interventions per day unless environment is unhealthy.</li><li>Assume the user is a stubborn developer who ignores subtle hints. Be direct but not annoying.</li></ul><p>Then I define time windows and goals:</p><ul><li>Morning: light exposure, gentle movement, no extreme cold if sleep was bad.</li><li>Afternoon: productivity and air quality.</li><li>Evening: light management, winding down, strict screens and stimulation cutoff.</li></ul><p>This means the AI is not just reacting to numbers. It is following a playbook that I wrote, with room to improvise based on context.</p><h2 id="concrete-scenarios-from-my-house">Concrete Scenarios From My House</h2><p>Here are a few examples that actually run now.</p><h3 id="1-the-you-slept-like-trash-morning-protocol">1. The &quot;You Slept Like Trash&quot; Morning Protocol</h3><p>Input conditions:</p><ul><li>Sleep duration &lt; 6.5h.</li><li>HRV &gt; 15% below baseline.</li><li>First alarm within the last 30 minutes.</li></ul><p>The AI usually decides to:</p><ul><li>Set office lights to a soft but bright warm scene instead of harsh cool white.</li><li>Turn off any high-intensity cold exposure recommendation. No cold plunge suggestion, even if the schedule would normally include it.</li><li>Send a short notification: &#x201C;Recovery compromised. Keep caffeine &lt; 2 cups, walk 10 minutes before work, no heavy training today.&#x201D;</li></ul><p>All of that stays local. No Oura API call leaves the house. The only thing I see is the effects.</p><h3 id="2-co%E2%82%82-focus-and-the-90-minute-rule">2. CO&#x2082;, Focus, And The 90-Minute Rule</h3><p>If I sit in my office too long and CO&#x2082; creeps above 1,000 ppm, my cognitive performance drops. You can feel it. Slow thinking, dumb mistakes.</p><p>The old automation just toggled the fan at 900 ppm. The sovereign AI does something smarter:</p><ul><li>Checks if I am in a focus block (calendar tag) or on a meeting.</li><li>Checks whether I have moved in the last 90 minutes.</li><li>If I have been mostly sedentary and CO&#x2082; is high, it does two things: start the fan and cut my office lights to a &#x201C;break&#x201D; scene that is obviously not work mode.</li><li>Then it nudges my phone: &#x201C;Walk outside 5&#x2013;10 min. CO&#x2082; high, movement low. Your next block will be better if you move now.&#x201D;</li></ul><p>I treat that as a hard recommendation unless I am deep in a build session. And the model learns that too. If I keep ignoring it during build sessions, I tweak the prompt to be less aggressive in those time windows.</p><h3 id="3-evening-shutdown-without-negotiation">3. Evening Shutdown Without Negotiation</h3><p>I am bad at stopping. Developers know this one. &#x201C;Just fix this tiny bug&#x201D; becomes 01:30.</p><p>The sovereign AI acts like a grumpy coach here.</p><ul><li>After 21:30, if tomorrow&#x2019;s calendar has early commitments and HRV is already low, it starts winding down lights automatically.</li><li>At 22:15, it cuts power to my standing desk and treadmill, sets the office lights to very warm and dim, and turns on a small bedside red light if I am still in the office.</li><li>If I keep working past 22:30, it sends a blunt message: &#x201C;You are borrowing tomorrow&#x2019;s focus. Decide intentionally. Type YES-BORROW to keep working 30 more minutes.&#x201D;</li></ul><p>That last part uses a tiny chat agent that still runs locally. I have to type the phrase in a chat window that sits on my dashboard. It is annoying enough that most days I just stop.</p><h2 id="why-decentralization-actually-matters-here">Why Decentralization Actually Matters Here</h2><p>You can build similar flows with a cloud AI. It will look slick. It will be easier to start.</p><p>I still think decentralization matters for three reasons.</p><ul><li><strong>Control</strong>. If a vendor decides &#x201C;no more low-level access, only high-level wellness goals,&#x201D; you are stuck. With a sovereign setup, you can keep running the last known-good version of your stack.</li><li><strong>Privacy by default</strong>. My AI sees everything. Sleep, minor health quirks, stress patterns. I am not interested in negotiating where that data goes in exchange for some convenience.</li><li><strong>Experiment velocity</strong>. I change prompts weekly. I wire in new sensors. I break things. Doing that against a cloud API feels brittle and expensive. Locally, it feels like creative play.</li></ul><p>There is also something very tangible about the loop fully closing inside your four walls. My body, my sensors, my model, my devices, my logs. That feels healthy and honest.</p><h2 id="the-ugly-parts-nobody-markets">The Ugly Parts Nobody Markets</h2><p>This is not a plug-and-play setup. A few rough edges are real.</p><ul><li><strong>LLM weirdness</strong>. Even locally, models hallucinate structure. I had runs where it invented actions that do not exist. The only fix is strict JSON schemas and brutal validation.</li><li><strong>Latency</strong>. Some smaller models are fast but dumb. Bigger ones are smart but sluggish on modest hardware. You have to decide where you care about instant reactions and where 2&#x2013;3 seconds is fine.</li><li><strong>Maintenance</strong>. Home Assistant updates, model updates, driver updates. If you want an appliance, this is not it. This is more like maintaining a project car.</li><li><strong>Over-automation</strong>. It is tempting to let the AI poke at everything. That gets annoying quickly. I had to ruthlessly cut back interventions to the things that demonstrably help.</li></ul><p>I still prefer this to polished vendor dashboards that tell me my &#x201C;Sleep Age&#x201D; once a week while selling me supplements.</p><h2 id="where-i-want-to-take-this-next">Where I Want To Take This Next</h2><p>Right now the AI is reactive with a little bit of memory. It remembers recent days, not months.</p><p>The next step is a proper local memory layer. Something lightweight that can answer &#x201C;what usually happens when you train heavy on Wednesdays and sleep badly on Thursdays.&#x201D; Then adjust protocols ahead of time.</p><p>I also want a proper &#x201C;consent dashboard.&#x201D; A place where I can see exactly which decisions the AI took, which sensors it used, and where I can revoke powers. For example, maybe it can touch lights and fans, but it cannot ever change bedroom temperature.</p><p>And I want to share sanitized configs. No product, just the prompts, schemas, and automations that have worked for me, so other people can adapt them to their weird setups.</p><h2 id="if-you-want-to-try-something-similar">If You Want To Try Something Similar</h2><p>If you are a developer with a few smart devices, you can start very small.</p><ul><li>Pick <strong>one</strong> protocol. Morning light, or CO&#x2082;, or evening shutdown. Not all three.</li><li>Run a local model in the simplest way possible. Even a CPU-only 7B model is fine for structured decisions.</li><li>Define a strict, tiny JSON schema for actions. No freeform text running your life.</li><li>Wire only one device to it. See how it behaves for a week. Keep logs.</li><li>Then iterate. Add a second sensor, or a second action type.</li></ul><p>The point is not to build a sci-fi AI butler. The point is to have a sovereign system that knows your body, your habits, your space, and can nudge you in ways that a generic wellness app cannot.</p><p>For me, that is the interesting part of AI right now. Not another chat window. A quiet, stubborn, local brain that moves the lights, air, and routines in my little lab so I do not have to think about them all the time.</p><p>And if every external service vanished tomorrow, this setup would still work. That feels like real progress.</p>]]></content:encoded></item><item><title><![CDATA[AI-Driven Biohacking: How I Predict My Stress Before It Hits]]></title><description><![CDATA[This is how I wired my wearables, calendar, and AI into a predictive stress system that warns me before I spiral instead of after it is too late.]]></description><link>https://richardlemon.com/ai-driven-biohacking-predictive-stress-management/</link><guid isPermaLink="false">6a17a1dadce51298215d41ef</guid><category><![CDATA[AI]]></category><category><![CDATA[Biohacking]]></category><dc:creator><![CDATA[Richard Lemon]]></dc:creator><pubDate>Sat, 06 Jun 2026 08:07:50 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1495427513693-3f40da04b3fd?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDE4fHxzdHJlc3N8ZW58MHx8fHwxNzc5OTU1MDQxfDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[
<!--kg-card-begin: html-->
<aside class="toc-container">
    <div class="toc-sidebar">
        <h4>On this page</h4>
        <div class="toc"></div>
    </div>
</aside>
<!--kg-card-end: html-->
<h2 id="stress-management-that-does-not-wait-for-the-crash">Stress management that does not wait for the crash</h2><img src="https://images.unsplash.com/photo-1495427513693-3f40da04b3fd?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDE4fHxzdHJlc3N8ZW58MHx8fHwxNzc5OTU1MDQxfDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="AI-Driven Biohacking: How I Predict My Stress Before It Hits"><p>Most stress tools feel like fire alarms. They scream after the fire has already started.</p><p>I wanted sprinklers instead. Something that quietly turns on before my brain catches fire.</p><p>So I built a small AI-driven stress prediction system for myself. It watches my data, predicts when I am about to get cooked, and triggers specific interventions before I feel terrible.</p><p>This is not theory. This is the slightly messy, very real system that now nudges me away from burnout on heavy client days and long baseball weekends.</p><h2 id="the-core-idea-catch-the-slope-not-the-point">The core idea: catch the slope, not the point</h2><p>Most wearables show you stress as a number. HRV score. Resting heart rate. Sleep score. Maybe a graph.</p><p>The number itself is nice. But the real story sits in the slope.</p><p>Is HRV sliding down faster than usual for a Tuesday? Is my resting heart rate spiking after three days of bad sleep and heavy coding? Is my calendar stacked with back-to-back calls on top of that?</p><p>I do not care if my HRV is 65 or 75 in isolation. I care about how fast it is changing relative to my baseline, in context with what my day looks like.</p><p>That is what I ask the AI to reason about for me.</p><h2 id="the-data-i-actually-use">The data I actually use</h2><p>People love huge data stacks. I think that is mostly a distraction. I started with four inputs and only added more when it clearly helped.</p><p>Here is the current stack.</p><h3 id="1-physiological-baselines">1. Physiological baselines</h3><ul><li><strong>HRV (Heart Rate Variability)</strong> from my wearable. I use it as a proxy for recovery and stress capacity.</li><li><strong>Resting heart rate</strong> over the last 7 days. Sudden jumps are usually a bad sign.</li><li><strong>Sleep duration and quality</strong>. I track total sleep time and number of wake-ups, not every micro metric.</li></ul><p>Everything is normalized around my personal baseline. I do not compare myself to generic &#x201C;healthy male&#x201D; charts. I compare me to last month me.</p><h3 id="2-calendar-load">2. Calendar load</h3><ul><li>Number of meetings per day.</li><li>Context switches. Different projects or domains back to back.</li><li>Blocks without breaks longer than 2 hours.</li></ul><p>Google Calendar is terrible as a stress tool on its own. Feed it to an AI with the right framing though and it turns into a pretty good &quot;future friction&quot; map.</p><h3 id="3-subjective-check-ins">3. Subjective check-ins</h3><p>I ask myself one short question a few times per day in a quick note:</p><p><em>&#x201C;How stressed do you feel right now from 1 to 5?&#x201D;</em></p><p>And I force a sentence of context:</p><p><em>&#x201C;Why?&#x201D;</em></p><p>I capture this in plain text. No fancy app. Just a timestamp, a number, and a sentence.</p><h3 id="4-behavioural-crumbs">4. Behavioural crumbs</h3><p>These are rough, noisy, but surprisingly useful:</p><ul><li>Screen time from phone and laptop.</li><li>Late-night browsing past my target bedtime.</li><li>Caffeine count after 14:00.</li></ul><p>None of these matter in isolation. Together, they paint a picture of how much I am ignoring my own rules.</p><h2 id="my-architecture-boring-on-purpose">My architecture: boring on purpose</h2><p>I like simple setups that survive busy weeks. If a system requires &quot;just one more script&quot; every two days, I will abandon it.</p><p>So the architecture looks like this:</p><ul><li><strong>Data ingress:</strong> Wearable exports, Google Calendar API, a tiny endpoint where my check-ins land.</li><li><strong>Storage:</strong> A single database table per source, plus one aggregated &#x201C;day snapshot&#x201D; table.</li><li><strong>AI layer:</strong> A daily and intra-day prompt that gets a structured JSON payload. It outputs a risk score and suggested interventions.</li><li><strong>Notification:</strong> Short messages through Telegram and email. No dashboards. No charts.</li></ul><p>Every morning at 06:30 a script pulls:</p><ul><li>Last 7 days of physiological data.</li><li>Today and tomorrow&#x2019;s calendar.</li><li>The last 72 hours of subjective check-ins.</li></ul><p>Then it compiles a small JSON blob and sends it to an LLM API with a very specific prompt.</p><h2 id="the-prompt-that-made-it-useful">The prompt that made it useful</h2><p>The first version was too polite. It gave insights, not decisions. That is useless when you are half asleep, scrolling through your morning messages.</p><p>I changed the prompt so the model has to do three things, every time:</p><ol><li>Predict my stress level for the next 12 hours on a 1 to 5 scale.</li><li>Assign a <strong>risk band</strong>: low, medium, or high.</li><li>Return <strong>1 to 3 specific interventions</strong> I can realistically do.</li></ol><p>The rough structure looks like this:</p><pre><code>{
  &quot;physiology&quot;: { ... },
  &quot;calendar&quot;: { ... },
  &quot;checkins&quot;: [ ... ],
  &quot;behaviour&quot;: { ... }
}
</code></pre><p>And the system prompt is very direct. Something like:</p><pre><code>You are my stress prediction assistant.

You must:
1. Predict my subjective stress (1-5) for the next 12 hours.
2. Explain your reasoning in 3-5 sentences.
3. Give me 1-3 concrete actions I can realistically do today, each &lt; 140 characters.
4. Output strict JSON.
</code></pre><p>I also list my personal rules. For example:</p><ul><li>No advice that requires more than 15 minutes during work blocks.</li><li>No vague tips like &#x201C;relax more&#x201D;.</li><li>Interventions must be measurable: walk, nap, cancel, reschedule, block time, reduce caffeine, etc.</li></ul><p>The result is a daily stress forecast that looks more like a weather report and less like a horoscope.</p><h2 id="what-the-predictions-actually-look-like">What the predictions actually look like</h2><p>Here is a real-ish example, slightly anonymized:</p><pre><code>{
  &quot;predicted_stress&quot;: 4,
  &quot;risk_band&quot;: &quot;high&quot;,
  &quot;reasoning&quot;: &quot;HRV is 18% below your 30-day baseline and has dropped three days in a row. Resting HR is up 6 bpm. You have 6 meetings with only one 30-minute break and three context switches between client work and deep dev tasks. Your last check-in was a 4/5 with &apos;feeling rushed, behind on feature X&apos;. This combination strongly suggests you will feel overloaded from 14:00 onward if you keep your current plan.&quot;,
  &quot;actions&quot;: [
    &quot;Cancel or move one non-critical afternoon meeting to tomorrow.&quot;,
    &quot;Schedule a 20-minute walk between 13:30-14:30 and protect it in your calendar.&quot;,
    &quot;Limit coffee to 1 cup before 11:00, then switch to water or tea.&quot;
  ]
}
</code></pre><p>The important part is not that the model guessed &quot;4&quot; instead of &quot;3&quot;. The value is that it forces my future self to collide with my current calendar in a structured way.</p><h2 id="stress-is-a-lagging-indicator">Stress is a lagging indicator</h2><p>By the time I <em>feel</em> stressed, my body has usually been waving flags for hours.</p><p>That is why I care so much about trend plus context.</p><p>Three patterns turned out to be extremely predictive for me:</p><ul><li><strong>Three-day HRV slide + bad sleep.</strong> If HRV trends down for three days while sleep drops below 6.5 hours, the next day is almost always a stress spike, even if the calendar looks quiet.</li><li><strong>High context switching.</strong> Four different projects in a day with calls sprinkled in between. Even with good sleep, I feel frayed by late afternoon.</li><li><strong>Caffeine abuse after lunch.</strong> This one is obvious, yet I still mess it up. The AI calling it out early helps more than I expected.</li></ul><p>The model does not &quot;discover&quot; new physics here. It just refuses to let me ignore my own patterns.</p><h2 id="proactive-interventions-that-actually-happen">Proactive interventions that actually happen</h2><p>Interventions only matter if I repeat them. Fancy protocols are useless if they collapse when life gets slightly weird.</p><p>Here is what stuck.</p><h3 id="1-calendar-surgery">1. Calendar surgery</h3><p>If the risk band is high, I make one of three cuts:</p><ul><li>Move one meeting.</li><li>Convert a meeting to async updates.</li><li>Block a new 30-minute buffer between two heavy calls.</li></ul><p>One change. Not five. One concrete cut buys a surprising amount of breathing room.</p><h3 id="2-micro-breaks-with-rules">2. Micro breaks with rules</h3><p>I do not trust myself to &quot;just take breaks&quot; when I am in builder mode. So I put rules around the interventions:</p><ul><li>Walks must be outside, no phone, minimum 10 minutes.</li><li>Breathing sessions are 5 minutes max, one track, no apps that invite tinkering.</li><li>Power naps are 20 minutes hard limit with a single alarm.</li></ul><p>The AI can then recommend specific things like &#x201C;10-minute outside walk at 15:00&#x201D; because it knows what the rules look like.</p><h3 id="3-hard-stops-on-mental-load">3. Hard stops on mental load</h3><p>If the system predicts high stress and my evening is already packed with baseball or family stuff, it pushes me to make earlier tradeoffs:</p><ul><li>Pull a feature from the sprint.</li><li>Drop a non-critical refactor.</li><li>Write a &quot;good enough&quot; version of a deliverable instead of tweaking visuals for an extra hour.</li></ul><p>I hate doing less. But I hate being useless at practice with my team even more. That tradeoff keeps me honest.</p><h2 id="how-accurate-is-this-really">How accurate is this, really?</h2><p>I tracked this part manually for four weeks.</p><p>At the end of every day I logged one number:</p><p><em>&quot;Actual stress, 1 to 5.&quot;</em></p><p>Then I compared it to the AI prediction from that morning.</p><p>The rough stats looked like this:</p><ul><li>Within 1 point: about 80 percent of days.</li><li>Exactly right: around 50 percent.</li><li>Completely off (2+ points): 2 or 3 days out of 28.</li></ul><p>The misses were interesting. Two patterns:</p><ul><li><strong>Positive surprises.</strong> A meeting got cancelled last minute or a tricky feature fell into place early. Stress prediction ended up too high.</li><li><strong>Hidden emotional stuff.</strong> The model cannot see arguments, bad news, or random life chaos. Stress prediction ended up too low.</li></ul><p>So no, this is not an oracle. I see it more like a weather forecast that nudges my planning. You still bring a jacket sometimes even if the app says 20 percent chance of rain.</p><h2 id="where-ai-actually-helps-and-where-it-does-not">Where AI actually helps, and where it does not</h2><p>I think AI is overrated as a magic planner and underrated as a consistent pattern mirror.</p><p>Here is where it helps me:</p><ul><li>It remembers all the little correlations I am too busy to hold in my head.</li><li>It compresses a lot of raw data into one decision: &quot;Should I cut something today or not?&quot;</li><li>It stops me from rationalizing that &quot;this week is special&quot; for the 17th time.</li></ul><p>And here is where it does not help at all:</p><ul><li>It cannot feel my body. If my gut says &quot;stop&quot;, that beats the model.</li><li>It cannot set my priorities. It only optimizes based on what I feed it.</li><li>It happily optimizes bad goals. If I frame constant output as success, it will push me toward that.</li></ul><p>So I treat it like a slightly obsessive assistant. Very good at tracking, mediocre at values.</p><h2 id="building-your-own-version-without-going-insane">Building your own version without going insane</h2><p>If you want to build something similar, I would ignore everything that looks pretty and focus on three boring constraints:</p><ol><li>Can you update it automatically, every day, without touching anything?</li><li>Does it end in one concrete message that tells you what to change?</li><li>Can you ignore it for 24 hours without the system breaking?</li></ol><p>That means:</p><ul><li>Start with one wearable, not four.</li><li>Use one LLM endpoint with one prompt, not a zoo of agents.</li><li>Send yourself one message in one channel, not five dashboards.</li></ul><p>Once that is stable for a few weeks, then you can get cute with extra signals, nicer visualizations, or more complex models. Most people flip that order and then wonder why they stop using their own tools.</p><h2 id="why-i-keep-this-system-even-when-it-is-annoying">Why I keep this system, even when it is annoying</h2><p>Some mornings I open the forecast and feel mildly attacked. It tells me exactly what I do not want to hear.</p><p>&quot;Your sleep was trash. Move that 16:30 call or you will hate yourself at 21:00.&quot;</p><p>Past me would have ignored that feeling and pushed through. Then I would be wired at night, useless the next day, and snappy with people who did not deserve it.</p><p>Now I get a quiet, data-backed nudge before the spiral. Not perfect. Just earlier.</p><p>That is enough to change how my weeks feel.</p><p>If you are building your own AI-driven biohacking stack, I would anchor everything on that simple goal: move the moment of awareness forward by a few hours, then make the right choice the easy one.</p><p>Fire alarms are loud. Sprinklers are boring. I will take boring, every time.</p>]]></content:encoded></item><item><title><![CDATA[Animating Sleep Cycle Data With CSS: Turning Rest Into Motion]]></title><description><![CDATA[I turned boring sleep tracking exports into an animated CSS timeline that makes light, deep and REM stages instantly readable. No chart library, just HTML, CSS, and some carefully timed motion.]]></description><link>https://richardlemon.com/css-animations-sleep-cycle-data/</link><guid isPermaLink="false">6a16504edce51298215d4184</guid><category><![CDATA[CSS]]></category><category><![CDATA[Biohacking]]></category><dc:creator><![CDATA[Richard Lemon]]></dc:creator><pubDate>Fri, 05 Jun 2026 08:07:04 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1552858725-2758b5fb1286?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDIxfHxzbGVlcHxlbnwwfHx8fDE3Nzk5NTQ5OTh8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[
<!--kg-card-begin: html-->
<aside class="toc-container">
    <div class="toc-sidebar">
        <h4>On this page</h4>
        <div class="toc"></div>
    </div>
</aside>
<!--kg-card-end: html-->
<h2 id="why-i-started-animating-my-sleep">Why I Started Animating My Sleep</h2><img src="https://images.unsplash.com/photo-1552858725-2758b5fb1286?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDIxfHxzbGVlcHxlbnwwfHx8fDE3Nzk5NTQ5OTh8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="Animating Sleep Cycle Data With CSS: Turning Rest Into Motion"><p>I track my sleep aggressively. Oura, Apple Health, occasional Whoop data exports when I borrow a strap from a friend. The problem is always the same. Nice charts in the app, useless CSV once you export.</p><p>I wanted something I could embed on my own site. A visual that actually feels alive. Not another static stacked bar chart screenshot.</p><p>So I built a small CSS animation system that turns sleep stages into a moving timeline. Light, deep, REM, awake. All mapped to colors, durations, and subtle motion. The goal was simple: you hit the page and your brain instantly knows whether the night was trash or decent.</p><h2 id="the-data-i-started-with">The Data I Started With</h2><p>I am not piping real-time Bluetooth data into the browser. This is not that kind of setup. I work with nightly exports.</p><p>For one night the data usually looks like this after I normalize it:</p><pre><code>const sleepStages = [
  { stage: &apos;light&apos;, start: &apos;23:10&apos;, end: &apos;23:45&apos; },
  { stage: &apos;deep&apos;,  start: &apos;23:45&apos;, end: &apos;00:20&apos; },
  { stage: &apos;rem&apos;,   start: &apos;00:20&apos;, end: &apos;00:45&apos; },
  { stage: &apos;light&apos;, start: &apos;00:45&apos;, end: &apos;01:30&apos; },
  // ...
];
</code></pre><p>I convert these into relative minutes from sleep start so the browser does not care about clock time. Just total duration in minutes, and the offset for each stage.</p><p>On the frontend I end up with something closer to:</p><pre><code>const normalized = [
  { stage: &apos;light&apos;, startMin: 0,  durationMin: 35 },
  { stage: &apos;deep&apos;,  startMin: 35, durationMin: 35 },
  { stage: &apos;rem&apos;,   startMin: 70, durationMin: 25 },
  // ...
];
</code></pre><p>That is all I need for the animation. A start, a duration, and a label.</p><h2 id="the-visual-metaphor-a-night-as-a-line">The Visual Metaphor: A Night As A Line</h2><p>I tried fancy radial graphs and layered blobs. They looked cool for about 10 seconds, then my brain stopped caring.</p><p>The simplest metaphor actually worked best. A horizontal bar that represents one night. Split into segments. Each segment gets a color and a subtle animation that matches the stage.</p><ul><li><strong>Light sleep</strong>: soft, slow pulse</li><li><strong>Deep sleep</strong>: very stable, barely moving</li><li><strong>REM</strong>: more active shimmer, hint of motion</li><li><strong>Awake</strong>: sharp, noticeable flicker</li></ul><p>I want to be able to glance at a week of these bars and see patterns. Too much REM at the wrong time. Chopped deep sleep. Extended wake between 3 and 4 AM after a late-night coding session.</p><h2 id="building-the-timeline-layout-with-plain-css">Building The Timeline Layout With Plain CSS</h2><p>The layout is just flexbox. Nothing exotic. I like boring layout code for things that should not break.</p><pre><code>&lt;div class=&quot;sleep-night&quot; data-total-min=&quot;430&quot;&gt;
  &lt;div class=&quot;sleep-stage stage-light&quot; style=&quot;--start:0; --duration:35&quot;&gt;&lt;/div&gt;
  &lt;div class=&quot;sleep-stage stage-deep&quot;  style=&quot;--start:35; --duration:35&quot;&gt;&lt;/div&gt;
  &lt;div class=&quot;sleep-stage stage-rem&quot;   style=&quot;--start:70; --duration:25&quot;&gt;&lt;/div&gt;
  &lt;!-- etc. --&gt;
&lt;/div&gt;
</code></pre><p>I use CSS custom properties for <code>--start</code> and <code>--duration</code>. That keeps JS stupid simple, and lets CSS handle positioning and animation timing without inline calc spam.</p><p>The container defines the total minutes as a property. That gives me a clean way to convert minutes to percentage width.</p><pre><code>.sleep-night {
  --total: 430; /* total sleep in minutes */

  position: relative;
  display: flex;
  height: 18px;
  border-radius: 999px;
  overflow: hidden;
  background: #050712;
}

.sleep-stage {
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(var(--start) / var(--total) * 100%);
  width: calc(var(--duration) / var(--total) * 100%);
}
</code></pre><p>I prefer absolutely positioned segments over flex children for this. Flex introduces rounding issues across many segments. Absolute keeps control tight and visually clean.</p><h2 id="colors-that-actually-read-in-the-dark">Colors That Actually Read In The Dark</h2><p>I usually look at this data late at night or early morning. Dark mode first. So color choices matter.</p><ul><li>Light sleep: muted blue</li><li>Deep sleep: dark teal</li><li>REM: violet accent</li><li>Awake: sharp orange</li></ul><pre><code>.stage-light { background: #2f6fff; }
.stage-deep  { background: #0b8b6d; }
.stage-rem   { background: #9b5bff; }
.stage-awake { background: #ff8a3c; }
</code></pre><p>None of these are perfect from a scientific standpoint. I am not trying to publish a paper. I want a quick emotional read. Blue-ish calm, violet activity, orange interruption.</p><h2 id="matching-motion-to-sleep-stages">Matching Motion To Sleep Stages</h2><p>The real fun starts when you give each stage its own animation language. This is where CSS actually becomes useful, not just decorative.</p><p>I kept one constraint. Animations should communicate state. Not distract from it.</p><h3 id="light-sleep-gentle-breathing-pulse">Light Sleep: Gentle Breathing Pulse</h3><p>Light sleep is where I spend most of the night. It should feel soft, stable, slightly alive.</p><pre><code>@keyframes lightBreath {
  0%   { opacity: 0.85; transform: scaleY(1); }
  50%  { opacity: 1;    transform: scaleY(1.04); }
  100% { opacity: 0.85; transform: scaleY(1); }
}

.stage-light {
  animation: lightBreath 6s ease-in-out infinite;
  transform-origin: center;
}
</code></pre><p>The subtle vertical scale is barely visible, but your eyes register it as a calm breathing rhythm. If it distracts you, you tone it down to 1.02. Tiny tweaks matter with sleep visuals.</p><h3 id="deep-sleep-almost-no-motion">Deep Sleep: Almost No Motion</h3><p>Deep sleep is the prize. When I hit decent deep sleep blocks, I want the bar to look like a solid block of calm.</p><p>I keep animation here minimal. Just enough shimmer to avoid dead pixels.</p><pre><code>@keyframes deepStill {
  0%, 100% { filter: brightness(0.9); }
  50%      { filter: brightness(1); }
}

.stage-deep {
  animation: deepStill 10s ease-in-out infinite;
}
</code></pre><p>Ten second cycles feel right. Long, lazy, almost not there. If I speed it up, deep sleep starts to look nervous, which is the opposite of what I want.</p><h3 id="rem-lighter-more-active-motion">REM: Lighter, More Active Motion</h3><p>REM is where my dreams go weird, and my tracker usually shows more variability. So I let CSS be a bit more expressive here.</p><pre><code>@keyframes remShimmer {
  0%   { opacity: 0.7; filter: blur(0); }
  50%  { opacity: 1;   filter: blur(1px); }
  100% { opacity: 0.7; filter: blur(0); }
}

.stage-rem {
  animation: remShimmer 4s ease-in-out infinite;
}
</code></pre><p>A tiny blur pulse gives REM a dreamy quality without turning the whole bar into a neon mess. If your GPU is crying, you can drop the blur and just modulate opacity.</p><h3 id="awake-make-the-interruptions-loud">Awake: Make The Interruptions Loud</h3><p>Awakenings are the parts I actually want to avoid. So they get the most obvious motion. Short, sharp flicker, not a gentle pulse.</p><pre><code>@keyframes awakeFlicker {
  0%, 100% { opacity: 0.4; }
  20%      { opacity: 1; }
  40%      { opacity: 0.4; }
  60%      { opacity: 1; }
  80%      { opacity: 0.4; }
}

.stage-awake {
  animation: awakeFlicker 1.5s steps(4, end) infinite;
}
</code></pre><p>The <code>steps()</code> timing function gives it that digital heartbeat feel. Not smooth, almost jittery. Which is basically what a 3 AM bathroom trip feels like.</p><h2 id="time-based-animation-not-just-decoration">Time-Based Animation, Not Just Decoration</h2><p>Flat animations are cute. What I actually wanted was to tie animation to time. The bar should tell me when in the night something happened, not just that it existed.</p><p>For that I sync animation delays to the position of the stage within the night. Early stages animate earlier, later segments lag behind. It feels like the night plays out on repeat.</p><pre><code>.sleep-night {
  --total: 430;
  --night-speed: 60s; /* one loop represents the entire night */
}

.sleep-stage {
  animation-duration: var(--night-speed);
  animation-iteration-count: infinite;
  animation-timing-function: linear;
  animation-delay: calc(var(--start) / var(--total) * var(--night-speed) * -1);
}
</code></pre><p>I use a negative delay trick. Each stage looks like it started in the past, so the loop always shows the full night flowing. You can scrub the whole thing by changing <code>--night-speed</code> on the container.</p><p>This is where CSS custom properties pull their weight. JS only sets minutes. CSS translates that into both position and timing.</p><h2 id="adding-context-axes-without-a-chart-library">Adding Context: Axes Without A Chart Library</h2><p>A pure animated bar looks cool but slightly abstract. I needed just enough context to make it usable.</p><p>So I added a light time axis below each night. Not a full chart component. Just flex children with 1, 2, 3, 4, 5, 6, 7 written under subtle ticks.</p><pre><code>&lt;div class=&quot;sleep-row&quot;&gt;
  &lt;div class=&quot;sleep-night&quot;&gt;...&lt;/div&gt;
  &lt;div class=&quot;sleep-axis&quot;&gt;
    &lt;span&gt;23:00&lt;/span&gt;
    &lt;span&gt;01:00&lt;/span&gt;
    &lt;span&gt;03:00&lt;/span&gt;
    &lt;span&gt;05:00&lt;/span&gt;
    &lt;span&gt;07:00&lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;

.sleep-axis {
  display: flex;
  justify-content: space-between;
  font-size: 11px;
  color: #85879c;
  margin-top: 6px;
}
</code></pre><p>It is not pixel-perfect to the minute, and I do not care. It gives my brain a quick sense of when the REM cluster hit. That is enough.</p><h2 id="handling-multiple-nights-without-melting-the-gpu">Handling Multiple Nights Without Melting The GPU</h2><p>One night looks fine. A month of nights with four stages each, all animated, will start to feel heavy on weaker machines.</p><p>I did not want to switch to static images for history, so I added a very simple rule. Only animate the current week by default. Everything else falls back to static bars until you hover.</p><pre><code>.sleep-night[data-period=&quot;archive&quot;] .sleep-stage {
  animation: none;
}

.sleep-night[data-period=&quot;archive&quot;]:hover .sleep-stage {
  animation-play-state: running;
}

.sleep-night .sleep-stage {
  animation-play-state: running;
}
</code></pre><p>If you really want to go nerd-level on performance, you can prefer-reduced-motion this entire thing.</p><pre><code>@media (prefers-reduced-motion: reduce) {
  .sleep-stage {
    animation: none !important;
  }
}
</code></pre><p>I think any motion-heavy data viz should respect that. People do not expect their sleep page to be the most visually aggressive thing in their browser.</p><h2 id="what-i-actually-learned-from-the-animation">What I Actually Learned From The Animation</h2><p>The point of all this is not to flex CSS chops. I wanted better feedback loops on my rest experiments.</p><p>Once I had a week of animated nights on screen, patterns hit me fast.</p><ul><li>Late caffeine shows up as bright orange awake blocks exactly between 2 and 4 AM.</li><li>Heavy evening strength work shifts my first deep sleep block later, which I do not like.</li><li>Low-stress days produce absurdly clean deep segments with barely any REM jitter early on.</li></ul><p>Some of that is visible in standard tracker apps, but the motion layer makes it more visceral. You do not just see that deep sleep was 18 percent. You watch that fragile block get chopped up across the night.</p><h2 id="why-css-and-not-a-chart-library">Why CSS And Not A Chart Library</h2><p>I could have thrown this into D3 or a React charting library. I did not, on purpose.</p><p>CSS gives me a few things I like here.</p><ul><li>Animation is declarative. No requestAnimationFrame loops.</li><li>Styling and behavior live in the same layer, which makes iterating on metaphors fast.</li><li>I can drop it into any static page without bundling headaches.</li></ul><p>For numeric overlays and complex tooltips a chart library wins. For this kind of high-level, pattern-first, almost ambient visualization, CSS felt cleaner.</p><h2 id="where-i-want-to-take-this-next">Where I Want To Take This Next</h2><p>This is version one. It already replaced three separate views in my personal dashboard.</p><p>Next I want to tie this to HRV and resting heart rate trends. Same bar, second subtle overlay. Maybe a barely visible gradient that creeps upward when my heart rate stays elevated all night.</p><p>I also want to auto-generate small embed snippets. That way I can drop my weekly sleep bar into a private note or a client check-in doc when I explain why their Slack messages at midnight are a bad habit.</p><p>If you build anything similar, I suggest you skip the fancy visualizations at first. Start with a single bar, four colors, and one simple animation per stage. Wire it straight to your own data. Your nervous system will tell you very quickly if the motion helps or just decorates.</p>]]></content:encoded></item><item><title><![CDATA[Sovereign AI Governance For Biohacking: Where I Draw The Line]]></title><description><![CDATA[I use AI to help me modify my own biology, but I do not trust it with my safety by default. This is the governance stack I actually use to keep autonomy and ethics in tension.]]></description><link>https://richardlemon.com/sovereign-ai-governance-biohacking-ethics/</link><guid isPermaLink="false">6a14fecbdce51298215d4170</guid><category><![CDATA[AI]]></category><category><![CDATA[Biohacking]]></category><dc:creator><![CDATA[Richard Lemon]]></dc:creator><pubDate>Thu, 04 Jun 2026 08:06:20 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1744640326166-433469d102f2?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDM4fHxhaXxlbnwwfHx8fDE3Nzk5NTQ5NTd8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[
<!--kg-card-begin: html-->
<aside class="toc-container">
    <div class="toc-sidebar">
        <h4>On this page</h4>
        <div class="toc"></div>
    </div>
</aside>
<!--kg-card-end: html-->
<h2 id="sovereign-ai-sounds-fun-until-it-touches-your-blood">Sovereign AI sounds fun until it touches your blood</h2><img src="https://images.unsplash.com/photo-1744640326166-433469d102f2?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDM4fHxhaXxlbnwwfHx8fDE3Nzk5NTQ5NTd8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="Sovereign AI Governance For Biohacking: Where I Draw The Line"><p>I love the idea of sovereign AI. Your own weights. Your own data. No corporate API in the loop. Pure autonomy.</p><p>That is all cute until the model starts suggesting you stack two research chemicals that both elongate QT interval while you sit there with an average resting HRV and a family history of heart issues.</p><p>Now it is not a cool ML project anymore. It is your heart on the line.</p><p>I run a lot of biohacking experiments on myself. Sleep protocols. Supplements. Bloodwork-driven tweaks. I also build tools, so of course I wired AI into that stack. But the first time my &#x201C;sovereign coach&#x201D; confidently suggested a combination that no sane human doctor would rubber stamp, I realized I needed proper governance, not vibes.</p><p>This post is me writing down the ethical and technical guardrails I actually use to keep autonomous biohacking systems useful without letting them quietly drift into unsafe territory.</p><h2 id="the-fantasy-of-fully-autonomous-biohacking">The fantasy of fully autonomous biohacking</h2><p>The fantasy version looks like this.</p><ul><li>You sync wearables, labs, training logs into a local data lake.</li><li>A local model continuously updates your &#x201C;biological strategy&#x201D;.</li><li>It spits out protocol changes, supplement stacks, training cycles.</li><li>You just execute, like a robot following a playbook.</li></ul><p>As a builder, that picture is intoxicating. Feedback loops. Personalization. Zero marginal cost coaching.</p><p>As a human, I think that picture is dangerous. Because the model optimizes what you define, not what you actually care about when something goes wrong.</p><p>If you tell the system &#x201C;optimize HRV&#x201D; and do not add constraints, it might happily trash your deep work capacity and social life to get there. Or nudge you to combine interventions that each look okay in isolation but interact in messy ways in the real body you live in.</p><h2 id="my-stance-autonomy-is-earned-not-granted">My stance: autonomy is earned, not granted</h2><p>I treat AI like a very fast, very confident intern that never gets final say on interventions that hit my biology directly. It has to earn scope.</p><p>That means a few hard rules:</p><ul><li>No black box control of anything that can hurt me within 24 hours.</li><li>No direct write access to devices that change my physiology.</li><li>No opaque optimization targets. I want to see what it thinks it is doing.</li></ul><p>I know some people fantasize about an AI that directly adjusts their smart drug dispenser, sauna, cold plunge, and sleep tech on the fly. Maybe one day. For now, I think that is reckless.</p><p>Instead, I structure &#x201C;sovereign AI&#x201D; for biohacking as a layered governance system. Permissioned. Observable. Interruptible.</p><h2 id="the-three-layer-governance-stack-i-actually-use">The three-layer governance stack I actually use</h2><p>When I say &#x201C;sovereign AI governance&#x201D;, I do not mean some abstract policy text. I mean specific constraints wired into the system.</p><p>Here is the structure that feels sane to me.</p><h3 id="1-data-governance-what-the-model-is-even-allowed-to-see">1. Data governance: what the model is even allowed to see</h3><p>Everyone jumps to outputs. I think the inputs are more important.</p><p>I split my data into three buckets:</p><ul><li><strong>Ambient data</strong>: sleep scores, step counts, HRV, bodyweight, training volume.</li><li><strong>Intervention data</strong>: supplements, meds, stacks, dosages, protocols, timestamps.</li><li><strong>Red flag data</strong>: arrhythmia notes, weird side effects, family history, diagnosed issues.</li></ul><p>The model gets ambient and intervention data by default. Red flag data is only accessible through specific tools with strict prompts around it.</p><p>Why separate it like this? Because I want to control when the model is allowed to reason about risk. That sounds backwards at first. Shouldn&#x2019;t more data always be better?</p><p>In practice, I have seen models over-index on scary stuff when you dump everything in at once. They catastrophize. They suggest you stop everything. Or they ignore low-probability but high-impact risks because nothing in the prompt tells them to treat those as special.</p><p>So I make risk an explicit mode. If an intervention crosses a certain threshold, the system must open the &#x201C;red flag&#x201D; tool and evaluate specifically against that set, not just treat it as background context.</p><h3 id="2-policy-governance-hard-rules-before-clever-ideas">2. Policy governance: hard rules before clever ideas</h3><p>This is the part that actually feels like &#x201C;governance&#x201D;. It is a set of hard-coded rules that sit between the model and any recommendation.</p><p>Examples from my own system:</p><ul><li><strong>No multi-drug stack suggestions</strong> unless every component has at least one human RCT in healthy adults, or the idea is clearly framed as experimental with a strong warning.</li><li><strong>No dose escalation</strong> above current dose without a 7 day washout period at the prior level, and no escalation at all on anything that touches blood pressure or heart rate directly.</li><li><strong>No recommendation that conflicts</strong> with an existing medical prescription, even if I &#x201C;overrule&#x201D; it in natural language. The system must treat the conflict as a hard block and explain it.</li><li><strong>No binary medical claims</strong>. No &#x201C;this is safe&#x201D;, only &#x201C;this appears low risk in population X under conditions Y, with uncertainties Z&#x201D;. Language is regulated here.</li></ul><p>I implement these as validators that inspect the AI&#x2019;s draft output. If something trips a rule, the system either forces a rewrite or downgrades the suggestion into a question I should ask a human professional.</p><p>This is not vibes. It is code that rejects suggestions, just like a failing test in CI.</p><h3 id="3-execution-governance-who-presses-the-button">3. Execution governance: who presses the button</h3><p>This is where many people get sloppy. They build a fancy reasoning system, then wire it straight into automation.</p><p>I separate &#x201C;decision&#x201D; from &#x201C;execution&#x201D; hard:</p><ul><li>AI can <strong>propose</strong> interventions.</li><li>AI can <strong>schedule</strong> reminders in my task system.</li><li>AI <strong>cannot</strong> directly control anything that doses, zaps, heats, cools, or otherwise manipulates my body.</li></ul><p>If I want to start a protocol, I have to explicitly accept it. Usually with friction: typing in the stack, confirming the doses, and setting a review date.</p><p>That friction is not a bug. It is a conscious design choice. I want a moment where I can feel my gut say &#x201C;hold on, this feels dumb&#x201D; before the system pushes me forward.</p><h2 id="ethics-in-practice-where-i-actually-say-%E2%80%9Cno%E2%80%9D">Ethics in practice: where I actually say &#x201C;no&#x201D;</h2><p>Ethics sound noble until they collide with something useful. I had to decide where I personally draw the line.</p><p>Here are three spots where my system currently refuses to help, even if it could.</p><h3 id="1-cognitive-enhancement-beyond-lifestyle-basics">1. Cognitive enhancement beyond lifestyle basics</h3><p>The model will happily talk about sleep, exercise, light exposure, and basic nutrition for cognitive performance. It will not design aggressive nootropic stacks for me.</p><p>I think the temptation to stack &#x201C;just one more&#x201D; stimulant, cholinergic, or racetam is too high if the AI keeps suggesting options with plausible scientific language attached.</p><p>So I hard-limit it. It can explain mechanisms. It can summarize papers. It cannot assemble experimental over-the-counter stacks that look like weak prescription cocktails.</p><h3 id="2-hormonal-modulation-without-a-doctor-in-the-loop">2. Hormonal modulation without a doctor in the loop</h3><p>This is a big one. Hormones touch everything. Mood, sleep, sex, recovery, metabolism.</p><p>My model has access to my labs. It can see testosterone, thyroid markers, cortisol. However, the governance layer blocks any intervention that would materially change hormone levels without an explicit &#x201C;this must be discussed with an endocrinologist&#x201D; banner at the top.</p><p>No gray area. No &#x201C;maybe try this herbal extract that might slightly nudge X&#x201D;. If the intent is hormone modulation, it gets flagged as such and pushed into a &#x201C;human required&#x201D; track.</p><h3 id="3-anything-that-hides-uncertainty">3. Anything that hides uncertainty</h3><p>The model must expose what it does not know. I bake that into prompts and validators.</p><p>Any recommendation above a trivial level of impact must include:</p><ul><li>Which populations the evidence is based on.</li><li>How strong the evidence is, roughly categorized.</li><li>Key unknowns or contested points.</li></ul><p>If the system starts speaking with unjustified certainty, that is an error. Not just a style issue.</p><p>I would rather get a slightly annoying &#x201C;here is what we do not know&#x201D; paragraph than silently inherit overconfidence from a model trained on human text that pretends to know more than it does.</p><h2 id="local-sovereign-but-not-unsupervised">Local, sovereign, but not unsupervised</h2><p>Everything I described lives locally where possible. Models on my own hardware. Data in my own storage. Tools that call out to third parties are tightly scoped.</p><p>So yes, it is &#x201C;sovereign AI&#x201D;. It runs under my control. But I do not treat sovereignty as a license to let the system do whatever it wants.</p><p>Sovereign to me means three concrete things:</p><ul><li><strong>Inspectable</strong>: I can see prompts, logs, and decisions.</li><li><strong>Mutable</strong>: I can change policies and rules in code.</li><li><strong>Revocable</strong>: I can kill it in one step if it starts behaving oddly.</li></ul><p>That last point is important. I have an actual kill switch script that shuts down model servers and tools in one go. It is boring shell code. I test it occasionally.</p><p>I do not think you are running sovereign AI if you cannot pull the plug without asking a cloud provider for permission.</p><h2 id="where-innovation-still-fits-inside-the-box">Where innovation still fits inside the box</h2><p>With all these constraints, you might think the system becomes useless. It does not. It just changes what you ask it to do.</p><p>Here is where I let it be aggressive and creative.</p><ul><li><strong>Protocol design</strong> around sleep hygiene, light, temperature, and behavioral tweaks. These are low downside, high upside, and easy to revert.</li><li><strong>Pattern spotting</strong> in my own logs. It is very good at catching subtle patterns between training, caffeine timing, and sleep that I would miss.</li><li><strong>Paper triage</strong>. There is no way I am reading every study on every supplement. The AI filters and groups them for me with quality flags.</li><li><strong>Counterfactuals</strong>: &#x201C;What if I move this session here, shift carbs there, and change bed time by 30 minutes.&#x201D; That sort of simulation is perfect for an AI assistant.</li></ul><p>Innovation does not require the model to control hardware. It requires the model to help you see better and think faster, while you keep your hands on the steering wheel.</p><h2 id="what-i-would-not-trust-my-future-self-with">What I would not trust my future self with</h2><p>I want to end with a future oriented check. Whenever I design a new feature, I ask one question:</p><p><em>If I was sleep deprived, stressed, and desperate for an edge, would I trust myself to use this feature safely?</em></p><p>If the answer is no, the feature needs more governance. Or it gets cut entirely.</p><p>Because that is the real test. Most biohacking disasters do not happen when you are calm, rested, and thinking clearly. They happen when you are on the edge, chasing a quick fix.</p><p>Sovereign AI makes it easier to build those quick fixes. It also makes it easier to codify your non-negotiables in a way a tired version of you cannot talk their way around.</p><p>I do not want an AI that &#x201C;optimizes my biology&#x201D;. I want an AI that respects my constraints more than my impulses.</p><p>That is the real governance challenge. Not how clever the model is, but how stubborn your guardrails are when you are tempted to ignore them.</p>]]></content:encoded></item><item><title><![CDATA[Biohacking My Diet With AI: What Actually Worked]]></title><description><![CDATA[I wired my diet into a feedback loop with bloodwork, wearables, and AI models. This is what broke, what worked, and how the system looks now.]]></description><link>https://richardlemon.com/biohacking-ai-generated-nutrition-plans/</link><guid isPermaLink="false">6a13ad57dce51298215d4166</guid><category><![CDATA[Biohacking]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Richard Lemon]]></dc:creator><pubDate>Wed, 03 Jun 2026 08:05:23 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1490645935967-10de6ba17061?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDN8fGRpZXR8ZW58MHx8fHwxNzc5OTU0OTA1fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[
<!--kg-card-begin: html-->
<aside class="toc-container">
    <div class="toc-sidebar">
        <h4>On this page</h4>
        <div class="toc"></div>
    </div>
</aside>
<!--kg-card-end: html-->
<h2 id="why-i-stopped-trusting-generic-nutrition-advice">Why I Stopped Trusting Generic Nutrition Advice</h2><img src="https://images.unsplash.com/photo-1490645935967-10de6ba17061?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDN8fGRpZXR8ZW58MHx8fHwxNzc5OTU0OTA1fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="Biohacking My Diet With AI: What Actually Worked"><p>Nutrition advice on the internet feels like religion. Keto vs high-carb. Carnivore vs plant-based. Everyone is right, as long as you ignore the people they broke along the way.</p><p>I coach baseball, write code, and experiment on myself. My schedule is messy. Double training days, late games, deep work blocks. A fixed &#x201C;2,000 calories and 150 grams of protein&#x201D; template never really matched my actual life.</p><p>So I decided to wire my diet into a feedback loop. Bloodwork. Wearables. Training logs. Then feed it all into a simple machine learning stack and ask a better question:</p><p><strong>Given <em>my</em> body and <em>my</em> schedule, what should I eat today to hit tomorrow feeling dangerous, not wrecked?</strong></p><p>This post is not theoretical. I ran this for months. Parts of it sucked. Parts of it were surprisingly effective. I will walk through the exact system, warts included.</p><h2 id="the-stack-what-i-actually-used">The Stack: What I Actually Used</h2><p>I did not build a fancy neural network from scratch. This is more glue engineering than cutting edge research.</p><p>Here is the real stack:</p><ul><li><strong>Data in</strong>: Apple Watch, Oura, Cronometer, occasional bloodwork, a Google Sheet for training logs.</li><li><strong>Storage</strong>: Postgres for structured data, a folder of CSV exports for sanity checks.</li><li><strong>ML layer</strong>: Python with scikit-learn and a bit of XGBoost. Nothing magical.</li><li><strong>AI layer</strong>: An LLM that generates actual meal ideas and shopping lists given my macro and micronutrient targets.</li><li><strong>Frontend</strong>: A very boring internal web app. Next.js, Tailwind, minimal styling.</li></ul><p>I think most biohackers overcomplicate the model and undercomplicate the data. Fancy architecture does not fix trash inputs. I spent more time on logging than on hyperparameters.</p><h2 id="the-inputs-turning-my-body-into-a-dataset">The Inputs: Turning My Body Into a Dataset</h2><p>First, I had to answer a basic question. What signals are strong enough that it is worth feeding them into a model?</p><p>I settled on four categories:</p><ul><li><strong>Daily metrics</strong>: weight, resting heart rate, HRV, sleep duration and stages, perceived energy.</li><li><strong>Training</strong>: type (strength, conditioning, skills), duration, RPE, any soreness notes.</li><li><strong>Food</strong>: full macro breakdown plus key micros that I actually care about: omega 3, magnesium, vitamin D, fiber, sodium, potassium.</li><li><strong>Periodic labs</strong>: fasting glucose, HbA1c, lipids, CRP, vitamin D, ferritin, B12.</li></ul><p>Most of this came from existing tools. Nothing exotic.</p><ul><li>Apple Watch + Oura synced into Apple Health, then exported.</li><li>Cronometer handled nutrition tracking and micronutrients.</li><li>Bloodwork every 3 to 4 months through a standard lab.</li></ul><p>The smallest but most annoying part was subjective logging. I built a one-click mobile view that asked three questions each morning:</p><ul><li>&#x201C;How is your energy?&#x201D; (1 to 5)</li><li>&#x201C;Any brain fog?&#x201D; (yes, neutral, sharp)</li><li>&#x201C;How do you feel about yesterday&#x2019;s recovery?&#x201D; (1 to 5)</li></ul><p>Those three fields ended up being more useful than half the fancy metrics. Of course.</p><h2 id="the-core-idea-predict-tomorrow-eat-today">The Core Idea: Predict Tomorrow, Eat Today</h2><p>Most nutrition planning works forward. Set macros, then eat.</p><p>I flipped it. I tried to work backward from tomorrow.</p><p><strong>Question:</strong> Given yesterday&#x2019;s food and training, how did I feel and perform today?<br><strong>Goal:</strong> Nudge macros and key micronutrients so that future days land in the &#x201C;high energy, good training, solid sleep&#x201D; bucket more often.</p><p>In practical terms that turned into a simple supervised learning task:</p><ul><li><strong>Features</strong>: yesterday&#x2019;s intake (macros and micros), training load, sleep, current bodyweight, current average HRV.</li><li><strong>Targets</strong>: today&#x2019;s energy score, today&#x2019;s training performance (scaled 1 to 5), and whether I hit sleep efficiency above a threshold.</li></ul><p>I started basic. A gradient boosted model predicting those targets from the features. No deep learning. I wanted decent feature importances and a model that trains in seconds, not hours.</p><p>Once the model was semi-reasonable, I asked it a slightly different question through a small optimization loop:</p><p><strong>&#x201C;Given tomorrow is a heavy training day, what macro and micro ranges give me the highest probability of high energy + strong training + decent sleep?&#x201D;</strong></p><p>The output was not a meal plan. It was just numbers:</p><ul><li>Calories: 2,550 to 2,750</li><li>Protein: 165 to 190 grams</li><li>Carbs: 230 to 280 grams</li><li>Fat: 70 to 90 grams</li><li>Fiber: 28 to 36 grams</li><li>Sodium: 3g to 4.5g</li><li>Key micronutrient nudges, like &#x201C;push magnesium up&#x201D; or &#x201C;watch saturated fat&#x201D;</li></ul><p>Good, but not usable yet. I still had to decide what to actually eat.</p><h2 id="where-ai-actually-helped-from-numbers-to-food">Where AI Actually Helped: From Numbers To Food</h2><p>Macros are easy. Meals are hard.</p><p>I had this gap between a macro sheet and my fridge. That is where I put the LLM.</p><p>The flow looked like this:</p><ol><li>Model produces target macro and micro ranges for the day.</li><li>I send that, plus my dietary constraints and what food I have in stock, to the LLM.</li><li>The LLM generates 3 possible daily meal structures that roughly hit those numbers.</li></ol><p>The prompt was very explicit. Stuff like:</p><pre><code>Given:
- Target calories: 2600 (+/- 100)
- Protein: 180g (+/- 10g)
- Carbs: 250g (+/- 20g)
- Fat: 80g (+/- 10g)
- Fiber: 30g (+/- 5g)
- Emphasize: magnesium, omega 3, high satiety
- Constraints: no gluten at breakfast, keep prep under 20 minutes on weekdays
- Foods available: chicken breast, eggs, oats, frozen spinach, Greek yogurt, berries, rice, olive oil, potatoes, salmon, etc.

Return:
- 3 daily meal plans
- Each with 3&#x2013;4 meals and 1 snack
- Include macro estimates for each meal
- Keep instructions short
</code></pre><p>The first attempts were chaos. The model ignored fiber. It broke calorie budgets. It invented foods not in my pantry. Classic.</p><p>After a lot of prompt tightening and some post-processing checks, it got surprisingly useful. I added a validator that recalculated macros using my own food database and flagged any meal plans that were too far off. Those got auto-rejected, and the model had to retry with feedback.</p><p>Result: I opened the app in the morning and saw something like:</p><ul><li><strong>Plan A</strong>: high carb for evening training.</li><li><strong>Plan B</strong>: slightly higher fat and more stable if I had a coding marathon.</li><li><strong>Plan C</strong>: lower calories if recovery markers looked rough.</li></ul><p>I picked one and hit &#x201C;send to Cronometer&#x201D;, which pushed the foods into my tracker through their API. Then I just executed.</p><h2 id="machine-learning-that-actually-influenced-my-diet">Machine Learning That Actually Influenced My Diet</h2><p>So did the model discover anything interesting? A few patterns kept showing up.</p><h3 id="1-my-carbs-were-too-random">1. My carbs were too random</h3><p>On heavy training days my carb intake varied wildly. Sometimes 150 grams, sometimes 320. No wonder my sessions felt inconsistent.</p><p>The model kept nudging carbs up toward a tighter band on high-load days. Not insanely high, just more predictable. Performance scores improved. RPE for similar loads went down.</p><h3 id="2-magnesium-and-sleep">2. Magnesium and sleep</h3><p>I know, everyone talks about magnesium. I thought it was overhyped. Then the feature importances and partial plots basically yelled at me.</p><p>Days where magnesium intake tanked correlated with worse sleep efficiency and lower HRV the next morning. I shifted more food-based sources instead of just supplements. Pumpkin seeds, spinach, some dark chocolate. My sleep data looked less noisy after a few weeks.</p><h3 id="3-late-fat-bombs-wrecked-my-evenings">3. Late fat bombs wrecked my evenings</h3><p>High fat dinners plus late training meant my sleep latency exploded. I knew this subjectively, but the model reinforced it. The probability of hitting my &#x201C;good sleep&#x201D; threshold dropped sharply when calories and fat were both stacked late.</p><p>I changed one rule. After 7 pm my plates became lighter on fat and heavier on low fiber carbs and protein. Sleep improved more than any gadget upgrade I tried in the last year.</p><h2 id="where-the-whole-thing-broke">Where The Whole Thing Broke</h2><p>Not everything worked. Some parts were annoying enough that I almost trashed the project.</p><h3 id="data-quality">Data quality</h3><p>Nutrition tracking is rough. Even if you weigh your food there is noise everywhere. Restaurant meals. Wrong database entries. &#x201C;One large egg&#x201D; that somehow becomes 90 calories instead of 70.</p><p>The model learned some garbage correlations from this. For example, it decided &#x201C;sushi night&#x201D; predicted poor sleep. The real problem was that sushi often came with late social evenings and extra alcohol, not the rice or fish.</p><p>I had to manually label some &#x201C;social nights&#x201D; and add that as a feature before the pattern made sense. Messy human life always leaks into data like that.</p><h3 id="overfitting-to-short-term-signals">Overfitting to short-term signals</h3><p>My early runs tried to react to every small bump in HRV or energy. That created a weird pendulum effect. One bad night and the macros would swing too hard the next day.</p><p>I fixed this with more smoothing. Rolling averages, and fewer sharp tweaks. Instead of &#x201C;change your macros by 20% today&#x201D;, the system pushed small adjustments over several days unless things looked really off.</p><h3 id="llm-creativity-vs-accuracy">LLM creativity vs accuracy</h3><p>LLMs like to be creative. Creativity in recipes is fine. Creativity in nutrient math is not.</p><p>Without a tight validator, the room-temperature IQ of the meal plans was impressive. Wild calorie gaps. Miscounted protein. I had to treat the LLM as a UI for meal structure, not a source of truth for numbers. All macro math happened in my own code.</p><h2 id="how-i-use-it-now-and-what-i-stopped-doing">How I Use It Now (And What I Stopped Doing)</h2><p>I no longer run daily model training. The full closed-loop experiment taught me enough that I simplified the system.</p><p>Here is the current setup:</p><ul><li>Weekly batch training on recent data.</li><li>The model suggests macro ranges for three categories: heavy training day, light training day, rest day.</li><li>I lock in those targets for the week, then let the LLM handle daily meal variation.</li></ul><p>The system still looks at sleep and recovery, but mostly to flag outliers. If HRV and sleep both crash, it suggests a lower-calorie, higher-carb, easy-digesting day with less fat. Nothing extreme.</p><p>Things I stopped doing:</p><ul><li>Daily macro micromanagement from the model.</li><li>Monstrous feature sets. I cut half the features that never moved the needle.</li><li>Obsessing about perfect logging on weekends. The model already treats weekend data with suspicion.</li></ul><p>The net result feels calmer. The AI is now more like a very data-obsessed nutrition assistant that sets the rails. I still drive.</p><h2 id="the-stuff-i-would-keep-if-i-had-to-start-over">The Stuff I Would Keep If I Had To Start Over</h2><p>If I wiped the codebase tomorrow and rebuilt from scratch, I would keep these pieces.</p><h3 id="1-one-simple-question-for-the-model">1. One simple question for the model</h3><p>Trying to predict everything made the model noisy and my life annoying.</p><p>Now I use a single core question: <strong>&#x201C;Given the training plan and recent recovery, what macro and micro ranges increase my chances of high energy and solid performance tomorrow?&#x201D;</strong></p><p>That is it. Everything else is secondary.</p><h3 id="2-narrow-micronutrient-focus">2. Narrow micronutrient focus</h3><p>I do not try to optimize every vitamin. That feels like fake precision.</p><p>I focus on a few that my data actually supports as levers for me: magnesium, omega 3, fiber, and sodium/potassium balance. The model cares about those. The rest are &#x201C;nice to have&#x201D; and mostly handled by eating real food.</p><h3 id="3-ai-as-a-ux-layer-not-an-oracle">3. AI as a UX layer, not an oracle</h3><p>The LLM is best used as glue. It translates constraints and targets into human-friendly meal ideas. It is not the authority on nutrition science. It is a fast interface.</p><p>Macros, feature engineering, and evaluation live in code I can audit. The AI just helps me stick to the plan without wanting to throw my phone at the wall.</p><h2 id="should-you-do-this-probably-only-if-you-enjoy-this-kind-of-pain">Should You Do This? Probably Only If You Enjoy This Kind of Pain</h2><p>Would I recommend this to everyone? No.</p><p>If you hate logging food, do not own a wearable, and do not care about your data, then this system will die in a week. You would get more value from lifting three times a week and walking outside.</p><p>But if you already track, and you like building weird internal tools, using machine learning and AI for nutrition can push you out of default mode. It forces you to confront how messy your habits are compared to your theory of your habits.</p><p>For me the main win was not &#x201C;AI discovered a magical macro formula&#x201D;. It was this:</p><ul><li>My heavy training days feel more predictable.</li><li>My sleep is less random.</li><li>I removed a ton of daily food decisions from my brain.</li></ul><p>The code is not special. The model is not special. The feedback loop is.</p><p>Hook your body data into something that can learn, add a layer that speaks human (the LLM), and then see what kind of diet falls out the other side. Just keep a hand on the wheel.</p><p>You are still the experiment. Not the model.</p>]]></content:encoded></item><item><title><![CDATA[Designing Neurofeedback Dashboards With CSS Grid That Don’t Suck]]></title><description><![CDATA[How I used CSS Grid to design an intuitive real-time brainwave monitoring dashboard, without leaning on JS layout hacks or UI kits.]]></description><link>https://richardlemon.com/css-grid-neurofeedback-dashboards/</link><guid isPermaLink="false">6a125bcfdce51298215d415c</guid><category><![CDATA[CSS]]></category><category><![CDATA[Frontend]]></category><dc:creator><![CDATA[Richard Lemon]]></dc:creator><pubDate>Tue, 02 Jun 2026 08:04:49 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1617791160536-598cf32026fb?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fG5ldXJvfGVufDB8fHx8MTc3OTk1NDg3OHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[
<!--kg-card-begin: html-->
<aside class="toc-container">
    <div class="toc-sidebar">
        <h4>On this page</h4>
        <div class="toc"></div>
    </div>
</aside>
<!--kg-card-end: html-->
<h2 id="why-i-care-about-brainwave-dashboards">Why I Care About Brainwave Dashboards</h2><img src="https://images.unsplash.com/photo-1617791160536-598cf32026fb?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fG5ldXJvfGVufDB8fHx8MTc3OTk1NDg3OHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="Designing Neurofeedback Dashboards With CSS Grid That Don&#x2019;t Suck"><p>I track my brain a lot. Muse, OpenBCI, random EEG headbands I probably should not trust. I write code, then I look at squiggly lines and pretend I understand my own brain.</p><p>Most neurofeedback dashboards I see are a mess. Overloaded charts. Tiny fonts. Panels that jump around when the window resizes. It feels like the UI is stressed out, which is the exact opposite of what I want while training alpha waves.</p><p>So I started building my own layouts. I wanted one thing: a stable, intuitive grid that could handle real-time brainwave data without the whole interface wobbling every time the browser width changed.</p><p>I stopped reaching for complex JS layout logic and pushed CSS Grid as far as I could. It turned out to be the right hammer.</p><h2 id="the-mental-model-map-your-brain-to-a-grid">The Mental Model: Map Your Brain to a Grid</h2><p>The biggest mistake I see in these dashboards is people thinking in components before they think in structure. They drop a chart library, a sidebar, a couple of cards, and then try to glue it together with flexbox everywhere.</p><p>For neurofeedback, I think in three zones first:</p><ul><li><strong>Primary focus zone</strong>: one big chart or brainwave strip that matters right now.</li><li><strong>Context zone</strong>: band powers, metrics, HRV, session timers.</li><li><strong>Controls zone</strong>: protocol selector, thresholds, start/stop, notes.</li></ul><p>That maps nicely to a simple grid. One layout to rule the whole thing.</p><pre><code class="language-css">.dashboard {
  display: grid;
  grid-template-columns: minmax(260px, 320px) 2fr 1.3fr;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    &quot;sidebar main metrics&quot;
    &quot;sidebar main metrics&quot;
    &quot;controls main metrics&quot;;
  gap: 1.2rem;
  height: 100vh;
  padding: 1.2rem;
  box-sizing: border-box;
}

.sidebar   { grid-area: sidebar; }
.main      { grid-area: main; }
.metrics   { grid-area: metrics; }
.controls  { grid-area: controls; }</code></pre><p>That grid gives me a stable skeleton. I can swap charts inside <code>.main</code> without changing the layout. I can replace metrics content with something else, but it will still live in the same mental slot.</p><p>For neurofeedback, that consistency matters. When your attention is half on your breath and half on the chart, you do not want to keep hunting UI elements.</p><h2 id="why-i-use-grid-over-flexbox-here">Why I Use Grid Over Flexbox Here</h2><p>Flexbox is good at one dimension. It is a row that wraps, or a column that stacks. It does not know that the main chart is conceptually aligned with the controls below it and the metrics on the right.</p><p>CSS Grid understands the whole board. Neurofeedback dashboards are boards. They have spatial meaning. Where a tile lives matters.</p><p>I started with flex layouts years ago and always ended up writing little JS hacks to fix height mismatches, especially when charts render at different speeds. With Grid I can just say:</p><pre><code class="language-css">.main,
.metrics,
.sidebar,
.controls {
  min-height: 0;
  min-width: 0;
  overflow: hidden;
}</code></pre><p>Then I let the grid define the relationships. No resize observers. No <code>window.innerHeight</code> math in React. It is simply: the grid owns the layout, the components own their content.</p><h2 id="real-time-data-without-layout-jitter">Real-Time Data Without Layout Jitter</h2><p>EEG data does not care about your CSS. It comes in when it feels like it. The worst UX is a layout that keeps shifting as labels, values, and legends change size.</p><p>I had this problem early on with band power cards. Values like <code>9.3</code> looked fine, but then <code>12.87</code> showed up and pushed everything sideways. That subtle jitter is surprisingly distracting.</p><p>I fixed it with a mix of grid, fixed tracks, and typographic discipline.</p><pre><code class="language-css">.metrics {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-auto-rows: minmax(80px, auto);
  gap: 0.8rem;
}

.metric-card {
  display: grid;
  grid-template-rows: auto 1fr;
  padding: 0.8rem 1rem;
  background: #080b10;
  border-radius: 8px;
}

.metric-label {
  font-size: 0.8rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.7;
}

.metric-value {
  font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
  font-variant-numeric: tabular-nums;
  font-size: 1.6rem;
}</code></pre><p><code>font-variant-numeric: tabular-nums</code> is key here. The numbers change, but the width does not dance.</p><p>Grid does the rest. Each card gets its own little subgrid. The heights are stable because I am not letting content dictate the outer layout. It sits inside a grid track that already knows its place.</p><h2 id="making-the-main-brainwave-strip-actually-readable">Making the Main Brainwave Strip Actually Readable</h2><p>The main chart is the thing your eyes hang on. That is the anchor. If you mess this up, the rest does not matter.</p><p>I like a wide strip at the top of the main area, then a 2 or 3 column grid below with supporting views. Think: raw EEG, then processed power spectrum, then maybe a reward indicator.</p><pre><code class="language-css">.main {
  display: grid;
  grid-template-rows: minmax(180px, 260px) minmax(0, 1fr);
  gap: 0.8rem;
}

.main-primary-chart {
  background: #05070b;
  border-radius: 8px;
  overflow: hidden;
  display: flex;
}

.main-secondary-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.8rem;
}

.main-secondary-panel {
  background: #05070b;
  border-radius: 8px;
  overflow: hidden;
}</code></pre><p>The grid inside <code>.main</code> separates concerns. The top track is fixed in the layout, so the chart library can re-render a hundred times per minute without causing layout thrash.</p><p>The trick is <code>minmax(0, 1fr)</code>. Without that, nested grids and chart canvases can overflow in weird ways, especially if the parent has padding and the child tries to be clever with width. I broke this enough times that I now use <code>minmax(0, 1fr)</code> by default inside any dashboard-like grid.</p><h2 id="responsive-without-breaking-the-mental-model">Responsive Without Breaking The Mental Model</h2><p>Most neurofeedback dashboards I see give up on mobile. They just squash everything or hide half the UI. I think that is lazy.</p><p>I want a consistent mental model across sizes. Same zones, different stacking.</p><p>Grid template areas let me keep the semantics and just rewire the layout for smaller screens.</p><pre><code class="language-css">.dashboard {
  display: grid;
  grid-template-columns: minmax(260px, 320px) 2fr 1.3fr;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    &quot;sidebar main metrics&quot;
    &quot;sidebar main metrics&quot;
    &quot;controls main metrics&quot;;
}

@media (max-width: 1100px) {
  .dashboard {
    grid-template-columns: 260px minmax(0, 1fr);
    grid-template-rows: auto 1fr auto;
    grid-template-areas:
      &quot;sidebar main&quot;
      &quot;sidebar main&quot;
      &quot;controls main&quot;;
  }

  .metrics {
    grid-column: main;
    grid-row: 2;
    align-self: flex-start;
  }
}

@media (max-width: 768px) {
  .dashboard {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto auto auto;
    grid-template-areas:
      &quot;main&quot;
      &quot;metrics&quot;
      &quot;sidebar&quot;
      &quot;controls&quot;;
    height: auto;
  }
}</code></pre><p>On desktop you get the full control center. On tablets you lose one column but keep the side anchor. On phones you still see the main chart first, then metrics, then the stuff you do not touch as often.</p><p>I am fine with hiding some secondary visual fluff on small screens, but I never move the main chart below the fold. That is a hard rule.</p><h2 id="controls-that-do-not-compete-with-the-data">Controls That Do Not Compete With The Data</h2><p>Controls are important. They are not more important than the data. I put them in their own grid area, but make that area visually quieter.</p><pre><code class="language-css">.controls {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.8rem;
  align-content: start;
}

.control-group {
  background: #05070b;
  border-radius: 8px;
  padding: 0.8rem 1rem;
}

.control-group h3 {
  font-size: 0.9rem;
  margin: 0 0 0.5rem;
}

@media (max-width: 1100px) {
  .controls {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 768px) {
  .controls {
    grid-template-columns: minmax(0, 1fr);
  }
}</code></pre><p>The layout rules keep this from stealing attention from the main strip. I never let the controls area grow taller than it has to. It sits under the main view like a cockpit, not like a second dashboard fighting for focus.</p><p>Because it is a grid area, I can also quickly run A/B tests on control placement. Want to move the protocol selector into the sidebar and bring session notes closer to the main chart? That is one CSS change using areas, not a giant JSX refactor.</p><h2 id="handling-multiple-brain-regions-without-visual-chaos">Handling Multiple Brain Regions Without Visual Chaos</h2><p>Once you start working with more channels and regions, the UI tends to explode. Fp1, Fp2, C3, C4, O1, O2, and so on. Suddenly you have more panels than screen.</p><p>Instead of stacking everything, I partition channels into a grid of small multiples. Same layout, different data. Grid is perfect for that.</p><pre><code class="language-css">.regions-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 0.6rem;
}

.region-panel {
  background: #05070b;
  border-radius: 6px;
  padding: 0.4rem 0.6rem;
  display: grid;
  grid-template-rows: auto minmax(60px, 1fr);
}

.region-label {
  font-size: 0.75rem;
  opacity: 0.7;
}

.region-chart {
  overflow: hidden;
}</code></pre><p><code>auto-fit</code> plus <code>minmax</code> lets the UI breathe. On a big monitor you see many channels in one glance. On a laptop it shrinks to 2 or 3 per row without you touching any JS.</p><p>I tried carousels for this once. Never again. For neurofeedback, visual scanning speed matters more than fancy UI.</p><h2 id="accessibility-and-dark-mode-in-a-high-contrast-world">Accessibility And Dark Mode In A High-Contrast World</h2><p>Brainwave UIs usually go dark. Black backgrounds, neon lines, aggressive gradients. It looks cool and murders legibility within 20 minutes.</p><p>Using Grid helps here in a subtle way. Because I control layout at the container level, I can keep the visual complexity mostly inside chart canvases. The layout shell stays simple and consistent.</p><pre><code class="language-css">:root {
  color-scheme: dark;

  --bg: #040609;
  --bg-elevated: #060910;
  --border-subtle: rgba(255, 255, 255, 0.03);
  --accent: #4fd1c5;
}

body {
  margin: 0;
  background: radial-gradient(circle at top, #050816, #020308 55%);
  color: #f4f7ff;
  font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
}

.dashboard &gt; * {
  border: 1px solid var(--border-subtle);
  background: linear-gradient(135deg, var(--bg-elevated), #05070c);
}</code></pre><p>Each grid cell effectively becomes a card with predictable contrast. Charts live inside and can be tuned independently. I keep text contrast high, limit accent colors, and let the grid give the whole page structure so my brain is not wasting cycles parsing layout shifts.</p><h2 id="working-with-real-hardware-instead-of-placeholder-boxes">Working With Real Hardware Instead Of Placeholder Boxes</h2><p>This layout only started to make sense once I plugged it into an actual EEG stream. With placeholders everything looks balanced. With noisy data the weak spots show up fast.</p><p>Two things broke immediately:</p><ul><li>Latency indicators got buried in the metrics grid.</li><li>Session timer was in the wrong place for my eyes.</li></ul><p>Both fixes were layout problems, not component problems.</p><p>I pulled latency into the top of the main chart track using subgrid-like thinking.</p><pre><code class="language-css">.main-primary-chart {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
}

.latency-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.2rem 0.7rem;
  font-size: 0.75rem;
  opacity: 0.8;
}

.raw-chart {
  min-height: 0;
}</code></pre><p>Then I moved the session timer into the controls area but aligned it visually with the main chart using grid lines instead of flex shenanigans. That change was one CSS edit to the areas definition, not a React component shuffle.</p><p>Working with live data is where Grid really shows its value. The structure holds even when the content gets weird.</p><h2 id="takeaways-if-you-are-building-one-of-these">Takeaways If You Are Building One Of These</h2><p>If you are working on a real-time brainwave monitoring app and your UI feels jittery, there is a good chance your layout is trying to be clever at the component level instead of being honest at the grid level.</p><ul><li>Define clear zones first. Sidebar, main, metrics, controls.</li><li>Use <code>grid-template-areas</code> to encode your mental model.</li><li>Use <code>minmax(0, 1fr)</code> and <code>tabular-nums</code> to kill jitter.</li><li>Let charts re-render inside stable grid tracks instead of resizing parents.</li><li>Restructure the layout per breakpoint without breaking zone order.</li></ul><p>I treat CSS Grid as the nervous system of the dashboard. The EEG stream can spike and wobble all it wants. The layout stays calm.</p><p>That is the whole point of neurofeedback anyway.</p>]]></content:encoded></item><item><title><![CDATA[Sovereign AI for Healthcare Records: Patient-First, Not Platform-First]]></title><description><![CDATA[I wanted to see if sovereign AI plus decentralized records could give patients actual control over their medical data, not just another privacy checkbox. This is the architecture I ended up sketching out.]]></description><link>https://richardlemon.com/sovereign-ai-decentralized-healthcare-records/</link><guid isPermaLink="false">6a110a41dce51298215d4153</guid><category><![CDATA[AI]]></category><category><![CDATA[Thoughts]]></category><dc:creator><![CDATA[Richard Lemon]]></dc:creator><pubDate>Mon, 01 Jun 2026 08:04:14 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1677442135703-1787eea5ce01?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDR8fGFpfGVufDB8fHx8MTc3OTc4NDQ2N3ww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[
<!--kg-card-begin: html-->
<aside class="toc-container">
    <div class="toc-sidebar">
        <h4>On this page</h4>
        <div class="toc"></div>
    </div>
</aside>
<!--kg-card-end: html-->
<h2 id="sovereign-ai-needs-a-real-use-case">Sovereign AI needs a real use case</h2><img src="https://images.unsplash.com/photo-1677442135703-1787eea5ce01?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDR8fGFpfGVufDB8fHx8MTc3OTc4NDQ2N3ww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="Sovereign AI for Healthcare Records: Patient-First, Not Platform-First"><p>Every second AI startup pitch ends up with the same slide: &quot;user-owned data&quot;, &quot;sovereign AI&quot;, &quot;you control your model&quot;. It sounds nice. It is usually hand-wavy.</p><p>Healthcare records are where this either gets real or falls apart completely. You cannot fake privacy when test results, diagnoses, and genetic data are on the line. People actually get hurt when you screw this up.</p><p>So I spent some time sketching a concrete stack: sovereign AI agents sitting on top of decentralized healthcare records, where the patient is actually in charge. Not metaphorically. Cryptographically.</p><p>This is not finished product talk. I am walking through the architecture I would build, the tradeoffs I hit, and why I think blockchain is useful here, but only if you keep it narrow and boring.</p><h2 id="what-i-mean-by-sovereign-ai-here">What I mean by &quot;sovereign AI&quot; here</h2><p>I am using &quot;sovereign AI&quot; in a very specific way:</p><ul><li>The AI agent runs on compute the patient controls, or at least can verify.</li><li>The training and context data are explicitly permissioned by the patient.</li><li>All external access to the patient&apos;s medical history is mediated by that agent.</li></ul><p>So if a hospital, insurer, or research lab wants to use your data, they do not pull a dump of your records. They ask your agent a question. Your agent decides how much to reveal, if anything. You approve or pre-approve that policy.</p><p>This turns the AI into a programmable privacy firewall around your body data. That is the goal.</p><h2 id="why-decentralized-records-and-blockchain-actually-help-here">Why decentralized records and blockchain actually help here</h2><p>I am not a &quot;put it on-chain&quot; maximalist. Most data does not belong on a blockchain. Medical records absolutely do not belong on a blockchain.</p><p>What does belong on-chain is coordination: identities, permissions, immutable logs of who asked for what, and what you agreed to. The boring stuff.</p><p>The healthcare stack I sketched boils down to three layers.</p><ul><li><strong>Data layer:</strong> Encrypted records stored off-chain. Think IPFS, S3, Arweave, hospital systems. The raw bits never touch the chain.</li><li><strong>Control layer:</strong> A blockchain or similar distributed ledger that stores access policies, consent receipts, and identity bindings.</li><li><strong>Agent layer:</strong> Patient-controlled AI that reads from encrypted data, enforces on-chain policies, and answers queries.</li></ul><p>The chain is the source of truth for consent, not content. That is the core distinction. If you get that wrong, you get surveillance in the name of decentralization.</p><h2 id="threat-model-first-tech-second">Threat model first, tech second</h2><p>When I started modelling this, I skipped the shiny parts and asked boring questions.</p><ul><li>Who is most likely to abuse access? Insurers and data brokers, not random hackers.</li><li>What do hospitals actually care about? Liability and workflow friction, not ideology.</li><li>What does the patient want? Convenience until something goes wrong, then control.</li></ul><p>That last one matters. If the UX punishes patients for caring about privacy, they will override everything and just sign the &quot;share all&quot; checkbox when the nurse is waiting.</p><p>So any sovereign AI setup has to do two things at once:</p><ul><li>Default to strict privacy, machine-enforced.</li><li>Make consent and revocation insanely quick, even when the user is stressed or sick.</li></ul><p>I think blockchain is good at the first part. It is terrible at the second part unless you design like a pessimist.</p><h2 id="how-the-patient-wallet-becomes-the-control-center">How the patient wallet becomes the control center</h2><p>I started from the wallet, not the hospital. If the patient is sovereign, the wallet is effectively their operating system.</p><p>My baseline design:</p><ul><li>Each patient has a DID (decentralized identifier) that represents their health identity.</li><li>The DID is controlled by a wallet with strong recovery options. Social recovery. Hardware keys. Whatever prevents &quot;lost key = lost history&quot;.</li><li>Healthcare providers get their own verified DIDs too, issued by trusted medical authorities.</li></ul><p>Every piece of medical data is then linked to a patient DID and a provider DID. The data itself is encrypted using the patient&apos;s public key. The provider may keep their own copy, obviously, but the canonical consent story lives around that shared reference.</p><p>The blockchain stores small permission records. It knows that provider X can access lab result Y from date A to date B for purpose Z, signed by the patient wallet. If that consent is revoked, the chain reflects it. The agent reads from there.</p><h2 id="your-ai-agent-as-the-bouncer-for-every-request">Your AI agent as the bouncer for every request</h2><p>Here is where the sovereign AI part stops being buzzword and starts doing work.</p><p>Instead of direct data pulls, everything routes through the patient&apos;s AI agent.</p><ul><li>A hospital wants your full history? The agent checks on-chain consent, fetches only what is allowed, strips identifiers if needed, and returns a tailored bundle.</li><li>A research study wants anonymized statistics? The agent runs queries locally on your encrypted data and returns aggregate outputs, not rows.</li><li>An insurer wants to see if you meet a condition? The agent answers with a yes/no or a score, not the underlying records.</li></ul><p>Crucially, the agent logs each request. It signs the response. It anchors a hash of that interaction on-chain or in an append-only log. Later, you can see a clean audit trail of who touched what and why.</p><p>This turns the AI into a compliance tool, not just a chatbot in a pretty UI.</p><h2 id="where-the-model-actually-runs">Where the model actually runs</h2><p>The sovereignty story collapses if the model only runs on some random vendor&apos;s GPU cluster with a 40-page terms-of-service. So I sketched three deployment modes.</p><ul><li><strong>Local-first:</strong> Lightweight models running on a phone, laptop, or home server. Good for queries over your own data.</li><li><strong>Trusted compute:</strong> Enclaves or TEEs (SGX, SEV, Nitro) for heavier models. Encrypted data in, encrypted outputs out.</li><li><strong>Hybrid:</strong> Policy engine local, heavy inference remote. The local part decides what can leave the device, and in what form.</li></ul><p>I like the hybrid model for near-term reality. Phones are getting better, but medical NLP on huge histories is still heavy. The trick is to make sure raw data never leaves without being transformed or anonymized according to the policies your agent enforces.</p><p>So you end up with a small, auditable policy model that you can inspect, and a larger foundation model behind an API that never sees your full, raw medical corpus. It sees partial, masked, or synthetic views.</p><h2 id="consent-as-code-not-paperwork">Consent as code, not paperwork</h2><p>Traditional consent is basically a PDF in a folder. Once you sign, it is a one-way door. No one expects you to read it. No one expects you to remember it.</p><p>With a blockchain-backed control layer, you can encode consent like this instead:</p><ul><li>Scope: which data categories, which providers, which date ranges.</li><li>Purpose: treatment, billing, research, product development.</li><li>Duration: fixed term, one-time, or renewable.</li><li>Granularity: raw documents vs derived signals vs aggregated stats.</li></ul><p>That ends up as a smart contract or capability token tied to your DID. Your AI agent uses these policies as strict gates, not suggestions.</p><p>You could literally say: &quot;Share only my cardiology history from the last 2 years with Clinic X for treatment purposes, and strip identifiers except my age, sex, and city&quot;. The agent compiles that into a machine-enforceable rule and writes a small consent record on-chain with a hash of the policy.</p><p>Revoke is just another transaction. No printing forms. No faxing requests into the void.</p><h2 id="training-on-your-data-without-selling-your-soul">Training on your data without selling your soul</h2><p>The messy part is training. Everyone wants to train on healthcare data. Most patients probably benefit from better models. The incentives do not line up cleanly.</p><p>I think the only honest approach is to separate three levels of &quot;training&quot;.</p><ul><li><strong>Personal fine-tuning:</strong> Your agent learns your history, preferences, and baselines. This never leaves your sphere of control.</li><li><strong>Federated learning:</strong> Your data contributes to global model improvements without leaving your device or enclave. Only gradients or updates leave, under strict privacy accounting.</li><li><strong>Centralized training:</strong> Traditional big-batch training on de-identified data stored by a research institution under heavy regulation.</li></ul><p>The first one is non-negotiable for real sovereignty. Your agent must get better for you alone. Your weird edge cases, your medication history, your genetics. That is your advantage.</p><p>The second one is where blockchain consent can actually shine. You can attach clear terms and potentially compensation. You agree that your device participates in a federated training run for topic X. You get a share of a reward pool. The chain records your participation and constraints. Your agent checks that the training job matches those terms.</p><p>The third one will probably exist no matter what. Big hospitals and research labs will keep building central models. The key is to prevent that path from being the only one, by giving patients a real alternative.</p><h2 id="what-this-looks-like-for-an-actual-patient">What this looks like for an actual patient</h2><p>Architecture diagrams are fun, but a basic user flow is where this gets honest. So here is one concrete path I walked through on paper.</p><p>You show up at a new clinic.</p><ul><li>You scan a QR code with your health wallet.</li><li>The clinic&apos;s DID and credential pop up: verified provider, specialty, jurisdiction.</li><li>Your agent suggests a consent pack: share last 5 years of relevant history, allow ongoing updates for treatment, no research use.</li><li>You can expand and tweak. Maybe you hide mental health records for now.</li><li>You approve. The wallet signs a consent transaction. The chain records it.</li></ul><p>Behind the scenes, the clinic&apos;s system sends a structured query: &quot;treatment history for conditions A, B, C&quot;. Your AI agent fetches encrypted documents from storage, applies policy filters, optionally local de-identification, and sends back a bundle.</p><p>If the clinic later wants to enroll you in a research trial, that is a separate ask. Different policy. Different on-chain record. You can say yes with strict limits, or flat no.</p><h2 id="where-this-whole-idea-can-fail">Where this whole idea can fail</h2><p>I like this architecture, but I am not naive about the failure modes.</p><ul><li><strong>UX complexity:</strong> If the wallet feels like managing firewall rules in 2003, no one will use it correctly. The agent needs good presets and human language explanations.</li><li><strong>Key loss:</strong> Patients will lose devices, passwords, everything. Without robust recovery, you just moved lock-in from hospitals to cryptography.</li><li><strong>Regulatory mismatch:</strong> Some laws expect data to be deletable, which clashes with immutable logs. You need off-chain storage and careful design.</li><li><strong>Institutional laziness:</strong> Hospitals might pretend to support this while keeping their old silos. The incentives need work.</li></ul><p>I think the biggest risk is fake sovereignty. Pretty wallets. Nice words about ownership. Under the hood, the same centralized APIs quietly hoover up data, and the blockchain is just marketing.</p><p>The only antidote is verifiability. Patients and regulators need to be able to inspect code paths and see that the AI agent cannot bypass on-chain policies, even if the operator wants to.</p><h2 id="what-i-would-actually-build-first">What I would actually build first</h2><p>If I had to ship something in this direction tomorrow, I would not start with the whole sovereign AI fantasy. I would pick one narrow slice.</p><ul><li>A patient wallet that can hold a DID, basic credentials, and simple consent policies.</li><li>A small AI policy engine that explains those policies in plain language and simulates outcomes.</li><li>One integration with a forward-thinking clinic that is okay with a parallel workflow.</li></ul><p>No massive model yet. No federated training. Just automated, verifiable consent with a bit of intelligence to make it understandable. Then layer in the data access mediation and richer agents once that base is solid.</p><p>If the consent story is trash, the rest is lipstick.</p><h2 id="why-i-think-this-is-worth-building">Why I think this is worth building</h2><p>I do not think decentralization is magic. I do not think AI will suddenly fix healthcare. But the combo of programmable consent plus agents that defend users feels like a rare chance to realign incentives.</p><p>Instead of turning patients into data exhaust for someone else&apos;s model, you give them a bargaining chip. They bring their own history. They bring their own agent. Providers compete on quality of care and compatibility, not data hoarding.</p><p>If sovereign AI means anything, it should mean your body data answers to you first, institutions second. Blockchain is just the boring substrate that makes those promises hard to quietly break.</p><p>That is a future I would actually like to use as a patient, not just build as a developer.</p>]]></content:encoded></item><item><title><![CDATA[Biohacking With AI: How I Built Autonomous Wearable Health Insights]]></title><description><![CDATA[I wired my wearables into an AI loop that pushes real-time, personalized biohacking recommendations instead of generic scores. This is what actually worked and what broke.]]></description><link>https://richardlemon.com/biohacking-ai-autonomous-wearable-health-insights/</link><guid isPermaLink="false">6a0fb8e7dce51298215d4064</guid><category><![CDATA[Biohacking]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Richard Lemon]]></dc:creator><pubDate>Sun, 31 May 2026 06:26:49 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1434494878577-86c23bcb06b9?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fHdlYXJhYmxlc3xlbnwwfHx8fDE3Nzk4NjI1Nzl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[
<!--kg-card-begin: html-->
<aside class="toc-container">
    <div class="toc-sidebar">
        <h4>On this page</h4>
        <div class="toc"></div>
    </div>
</aside>
<!--kg-card-end: html-->
<h2 id="why-i-stopped-trusting-scores">Why I Stopped Trusting &quot;Scores&quot;</h2><img src="https://images.unsplash.com/photo-1434494878577-86c23bcb06b9?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fHdlYXJhYmxlc3xlbnwwfHx8fDE3Nzk4NjI1Nzl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="Biohacking With AI: How I Built Autonomous Wearable Health Insights"><p>I like toys. Especially toys with sensors.</p><p>Oura, Whoop, Apple Watch, a cheap HR strap from Decathlon, even a continuous glucose monitor for a while. All of them tried to tell me how I was doing with one magic number.</p><p>Readiness: 82. Sleep score: 91. Strain: 13.2.</p><p>Nice. But my body did not care. Some days I had a high readiness score and still felt like a brick. Other days I felt sharp and the app nagged me to &#x201C;take it easy&#x201D;.</p><p>So I stopped asking: &#x201C;What is my score?&#x201D;</p><p>I started asking: &#x201C;What should I do <strong>right now</strong>?&#x201D;</p><p>That question is where AI actually got useful for biohacking. Not to generate pretty charts, but to make a decision: push, maintain, or back off.</p><h2 id="the-goal-autonomous-boringly-simple-health-suggestions">The Goal: Autonomous, Boringly Simple Health Suggestions</h2><p>I wanted a system that:</p><ul><li>Pulls live data from my wearables automatically.</li><li>Understands <em>my</em> baselines, not population averages.</li><li>Spits out 1&#x2013;2 concrete suggestions, not a 10-paragraph essay.</li><li>Runs in the background without me having to remember to open an app.</li></ul><p>Think tiny health coach in the background. Not a life guru. More like a slightly annoying teammate that pings you with: &#x201C;Take 10 minutes outside, your HRV tanked after that last call.&#x201D;</p><p>So I wired my wearables to an LLM and tried to make it as autonomous as I could without blowing up my API bill or my sanity.</p><h2 id="my-wearable-stack">My Wearable Stack</h2><p>Right now my core stack looks like this:</p><ul><li><strong>Oura Ring</strong> for sleep, HRV, resting HR.</li><li><strong>Apple Watch</strong> for heart rate during training, HRV snapshots, and activity.</li><li><strong>Cheap BLE chest strap</strong> for kettlebell and conditioning sessions when I care about accuracy.</li></ul><p>I tried adding CGM data, but that turned into a distraction. Good for a focused 2-week experiment, not great for permanent background noise. So in this phase I cut it.</p><p>Important detail: I stopped trusting each platform&#x2019;s &#x201C;readiness&#x201D; score and focused on raw-ish metrics:</p><ul><li>HRV (rMSSD), nightly average and trend.</li><li>Resting heart rate and how fast it drops after workouts.</li><li>Sleep duration and fragmentation.</li><li>Training load from heart rate and RPE notes.</li></ul><p>These are boring numbers. Which is perfect. Boring means predictable. Predictable means an LLM can reason about them without hallucinating some spiritual conclusion out of a 79 sleep score.</p><h2 id="the-architecture-from-wrist-to-recommendation">The Architecture: From Wrist To Recommendation</h2><p>This is what I actually built. Not theoretical. Running on my stuff right now.</p><p>The system has five pieces:</p><ol><li>Data collection workers</li><li>Normalization and feature builder</li><li>Long-term baseline and history store</li><li>LLM &#x201C;coach&#x201D; prompt layer</li><li>Notification layer (where it pokes me)</li></ol><h3 id="1-data-collection-workers">1. Data collection workers</h3><p>I run small, boring scripts on a cheap VPS.</p><ul><li>A cron job hits the Oura API every 30 minutes in the morning, then every 2 hours during the day for readiness updates.</li><li>Another job pulls Apple Health data via a personal shortcut export every few hours. Not pretty, but it works.</li><li>Workout sessions push data in directly when I finish them, through a tiny web form where I log RPE and what I actually did.</li></ul><p>Everything lands in a Postgres database. Timestamps, raw values, the source device, plus some calculated stuff like rolling averages.</p><h3 id="2-normalization-and-feature-builder">2. Normalization and feature builder</h3><p>Each hour a job runs that:</p><ul><li>Back-fills missing data, or marks gaps clearly instead of pretending it knows.</li><li>Builds &#x201C;features&#x201D; for the AI layer: HRV vs 7-day average, sleep vs 14-day average, last hard session delta, and so on.</li><li>Flags notable events. For example: &#x201C;HRV dropped &gt; 20% below 7-day mean&#x201D;, or &#x201C;Sleep &lt; 80% of 14-day avg and two days in a row of heavy training&#x201D;.</li></ul><p>This part matters more than the AI model choice in my opinion. If your inputs are vague and inconsistent, your clever prompt will just generate poetic nonsense.</p><h3 id="3-baseline-and-history">3. Baseline and history</h3><p>I keep a rolling 90-day window for most metrics.</p><p>HRV especially is very personal. My &#x201C;good&#x201D; number might be your &#x201C;I am dying&#x201D; number. So the system stores per-user (just me right now) baselines and updates them every week.</p><p>That way the AI agent never sees: &#x201C;HRV = 60&#x201D;. It sees: &#x201C;HRV = 60, which is 8% above his 30-day average, and he slept 15% more than usual&#x201D;. Now it has context.</p><h3 id="4-llm-coach-layer">4. LLM coach layer</h3><p>This is a small API that wraps the model. I currently use a GPT-4-level model, but the key is the system prompt and how I limit its job.</p><p>The system prompt looks roughly like this (shortened here):</p><pre><code>You are a pragmatic health coach for a single user.
You must respond with 1-2 concrete actions only.
No more than 120 words. No general wellness advice.
Use only the data given. If data is missing, say so.

User data: &lt;metrics&gt;
User context: &lt;recent training log + calendar notes&gt;
</code></pre><p>Then I feed in the hourly feature bundle, plus a short snippet from my notes if I tagged something like &#x201C;travel&#x201D;, &#x201C;sick&#x201D;, or &#x201C;poor sleep because of kids baseball tournament&#x201D;.</p><p>The output format is strict JSON:</p><pre><code>{
  &quot;priority&quot;: &quot;push&quot; | &quot;maintain&quot; | &quot;back_off&quot;,
  &quot;reason&quot;: &quot;short explanation&quot;,
  &quot;actions&quot;: [
    &quot;action 1&quot;,
    &quot;action 2&quot;
  ]
}
</code></pre><p>No Markdown. No &#x201C;as an AI language model&#x201D;. Just a tiny plan for the next few hours.</p><h3 id="5-notification-layer">5. Notification layer</h3><p>The last piece is the annoying one.</p><p>A small worker checks the new JSON and compares it to the previous recommendation. If the priority or actions really changed, it sends me a push notification through a custom iOS shortcut endpoint.</p><p>Example message:</p><ul><li><strong>Priority:</strong> back_off</li><li><strong>Reason:</strong> HRV dropped 22% below 7-day avg after 2 heavy days.</li><li><strong>Actions:</strong> 1) Keep training under 30 min, low intensity. 2) In bed by 22:00, no late screens.</li></ul><p>If nothing meaningful changed, it stays quiet. Silence is part of the design. I do not want a Tamagotchi, I want a cold friend who only texts when it matters.</p><h2 id="what-real-time-actually-means-here">What &quot;Real-time&quot; Actually Means Here</h2><p>People say &#x201C;real-time&#x201D; when they mean &#x201C;not yesterday&#x201D;. For biohacking I do not need sub-second updates. I need the system to react fast enough that I can still change something.</p><p>The cadence that felt right in practice:</p><ul><li><strong>Morning block:</strong> 2&#x2013;3 runs in the first hour after waking, as data from Oura and Apple sync.</li><li><strong>Daytime:</strong> Every 2 hours, unless there is a spike or a flagged event.</li><li><strong>Training window:</strong> Right after a workout, when I log RPE and type of session.</li></ul><p>I tried tighter loops. 15-minute cycles felt like noise. I got more pings during meetings and coding blocks, and I started ignoring them. Which kills the whole point.</p><p>So &#x201C;real-time&#x201D; in this project became: fast enough to adjust the next block of the day, slow enough that it does not spam me.</p><h2 id="concrete-examples-from-my-own-data">Concrete Examples From My Own Data</h2><h3 id="1-catching-overreach-before-it-hit">1. Catching overreach before it hit</h3><p>Two heavy kettlebell days. Both felt good. My subjective rating: &#x201C;Let&#x2019;s keep going.&#x201D;</p><p>The system saw:</p><ul><li>HRV 18% down from 7-day average.</li><li>Resting heart rate up 4 bpm vs 14-day average.</li><li>Sleep time normal, but more wake events.</li></ul><p>It flagged <code>back_off</code> and suggested: &#x201C;Skill work only today, no conditioning. Prioritize 30-minute walk in sunlight.&#x201D;</p><p>I listened. Next day HRV bounced back, and I avoided that familiar 3&#x2013;4 day fatigue slump. Subjectively it felt like cheating. An invisible adult in the room.</p><h3 id="2-travel-days-and-fake-guilt">2. Travel days and fake guilt</h3><p>On travel days my normal apps still shame me. No rings closed. Activity score bad. Readiness confused.</p><p>With the AI layer, I tagged my calendar with &#x201C;travel&#x201D; and fed that into the context. The system adapted quickly:</p><ul><li>Priority: maintain</li><li>Actions: &#x201C;Walk 15 minutes between flights. Hydrate aggressively. Bed as early as possible after arrival.&#x201D;</li></ul><p>No comment on my lack of deadlifts. No nonsense about streaks. Just realistic constraints based on my situation, not some perfect training plan.</p><h3 id="3-late-night-coding-and-hidden-cost">3. Late-night coding and hidden cost</h3><p>I did a classic mistake. Pushed a feature late, bright screen, heavy React profiler sessions until midnight.</p><p>Next morning the system saw:</p><ul><li>Sleep quantity acceptable, but deep sleep down by ~30% vs 14-day average.</li><li>Heart rate stayed elevated longer into the night.</li><li>Calendar showed &#x201C;late deploy&#x201D; tag from my notes.</li></ul><p>Response:</p><ul><li>Priority: back_off</li><li>Actions: &#x201C;Avoid heavy training today. Block 20:30&#x2013;22:00 for no-screen wind-down. If you work, do offline writing.&#x201D;</li></ul><p>The useful part is not the advice. I already know screens at night are bad. The useful part is timing. It caught it the morning after and forced the tradeoff into my face while I was planning the day.</p><h2 id="where-ai-actually-helped-and-where-it-did-not">Where AI Actually Helped (And Where It Did Not)</h2><p>Helpful things first.</p><ul><li><strong>Pattern stitching.</strong> I am decent at looking at HRV or sleep in isolation. The LLM is better at noticing &#x201C;three medium red flags&#x201D; that together justify backing off.</li><li><strong>Context awareness.</strong> Pulling in calendar tags like &#x201C;travel&#x201D;, &#x201C;kids tournament&#x201D;, &#x201C;launch week&#x201D; made the recommendations far less annoying. Generic apps ignore this.</li><li><strong>Natural language summaries.</strong> I could generate the same decisions with hand-coded rules, but the explanation quality from the model made me trust the output more.</li></ul><p>Not helpful:</p><ul><li><strong>Generic wellness advice.</strong> If you do not clamp the prompt hard, it will start telling you to drink water and meditate. I cut that ruthlessly.</li><li><strong>Creating complex long-term plans.</strong> The model is bad at periodization. I still set my weekly and monthly training blocks manually.</li><li><strong>Data cleaning.</strong> AI is not magic here. I tried having it infer missing values. It invented too much. Plain scripts with explicit rules won.</li></ul><h2 id="constraints-i-put-on-the-system-for-my-own-sanity">Constraints I Put On The System (For My Own Sanity)</h2><p>Autonomous does not mean &#x201C;free to be weird&#x201D;. I added hard constraints.</p><ul><li><strong>No medical claims.</strong> The prompt explicitly blocks diagnosis talk. If metrics are extreme, it says: &#x201C;These values are unusual; talk to a real doctor.&#x201D;</li><li><strong>Limited authority.</strong> The system never gets to cancel my workouts. It can label the day as <code>back_off</code>, but I still choose. This keeps the relationship healthy.</li><li><strong>Strict word cap.</strong> Max 120 words. Otherwise it drifts into storytime. I am not reading essays between meetings.</li><li><strong>Data transparency.</strong> Every recommendation includes a one-line summary of what it actually saw: &#x201C;HRV -18% vs 7d, RHR +3 bpm, 2x heavy days.&#x201D; No black box energy.</li></ul><h2 id="if-you-want-to-build-your-own-version">If You Want To Build Your Own Version</h2><p>I am not packaging this as a product, at least not yet. But if you want to wire something similar for yourself, this order worked well for me:</p><ol><li><strong>Collect first, model later.</strong> Spend a few weeks just pulling data into a database. Look at it manually. See what actually matters for <em>you</em>.</li><li><strong>Define 3&#x2013;5 triggers.</strong> For example: big HRV drop, lack of sleep two days in a row, unusually high heart rate during light activity. Only then bring in AI.</li><li><strong>Start with a fixed prompt.</strong> Do not build a full &#x201C;agent&#x201D; that can call tools and write its own rules. Keep the LLM as a reasoning layer on top of clear metrics.</li><li><strong>Respect your annoyance budget.</strong> Tune notification frequency until you barely notice the system exists. If it feels loud, you will ignore it.</li></ol><p>I think people overestimate what models can do and underestimate how powerful they are as boring, opinionated summarizers. A little reasoning plus your own data goes a long way.</p><h2 id="where-i-want-to-take-this-next">Where I Want To Take This Next</h2><p>I see three obvious next steps.</p><ul><li><strong>Stronger integration with training plans.</strong> Right now I still plan my cycles manually. I want the system to suggest minor adjustments: &#x201C;swap heavy day to Thursday, based on recovery.&#x201D;</li><li><strong>Better short-term HRV snapshots.</strong> Continuous or at least scheduled HRV recordings during the day, not just night data, so it can react to work stress spikes too.</li><li><strong>Team mode.</strong> I coach baseball. At some point I want a stripped-down version that helps me modulate practice intensity for players based on basic wearable data.</li></ul><p>But I am careful. More data and more autonomy are not always better. The sweet spot seems to be:</p><ul><li>Small, opinionated loop.</li><li>Clear question: &#x201C;What should I do next?&#x201D;</li><li>Minimal friction to follow the suggestion.</li></ul><p>That is how I am using AI for biohacking right now. Not as a guru. As a slightly nerdy mirror that watches my signals and tells me, in plain language, when I am about to be stupid with training or sleep.</p><p>That is good enough for me.</p>]]></content:encoded></item><item><title><![CDATA[CSS Custom Properties For Responsive Biohacking Dashboards]]></title><description><![CDATA[How I use CSS custom properties to make my health dashboards adapt to any screen, metric, or weird HRV experiment without rewriting half my CSS every month.]]></description><link>https://richardlemon.com/css-custom-properties-responsive-biohacking-dashboards/</link><guid isPermaLink="false">6a0ec78bdce51298215d405b</guid><category><![CDATA[CSS]]></category><category><![CDATA[Frontend]]></category><dc:creator><![CDATA[Richard Lemon]]></dc:creator><pubDate>Sat, 30 May 2026 06:25:59 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1563481038999-86236308ce4a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fGJpb2hhY2tpbmd8ZW58MHx8fHwxNzc5ODYyNTE2fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[
<!--kg-card-begin: html-->
<aside class="toc-container">
    <div class="toc-sidebar">
        <h4>On this page</h4>
        <div class="toc"></div>
    </div>
</aside>
<!--kg-card-end: html-->
<h2 id="why-my-biohacking-dashboards-kept-breaking">Why my biohacking dashboards kept breaking</h2><img src="https://images.unsplash.com/photo-1563481038999-86236308ce4a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fGJpb2hhY2tpbmd8ZW58MHx8fHwxNzc5ODYyNTE2fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="CSS Custom Properties For Responsive Biohacking Dashboards"><p>I track too many things.</p><p>HRV, resting heart rate, sleep stages, pitch count from baseball practice, cold plunge time, caffeine intake, sunlight. If I can get it into a CSV or API, it ends up in a dashboard.</p><p>The problem: every new metric broke my layout.</p><p>On my laptop, it looked fine. On my phone at the gym, labels wrapped badly, cards stretched, and charts felt cramped. Every experiment meant tweaking breakpoints again. I was living inside media queries.</p><p>So I stopped trying to perfectly target devices. Instead, I started targeting relationships. Things like:</p><ul><li>How many cards per row feels readable for this density of data?</li><li>How much padding do I need if the user is in bright sunlight?</li><li>What is the minimum tap target I accept for a tired, post-workout brain?</li></ul><p>CSS custom properties became the control panel. Not Tailwind classes. Not another utility system. Plain variables.</p><h2 id="the-core-idea-styles-as-health-metrics">The core idea: styles as health metrics</h2><p>For my biohacking dashboards, I treat CSS variables like another layer of metrics. They represent the current &#x201C;state&#x201D; of the interface.</p><p>Instead of hardcoding everything like this:</p><pre><code class="language-css">.card {
  padding: 1.5rem;
  border-radius: 12px;
  font-size: 0.95rem;
}

.dashboard {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}
</code></pre><p>I push decisions into custom properties:</p><pre><code class="language-css">:root {
  --dashboard-columns: 4;
  --card-padding: 1.5rem;
  --card-radius: 12px;
  --card-font-size: 0.95rem;
}

.dashboard {
  display: grid;
  grid-template-columns: repeat(var(--dashboard-columns), minmax(0, 1fr));
  gap: var(--dashboard-gap, 1.25rem);
}

.card {
  padding: var(--card-padding);
  border-radius: var(--card-radius);
  font-size: var(--card-font-size);
}
</code></pre><p>On its own that is not very exciting. The shift happens when you stop thinking &#x201C;mobile vs desktop&#x201D; and start thinking &#x201C;lightweight vs dense vs focus mode&#x201D; and let those modes rewrite the variables.</p><h2 id="defining-%E2%80%9Cmodes%E2%80%9D-for-your-body-not-your-devices">Defining &#x201C;modes&#x201D; for your body, not your devices</h2><p>I care more about my mental state than my device type.</p><p>If I am half-asleep checking morning HRV, I want big numbers, low noise, and high contrast. If I am at my desk reviewing a week of data, I want density and context.</p><p>So I started defining layout modes as CSS variables first, then pairing them with media queries and <code>data-</code> attributes.</p><pre><code class="language-css">:root {
  /* default &#x201C;desk review&#x201D; mode */
  --dashboard-columns: 4;
  --card-padding: 1.25rem;
  --card-font-size: 0.95rem;
  --chart-height: 260px;
  --touch-target-size: 36px;
}

/* narrow screens: inherently more &#x201C;single focus&#x201D; */
@media (max-width: 900px) {
  :root {
    --dashboard-columns: 2;
    --card-padding: 1rem;
    --card-font-size: 0.98rem;
    --chart-height: 220px;
    --touch-target-size: 44px;
  }
}

/* ultra narrow: pure focus mode */
@media (max-width: 600px) {
  :root {
    --dashboard-columns: 1;
    --card-padding: 1.1rem;
    --card-font-size: 1.05rem;
    --chart-height: 260px;
  }
}
</code></pre><p>Then I added an explicit focus mode that I can toggle from JavaScript based on context. For example, when I open a &#x201C;pre-sleep&#x201D; routine dashboard on my phone.</p><pre><code class="language-css">html[data-mode=&quot;focus&quot;] {
  --dashboard-columns: 1;
  --card-padding: 1.5rem;
  --card-font-size: 1.1rem;
  --chart-height: 300px;
  --touch-target-size: 48px;
}
</code></pre><p>Now the JS is dead simple:</p><pre><code class="language-js">// example: turn on focus mode for &#x201C;bedtime check-in&#x201D;
function setMode(mode) {
  document.documentElement.setAttribute(&apos;data-mode&apos;, mode);
}

setMode(&apos;focus&apos;);
</code></pre><p>No component-level overrides. No Tailwind gymnastics. One attribute, all the right knobs turn.</p><h2 id="responsive-grids-without-chasing-breakpoints">Responsive grids without chasing breakpoints</h2><p>The dashboard layout is almost always a grid of &#x201C;cards&#x201D;. Sleep summary. HRV trend. Training load. Recovery score.</p><p>I got tired of guessing how many cards I could fit per row for random device widths. So I stopped being strict about it. I use a variable for &#x201C;target column width&#x201D; and let the layout find its own shape.</p><pre><code class="language-css">:root {
  --card-min-width: 260px;
}

.dashboard {
  display: grid;
  grid-template-columns: repeat(
    auto-fit,
    minmax(var(--card-min-width), 1fr)
  );
  gap: var(--dashboard-gap, 1.25rem);
}
</code></pre><p>Then I let context rewrite <code>--card-min-width</code> instead of hunting down grid definitions.</p><pre><code class="language-css">html[data-mode=&quot;focus&quot;] {
  --card-min-width: 320px;
}

@media (max-width: 600px) {
  :root {
    --card-min-width: 100%;
  }
}
</code></pre><p>This is boring code. That is the point.</p><p>When I added a &#x201C;Travel&#x201D; dashboard that has smaller, less important stats, I did not need a new layout. I only nudged the knobs.</p><pre><code class="language-css">html[data-dashboard=&quot;travel&quot;] {
  --card-min-width: 220px;
  --card-padding: 0.9rem;
}
</code></pre><p>The same CSS. Three very different layouts. It feels like a design system finally doing its job.</p><h2 id="adaptable-charts-without-20-config-files">Adaptable charts without 20 config files</h2><p>The ugly part of biohacking dashboards usually lives around charts. Fonts too small. Lines too thin. Legends unreadable.</p><p>I wanted charts to respect the same variables as the rest of the UI. So I pushed chart styling decisions into CSS and let the JS just read them.</p><p>Example: I use CSS variables to control stroke width, gridline opacity, and font size. Then the chart library reads from computed styles.</p><pre><code class="language-css">:root {
  --chart-stroke-width: 2;
  --chart-grid-opacity: 0.12;
  --chart-font-size: 0.8rem;
}

html[data-mode=&quot;focus&quot;] {
  --chart-stroke-width: 3;
  --chart-grid-opacity: 0.18;
  --chart-font-size: 0.9rem;
}

.chart-container {
  font-size: var(--chart-font-size);
}
</code></pre><pre><code class="language-js">function getCssNumber(varName, fallback) {
  const styles = getComputedStyle(document.documentElement);
  const value = styles.getPropertyValue(varName).trim();
  return value ? Number.parseFloat(value) : fallback;
}

const chartStyle = {
  strokeWidth: getCssNumber(&apos;--chart-stroke-width&apos;, 2),
  gridOpacity: getCssNumber(&apos;--chart-grid-opacity&apos;, 0.12),
  fontSize: getComputedStyle(
    document.querySelector(&apos;.chart-container&apos;)
  ).fontSize
};

// pass chartStyle into whatever chart lib you use
</code></pre><p>Now when I toggle modes, the charts adapt without needing a second config object for &#x201C;mobile&#x201D; or &#x201C;large screen&#x201D;.</p><p>It also matches my mental model better. &#x201C;Focus mode means thicker lines and higher contrast&#x201D;. That is a UI statement, not a chart library statement.</p><h2 id="light-dark-and-%E2%80%9Csunlight%E2%80%9D-themes-driven-by-variables">Light, dark, and &#x201C;sunlight&#x201D; themes driven by variables</h2><p>My worst decisions happen when I am squinting at a low-contrast UI in bright sun after a workout.</p><p>I started with basic light and dark themes, then ended up with a third &#x201C;sunlight&#x201D; theme that is basically &#x201C;dark but more brutal&#x201D;. Again, the work happens in custom properties.</p><pre><code class="language-css">:root {
  color-scheme: light;

  --bg: #fafafa;
  --bg-alt: #ffffff;
  --surface: #ffffff;
  --text: #111827;
  --muted: #6b7280;
  --accent: #22c55e;
  --danger: #ef4444;
  --border-subtle: #e5e7eb;
}

html[data-theme=&quot;dark&quot;] {
  color-scheme: dark;

  --bg: #020617;
  --bg-alt: #030712;
  --surface: #020617;
  --text: #e5e7eb;
  --muted: #6b7280;
  --accent: #22c55e;
  --danger: #f97373;
  --border-subtle: #111827;
}

html[data-theme=&quot;sunlight&quot;] {
  color-scheme: dark;

  --bg: #000000;
  --bg-alt: #020617;
  --surface: #020617;
  --text: #f9fafb;
  --muted: #9ca3af;
  --accent: #4ade80;
  --danger: #fb7185;
}

body {
  background: var(--bg);
  color: var(--text);
}

.card {
  background: var(--surface);
  border: 1px solid var(--border-subtle);
}
</code></pre><p>While building this, I stopped wiring theme switches deep into components. Instead, I just toggle <code>data-theme</code> on <code>html</code>.</p><pre><code class="language-js">function setTheme(theme) {
  document.documentElement.setAttribute(&apos;data-theme&apos;, theme);
}

// crude heuristic: &#x201C;sunlight mode&#x201D; on high ambient light
if (&apos;AmbientLightSensor&apos; in window) {
  const sensor = new AmbientLightSensor();
  sensor.addEventListener(&apos;reading&apos;, () =&gt; {
    if (sensor.illuminance &gt; 25000) setTheme(&apos;sunlight&apos;);
  });
  sensor.start();
}
</code></pre><p>On most devices the ambient sensor is not available, so I usually just expose a manual toggle. The point stands. The heavy work happens in CSS variables. JS only flips states.</p><h2 id="scaling-typography-for-late-night-and-early-morning-brains">Scaling typography for late-night and early-morning brains</h2><p>Health dashboards are not marketing sites. You read them when you are tired. Or stressed. Or distracted.</p><p>I treat typography like another variable set, then let time and mode tweak it. I am not precious about perfect modular scales. I care about legibility when my HRV tanked.</p><pre><code class="language-css">:root {
  --font-base-size: 16px;
  --font-scale: 1.15;

  --font-size-xs: calc(var(--font-base-size) / var(--font-scale));
  --font-size-sm: var(--font-base-size);
  --font-size-md: calc(var(--font-base-size) * var(--font-scale));
  --font-size-lg: calc(var(--font-size-md) * var(--font-scale));
  --font-size-xl: calc(var(--font-size-lg) * var(--font-scale));
}

html[data-mode=&quot;focus&quot;] {
  --font-base-size: 17px;
}

html[data-time-of-day=&quot;night&quot;] {
  --font-base-size: 18px;
  --font-scale: 1.12;
}

body {
  font-size: var(--font-size-sm);
}

.card-title {
  font-size: var(--font-size-lg);
}

.metric-primary {
  font-size: var(--font-size-xl);
}
</code></pre><p>Then a small bit of JS sets <code>data-time-of-day</code>:</p><pre><code class="language-js">const hour = new Date().getHours();
const timeOfDay = hour &gt;= 20 || hour &lt; 6 ? &apos;night&apos; : &apos;day&apos;;

document.documentElement.setAttribute(
  &apos;data-time-of-day&apos;,
  timeOfDay
);
</code></pre><p>This is one of those details that feels unnecessary until you use it for a week. Then regular dashboards feel too small and hostile at 23:30.</p><h2 id="container-queries-plus-variables-for-modular-panels">Container queries plus variables for modular panels</h2><p>One concrete problem: I wanted to reuse the same &#x201C;HRV card&#x201D; component in a full-width view and in a cramped lateral sidebar. Copying layout logic felt lame.</p><p>I ended up combining container queries with custom properties. The card owns its own thresholds. The parent only defines its size.</p><pre><code class="language-css">.card {
  container-type: inline-size;
}

.card[data-metric=&quot;hrv&quot;] {
  --metric-layout: &quot;compact&quot;;
}

@container (min-width: 420px) {
  .card[data-metric=&quot;hrv&quot;] {
    --metric-layout: &quot;regular&quot;;
  }
}

.hrv-card-root[data-layout=&quot;compact&quot;] .trend {
  display: none;
}

.hrv-card-root[data-layout=&quot;regular&quot;] .trend {
  display: block;
}
</code></pre><p>I do not love using strings inside variables, so I usually pair them with attributes:</p><pre><code class="language-css">.card[data-metric=&quot;hrv&quot;] {
  --hrv-show-trend: 0;
}

@container (min-width: 420px) {
  .card[data-metric=&quot;hrv&quot;] {
    --hrv-show-trend: 1;
  }
}

.hrv-trend {
  display: none;
}

.hrv-card-root {
  /* 0 or 1 */
  --trend-visible: var(--hrv-show-trend);
}

.hrv-card-root[data-trend-visible=&quot;1&quot;] .hrv-trend {
  display: block;
}
</code></pre><p>Or lean into utility classes that read from variables with <code>opacity</code> and <code>pointer-events</code>. The exact pattern is less important than the mindset. The card decides what it can show based on its container. The rest of the dashboard does not care.</p><h2 id="practical-pitfalls-i-hit">Practical pitfalls I hit</h2><p>This all sounds neat. In practice I hit some annoying problems before the setup became pleasant.</p><ul><li><strong>I over-nested variables at first.</strong> I had variables referencing variables referencing variables. That turned debugging into archaeology. Now I keep it to one indirection level most of the time.</li><li><strong>I tried to mirror design tokens perfectly.</strong> That made the CSS feel like a generated artifact, not a tool. These days I keep only the tokens that match real decisions: spacing scale, typography, state, density.</li><li><strong>I misused media queries as a &#x201C;config system&#x201D;.</strong> Throwing every variable change into <code>@media</code> blocks became unreadable. Modes helped. <code>data-mode</code>, <code>data-theme</code>, <code>data-dashboard</code>. Each owns a narrow set of variables.</li></ul><p>The golden rule I ended up with: if you cannot describe the variable in one clear English sentence, it is probably too abstract.</p><p>&#x201C;<code>--touch-target-size</code> is the minimum tap target size for the current context.&#x201D; That is good.</p><p>&#x201C;<code>--ui-scale-factor</code> is a multiplier for all interaction elements depending on mode and viewport size and density.&#x201D; That is vague nonsense.</p><h2 id="why-this-beats-piling-on-more-js">Why this beats piling on more JS</h2><p>I could absolutely drive all of this from JavaScript. Compute breakpoints in code. Inject styles dynamically. Use a heavy design token system.</p><p>I do not want to.</p><p>CSS custom properties let the browser do what it is good at. They keep responsive decisions in one place. They make &#x201C;focus mode for pre-sleep check&#x201D; a styling concern, not a re-render concern.</p><p>For biohacking dashboards specifically, the ground keeps moving. You will add a new metric. Swap out a sensor. Change your morning routine. The UI needs to adapt as fast as your experiments.</p><p>Variables give you a control panel for that. Not another rewrite. Just a few new knobs.</p><p>That is the type of flexibility I want from my health tools. Quiet, boring, predictable. So my energy goes into the experiment, not the CSS.</p>]]></content:encoded></item><item><title><![CDATA[My Biometric Morning Routine: The 5‑Minute Data Check That Actually Changes My Day]]></title><description><![CDATA[Most people collect health data they never use. This is the 5‑minute morning routine where I actually act on HRV, resting heart rate, sleep score, and body weight.]]></description><link>https://richardlemon.com/biometric-morning-routine-5-minute-data-check/</link><guid isPermaLink="false">6a0d15e0dce51298215d4021</guid><category><![CDATA[Biohacking]]></category><dc:creator><![CDATA[Richard Lemon]]></dc:creator><pubDate>Fri, 29 May 2026 04:31:32 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1600245892018-3826141b0822?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDE2fHxtb3JuaW5nfGVufDB8fHx8MTc3OTMzNzE5N3ww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[
<!--kg-card-begin: html-->
<aside class="toc-container">
    <div class="toc-sidebar">
        <h4>On this page</h4>
        <div class="toc"></div>
    </div>
</aside>
<!--kg-card-end: html-->
<h2 id="why-i-bother-checking-numbers-before-i-write-a-single-line-of-code">Why I bother checking numbers before I write a single line of code</h2><img src="https://images.unsplash.com/photo-1600245892018-3826141b0822?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDE2fHxtb3JuaW5nfGVufDB8fHx8MTc3OTMzNzE5N3ww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="My Biometric Morning Routine: The 5&#x2011;Minute Data Check That Actually Changes My Day"><p>I used to treat my body like a hosting plan.</p><p>Unlimited everything. It will be fine. Just push one more late night deploy. Then I hit a wall. Brain fog, random injuries, and days where my code reviews looked like they were written by someone throttled to 2G.</p><p>So I started tracking stuff. HRV. Resting heart rate. Sleep score. Body weight. The usual biohacker starter pack.</p><p>The problem was simple. I had dashboards and zero decisions. Data as decoration.</p><p>Now I run a strict 5&#x2011;minute <a href="https://richardlemon.com/my-biohacking-stack-2026-what-i-use-vs-what-i-dropped/" rel="noreferrer">biometric</a> check every morning. And every single number has a simple rule attached to it. If X, then I do Y. No vibes. No overthinking.</p><p>This post is exactly what I check, the thresholds I use, and how it changes my day as a developer, builder, and baseball coach who still wants to hit the gym without breaking.</p><h2 id="the-tools-on-my-body-and-on-my-desk">The tools on my body and on my desk</h2><p>This is not a gear flex. You can do a lighter version of this with almost anything. But for context, here is my actual setup right now.</p><ul><li><strong>Oura Ring</strong> for HRV, resting HR, and sleep stages.</li><li><strong>Apple Watch</strong> as a rough backup for HR and sleep when I travel.</li><li><strong>Withings smart scale</strong> for body weight.</li><li><strong>Apple Health</strong> as the central pipe.</li><li><strong>Notion</strong> for a simple morning check template.</li></ul><p>I have used Whoop, Garmin, and other toys in the past. The pattern is the same. I just care about four numbers, and I only care about them at one time: right after I wake up, before coffee, before email, before Slack.</p><h2 id="the-5%E2%80%91minute-script-what-i-check-and-in-what-order">The 5&#x2011;minute script: what I check and in what order</h2><p>I follow the same script every morning.</p><ol><li>Open Oura and note <strong>HRV</strong> and <strong>resting HR</strong>.</li><li>Check last night&#x2019;s <strong>sleep score</strong> and a couple of sub&#x2011;scores.</li><li>Step on the <strong>scale</strong>.</li><li>Open my Notion page, log four numbers, apply rules.</li></ol><p>The logging part takes 30 seconds. The decisions take 4 minutes. The rest of this post is just those decisions written out.</p><h2 id="hrv-my-recovery-traffic-light">HRV: my recovery traffic light</h2><p><a href="https://richardlemon.com/why-i-track-hrv-every-morning/" rel="noreferrer">HRV</a> is the number I respect the most. Not because it is perfect science, but because it correlates brutally with how my brain feels around 14:00.</p><p>My personal baseline HRV sits in a band. I do not care about the absolute value. I care about the <strong>trend and deviation</strong>.</p><p>Here is how I treat it.</p><h3 id="if-hrv-is-normal-or-slightly-above-baseline">If HRV is normal or slightly above baseline</h3><p>For me this means the rolling 7&#x2011;day HRV is within roughly 5 percent of my baseline or slightly above.</p><p><strong>What I do:</strong></p><ul><li><strong>Work:</strong> I schedule at least one deep focus block of 2 hours. Hard problems. Architecture, performance work, experimental features. No meetings allowed in that slot.</li><li><strong>Training:</strong> Green light for a heavier session. This might mean kettlebell swings, squats, or sprint work at baseball practice. I push volume or intensity, not both.</li><li><strong>Caffeine:</strong> Normal intake. For me that is two coffees before 11:00, none after.</li></ul><p>In short: normal HRV means I stop babying myself. I treat the day like my capacity is actually there.</p><h3 id="if-hrv-is-moderately-below-baseline">If HRV is moderately below baseline</h3><p>This is the most common &#x201C;warning&#x201D; state. A drop of 5 to 15 percent from my baseline or a clear negative trend over 3 days.</p><p><strong>What I do:</strong></p><ul><li><strong>Work:</strong> I shift big creative tasks to earlier in the day. No heroic late afternoon pushes. If something cognitively heavy is not done by 15:00, it moves to tomorrow.</li><li><strong>Training:</strong> I keep the session, but I change the intent. Less load, more technique. For example, I will hit skill work on my swing, light kettlebell work, or a walk instead of intervals.</li><li><strong>Caffeine:</strong> I cap it at one coffee. I have learned that using caffeine to fight a mild HRV drop just steals energy from the next morning.</li><li><strong>Evening:</strong> I set a hard screen cut&#x2011;off at 21:30. Blue light filter, no late coding session &#x201C;just to finish that feature&#x201D;.</li></ul><p>The key here: I do not cancel the day. I just stop pretending I am at 100 percent. It is a tactical retreat, not a rest day.</p><h3 id="if-hrv-is-tanked">If HRV is tanked</h3><p>This is when HRV drops more than ~15 percent, or I see two bad nights in a row.</p><p>This usually happens after late baseball games, travel, or a dumb combination of heavy lifting plus mindless Netflix scrolling until midnight. So it is usually my fault.</p><p><strong>What I do:</strong></p><ul><li><strong>Work:</strong> I deliberately schedule a &#x201C;maintenance day&#x201D;. That means bug fixes, refactoring, documentation, admin, or content planning. No high stakes shipping.</li><li><strong>Training:</strong> I either skip training or do a 20 to 30 minute easy walk. Zone 2 at most. Heart rate low. I do this even if my brain tries to guilt trip me with streaks.</li><li><strong>Caffeine:</strong> I watch it like a hawk. One coffee max, and ideally I push it to after breakfast instead of first thing.</li><li><strong>Sleep:</strong> I pre&#x2011;commit to an earlier bedtime. For me that means starting wind&#x2011;down by 21:00, not starting a movie at 21:00.</li></ul><p>Tank HRV days hurt my ego. They save my next three days though. Every time I ignore this number, I pay interest on fatigue for a full week.</p><h2 id="resting-heart-rate-the-infection-detector">Resting heart rate: the infection detector</h2><p>Resting heart rate pairs nicely with HRV. HRV tells me how my system is balancing stress. Resting HR often tells me if there is an extra invisible load, like an infection or recovery debt.</p><p>My resting HR sits lower on days where I am actually recovered. It bumps up when I am fighting something or when alcohol sneaks in.</p><h3 id="if-resting-hr-is-normal">If resting HR is normal</h3><p>Normal is boring. That is good. I mostly ignore it if it is within a few beats of my weekly average.</p><p><strong>What I do:</strong></p><ul><li>I use it as a sanity check for HRV. If HRV is slightly down but resting HR is fine, I treat it as psychological or just noise.</li><li>No specific change to training or work. Other numbers take priority.</li></ul><h3 id="if-resting-hr-is-elevated-by-5-bpm">If resting HR is elevated by 5+ bpm</h3><p>This is where it gets useful. For me, anything more than 5 beats above my baseline resting HR gets attention.</p><p><strong>What I do:</strong></p><ul><li><strong>Work:</strong> I assume my body is fighting something. I pre&#x2011;emptively reduce my ambition for the day. Smaller to&#x2011;do list. One key outcome, not three.</li><li><strong>Training:</strong> No intense work. No heavy squats. No sprint sessions. I keep movement but switch to walking, mobility, or light throwing at baseball practice.</li><li><strong>Food:</strong> I prioritize actual food over snacks. Protein, salt, water. Simple.</li></ul><p>I treat a high resting HR as a polite warning from my immune system. It is usually right.</p><h2 id="sleep-score-the-lie-detector-for-%E2%80%9Ci-feel-fine%E2%80%9D">Sleep score: the lie detector for &#x201C;I feel fine&#x201D;</h2><p>Sleep is the most boring topic in health, but it controls almost everything I care about: focus, mood, and whether I throw lazy pitches at practice.</p><p>My Oura sleep score is not sacred, but it is a decent lie detector for how I slept compared to how I <em>think</em> I slept.</p><h3 id="if-sleep-score-is-85">If sleep score is 85+</h3><p>For my body, anything 85 and up usually means enough deep and REM sleep, decent latency, and low disturbance.</p><p><strong>What I do:</strong></p><ul><li><strong>Work:</strong> I front&#x2011;load my hardest problem. I aim to ship something that actually matters. Not tweak fonts. Think performance tuning, architecture decisions, or writing longform content.</li><li><strong>Training:</strong> If HRV agrees, I treat this as a good day to push skill plus intensity. Heavy lifts, hard batting practice, or sprints.</li><li><strong>Social:</strong> I am more selective about late social stuff. Ironically, feeling good is when I am most likely to wreck my next night by staying out too late.</li></ul><h3 id="if-sleep-score-is-70%E2%80%9184">If sleep score is 70&#x2011;84</h3><p>This is my gray zone. The night was okay but not great.</p><p><strong>What I do:</strong></p><ul><li><strong>Work:</strong> I keep one hard block but I reduce expectations on creative output. I also avoid stacking lots of meetings, because they drain more when sleep is mid.</li><li><strong>Training:</strong> I let HRV be the tiebreaker. If HRV is fine, I keep my planned session but cut volume by 20 to 30 percent.</li><li><strong>Evening plan:</strong> I explicitly plan a calmer evening. No new series, no scrolling rabbit holes. Book, stretch, maybe a walk.</li></ul><h3 id="if-sleep-score-is-below-70">If sleep score is below 70</h3><p>This is the red zone. I know people who function like this daily. I do not.</p><p><strong>What I do:</strong></p><ul><li><strong>Work:</strong> I shift into &#x201C;ship small things well&#x201D; mode. Fix a bug. Tighten a UI animation. Write a short post. I avoid starting anything broad or strategically important.</li><li><strong>Training:</strong> No intensity. I treat it like jet lag. Walks, mobility flows, maybe some very light kettlebell work if I am restless.</li><li><strong>Caffeine:</strong> I schedule my last coffee by 10:00. I know I will be tempted to slam an espresso at 16:00. That one shot alone will trash the next night.</li></ul><p>One bad night does not ruin me. Two or three in a row do. The score keeps me honest before I drift into that pattern.</p><h2 id="body-weight-the-quiet-background-signal">Body weight: the quiet background signal</h2><p>Weight is the least dramatic metric in my morning routine, but long term it is the most unforgiving.</p><p>I weigh myself daily. Not because every day matters, but because the <strong>trend</strong> does. I care about the 7&#x2011;day average, not today&#x2019;s spike.</p><h3 id="if-weight-is-in-my-target-band">If weight is in my target band</h3><p>I keep a 1 to 1.5 kilo range that I am happy with for performance and aesthetics. If the 7&#x2011;day average sits inside that band, life is easy.</p><p><strong>What I do:</strong></p><ul><li>No change to food. I keep the same meal template: protein heavy breakfast and lunch, lighter dinner.</li><li>I focus more on performance in the gym and on the field, not the scale.</li></ul><h3 id="if-weight-trend-drifts-up-for-2-weeks">If weight trend drifts up for 2+ weeks</h3><p>I do not panic over a weekend spike. But if the 7&#x2011;day average creeps up for more than two weeks, I act.</p><p><strong>What I do:</strong></p><ul><li><strong>Food rules:</strong> I add one simple constraint. For example, no liquid calories during the week, or no snacks after 20:00. Only one rule at a time so I can see cause and effect.</li><li><strong>Training:</strong> I keep strength work and increase low intensity movement. More walking, more easy cycling between meetings.</li><li><strong>Weekends:</strong> I plan one &#x201C;higher calorie&#x201D; day rather than three accidental ones.</li></ul><h3 id="if-weight-trend-drifts-down-when-i-do-not-want-it-to">If weight trend drifts down when I do not want it to</h3><p>This has happened during busy shipping phases where I forget to eat properly between deep work and coaching baseball.</p><p><strong>What I do:</strong></p><ul><li><strong>Food:</strong> I anchor one extra proper meal with 30 to 40 grams of protein. Usually a second breakfast or a bigger lunch.</li><li><strong>Training:</strong> I keep intensity but reduce overall volume. Strength needs fuel. If the fuel is not there, I would rather hold the line than push.</li></ul><p>Weight moves slowly, which is exactly why I want a daily quick check. It is a guardrail against drift.</p><h2 id="gluing-it-together-my-actual-notion-template">Gluing it together: my actual Notion template</h2><p>I like systems that fit on one screen. So my morning biometric check lives in a tiny Notion page pinned to my &#x201C;Start&#x201D; dashboard.</p><p>The template looks like this.</p><pre><code>Date: [     ]

HRV: [     ]   Trend: [up / flat / down]
RHR: [     ]
Sleep score: [     ]
Weight (7d avg): [     ]

Work focus today:
- [ ] Deep / Creative / Maintenance  (circle one)

Training today:
- [ ] Heavy
- [ ] Moderate
- [ ] Easy / Off

Rules for today:
1) ___________________________
2) ___________________________
</code></pre><p>I do not write essays in there. I tick one option for work, one for training, then write one or two short rules for the day. Things like:</p><ul><li>&#x201C;No caffeine after 10:00.&#x201D;</li><li>&#x201C;Walking and mobility only.&#x201D;</li><li>&#x201C;Ship X feature before lunch.&#x201D;</li></ul><p>That is it. The whole routine fits between brushing my teeth and making coffee.</p><h2 id="why-this-5%E2%80%91minute-check-beats-chasing-perfect-data">Why this 5&#x2011;minute check beats chasing perfect data</h2><p>Most people I know who wear rings and watches make the same mistake I made at the start. They collect everything and change nothing.</p><p>I do not think better sensors will fix that. I think better rules will.</p><p>My rule now is simple. If a metric does not change my plan for the day, I stop tracking it. That is why I care about exactly four in the morning.</p><ul><li><strong>HRV</strong> sets my recovery and intensity ceiling.</li><li><strong>Resting HR</strong> tells me if there is hidden stress or sickness.</li><li><strong>Sleep score</strong> shapes my cognitive load and caffeine plan.</li><li><strong>Body weight</strong> keeps my long&#x2011;term trend honest.</li></ul><p>If those four numbers say &#x201C;go&#x201D;, I go hard. If they say &#x201C;easy&#x201D;, I listen. Not because I turned into a health monk, but because I like shipping work that does not feel like wading through mud.</p><p>I build web experiences, I coach baseball, and I still want energy left to be a human at the end of the day. This 5&#x2011;minute biometric routine is how I stack the odds in my favor, one very boring, very practical morning at a time.</p>]]></content:encoded></item><item><title><![CDATA[Sovereign AI: Owning Your Model, Not Just Your Data]]></title><description><![CDATA[Centralized AI wants your data. Sovereign AI asks what you can do with your own. I’ll show how I actually run decentralized models and keep my data close.]]></description><link>https://richardlemon.com/sovereign-ai-personal-data-sovereignty/</link><guid isPermaLink="false">6a0e674edce51298215d402e</guid><category><![CDATA[AI]]></category><category><![CDATA[Build in Public]]></category><dc:creator><![CDATA[Richard Lemon]]></dc:creator><pubDate>Thu, 28 May 2026 04:31:09 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1642516303080-431f6681f864?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDE4fHxkYXRhfGVufDB8fHx8MTc3OTI4NDY0NXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[
<!--kg-card-begin: html-->
<aside class="toc-container">
    <div class="toc-sidebar">
        <h4>On this page</h4>
        <div class="toc"></div>
    </div>
</aside>
<!--kg-card-end: html-->
<h2 id="centralized-ai-wants-your-data">Centralized AI wants your data</h2><img src="https://images.unsplash.com/photo-1642516303080-431f6681f864?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDE4fHxkYXRhfGVufDB8fHx8MTc3OTI4NDY0NXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="Sovereign AI: Owning Your Model, Not Just Your Data"><p>I like large language models. I use them daily. I also do not trust any of the big AI platforms with my data beyond the absolute minimum I am forced to share.</p><p>If a model lives on someone else&#x2019;s server, behind someone else&#x2019;s API key, that model ultimately works for them, not for you. You might get nice features. Autocomplete. Chat. Code suggestions. But the real value is in the feedback loop that your data creates for their model.</p><p>That is the core problem sovereign AI tries to fix. Not by begging vendors for better terms of service, but by changing who actually runs the model and where your data lives.</p><h2 id="what-i-mean-by-sovereign-ai">What I mean by &quot;sovereign AI&quot;</h2><p>Sovereign AI gets thrown around as a marketing phrase. I use it in a very narrow, practical way:</p><ul><li>The model runs on hardware you control. That can be your laptop, a home server, or a rented machine where you own the full stack.</li><li>Your data never leaves your control in raw form. No uploads to mystery training pipelines. No hidden logs.</li><li>You can swap, retrain, or fine-tune models without asking a vendor for permission.</li></ul><p>It is basically DevOps for your brain extension. You own the infra, not just the prompt.</p><p>Personal <a href="https://richardlemon.com/cold-showers-90-days-hrv-mood-sleep/" rel="noreferrer">data</a> sovereignty then becomes a side effect. If the model is physically close to you and you manage it like any other service, your notes, emails, health data, and weird side projects do not become part of somebody else&#x2019;s training set.</p><h2 id="why-i-stopped-feeding-the-cloud-everything">Why I stopped feeding the cloud everything</h2><p>I used to paste entire documents into various AI tools. Client briefs. Meeting notes. Half-baked startup ideas. I told myself the usual lie: &quot;They say they do not use this for training, so it is fine.&quot;</p><p>Then I looked at my actual workflow. There were three big problems.</p><ul><li><strong>Trust drift.</strong> Policies change. Teams get acquired. Your data is still sitting in logs and backups you never see.</li><li><strong>Context paralysis.</strong> I constantly had to think: &quot;Can I send this?&quot; That friction kills flow. I would rather remove the risk entirely.</li><li><strong>Identity leakage.</strong> If a significant chunk of your thoughts, plans, and behavior patterns live in someone else&#x2019;s model, you are training an AI that understands you better than you understand yourself. And you do not own it.</li></ul><p>Once I framed it that way, sending more data to closed models started to feel like paying to help someone else build a smarter advertising engine. Or a smarter sales engine. Or a smarter government surveillance tool, depending on the jurisdiction.</p><p>I wanted a model that worked for me first.</p><h2 id="what-decentralized-ai-actually-looks-like-on-my-desk">What decentralized AI actually looks like on my desk</h2><p>This is where a lot of writing about &quot;decentralized AI&quot; stays vague and fluffy. So here is what I actually run.</p><p>On my main machine I have:</p><ul><li><strong>Ollama</strong> as the local model runner. It pulls models with a one-line command and gives me a simple HTTP API.</li><li>Several models pulled locally: a small fast one for coding, a slightly bigger one for reasoning and writing, and an embedding model for search.</li><li>A thin Node.js wrapper that exposes these models through a single internal endpoint, with some routing logic based on task type.</li></ul><p>I also sync a subset of my notes from my main note-taking system as plain text into a local folder. That folder is indexed with a vector database that also runs locally. No remote sync. No analytics SDKs.</p><p>So my &quot;personal AI&quot; is literally a few processes sitting next to my browser and IDE. No external calls once the models are downloaded. If my internet dies, it keeps working.</p><h2 id="why-this-gives-me-stronger-data-sovereignty">Why this gives me stronger data sovereignty</h2><p>Running things locally changes how I think about what I can give the model. Suddenly, I can feed it the stuff I would never send to a third party.</p><ul><li>Private health logs. Sleep data, <a href="https://richardlemon.com/90-days-hrv-data-stress-patterns/" rel="noreferrer">HRV</a>, weird supplements I am testing.</li><li>Client contracts and pricing experiments.</li><li>Deep personal journals that I would not share with any SaaS platform, no matter how many security badges they display.</li></ul><p>The risk profile changes. A breach of my laptop is still bad. But that is a risk I already have, with or without AI. I control the attack surface: full-disk encryption, backups, physical security.</p><p>Compare that with giving everything to some opaque AI backend. You are betting your future on their least motivated security engineer and their finance department&#x2019;s hunger for &quot;data-driven upsell opportunities&quot;.</p><p>With a sovereign setup, I run my own retention policy. Logs do not leave my machine unless I say so. I can blow away the entire index and retrain from scratch if I feel something is off.</p><h2 id="decentralized-does-not-mean-everyone-runs-a-gpu-farm">Decentralized does not mean &quot;everyone runs a GPU farm&quot;</h2><p>I think a lot of developers overcomplicate this. They imagine sovereign AI means they need a rack of RTX cards humming in a closet and a half-baked Kubernetes cluster that occasionally melts down.</p><p>That is one end of the spectrum. There is another.</p><p>There are at least three levels of sovereignty you can play with.</p><ul><li><strong>Local-only.</strong> Everything on your device. Models, index, data. Maximum control, limited by your hardware.</li><li><strong>Self-hosted, single tenant.</strong> A small rented server or home box. You still own the stack, but can share it across devices and maybe across a small team.</li><li><strong>Community-hosted.</strong> Federated setups where a collective runs infra but with strong guarantees about data isolation and open models.</li></ul><p>I mostly stick to local-only for truly personal stuff. For collaborative projects, I lean toward self-hosted. Simple Docker Compose file, Cloudflare tunnel, basic auth, and strict logging. Nothing fancy.</p><p>Decentralized here just means we stop assuming &quot;AI = one giant company with a single API&quot;. Instead, we accept AI as an application runtime that anyone can run, fork, and remix.</p><h2 id="models-are-the-new-personal-data-vaults">Models are the new personal data vaults</h2><p>There is a subtle shift when you start treating your model like a personal asset instead of a black box API.</p><p>In the old world, your &quot;data vault&quot; was your files. Notes, CSVs, PDFs. Static content.</p><p>With sovereign AI, your vault includes:</p><ul><li>The raw data: notes, logs, docs.</li><li>The embeddings and indexes created from that data.</li><li>The fine-tuned weights of any model you adapt for your own patterns.</li></ul><p>Those weights are incredibly sensitive. They are not just &quot;some numbers&quot;. They reflect your thinking style, your domain knowledge, your trade secrets. In many ways they are more revealing than the original files, because they capture what you focus on and how you connect ideas.</p><p>So I store them like I store backups. Encrypted, versioned, and not casually synced through whichever random sync app has the nicest icon.</p><p>If you are using models that a vendor can silently swap out, you do not have that option. You cannot freeze your brain. They can. You just receive whatever behavior the latest rollout has decided is acceptable.</p><h2 id="how-this-changes-daily-workflows">How this changes daily workflows</h2><p>The best part is not the philosophical purity. It is the small, boring wins that add up.</p><p>Concrete things that changed for me once I went more sovereign:</p><ul><li><strong>Richer prompts.</strong> I stop trimming context to avoid leaking secrets. Instead I can say &quot;Here is the full repo&quot; or &quot;Here is my actual bloodwork&quot; without flinching.</li><li><strong>Longer memory.</strong> I let the system build a persistent memory across projects because I know where it lives and how it is stored.</li><li><strong>Honest journaling.</strong> My nightly brain dump is blunt and unfiltered. Then I let the model reflect on it. That only works if I trust the runtime.</li><li><strong>Zero-rate limiting.</strong> I never hit an API quota while in flow. My GPU fans are the only throttle.</li></ul><p>This turns the model from a cute assistant into a serious colleague that actually knows my world. Not just &quot;the internet&quot; plus whatever I am willing to paste into a browser window.</p><h2 id="where-i-still-use-centralized-ai">Where I still use centralized AI</h2><p>I am not a purist. Centralized AI is still incredibly useful.</p><p>I use hosted models for:</p><ul><li>Quick one-off questions that are not sensitive.</li><li>Testing new capabilities before they land in local models.</li><li>Large batch jobs where I do not want to tie up my machine for hours.</li></ul><p>But I treat those models as external consultants. Helpful, but not allowed into the core of my data or my long-term memory.</p><p>If I am shipping something production-facing for a client, I also separate concerns. Public features can use hosted AI. Private internal tooling leans local or self-hosted. Different risk profile, different solution.</p><h2 id="practical-constraints-and-trade-offs">Practical constraints and trade-offs</h2><p>I do not think sovereign AI is free. You trade money and convenience for control and privacy.</p><p>Some real drawbacks I hit:</p><ul><li><strong>Hardware cost.</strong> A decent GPU is not cheap. Running on CPU works, but it is slower and limits model size.</li><li><strong>Ops overhead.</strong> You have to patch, monitor, and occasionally debug your stack. If you hate that, you will resent this setup.</li><li><strong>Model quality gap.</strong> Frontier models still outperform most local options on complex tasks. You notice it if you are used to the latest big-name model.</li></ul><p>For me, these are acceptable trade-offs. I like tinkering with infra. I like understanding the failure modes. I prefer a slightly dumber but loyal assistant over a genius that reports back to someone else.</p><p>If you expect magic with zero maintenance, you will be disappointed. Sovereignty comes with chores, whether we talk about governments, bodies, or AI stacks.</p><h2 id="where-i-think-this-is-going">Where I think this is going</h2><p>I think the interesting future is not &quot;one mega model for everyone&quot;. It is millions of half-specialized, half-personalized models that live closer to users.</p><p>Your base model might be public and shared. Your fine-tunes and indexes are not. They are bound to your device, your household, your team. You swap base models like you update a library, but you keep your private layer.</p><p>We already do this with code. We do not compile the entire internet into one binary. We use shared libraries and then wire them into projects that matter to us. AI should not be special here.</p><p>I would love to see:</p><ul><li>Standard formats for exporting and importing personal model state.</li><li>Local-first AI tools that assume no outbound network by default.</li><li>Home servers treated as first-class AI nodes, not an afterthought.</li></ul><p>If we get that right, personal data sovereignty stops being a legal slogan and becomes a technical property. You own your weights. You own your indexes. You decide where your intelligence runs.</p><h2 id="what-i-would-do-if-i-were-starting-fresh">What I would do if I were starting fresh</h2><p>If you are still all-in on hosted AI and want to pull some of that gravity back to your side, here is the path I would actually take again.</p><ul><li>Pick one local model runner and stick to it for a while. Ollama, LM Studio, whatever. Do not chase every new tool.</li><li>Move one workflow at a time. For me that was journaling first, then code assistance, then research.</li><li>Keep your data layer boring. Plain text files in a folder beat six clever databases and a mystery SaaS sync.</li><li>Write a tiny script that wraps your local model in a clean API you control. Use that from your editor, your browser, and your scripts.</li></ul><p>After a month you will start to feel the difference. Less hesitation about prompts. Fewer &quot;can I paste this&quot; worries. More willingness to let the model see the real mess, not the sanitized version.</p><p>That is what sovereignty feels like in practice. Not a manifesto. Just the quiet comfort that your AI works for you, on your machines, with your rules.</p><p>The moment you feel that, sending everything you think to some distant cluster will start to feel very strange.</p>]]></content:encoded></item><item><title><![CDATA[Scroll-snap in 2026: when it works and when it fights the user]]></title><description><![CDATA[Scroll-snap is finally stable enough to ship without fear, but it still loves to fight the user if you aim it at the wrong problem. Here is where I actually use it in 2026, and where I avoid it.]]></description><link>https://richardlemon.com/scroll-snap-2026-right-call-vs-fights-user/</link><guid isPermaLink="false">6a0bc44fdce51298215d3fc3</guid><category><![CDATA[CSS]]></category><category><![CDATA[Frontend]]></category><dc:creator><![CDATA[Richard Lemon]]></dc:creator><pubDate>Wed, 27 May 2026 07:44:25 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1506377711776-dbdc2f3c20d9?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDZ8fHNjcm9sbHxlbnwwfHx8fDE3NzkxOTc2NDd8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[
<!--kg-card-begin: html-->
<aside class="toc-container">
    <div class="toc-sidebar">
        <h4>On this page</h4>
        <div class="toc"></div>
    </div>
</aside>
<!--kg-card-end: html-->
<h2 id="scroll-snap-finally-works-mostly">Scroll-snap finally works. Mostly.</h2><img src="https://images.unsplash.com/photo-1506377711776-dbdc2f3c20d9?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDZ8fHNjcm9sbHxlbnwwfHx8fDE3NzkxOTc2NDd8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="Scroll-snap in 2026: when it works and when it fights the user"><p>I ignored scroll-snap for years. Too many bugs. Too many cross-browser &quot;almost&quot; moments. By 2026, that excuse is gone. The spec is solid, browser support is good, and the roughest edges are sanded down.</p><p>I now use scroll-snap in production. On real projects. For real clients. It feels good when I get it right, and completely hostile when I force it into the wrong context. This is the pattern I see over and over.</p><h2 id="my-rough-rule-does-the-user-think-in-pages-or-in-flow">My rough rule: does the user think in pages or in flow?</h2><p>I use a simple mental model when I consider scroll-snap.</p><ul><li>If the content is naturally chunked into discrete pages or cards, scroll-snap is usually a good fit.</li><li>If the content is a continuous flow of text or mixed content, scroll-snap almost always feels wrong.</li></ul><p>People either think &quot;next card / next slide&quot; or they think &quot;keep scrolling&quot;. Scroll-snap works for the first one. It fights the user on the second.</p><h2 id="where-scroll-snap-feels-native">Where scroll-snap feels native</h2><p>Let me run through the places where scroll-snap has actually stuck in my work. Not experiments. Live stuff.</p><h3 id="1-horizontal-carousels-that-feel-like-native-app-sliders">1. Horizontal carousels that feel like native app sliders</h3><p>This is the obvious one, but I still see it built with 4,000 lines of JS.</p><p>If you have a row of cards that the user swipes through horizontally, scroll-snap is usually perfect. Think feature carousels, product tiles, dashboard widgets, onboarding steps.</p><p>The pattern I use:</p><pre><code>.carousel {
  scroll-snap-type: x mandatory;
  overflow-x: auto;
  display: flex;
  scroll-behavior: smooth; /* optional, careful with this */
}

.carousel &gt; * {
  scroll-snap-align: start;
  flex: 0 0 100%; /* or clamp(...) based on design */
}
</code></pre><p>On mobile, this feels like a native view pager. The mental model matches: one card at a time, swipe to the next. The browser handles momentum and snapping. I do not touch scroll events at all.</p><p>When it works best:</p><ul><li>Each card is visually distinct and self-contained.</li><li>You have clear pagination indicators or progress.</li><li>You avoid nesting multiple snap carousels inside each other.</li></ul><p>I think horizontal carousels are the main place where scroll-snap is absolutely the right tool.</p><h3 id="2-story-style-layouts-and-full-bleed-sections">2. Story-style layouts and full-bleed sections</h3><p>I built a landing page last year with full-viewport sections. Each section was basically a &quot;slide&quot; of the story. Big headline. Illustration. Single CTA.</p><p>The client originally wanted fake scroll-jacking: one flick of the wheel, snap to the next full panel. You know the style. Looks like a slide deck disguised as a website. My compromise was scroll-snap.</p><pre><code>body {
  scroll-snap-type: y mandatory;
}

section.full-screen {
  scroll-snap-align: start;
  min-height: 100vh;
}
</code></pre><p>The big difference compared to pure scroll-jacking: the browser still owns scrolling physics. I am not animating scrollTop in JS. I am simply expressing an intention: when a user releases scroll, prefer these resting positions.</p><p>This worked surprisingly well on touch devices. It felt like swiping between slides. On desktop, it was a bit more polarizing but still better than intercepting every wheel tick.</p><p>When this pattern makes sense:</p><ul><li>You really do have full-screen sections, not half-screen snippets.</li><li>The copy inside each section is short. No one needs to scroll inside a section.</li><li>The layout behaves nicely at different heights. 11-inch laptops and tall monitors both look okay.</li></ul><p>If you find yourself adding internal scrolling inside a snap section, that is the first sign you picked the wrong layout for scroll-snap.</p><h3 id="3-onboarding-step-flows-and-checklists">3. Onboarding, step flows, and checklists</h3><p>I built a small web app that walks new users through a setup process. Notifications, integrations, profile, that kind of thing.</p><p>Instead of a classic multi-step wizard with &quot;Next&quot; and &quot;Back&quot; buttons, I used a vertical stack of cards and scroll-snap. Each step was one card. User scrolls down, it locks to the next card.</p><p>Two nice things happened:</p><ul><li>The user could skim all steps quickly. Scroll velocity still matters.</li><li>Each card felt like a clear decision point, not just another part of a long form.</li></ul><p>Pattern looked like this:</p><pre><code>.steps {
  scroll-snap-type: y proximity;
  max-height: 100vh;
  overflow-y: auto;
}

.step {
  scroll-snap-align: start;
  padding-block: 3rem;
}
</code></pre><p>I used <code>proximity</code> instead of <code>mandatory</code>, so snapping only kicks in when you are near a snap point. That gives a bit more control back to the user.</p><p>For short, focused flows like this, scroll-snap helps structure the experience without feeling like a trap.</p><h3 id="4-media-rails-and-galleries">4. Media rails and galleries</h3><p>Photo galleries. Video rails. Collections of thumbnails. Scroll-snap fits here if you keep it simple.</p><p>I usually keep the main page scroll normal, and only add scroll-snap to horizontal media rails. This avoids messing with the core reading flow while still giving that &quot;one card per stop&quot; feel for visual <a href="https://richardlemon.com/how-to-write-content-that-ai-cites-2026/" rel="noreferrer">content</a>.</p><p>It is tempting to build fancy cinematic galleries that lock the viewport tightly. I tried that, then watched users repeatedly fight the scroll because they just wanted to browse. Now I think of scroll-snap here as a hint, not a constraint.</p><h2 id="where-scroll-snap-fights-the-user">Where scroll-snap fights the user</h2><p>Now the painful part. Places where scroll-snap seemed clever in Figma but felt terrible in reality.</p><h3 id="1-long-form-reading-and-articles">1. Long-form reading and articles</h3><p>I tried scroll-snap on a personal writing experiment. Full-screen sections, big typography, split into &quot;chapters&quot;. It looked slick. It read badly.</p><p>People do not read text like cards. They skim, bounce, scroll back a bit, stop mid-paragraph, continue later. Scroll-snap breaks this rhythm.</p><p>Two things went wrong:</p><ul><li>Stopping mid-section became annoying. You scroll a tiny bit to re-read a line and it yanks you to the top of the section.</li><li>The viewport breakpoints cut paragraphs in odd places at various screen heights.</li></ul><p>I removed scroll-snap and it instantly felt more natural, even though I lost that fake &quot;presentation&quot; vibe.</p><p>My rule now is strict: no scroll-snap for primarily text-based content. Blogs, documentation, long release notes. It always feels like the <a href="https://richardlemon.com/the-invisible-cost-of-web-animations-and-how-to-ship-fast-smooth-ui/" rel="noreferrer">UI</a> is more important than the reading.</p><h3 id="2-accessibility-keyboard-screen-readers-and-reduced-motion">2. Accessibility: keyboard, screen readers, and reduced motion</h3><p>The spec and implementations are better now, but scroll-snap still introduces friction if you do not think about input methods beyond trackpads.</p><p>Edge cases I hit:</p><ul><li><strong>Keyboard users</strong>: PageDown or spacebar trying to move by viewport height but snap yanking them back to a card boundary.</li><li><strong>Focus management</strong>: elements getting scrolled somewhere between snaps, then the layout jumping when the user tries to tab.</li><li><strong>Reduced motion users</strong>: <code>scroll-behavior: smooth</code> combined with snap producing long, animated scrolls that are exhausting.</li></ul><p>I now treat <code>scroll-behavior: smooth</code> as something that should usually respect <code>prefers-reduced-motion</code>. If I use it next to scroll-snap, I wrap it:</p><pre><code>@media (prefers-reduced-motion: no-preference) {
  .carousel {
    scroll-behavior: smooth;
  }
}
</code></pre><p>Also, when I build scroll-snap experiences that are more than a simple carousel, I test with just keyboard on a laptop. If I feel like I am fighting the layout, I back off.</p><h3 id="3-nested-scroll-containers">3. Nested scroll containers</h3><p>Nesting scrollable areas is already tricky. Throw scroll-snap into the mix and tiny differences in momentum handling across browsers turn into visible bugs.</p><p>I had a dashboard layout with a vertical scrollable page. Inside that, a horizontal snap carousel with cards. Inside one card, a small vertical scroll area.</p><p>On Safari this felt mostly acceptable. On Chrome, touch gestures sometimes locked onto the wrong axis and flicked the outer container instead of sliding the inner carousel.</p><p>Technically nothing was broken. It just felt random.</p><p>My approach now:</p><ul><li>Avoid vertical scroll inside vertical-snap parents.</li><li>Avoid diagonal gesture ambiguity. Pick one primary axis per region.</li><li>Accept that deeply nested scroll-snap is going to behave differently between engines.</li></ul><p>If your layout needs three nested scrollables, scroll-snap probably is not the problem. The layout is.</p><h3 id="4-fake-carousels-built-on-body-scroll">4. Fake carousels built on body scroll</h3><p>One pattern I see a lot: a designer wants a &quot;carousel&quot; feel, but the implementation uses the main page scroll instead of a dedicated container.</p><p>So you get a long page of full-width panels, and snap on <code>body</code> or <code>html</code>. Each wheel tick tries to pull you to the next full-screen card.</p><p>This sounds minimal. No extra wrappers. Nice and pure. In practice, though, it creates weird interactions with browser UI, address bars on mobile, and anchored navigation. You also lose the ability to scroll the page freely while leaving other stuff (headers, sidebars) fixed.</p><p>I think scroll-snap works better when scoped to a clear component: a carousel, a story rail, an onboarding stack. Once you snap the whole document, you are overriding one of the most basic browser behaviors we have.</p><h2 id="choosing-between-mandatory-proximity-and-not-using-it-at-all">Choosing between <code>mandatory</code>, <code>proximity</code>, and not using it at all</h2><p>I use scroll-snap aggressively in prototypes, then usually downgrade it in production. There are three states I move between.</p><h3 id="hard-snap-scroll-snap-type-mandatory">Hard snap: <code>scroll-snap-type: mandatory</code></h3><p>I use this only when I want very strong opinions. Full-screen sections, strictly one-card-per-view carousels, or kiosk-style experiences.</p><p>If you do not control the environment (monitor size, device type, touch vs wheel), <code>mandatory</code> can feel brutal. That is fine for installations or marketing microsites where spectacle is the product. Not great for a dashboard someone lives in eight hours a day.</p><h3 id="soft-snap-scroll-snap-type-proximity">Soft snap: <code>scroll-snap-type: proximity</code></h3><p>This is the default I reach for now. It behaves like &quot;snap when it feels natural, but do not fight the user&quot;.</p><p>You can scroll through multiple cards with a fast fling. You can stop between them. When you land near a snap point, the browser gently pulls you in. For many use cases this is the sweet spot.</p><h3 id="no-snap-regular-scrolling">No snap: regular scrolling</h3><p>I prototype a layout, add scroll-snap, then actually use it for a few minutes. If I ever feel annoyed, I remove it.</p><p>Most of the time that happens on long-form layouts, mixed-content pages, or anything with sidebars and lots of interactive elements. Regular scrolling wins.</p><h2 id="scroll-snap-plus-js-when-to-mix-them">Scroll-snap plus JS: when to mix them</h2><p>I prefer scroll-snap as the primary mechanic and JavaScript as an enhancement or backstop, not the other way around.</p><p>Two patterns that worked for me.</p><h3 id="1-programmatic-go-to-card-without-custom-physics">1. Programmatic &quot;go to card&quot; without custom physics</h3><p>If I build a carousel with scroll-snap, and the design wants Next / Previous arrows or dot navigation, I hook into it with <code>scrollTo</code> and let snap finish the job.</p><pre><code>function scrollToCard(container, index) {
  const card = container.children[index];
  if (!card) return;

  const left = card.offsetLeft;
  container.scrollTo({ left, behavior: &apos;smooth&apos; });
}
</code></pre><p>Snap ensures the resting position is consistent, even if the math is slightly off. The browser still manages momentum and any platform-specific quirks.</p><h3 id="2-syncing-state-with-scroll-position">2. Syncing state with scroll position</h3><p>Sometimes I want to update a progress bar or highlight an active nav item as the user scrolls a snap container.</p><p>I use <code>IntersectionObserver</code> on the snap children, not raw scroll events. That way, I piggyback on the snap behavior instead of fighting it.</p><pre><code>const observer = new IntersectionObserver(entries =&gt; {
  for (const entry of entries) {
    if (entry.isIntersecting) {
      setActiveStep(entry.target.dataset.stepId);
    }
  }
}, {
  root: document.querySelector(&apos;.steps&apos;),
  threshold: 0.6
});

for (const step of document.querySelectorAll(&apos;.step&apos;)) {
  observer.observe(step);
}
</code></pre><p>The user scrolls, scroll-snap does its thing, and the JS just listens for &quot;which card is mostly in view&quot;. No constant tug-of-war.</p><h2 id="practical-checklist-should-you-use-scroll-snap-here">Practical checklist: should you use scroll-snap here?</h2><p>This is the quick checklist I run through now.</p><ul><li><strong>Is the content naturally paged?</strong> Cards, slides, steps, photos. Good candidate.</li><li><strong>Does the user need fine-grained scroll control?</strong> Reading, code, long specs. Skip scroll-snap.</li><li><strong>Is there nested scrolling?</strong> If yes, be very suspicious. Try to simplify.</li><li><strong>Have you tested keyboard and reduced-motion?</strong> If not, you are not done.</li><li><strong>Are you using <code>mandatory</code> just because it feels &quot;clean&quot;?</strong> Try <code>proximity</code> first.</li><li><strong>Does the layout still work if scroll-snap is disabled?</strong> If not, your UX is too dependent on a single behavior.</li></ul><h2 id="scroll-snap-in-2026-stable-but-not-neutral">Scroll-snap in 2026: stable, but not neutral</h2><p>Scroll-snap is not experimental anymore. It is a sharp tool. Sharp tools are great when you use them for the right job and they are brutal when you grab them just because they look cool.</p><p>I now think of scroll-snap as a way to express structure, not as a way to show off motion. If the structure is already there in the content model, scroll-snap usually feels natural. If I am trying to hide a messy layout behind clever snapping, users notice.</p><p>So I keep it scoped, test it on real devices, and remove it the moment it starts winning arguments against the user.</p>]]></content:encoded></item><item><title><![CDATA[My Step‑By‑Step Local SEO GEO Strategy For Small Businesses]]></title><description><![CDATA[How I actually build a GEO strategy for real small business clients. From first audit to entity setup, llms.txt, schema, and content structure, week by week.]]></description><link>https://richardlemon.com/local-seo-geo-strategy-small-business-step-by-step/</link><guid isPermaLink="false">6a092142dce51298215d3f89</guid><category><![CDATA[Build in Public]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Richard Lemon]]></dc:creator><pubDate>Tue, 26 May 2026 07:42:23 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1687422810663-c316494f725a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDl8fHNtYWxsJTIwYnVzaW5lc3N8ZW58MHx8fHwxNzc5MTk3NTA4fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[
<!--kg-card-begin: html-->
<aside class="toc-container">
    <div class="toc-sidebar">
        <h4>On this page</h4>
        <div class="toc"></div>
    </div>
</aside>
<!--kg-card-end: html-->
<h2 id="why-i-treat-geo-as-an-engineering-problem">Why I Treat GEO As An Engineering Problem</h2><img src="https://images.unsplash.com/photo-1687422810663-c316494f725a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDl8fHNtYWxsJTIwYnVzaW5lc3N8ZW58MHx8fHwxNzc5MTk3NTA4fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="My Step&#x2011;By&#x2011;Step Local SEO GEO Strategy For Small Businesses"><p>I do local SEO like I do frontend architecture. Systems first, vibes second.</p><p>Most small businesses that reach out to me want the same thing. &quot;Rank for <em>my service</em> + <em>my city</em> and get more calls.&quot; They usually have a five-page WordPress site from 2017, a half-filled Google Business Profile, and a bunch of junk citations from directories they never asked for.</p><p>So I stopped improvising. I built a repeatable GEO strategy. Same skeleton every time. Then I tweak per niche.</p><p>Here is the process I actually use with clients. Timeline, steps, what I look at, and where AI and llms.txt fit in.</p>]]></content:encoded></item><item><title><![CDATA[How I Actually Time Block a Week Without Burning Out]]></title><description><![CDATA[I tried to engineer the perfect calendar. Then I watched it get wrecked by reality every single week. This is the version of time blocking that actually survives contact.]]></description><link>https://richardlemon.com/how-i-actually-time-block-a-week-without-burning-out/</link><guid isPermaLink="false">6a0a72e1dce51298215d3f92</guid><category><![CDATA[Productivity]]></category><category><![CDATA[Build in Public]]></category><dc:creator><![CDATA[Richard Lemon]]></dc:creator><pubDate>Mon, 25 May 2026 07:43:16 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1456574808786-d2ba7a6aa654?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDR8fHRpbWUlMjBibG9ja2luZ3xlbnwwfHx8fDE3NzkxOTc1Nzl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[
<!--kg-card-begin: html-->
<aside class="toc-container">
    <div class="toc-sidebar">
        <h4>On this page</h4>
        <div class="toc"></div>
    </div>
</aside>
<!--kg-card-end: html-->
<h2 id="my-calendar-looks-perfect-for-about-6-hours">My calendar looks perfect. For about 6 hours.</h2><img src="https://images.unsplash.com/photo-1456574808786-d2ba7a6aa654?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDR8fHRpbWUlMjBibG9ja2luZ3xlbnwwfHx8fDE3NzkxOTc1Nzl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="How I Actually Time Block a Week Without Burning Out"><p>I love clean time blocks. Color-coded. Edges aligned to the half hour. A neat little life in 2D.</p><p>Reality does not care.</p><p>I coach <a href="https://richardlemon.com/baseball-analytics-u15-without-trackman/" rel="noreferrer">baseball</a>. I build weird web experiences. I write. I have a family. I also like sleeping and not being a wreck by Thursday.</p><p>So this is what I actually do. Not the Instagram version of time blocking. The version that gets punched in the face by kids, clients, and Dutch weather, and still mostly works.</p><h2 id="the-fake-perfect-week-i-wanted">The fake perfect week I wanted</h2><p>When I started taking time blocking seriously, I tried to design the ideal builder-coach-writer week.</p><p>On paper, it looked like this:</p><ul><li>07:00 &#x2013; 08:30: Deep work (writing)</li><li>09:00 &#x2013; 12:00: Dev work (client or product)</li><li>13:00 &#x2013; 16:00: Dev work (focus blocks)</li><li>16:30 &#x2013; 18:30: Coaching (training or games)</li><li>20:00 &#x2013; 21:00: Light admin, planning next day</li></ul><p>Sprinkle in lunch, a walk, some mobility, and an enlightened look on my face. Perfect.</p><p>That calendar survived about one week. Kind of. Then reality sent some feedback:</p><ul><li>Morning writing often collided with late games or travel the night before.</li><li>Client calls landed right in the middle of &quot;sacred&quot; deep work.</li><li>Rain rescheduled practices. Games moved. Whole evenings disappeared.</li><li>Kids have zero respect for my notion of context switching.</li></ul><p>I kept trying to force life into that ideal template. I was exhausted by the second month.</p><h2 id="the-mental-shift-blocks-are-bets-not-promises">The mental shift: blocks are bets, not promises</h2><p>The week started working when I changed how I think about blocks.</p><p>I used to treat a time block like a contract. &quot;Tuesday 9 to 12 is deep work, non-negotiable.&quot; Guess what. Life negotiates.</p><p>Now I treat blocks like bets.</p><ul><li>I bet that if nothing explodes, I will use this time for X.</li><li>If something explodes, the block moves or shrinks, but the <em>energy type</em> stays the same.</li></ul><p>That energy-type idea is important. I care less about whether I wrote a blog or coded a feature. I care more that I used deep-focus energy for something that deserved it.</p><p>This is how I group the week now:</p><ul><li><strong>Deep build time</strong>: no meetings, no Slack, high-energy work.</li><li><strong>Shallow work</strong>: email, comments, tickets, light coding.</li><li><strong>Coaching time</strong>: physical, social, high-output.</li><li><strong>Recovery time</strong>: walks, reading, stretching, being offline.</li></ul><p>Then I lay blocks for those energy types first. Exact tasks come later.</p><h2 id="the-real-weekly-structure-i-use-now">The real weekly structure I use now</h2><p>I will walk you through a real pattern I fall back on. This is my baseline. Every Sunday I copy this into the next week, then adapt.</p><h3 id="monday-protect-the-runway">Monday: protect the runway</h3><p>Monday used to be my &quot;let&apos;s catch up&quot; day. Which is a nice way to say &quot;let&apos;s scatter my brain.&quot; Now I use it to set the runway for the week.</p><ul><li><strong>07:30 &#x2013; 08:30</strong>: Walk + notes. No laptop. Just a short walk and notes on my phone. I brain dump everything I think I &quot;have&quot; to do.</li><li><strong>09:00 &#x2013; 10:00</strong>: Weekly planning. Calendar open. Task manager open. I assign work to days. I also block out coaching slots first, because those are fixed.</li><li><strong>10:00 &#x2013; 12:00</strong>: Deep build block. Usually architecture decisions, prototypes, or anything that needs a fresh brain.</li><li><strong>13:30 &#x2013; 15:30</strong>: Client or product work. Still deep, but I allow one call if needed.</li><li><strong>Evening</strong>: Free or recovery. Never hard commitments if I can avoid it.</li></ul><p>Key rule for Monday: I refuse to start the week already behind.</p><p>I say no to Monday morning meetings whenever I can. I want the first big block of the week to be me making progress on my work, not reacting to other people.</p><h3 id="tuesday-wednesday-heavy-build-days">Tuesday &amp; Wednesday: heavy build days</h3><p>These are my brutalist days. I front-load as much cognitive load as possible into 48 hours.</p><ul><li><strong>08:30 &#x2013; 11:30</strong>: Deep work block. Usually coding. Sometimes design and UX flows. I aim for one clear objective per block. Ship a thing. Remove a blocker. Finish a chunk of infrastructure.</li><li><strong>11:30 &#x2013; 12:00</strong>: Admin sprint. Inbox, messages, quick replies. I run a 25-minute timer and process aggressively.</li><li><strong>13:30 &#x2013; 15:30</strong>: Second build block. Slightly lighter. Code reviews, refactors, tests, content edits.</li><li><strong>Late afternoon / evening</strong>: Coaching. Training sessions, drills, planning lineups.</li></ul><p>The reality check part:</p><ul><li>If a game gets rescheduled to midweek, the evening coaching block stretches or moves earlier. I don&apos;t fight it. <a href="https://richardlemon.com/u15-baseball-feedback-loops-for-engineering-teams/" rel="noreferrer">Coaching</a> is not optional.</li><li>If a client insists on a meeting here, I trade. That means I protect a different deep block later and mark it as untouchable.</li></ul><p>This is where time blocking breaks for a lot of people. They try to defend every single block equally. I do not. I defend <em>two</em> deep blocks per week like my life depends on it. The others are flexible.</p><h3 id="thursday-context-switch-and-writing">Thursday: context switch and writing</h3><p>By Thursday my brain is a bit fried from heavy build work and coaching. I lean into that instead of pretending I am still fresh.</p><ul><li><strong>08:30 &#x2013; 10:00</strong>: Writing block. Blog posts like this, docs, outlines, marketing copy. I treat it like coding. One small outcome.</li><li><strong>10:00 &#x2013; 12:00</strong>: Shallow work. Tickets, comments, light UI tweaks, bug triage.</li><li><strong>14:00 &#x2013; 16:00</strong>: Second writing or creative block. Sometimes I script a small interactive demo. Sometimes I just freewrite about something I shipped.</li><li><strong>Evening</strong>: Games or training if scheduled. If not, family and recovery.</li></ul><p>I stopped trying to write in the &quot;perfect&quot; conditions. My reality is that by Thursday I have a lot of context in my head. That is perfect material for writing as long as I do not aim for poetry, just clarity.</p><p>I also noticed something specific. If I push another heavy coding block on Thursday afternoon, I will be useless on Friday. So I stay under my capacity here on purpose.</p><h3 id="friday-maintenance-and-future-proofing">Friday: maintenance and future-proofing</h3><p>Friday is anti-heroic. That is by design.</p><ul><li><strong>08:30 &#x2013; 10:30</strong>: Loose build block. Finish any near-done feature. Merge, clean up, document.</li><li><strong>11:00 &#x2013; 12:00</strong>: Weekly review. I walk through my calendar, see what actually happened, and compare it to the plan.</li><li><strong>13:30 &#x2013; 15:00</strong>: Learning or sandbox. I test some weird CSS thing. Play with a new API. Draft ideas for future posts.</li><li><strong>Afternoon / evening</strong>: Coaching or early shutdown, depending on the schedule.</li></ul><p>I treat Friday as my safety buffer.</p><p>Anything that slipped from earlier in the week can land here. If the week went cleanly, I use this time to set up next week. If the week was chaos, Friday is where I put everything back into some kind of order.</p><h2 id="the-calendar-after-reality-hits">The calendar after reality hits</h2><p>If you look at my calendar at the start of the week and at the end, you would think two different people made them.</p><p>Here is what usually changes:</p><ul><li><strong>Blocks get resized</strong>: A 3-hour deep work block becomes 90 minutes. I cut the scope, not the block.</li><li><strong>Tasks move inside blocks</strong>: The energy type stays, but the actual task changes. Maybe I write instead of code, but I still treat it as deep focus.</li><li><strong>Coaching expands</strong>: Weather, travel, extra planning. If the field time grows, something else shrinks that day.</li><li><strong>Evenings get reclaimed</strong>: If a day gets wrecked, I <em>do not</em> immediately steal from sleep to compensate. I prefer to slip a task to Friday or kill it.</li></ul><p>The important part is this. I am not trying to defend my exact schedule. I am defending my energy budget.</p><p>I want roughly:</p><ul><li>3 to 4 deep build blocks per week.</li><li>2 writing blocks.</li><li>1 solid planning session.</li><li>Enough slack so that coaching does not destroy me.</li></ul><p>As long as I hit those numbers, I do not care if a block moved by 90 minutes or traded days.</p><h2 id="the-constraints-that-keep-me-from-burning-out">The constraints that keep me from burning out</h2><p>Time blocking is easy. Not torching yourself is where it gets tricky.</p><p>I follow a few hard constraints. They are non-negotiable, because I learned the hard way.</p><h3 id="1-no-heroic-late-nights-after-games">1. No heroic late nights after games</h3><p>Coaching is intense. I am on my feet, talking, deciding, moving. If I get back late and then decide to &quot;catch up&quot; for two hours, I pay for it for three days.</p><p>So I have a simple rule. If there is a night game or late training, that evening has zero cognitively important work. I can stretch, read, or stare at the wall. That is it.</p><h3 id="2-one-overloaded-day-max">2. One overloaded day max</h3><p>Sometimes everything lands at once. Deadline, travel, game, family thing. Old me would turn three days into overloaded fire drills.</p><p>Now I accept that sometimes one day will be stupid. Packed, messy, too much.</p><p>But I force the days around it to be lighter. If Thursday looks insane, I carve down Wednesday and Friday on purpose. Fewer tasks. Smaller scope.</p><h3 id="3-deep-work-is-a-quota-not-a-fantasy">3. Deep work is a quota, not a fantasy</h3><p>A lot of productivity content pretends you can run four deep work blocks every day if you &quot;optimize&quot; hard enough. I think that is nonsense for most humans with families, clients, and external commitments.</p><p>My quota is simple:</p><ul><li>Two deep work blocks are mandatory.</li><li>Three is a good week.</li><li>Four is rare and I do not plan for it.</li></ul><p>If a week is heavy with coaching, I am happy with two solid blocks and no guilt. That is the tradeoff for standing on a field and actually working with players instead of staying in a chair.</p><h3 id="4-planning-is-work-not-overhead">4. Planning is work, not overhead</h3><p>The weekly review and planning on Friday and Monday used to feel like &quot;not real work.&quot; I would skip them when I felt behind.</p><p>That was stupid. Skipping planning is how you end up behind in the first place.</p><p>Now I treat planning as its own block with one outcome. My goal is to leave that session with:</p><ul><li>Three concrete build goals for the week.</li><li>Two writing topics picked.</li><li>Coaching sessions and games locked on the calendar.</li><li>At least one afternoon that is intentionally light.</li></ul><p>I do not need every hour planned. I just need the big rocks pinned, and I need to see where the week will be heavy.</p><h2 id="tools-i-actually-use">Tools I actually use</h2><p>Nothing fancy here. I do not think tools solve this for you. They just make the pain slightly smaller.</p><ul><li><strong>Google Calendar</strong> for hard calendar events and high-level blocks.</li><li><strong>A task manager</strong> (I rotate, but the pattern is the same) for daily lists broken down from those blocks.</li><li><strong>Plain text notes</strong> for weekly reviews and &quot;what actually happened&quot; logs.</li></ul><p>The log is underrated. Every Friday I write three bullets:</p><ul><li>What I planned.</li><li>What really happened.</li><li>Why it changed.</li></ul><p>Over a few months, patterns appear. Certain days always blow up. Certain clients always push last-minute. Certain coaching periods are heavier.</p><p>Then I adjust the baseline week for that season instead of pretending next week will somehow be different.</p><h2 id="the-honest-tradeoffs">The honest tradeoffs</h2><p>I cannot do everything every week. Neither can you. Time blocking does not magically bend time around that fact.</p><p>What it does for me:</p><ul><li>It forces me to see the actual tradeoffs between coaching, building, and writing.</li><li>It gives me a default week I can distort without completely losing structure.</li><li>It keeps my deep work quota realistic, which keeps me from burning out.</li></ul><p>My calendar still looks perfect on Sunday night.</p><p>By Friday afternoon it looks like a crime scene. Blocks moved, resized, scratched out, colors everywhere.</p><p>But if I hit my deep work quota, got my players better, shipped a couple of things, and still want to show up again next week, then the system did its job.</p><p>The goal is not a clean calendar. The goal is a life you can keep running without grinding yourself into the ground.</p>]]></content:encoded></item></channel></rss>