<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>WebsterWebs — musings</title>
  <subtitle>A slowly growing collection of things I&#39;ve made and thought about.</subtitle>
  <link href="https://websterwebs.online/feed.xml" rel="self"/>
  <link href="https://websterwebs.online/"/>
  <updated>2026-05-06T00:00:00Z</updated>
  <id>https://websterwebs.online/</id>
  <author>
    <name>Ollie Webster</name>
  </author>
  <entry>
    <title>On surfing</title>
    <link href="https://websterwebs.online/musings/on-surfing/"/>
    <updated>2026-05-06T00:00:00Z</updated>
    <id>https://websterwebs.online/musings/on-surfing/</id>
    <content type="html">&lt;p&gt;There are two traps inside any large system.&lt;/p&gt;
&lt;p&gt;The first is to stand wholly outside it — self-sufficient, the small things you have made keeping their own quiet momentum, no one&#39;s permission required. It is appealing for about a week and then begins, gently, to feel like managed decline. The water pools when it has nowhere to go.&lt;/p&gt;
&lt;p&gt;The second is to stand wholly inside it — to let the role become the self. The danger here is more honest. A few months in you wake up one morning and find you have been a zombie. The system rewards the zombie state; that is part of what large systems are for.&lt;/p&gt;
&lt;p&gt;The interesting position is neither of these. It is the unstable peak between them — grounded in something the system does not own, and from that ground stepping back into the system as if into a game. You can play because you do not need it. You can leave without losing anything. People notice this kind of person; they appear effortless. Effortlessness is what someone looks like when the stakes are not theirs.&lt;/p&gt;
&lt;p&gt;It is, like surfing, an active balance. The wave does not stop. The peak is not a place you settle.&lt;/p&gt;
&lt;figure class=&quot;surf-fig&quot;&gt;
  &lt;svg id=&quot;surf-svg&quot; viewBox=&quot;0 0 600 320&quot; width=&quot;600&quot; height=&quot;320&quot; style=&quot;display:block; width:100%; height:auto; cursor: grab;&quot;&gt;
    &lt;text x=&quot;100&quot; y=&quot;42&quot; text-anchor=&quot;middle&quot; font-size=&quot;15&quot; fill=&quot;currentColor&quot; opacity=&quot;0.65&quot; font-style=&quot;italic&quot;&gt;the beach&lt;/text&gt;
    &lt;text x=&quot;300&quot; y=&quot;42&quot; text-anchor=&quot;middle&quot; font-size=&quot;17&quot; fill=&quot;currentColor&quot; font-weight=&quot;700&quot;&gt;the surf&lt;/text&gt;
    &lt;text x=&quot;500&quot; y=&quot;42&quot; text-anchor=&quot;middle&quot; font-size=&quot;15&quot; fill=&quot;currentColor&quot; opacity=&quot;0.65&quot; font-style=&quot;italic&quot;&gt;captured&lt;/text&gt;
    &lt;line x1=&quot;200&quot; y1=&quot;55&quot; x2=&quot;200&quot; y2=&quot;295&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;0.6&quot; opacity=&quot;0.18&quot; stroke-dasharray=&quot;2 4&quot;&gt;&lt;/line&gt;
    &lt;line x1=&quot;400&quot; y1=&quot;55&quot; x2=&quot;400&quot; y2=&quot;295&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;0.6&quot; opacity=&quot;0.18&quot; stroke-dasharray=&quot;2 4&quot;&gt;&lt;/line&gt;
    &lt;path id=&quot;surf-fill&quot; d=&quot;&quot; fill=&quot;currentColor&quot; fill-opacity=&quot;0.06&quot;&gt;&lt;/path&gt;
    &lt;path id=&quot;surf-curve&quot; d=&quot;&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2.4&quot; fill=&quot;none&quot; stroke-linejoin=&quot;round&quot; stroke-linecap=&quot;round&quot;&gt;&lt;/path&gt;
    &lt;circle id=&quot;surf-halo&quot; cx=&quot;300&quot; cy=&quot;0&quot; r=&quot;22&quot; fill=&quot;none&quot; stroke=&quot;var(--mark-color, currentColor)&quot; stroke-width=&quot;1.4&quot; opacity=&quot;0&quot;&gt;&lt;/circle&gt;
    &lt;circle id=&quot;surf-ball&quot; cx=&quot;300&quot; cy=&quot;0&quot; r=&quot;14&quot; fill=&quot;var(--mark-color, currentColor)&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot;&gt;&lt;/circle&gt;
  &lt;/svg&gt;
  &lt;figcaption&gt;Drag the ball. Let go anywhere. Where it ends up depends on where you released it — and on whether you released it precisely enough to balance on the middle.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;script type=&quot;module&quot;&gt;
import * as d3 from &quot;https://cdn.jsdelivr.net/npm/d3@7/+esm&quot;;

const svg = document.getElementById(&quot;surf-svg&quot;);
const ball = document.getElementById(&quot;surf-ball&quot;);
const halo = document.getElementById(&quot;surf-halo&quot;);
const curve = document.getElementById(&quot;surf-curve&quot;);
const fill = document.getElementById(&quot;surf-fill&quot;);

const W = 600, GROUND = 300;

function landscape(x) {
  const peak    = -90 * Math.exp(-Math.pow((x - 300) / 70,  2));
  const leftV   =  60 * Math.exp(-Math.pow((x - 100) / 80,  2));
  const rightV  =  60 * Math.exp(-Math.pow((x - 500) / 80,  2));
  return 200 + peak + leftV + rightV;
}
function slope(x) {
  const h = 0.5;
  return (landscape(x + h) - landscape(x - h)) / (2 * h);
}

let pathD = &quot;&quot;, fillD = &quot;&quot;;
for (let x = 0; x &lt;= W; x += 3) {
  const y = landscape(x).toFixed(1);
  pathD += (x === 0 ? &quot;M&quot; : &quot;L&quot;) + x + &quot;,&quot; + y + &quot; &quot;;
}
fillD = pathD + &quot;L&quot; + W + &quot;,&quot; + GROUND + &quot; L0,&quot; + GROUND + &quot; Z&quot;;
curve.setAttribute(&quot;d&quot;, pathD);
fill.setAttribute(&quot;d&quot;, fillD);

let x = 300, vx = 0, dragging = false;

function placeBall() {
  const y = landscape(x);
  ball.setAttribute(&quot;cx&quot;, x.toFixed(1));
  ball.setAttribute(&quot;cy&quot;, y.toFixed(1));
  halo.setAttribute(&quot;cx&quot;, x.toFixed(1));
  halo.setAttribute(&quot;cy&quot;, y.toFixed(1));
}

function tick() {
  if (!dragging) {
    const s = slope(x);
    vx += s * 0.6;
    vx *= 0.962;
    x += vx;
    if (x &lt; 8)   { x = 8;   vx = 0; }
    if (x &gt; W-8) { x = W-8; vx = 0; }
  }
  placeBall();

  const inSurfZone = x &gt; 270 &amp;&amp; x &lt; 330;
  const t = performance.now() / 1000;
  if (inSurfZone) {
    const pulse = (Math.sin(t * 2.4) + 1) / 2;
    const r = 14 + pulse * 1.2;
    ball.setAttribute(&quot;r&quot;, r.toFixed(2));
    halo.setAttribute(&quot;r&quot;, (24 + pulse * 6).toFixed(2));
    halo.setAttribute(&quot;opacity&quot;, (0.55 + pulse * 0.25).toFixed(2));
  } else {
    ball.setAttribute(&quot;r&quot;, &quot;14&quot;);
    halo.setAttribute(&quot;opacity&quot;, &quot;0&quot;);
  }

  requestAnimationFrame(tick);
}

d3.select(svg).call(
  d3.drag()
    .on(&quot;start&quot;, function (event) {
      dragging = true; vx = 0;
      svg.style.cursor = &quot;grabbing&quot;;
      const [px] = d3.pointer(event, svg);
      x = Math.max(8, Math.min(W - 8, px));
    })
    .on(&quot;drag&quot;, function (event) {
      const [px] = d3.pointer(event, svg);
      x = Math.max(8, Math.min(W - 8, px));
    })
    .on(&quot;end&quot;, function () {
      dragging = false;
      svg.style.cursor = &quot;grab&quot;;
    })
);

x = 295;
placeBall();
requestAnimationFrame(tick);
&lt;/script&gt;
&lt;p&gt;The point is not which destination is good and which is bad — you don&#39;t get to live on the peak forever; the wave keeps coming, and any of us, on a long enough timescale, ends up in one of the valleys. The point is that the peak only exists because the valleys are on the same curve. Without ground outside the room, engagement slides into capture. Without engagement, ground slides into withdrawal.&lt;/p&gt;
&lt;p&gt;The work, then, is to keep both alive at once. Build the thing outside the room that means the room is not your only room. Then walk back in lightly.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>On routines</title>
    <link href="https://websterwebs.online/musings/on-routines/"/>
    <updated>2026-04-30T00:00:00Z</updated>
    <id>https://websterwebs.online/musings/on-routines/</id>
    <content type="html">&lt;!-- placeholder; rewrite freely --&gt;
&lt;p&gt;A routine is not a cage. A routine is the riverbank that lets the water move at all.&lt;/p&gt;
&lt;p&gt;Without it the day pools, evaporates, and you wonder where the hours went. With it, even a small amount of attention compounds — the run at six, the page at seven, the same coffee at the same hour, the practice at the same time each week. The texture of repetition is not boredom; it is the only thing slow enough to notice change against.&lt;/p&gt;
&lt;p&gt;Imagine two people, both wanting to learn the same thing — playing an instrument, perhaps, or a language. The first plays for fifteen minutes a day. The second plays for two hours when the mood strikes, and otherwise not at all. Drag the day-counter and watch what each is left with at the end of a month.&lt;/p&gt;
&lt;div class=&quot;routines-diagram-container&quot;&gt;
  &lt;svg class=&quot;routines-diagram&quot; viewBox=&quot;0 0 360 140&quot; width=&quot;360&quot; height=&quot;140&quot; aria-hidden=&quot;true&quot;&gt;
    &lt;line x1=&quot;20&quot; y1=&quot;120&quot; x2=&quot;340&quot; y2=&quot;120&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;0.8&quot; opacity=&quot;0.4&quot;&gt;&lt;/line&gt;
    &lt;line x1=&quot;20&quot; y1=&quot;20&quot; x2=&quot;20&quot; y2=&quot;120&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;0.8&quot; opacity=&quot;0.4&quot;&gt;&lt;/line&gt;
    &lt;text x=&quot;20&quot; y=&quot;135&quot; font-size=&quot;9&quot; fill=&quot;currentColor&quot; opacity=&quot;0.6&quot;&gt;day 1&lt;/text&gt;
    &lt;text x=&quot;298&quot; y=&quot;135&quot; font-size=&quot;9&quot; fill=&quot;currentColor&quot; opacity=&quot;0.6&quot;&gt;day 30&lt;/text&gt;
    &lt;path id=&quot;routine-steady&quot; d=&quot;&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; fill=&quot;none&quot;&gt;&lt;/path&gt;
    &lt;path id=&quot;routine-spiky&quot; d=&quot;&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;1.5&quot; fill=&quot;none&quot; stroke-dasharray=&quot;3 2&quot; opacity=&quot;0.6&quot;&gt;&lt;/path&gt;
    &lt;text id=&quot;routine-label-steady&quot; x=&quot;0&quot; y=&quot;0&quot; font-size=&quot;10&quot; fill=&quot;currentColor&quot; font-weight=&quot;700&quot;&gt;daily&lt;/text&gt;
    &lt;text id=&quot;routine-label-spiky&quot; x=&quot;0&quot; y=&quot;0&quot; font-size=&quot;10&quot; fill=&quot;currentColor&quot; opacity=&quot;0.7&quot;&gt;spurts&lt;/text&gt;
  &lt;/svg&gt;
  &lt;div class=&quot;lab-controls&quot;&gt;
    &lt;label for=&quot;routine-day&quot;&gt;day 1&lt;/label&gt;
    &lt;input id=&quot;routine-day&quot; type=&quot;range&quot; min=&quot;1&quot; max=&quot;30&quot; step=&quot;1&quot; value=&quot;30&quot; /&gt;
    &lt;label for=&quot;routine-day&quot;&gt;day 30&lt;/label&gt;
  &lt;/div&gt;
  &lt;p class=&quot;routines-readout&quot; id=&quot;routine-readout&quot;&gt;After 30 days&amp;#58; daily 30 units, spurts 12 units.&lt;/p&gt;
&lt;/div&gt;
&lt;script&gt;
(function () {
  const W = 360, H = 140, X0 = 20, X1 = 340, Y0 = 20, Y1 = 120;
  const days = 30;
  const steady = Array.from({length: days}, (_, i) =&gt; i + 1);
  const spiky = (() =&gt; {
    const out = [];
    let acc = 0;
    for (let i = 0; i &lt; days; i++) {
      const day = i + 1;
      if (day === 3 || day === 11 || day === 22) acc += 4;
      else if (Math.random() &lt; 0.06) acc += 1;
      acc *= 0.97;
      out.push(acc);
    }
    return out;
  })();
  function px(d) { return X0 + (X1 - X0) * (d - 1) / (days - 1); }
  function py(v, max) { return Y1 - (Y1 - Y0) * Math.min(v, max) / max; }
  const max = Math.max(...steady, ...spiky);
  function buildPath(arr, upto) {
    let d = &quot;&quot;;
    for (let i = 0; i &lt; upto; i++) {
      const x = px(i + 1), y = py(arr[i], max);
      d += (i === 0 ? &quot;M&quot; : &quot;L&quot;) + x.toFixed(1) + &quot;,&quot; + y.toFixed(1) + &quot; &quot;;
    }
    return d;
  }
  const pSteady = document.getElementById(&quot;routine-steady&quot;);
  const pSpiky = document.getElementById(&quot;routine-spiky&quot;);
  const lblSteady = document.getElementById(&quot;routine-label-steady&quot;);
  const lblSpiky = document.getElementById(&quot;routine-label-spiky&quot;);
  const readout = document.getElementById(&quot;routine-readout&quot;);
  const slider = document.getElementById(&quot;routine-day&quot;);
  function update() {
    const upto = parseInt(slider.value, 10);
    pSteady.setAttribute(&quot;d&quot;, buildPath(steady, upto));
    pSpiky.setAttribute(&quot;d&quot;, buildPath(spiky, upto));
    if (upto &gt; 1) {
      const sx = px(upto), sy = py(steady[upto - 1], max);
      const px2 = px(upto), py2 = py(spiky[upto - 1], max);
      lblSteady.setAttribute(&quot;x&quot;, (sx + 4).toFixed(1));
      lblSteady.setAttribute(&quot;y&quot;, (sy + 4).toFixed(1));
      lblSpiky.setAttribute(&quot;x&quot;, (px2 + 4).toFixed(1));
      lblSpiky.setAttribute(&quot;y&quot;, (py2 + 4).toFixed(1));
    }
    readout.textContent = &quot;After &quot; + upto + &quot; day&quot; + (upto === 1 ? &quot;&quot; : &quot;s&quot;) + &quot;: daily &quot; + steady[upto - 1].toFixed(0) + &quot; units, spurts &quot; + spiky[upto - 1].toFixed(0) + &quot; units.&quot;;
  }
  slider.addEventListener(&quot;input&quot;, update);
  update();
})();
&lt;/script&gt;
&lt;p&gt;The trick is not in choosing the right routine. The trick is staying in any routine long enough for it to feel ordinary. The early days are loud. They want to be remarkable. Keep going past the loudness, and the routine quiets down, and what remains underneath is the actual life.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>On beginnings</title>
    <link href="https://websterwebs.online/musings/on-beginnings/"/>
    <updated>2026-04-12T00:00:00Z</updated>
    <id>https://websterwebs.online/musings/on-beginnings/</id>
    <content type="html">&lt;!-- placeholder; rewrite freely --&gt;
&lt;p&gt;I wanted this site to be a place rather than a performance.&lt;/p&gt;
&lt;p&gt;A place is where you can leave a chair half-pulled out from the table, where the kitchen smells faintly of last night&#39;s dinner, where the bookshelf holds books you have not finished and may never finish. A performance is a stage with the lights up. The two are not the same; the second one tires you out, while the first one keeps you company.&lt;/p&gt;
&lt;p&gt;So this is a place. The corners are still empty. Some of the rooms are not built yet. &lt;span class=&quot;sidenote&quot;&gt;I keep noticing that the most generous websites I read all feel built rather than launched — accumulated over a decade of small decisions.&lt;/span&gt; There is no manifesto on the wall, and the front door does not announce itself. If you are reading this, you have wandered in past the threshold, and that is the only invitation I know how to write.&lt;/p&gt;
&lt;p&gt;For now the plan is small. One short essay a month. A log entry when something is worth recording. A trip note when there is something to say about the trip beyond &lt;em&gt;we went there.&lt;/em&gt; The rest is texture.&lt;/p&gt;
</content>
  </entry>
</feed>
