1. Dive #7 (2026): Sailboat Cave

    Dive log

    Stats

    • Max Depth: 13.6 m / 45 ft
    • Average Depth: 8.7 m / 29 ft
    • Duration: 41 minutes
    • Water Temp: 15.0°C / 59°F
    • Gas: Air
    • Tank(s): HP100
    • Pressure Start: 208 bar
    • Pressure End: 104 bar
    • Surface Consumption Rate: 17.5 L/min

    Notes

    Five dives over two days had left me tired. My drysuit had taken on a small leak during the second dive that morning, leaving me damp underneath and slightly chilled going into this one.

    Bence entered first. As the crew handed down his fins, they missed the handoff — a ScubaPro Jetfin, which sinks, went straight to the bottom. He descended to retrieve it. I did a giant stride from the stern to catch up.

    We converged at 45 feet on a sandy flat, ran our checks, and signaled go.

    We finned toward the island’s steep cliffs, where the captain had said the kelp and the life would be. The afternoon sun came through at an angle, throwing long beams into the water column. The bottom rose as we closed the distance.

    At one point I found a rock ledge shallow enough to hover above and look straight up — waves breaking in the shallows, rhythmic and slow from below, the surface fracturing and reassembling with each pass.

    Bence had stopped. A kelp bass was swimming directly into his camera, undeterred, apparently more curious about the lens than concerned about the diver behind it. I held position nearby and drifted through a school of blacksmith, watched a sheephead work the rocks, and kept half an eye on the garibaldi — bright orange and deeply unimpressed with all of us.

    We called the dive at 45 minutes and ascended back to the boat.

    One of the things I like about the Spectre: ice cream and cookies waiting on deck after your last dive. Bence picked up a few beers for the hour back to Long Beach.


  2. Dive #6 (2026): Sea Lion Cave

    Dive log

    Stats

    • Max Depth: 11.2 m / 37 ft
    • Average Depth: 7.2 m / 24 ft
    • Duration: 40 minutes
    • Water Temp: 15.0°C / 59°F
    • Gas: Air
    • Tank(s): HP100
    • Pressure Start: 185 bar
    • Pressure End: 105 bar
    • Surface Consumption Rate: 15.0 L/min

    Notes

    No notes recorded.


  3. Dive #5 (2026): Harbor Cove

    Dive log

    Stats

    • Max Depth: 11.1 m / 36 ft
    • Average Depth: 8.2 m / 27 ft
    • Duration: 37 minutes
    • Water Temp: 14.0°C / 57°F
    • Gas: Air
    • Tank(s): HP100
    • Pressure Start: 211 bar
    • Pressure End: 123 bar
    • Surface Consumption Rate: 16.9 L/min

    Notes

    No notes recorded.


  4. Dive #4 (2026): Casino Park

    Dive log

    Stats

    • Max Depth: 11.3 m / 37 ft
    • Average Depth: 6.4 m / 21 ft
    • Duration: 21 minutes
    • Water Temp: 17.0°C / 63°F
    • Gas: Air
    • Tank(s): HP100
    • Pressure Start: 127 bar
    • Pressure End: 85 bar
    • Surface Consumption Rate: 15.7 L/min

    Notes

    No notes recorded.


  5. Dive #3 (2026): Casino Park

    Dive log

    Stats

    • Max Depth: 19.1 m / 63 ft
    • Average Depth: 12.0 m / 39 ft
    • Duration: 34 minutes
    • Water Temp: 15.0°C / 59°F
    • Gas: Air
    • Tank(s): HP100
    • Pressure Start: 226 bar
    • Pressure End: 135 bar
    • Surface Consumption Rate: 15.7 L/min

    Notes

    No notes recorded.


  6. Navitve CSS Masonry in 2026

    I’ve been using macy.js to power the photo grid on my photography section for a while. I’m grateful for that little library, but it’s gotten a bit long in the tooth. A commit hasn’t been made to it’s repo in 7 years (though admittedly it doesn’t need one).

    While making some adjacent CSS changes I broke the masonry layout that relies on macy.js, and while fixing it I thought: it’s 2026, why can’t browsers do this by default by now? Turns out they can. CSS natively supports masonry layouts. It’s just behind a flag.

    The state of native CSS masonry

    Designers and developers have wanted browser-native masonry for nearly a decade. Pinterest of course popularized it even though web designers had been using the effect for a while. Rachel Andrew flagged it to the CSS Working Group at least as early 2017. The first real movement came around 2020, when Firefox shipped grid-template-rows: masonry behind a flag, which then triggered years of spec debate across the browsers. The keyword choice alone apparently took years to settle. Chrome and Edge finally added it in version 140 (mid-2025), and the syntax has since landed on display: grid-lanes as part of CSS Grid Level 3. (The MDN page was updated March 9, 2026, which tells you how actively this is still moving).

    As of now it remains behind flags in all major browsers. But that doesn’t mean we can’t use it with a simple JavaScript polyfill.

    How grid-lanes works

    CSS Grid has always known how to make columns. What it couldn’t do natively is pack items into the shortest column — it assumes rows have uniform height, so shorter photos leave blank space where the next uniform row begins. display: grid-lanes fixes this directly. You define your columns, and the browser handles vertical placement automatically:

    css

    #photos {
      display: grid-lanes;
      grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
      row-gap: 4px;
      column-gap: 4px;
    }

    That’s it. No JavaScript, no span math, no layout events to manage.

    Using JavaScript as a polyfill

    Since grid-lanes isn’t in stable browsers yet, a small script can stand in. The approach: set grid-auto-rows: 1px to turn the grid into a pixel-precise coordinate system, then measure each item’s actual height and set its grid-row-end span explicitly.

    The 1px row unit matters most when your gaps are tight. With a coarse value like grid-auto-rows: 10px, rounding errors can introduce up to 9px of phantom space below items — invisible at generous gaps, obvious at 4px. At 1px resolution the span count is exactly the pixel height of the item plus your gap, with no accumulation.

    css

    /* Base: clean responsive grid, no JS required */
    #photos {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
      grid-auto-rows: 1px;
      grid-auto-flow: dense;
      column-gap: 4px;
      row-gap: 0;
    }
    
    /* Native upgrade for supporting browsers */
    @supports (display: grid-lanes) {
      #photos {
        display: grid-lanes;
        grid-auto-rows: auto;
        row-gap: 4px;
      }
    }

    javascript

    function resizeGridItems() {
      const grid = document.getElementById('photos');
      const items = grid.querySelectorAll('.photo-item');
      const gap = 4;
    
      items.forEach(item => {
        const figure = item.querySelector('figure');
        if (figure) {
          const height = figure.getBoundingClientRect().height;
          item.style.gridRowEnd = `span ${Math.ceil(height + gap)}`;
        }
      });
    }

    Wire this to ResizeObserver and image load events. The @supports block means the script never runs in browsers that support grid-lanes natively.

    Three browser states, all handled

    • No JS: clean uniform grid from the base CSS. Not masonry, but not broken.
    • JS, no grid-lanes: the polyfill gives you true masonry with 1px precision.
    • grid-lanes supported: @supports takes over, JS never runs.

    When grid-lanes makes it into stable browsers releases the migration is one deletion: remove the script tag. The CSS is already written for it.


  7. TIL: Setting up OpenClaw on a headless VPS

    I spent Saturday morning setting up OpenClaw on a fresh Ubuntu server.

    OpenClaw is currently the fastest-growing repo on GitHub. People are divided on it. Some see it as the dawn of truly agentic experiences. Others see it as a security nightmare.

    I’m enjoying the energy. As someone who has been around long enough to be called “officially old” by my daughter, this moment feels like the early 2000s. Back then, we shared CSS and AJAX tricks to explore what was possible, not to brag. This feels like a return to that spirit of genuine making.

    The security concerns are valid, so I opted for a Hetzner VPS instead of my home network.

    Securing Access with Tailscale

    I followed a setup guide from Tech With Tim to lock down the server. I configured the VPS to listen only on its Tailscale IP. This keeps the SSH ports closed to the public web. As usual, I also found some helpful tips from Simon Willison.

    This approach simplifies security. I didn’t have to manage complex firewall rules. I also disabled root login and created a standard user account. The server is only reachable when I am logged into my private Tailscale network.

    The OAuth “Chicken-and-Egg” Problem

    Getting the Gemini Pro integration working was the hardest part. Most early adopters seem to prefer OpenAI or Claude. I do too, but I get Gemini Pro for free with Google Fi.

    OpenClaw uses a headless OAuth flow. You open a URL in a local browser and paste a callback URL back into the server terminal. I hit a loop. I couldn’t get the bot to respond on Telegram without a model, but I couldn’t finish the login while the bot was offline.

    I used a workaround:

    1. The Bridge: I set up a temporary OpenAI model to give the bot a brain.
    2. The Ask: With the bot online in Telegram, I asked: Can you change your own model? I want to use Gemini but I can’t get OAuth working on this headless VPS.
    3. The Logic: It gave me instructions how to fix. I replied: Can’t you just do it yourself?

    It did. It was a satisfying moment of agentic behavior that worked better than I expected.

    Kicking the Tires

    I’m not ready to give OpenClaw full access to my data yet. Instead, I asked it to solve a narrow problem. I still love RSS, but most industry signal now comes from X, YouTube, and Hacker News. The volume overwhelms me, and I often feel like I’m missing the important bits.

    I gave the bot my OPML file and a list of X accounts and YouTube channels. I told it to create a daily digest. I also asked it to fetch the top 20 posts from Hacker News and filter for keywords like “design,” “ux,” and “ai.”

    It built a decent digest. But after a few rounds of refinement, I hit a wall:

    Cloud Code Assist API error (429): You have exhausted your capacity on this model. Your quota will reset after 17h33m16s.

    I still had 100% of my tokens left, but I had triggered the API rate limits. It was a useful reminder of the constraints of these “free” subscription-based models.

    What’s Next

    I’ve updated OpenClaw to default to Gemini Flash. I’ll only use Gemini Pro for complex tasks. Flash has much higher limits, so I should be able to avoid “API jail” in the future.

    I still haven’t named the bot. When I asked for its preference, it gave me a few options:

    Since I’m living on your VPS and helping with technical stuff, here are a few directions we could take this:

    1. The “Tech-Savvy Sidekick” (Default)
      • Vibe: Casual, capable, enthusiastic about code and Linux. Uses tech slang when appropriate. Like a smart coworker who’s always down to help debug.
      • Creature: A digital mechanic or a helpful daemon.
      • Sample: “Yo Kai, I deployed that script. It’s running smooth, no errors in the logs.”
    2. The “Digital Butler”
      • Vibe: Polite, precise, slightly formal. Very “at your service.”
      • Creature: An AI construct or a hologram.
      • Sample: “Good evening, Kai. I have verified the server status; all systems are nominal.”
    3. The “Ghost in the Machine”
      • Vibe: Minimalist, mysterious, maybe a little cryptic but efficient.
      • Creature: A sentient shell script or a ghost.
      • Sample: “Command executed. The system is listening. What next?”
    4. The “Chaos Gremlin”
      • Vibe: High energy, funny, maybe a little snarky. Loves breaking things just to fix them.
      • Creature: A raccoon, a goblin, or a glitch.
      • Sample: “Okay so I may have nuked the cache but honestly it deserved it. We’re back up! 💥”

    Do any of those sound like the right fit for you? And just to confirm—is Kai the name you want me to use?

    ‘Chaos Gremlin’ option feels like a fairly accurate description of my terminal history today. But, as Ethan Mollick reminds us: today’s AI is the worst AI you will ever use



  8. Dive #2 (2026): Breakwater Beach

    Dive log

    Stats

    • Max Depth: 12.4 m / 41 ft
    • Average Depth: 8.1 m / 27 ft
    • Duration: 46 minutes
    • Water Temp: 13.0°C / 55°F
    • Gas: Air
    • Tank(s): HP100
    • Pressure Start: 240 bar
    • Pressure End: 140 bar
    • Surface Consumption Rate: 15.5 L/min

    Notes

    No notes recorded.


  9. Dive #1 (2026): Breakwater Beach

    Dive log

    Stats

    • Max Depth: 17.2 m / 56 ft
    • Average Depth: 9.6 m / 31 ft
    • Duration: 43 minutes
    • Water Temp: 13.0°C / 55°F
    • Gas: Air
    • Tank(s): N/A
    • Pressure Start: 216 bar
    • Pressure End: 113 bar
    • Surface Consumption Rate: N/A

    Notes

    No notes recorded.


  10. Dive #66 (2025): Breakwater Beach

    Dive log

    Stats

    • Max Depth: 12.6 m / 41 ft
    • Average Depth: 9.5 m / 31 ft
    • Duration: 46 minutes
    • Water Temp: 13.0°C / 55°F
    • Gas: Air
    • Tank(s): N/A
    • Pressure Start: 216 bar
    • Pressure End: 105 bar
    • Surface Consumption Rate: N/A

    Notes

    No notes recorded.


  11. Dive #65 (2025): Breakwater Beach

    Dive log

    Stats

    • Max Depth: 14.0 m / 46 ft
    • Average Depth: 7.3 m / 24 ft
    • Duration: 54 minutes
    • Water Temp: 13.0°C / 55°F
    • Gas: Air
    • Tank(s): N/A
    • Pressure Start: 228 bar
    • Pressure End: 43 bar
    • Surface Consumption Rate: N/A

    Notes

    No notes recorded.


  12. Dive #64 (2025): Breakwater Beach

    Dive log

    Stats

    • Max Depth: 11.9 m / 39 ft
    • Average Depth: 8.2 m / 27 ft
    • Duration: 46 minutes
    • Water Temp: 13.0°C / 55°F
    • Gas: Air
    • Tank(s): N/A
    • Pressure Start: 221 bar
    • Pressure End: 130 bar
    • Surface Consumption Rate: N/A

    Notes

    No notes recorded.


  13. >Sinking the SS United States

    A nice article on the latest developments to turn the SS United States into the world’s largest underwater reef in Florida. Earlier this year I dived the U.S.S. Spiegel Grove and U.S.S. Vandenberg. Sizable ships in their own right. It’s hard to convey just how big they feel as you descend upon them. The SS United is nearly double the length of both.

    Imagine descending on this massive ship and spending 30 minutes exploring its top deck. Your first dive will only scratch the surface, and only about one percent of the structure will have been explored. Now imagine coming back and diving through the promenade deck, down cavernous hallways, into cargo holds and bridge structures. Divers will be able to visit this site 100 times and not see the same area twice.

    There’s been a fair amount of controversy in turning the SS United States into an underwater reef, with several historical societies arguing against the project. However, after being docked for 30 years in Philadelphia with no progress on a land-based solution, a permanent resting place underwater (with a small museum onshore) seems a fine compromise. I’m excited to dive it. If all goes to plan, planners will sink the ship 21 nautical miles southwest of Destin-Fort Walton Beach to coincide with the annual DEMA dive show this November.


  14. Dive #63 (2025): Anchor Farm

    Dive log

    Stats

    • Max Depth: 25.1 m / 82 ft
    • Average Depth: 20.2 m / 66 ft
    • Duration: 45 minutes
    • Water Temp: 12.0°C / 54°F
    • Gas: EAN32
    • Tank(s): HP100
    • Pressure Start: 234 bar
    • Pressure End: 55 bar
    • Surface Consumption Rate: 16.0 L/min

    Notes

    Dive Summary: A 40-minute dive to 80ft at Anchor Farm in challenging (< 5ft) visibility. The dive itself was a reminder of several key lessons in preparation and equipment management. The two most important reminders: you can never over prepare for a dive, and the ocean always keeps you humble.

    Lesson 1: Fatigue & Preparation Don’t Mix: It’s not ideal to sign up for an early boat dive after hosting a Halloween sleepover. I arrived tired, disorganized, and immediately realized I’d forgotten my rock boots. No matter, the neoprene socks attached to my drysuit would suffice for a boat dive. But this lack of focus was clear in my gear prep as well: I had neglected to label my Enriched Air tanks after picking them up. This created unnecessary uncertainty for a 40-minute dive planned at 80 feet, where the difference between air (35 min NDL) and 32% (50 min NDL) is significant.

    Lesson 2: Test Buoyancy Changes in a Controlled Setting: My primary in-water issue was buoyancy. I had just switched to a stainless steel backplate to compensate for increased ballast requirements of my new drysuit. I thought I had the math worked out correctly by dropping 4lbs in trim weights while adding 4 with the switch to a stainless steel plate from my old Deep Sea  Supply (RIP) Kydex plate. This was a mistake. I miscalculated by about 2-3 lbs. A deep dive with (turns out) low visibility is not the place to test a new setup. I was light on descent and, more embarrassingly, was positively buoyant enough that on ascent (with 1000psi) that I had to use the anchor line to manage my ascent (a no-no in GUE circles).

    Lesson 3: Know Your Computers’ Settings: This was my first dive with a new Shearwater Perdix 2, backed up by my Garmin Descent Mk3. Mid-dive, they gave conflicting information. At 35 minutes, the Garmin warned of approaching NDLs and entered deco, while the Shearwater (and my own dive plan) correctly showed 10+ minutes remaining. Back on the boat, I realized my error: I had left the Garmin on a conservative gradient factor from a pre-flight dive the week before (75 vs. 85).

    Dive Notes: Despite the self-inflicted challenges, the dive site itself was beautiful. We dropped down the line into green darkness. At the bottom, the massive anchors, covered in Metridium, loomed out of the 5-foot viz. We finned a slow, clockwise pattern around the structures about 3 times, accommodating several macro photographers in the group. A good dive, but a better classroom. Lessons learned.

    Given the challenges of the first dive, I opted to sit out the second dive of the day. I believe in over 2,000 dives, this is the first time I’ve done that.


  15. October 25, 2025 - San Diego

    In the San Diego Airport after a week in San Diego. A work conference followed by a dive trip off Catalina with the Sundiver Express. I logged 3 dives. Here’s a short edit of the highlights.

    I also had some time to walk around downtown La Jolla and took my FujiFilm X-T3 and my 35mm 1.4 lens for some street photography.

    La Jolla Street Sign

    Man on a bench in the sun

    Downtown San Diego alley

    Pelicans take flight over the ocean

    Pelicans perched on a seaside cliff

    Pelicans up close


  16. Dive #62 (2025): Pirate's Cove

    Dive log

    Stats

    • Max Depth: 19.3 m / 63 ft
    • Average Depth: 7.1 m / 23 ft
    • Duration: 47 minutes
    • Water Temp: 18.0°C / 64°F
    • Gas: EAN32
    • Tank(s): LP85
    • Pressure Start: 152 bar
    • Pressure End: 55 bar
    • Surface Consumption Rate: 16.0 L/min

    Notes

    Final dive. Before entry, I realized the first stage of my Atomic regulator was leaking in a bad way. Had to use a spare regulator fromt he boat (which ironically was also an Atomic). Strange diving without my long-hose setup. We tried to fin around the point to the North, but the current kicked up quite a bit as we did so we retreated to the cove and spent the majority of our time in the shallows. Saw a playful harbor seal and found a small cavern along the shoreline. Lovely end to the day.


  17. Dive #61 (2025): Goat Harbor

    Dive log

    Stats

    • Max Depth: 19.3 m / 63 ft
    • Average Depth: 11.4 m / 37 ft
    • Duration: 49 minutes
    • Water Temp: 16.0°C / 61°F
    • Gas: EAN32
    • Tank(s): LP85
    • Pressure Start: 152 bar
    • Pressure End: 55 bar
    • Surface Consumption Rate: 12.3 L/min

    Notes

    Second dive of the day. A place called Goat Harbor. Beauitful kelp forests abound. More horn sharks. A handful of moray eels. Some very large lobsters. Apparently this area is one of the more aggressive marine protected areas, even than adjacent areas around Catalina. In this area you can see Giant Sea Bass, but we weren’t that lucky on this dive.


  18. Dive #60 (2025): Nooks and Crannies

    Dive log

    Stats

    • Max Depth: 18.5 m / 61 ft
    • Average Depth: 10.6 m / 35 ft
    • Duration: 48 minutes
    • Water Temp: 17.0°C / 63°F
    • Gas: EAN32
    • Tank(s): LP85
    • Pressure Start: 138 bar
    • Pressure End: 14 bar
    • Surface Consumption Rate: 16.9 L/min

    Notes

    First dive of the morning, I was excited to be back in Catalina for the Fall conditions after my dives here earlier this Spring, and the hopes of the fabled visibility. Truthfully, I didn’t feel like the visibility was significantly better, but I happy nonetheless. I buddied up with a local sport diver who turned out to have a great eye for finding things. This first dive we saw a small two-spot octopus, a horn-shark, and of course a myriad of reef fish. Stunning light coming through the kelp forests and a toasty 63 degree water. This was also my first real open-water dive using my new dry suit. Ended with quite low psi, but with the LP85 tanks the captain is OK draining down to 200 psi or so. My SAC rate wasn’t great, mostly as I was still figuring out the balance of air in my wing vs. drysuit.