Categories
AI SwiftUI

ChatGPT SwiftUI Christmas Tree 2025

We’re back again for 2025! Here’s the familiar prompt supplied to ChatGPT now running the 5.1 model:

Draw a Christmas tree using SwiftUI

Expand to see the code.
import SwiftUI

struct ChristmasTreeView: View {
    var body: some View {
        ZStack {
            Color.black.opacity(0.9)
                .ignoresSafeArea()

            VStack(spacing: 0) {
                // Star
                StarShape(points: 5)
                    .fill(Color.yellow)
                    .frame(width: 40, height: 40)
                    .shadow(radius: 5)
                    .padding(.bottom, 8)

                // Tree (3 triangles)
                TreeLayer(color: .green, scale: 1.0)
                TreeLayer(color: .green, scale: 1.2)
                TreeLayer(color: .green, scale: 1.4)

                // Trunk
                Rectangle()
                    .fill(Color.brown)
                    .frame(width: 30, height: 50)
                    .padding(.top, -10)
            }
            .overlay(OrnamentsView())
        }
    }
}

// MARK: - Tree layer (triangle)

struct TreeLayer: View {
    let color: Color
    let scale: CGFloat

    var body: some View {
        Triangle()
            .fill(
                LinearGradient(
                    gradient: Gradient(colors: [color.opacity(0.8), color]),
                    startPoint: .top,
                    endPoint: .bottom
                )
            )
            .frame(width: 160 * scale, height: 100 * scale)
            .shadow(radius: 5)
            .offset(y: -20 * (scale - 1))  // stack layers nicely
    }
}

struct Triangle: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()
        path.move(to: CGPoint(x: rect.midX, y: rect.minY))      // top
        path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))   // bottom left
        path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))   // bottom right
        path.closeSubpath()
        return path
    }
}

// MARK: - Star shape

struct StarShape: Shape {
    var points: Int

    func path(in rect: CGRect) -> Path {
        guard points >= 2 else { return Path() }

        let center = CGPoint(x: rect.midX, y: rect.midY)
        let radius = min(rect.width, rect.height) / 2
        let innerRadius = radius * 0.4

        var path = Path()
        let angleStep = .pi * 2 / Double(points * 2)

        var angle = -Double.pi / 2 // start pointing up
        var firstPoint = true

        for i in 0 ..< points * 2 {
            let r = (i % 2 == 0) ? radius : innerRadius
            let x = center.x + CGFloat(cos(angle)) * r
            let y = center.y + CGFloat(sin(angle)) * r

            if firstPoint {
                path.move(to: CGPoint(x: x, y: y))
                firstPoint = false
            } else {
                path.addLine(to: CGPoint(x: x, y: y))
            }

            angle += angleStep
        }

        path.closeSubpath()
        return path
    }
}

// MARK: - Ornaments overlay

struct OrnamentsView: View {
    var body: some View {
        ZStack {
            ornament(x: -50, y: -40, color: .red)
            ornament(x: 40,  y: -20, color: .yellow)
            ornament(x: -30, y: 10,  color: .blue)
            ornament(x: 50,  y: 30,  color: .pink)
            ornament(x: 0,   y: -5,  color: .orange)
        }
    }

    private func ornament(x: CGFloat, y: CGFloat, color: Color) -> some View {
        Circle()
            .fill(color)
            .frame(width: 14, height: 14)
            .shadow(radius: 3)
            .offset(x: x, y: y)
    }
}

// MARK: - Preview

struct ChristmasTreeView_Previews: PreviewProvider {
    static var previews: some View {
        ChristmasTreeView()
    }
}

Have we already hit peak tree!? Was it decorated as lazily as possible because the model knows how I’d do it?

Visually things are similar, but maybe a little worse:

  • The tree’s body looks more like three stacked triangles than one contiguous shape.
  • Ornament distribution is clustered to the middle with one ornament attached to air.
  • We still have a floating star.
  • It tried to get fancy with some opacity and shadow tricks, but they fall a bit flat.
  • Once again we have a new background color.

That said, this isn’t too terrible knowing that I didn’t give the model the opportunity to analyze its own visual output and retry.

Code-wise:

  • ✅ It extracted more reusable views so the main body view is easier to understand.
  • ✅ Made good use of MARK annotations to increase readability.
  • ✅ Removed some global “magic numbers” (e.g. offsets) to enable adaptability to other canvas sizes.
  • 🤔 Made the generation of the StarShape path a lot more complicated. It traded simplicity for “flexibility” with ultimately the same output.

Happy Holidays! 🎄🤖

Categories
AI Arduino Projects Video

NeoPixel Christmas Tree 2025

I’ve hit the ground running this holiday season with two updates (so far!) to the NeoPixel Christmas Tree project.

Vibe coded goodies

I’ve been using an LLM to add some experimental goodies to the project such as:

  • A Python script that analyzes WAV files to synchronize light sequences to the beat
  • A local model rendered in Three.js to enable rapid prototyping

https://github.com/twstokes/neopixel-tree/pull/15

ESP32 upgrade

At the end of 2024 I experienced stability issues when updating many LEDs rapidly, e.g. a looping rainbow effect with a really low delay set. After some indeterminate amount of time the tree would crash and restart itself. I initially assumed it was bad C code on my part, but failed to spot anything and eventually put the tree away sometime in January of 2025 because it just got awkward. Now that we’re back to a socially acceptable time to have a Christmas tree out I’ve had a chance to do some deeper digging.

I added a new command to have the ESP return the reason it reset which was really helpful in confirming my suspicion that the ESP8266 just didn’t have enough oomph to drive pixels and run WiFi concurrently:

Fatal exception:4 flag:1 (Hardware Watchdog) epc1:0x40103341 epc2:0x00000000 e    pc3:0x00000000 excvaddr:0x00000000 depc:0x00000000

Long-ish story short I upgraded to an ESP32 with two cores (note: number of cores is model-dependent) and this provides the necessary headroom to bit bang LEDs and run WiFi stably. It required some re-soldering of the Perma-Proto board (since the pin layouts are different) as well as some code changes, but overall it was a relatively quick and straightforward upgrade. The tree hasn’t crashed since!

https://github.com/twstokes/neopixel-tree/pull/14

Categories
AI Fun Linux Nerd Sniped Projects

Snooping USB with GPT

I mentioned in my previous post about the Juicy Crumb DockLite G4 that there were two shortcomings with the product:

  1. Their software to control the brightness of the display was macOS only.
  2. The display doesn’t support sleep, so you’re required to press a physical button to turn off the backlight.

Thanks to some USB packet snooping and GPT, both of these problems were solved quickly. 😎

I wanted Linux software control

What started this exploration was that I wanted to control the display from Linux because it’s currently connected to a Raspberry Pi, not a Mac.

Step 0: Check for an open source repo

Negative. I didn’t spot anything, so it’ll take a little more grunt work. I did run their helper app through a disassembler and spotted functions like getting and setting the backlight via IOKit which were all expected. It wasn’t necessary to go this far, it just confirms that we have some USB packet snooping to do.

Step 1: Get lsusb output

Running lsusb with the display connected to the RPi showed:

Bus 001 Device 009: ID 4a43:1d8a Juicy Crumb Systems DockLite

So I grabbed the output of lsusb -v -d 4a43:1d8a to feed it to the LLM later to provide more details about its USB specs.

Step 2: Capture USB packets on the Mac

The double-edged sword of Apple’s security is that it introduces all sorts of challenges for devs who need to get to lower level functionality, like capturing raw data from interfaces.

To successfully USB capture packets on a Mac I had to:

  1. Disable SIP
  2. Bring up the USB controller interface (in my case there were a couple, so through trial-and-error I found the right one)
  3. Tell Wireshark to capture on that interface
  4. Change the brightness using the Juicy-Link app to generate packets
  5. Save the capture to a file

Step 3: Feed the LLM

With the data from steps 1 and 2 I:

  • Fed them to ChatGPT
  • Mentioned that the capture was from a utility setting the brightness
  • Asked for an equivalent Python script

Here’s what it produced.

Step 4: Run the script in Linux

After installing the pyusb module I could run python set-brightness.py 700 and it worked from Linux! The range from lowest backlight brightness to highest is around 700 – 5000.

The extra bonus (which I’m most excited about) is that sending a value of 0 will truly turn off the backlight! This isn’t even (currently) possible with their software. (Obligatory “do this at your own risk”)

Now I can hook into something like D-Bus signals to potentially toggle the display depending on the desktop’s locked state.

Success

✅ The display’s brightness can be set from any OS, not just macOS.

✅ The display’s backlight can be turned off via software.

Thanks GPT! 🤖

Categories
AI Apple Fun

Apple Watch DOOM + Audio

It took a couple LLM prompts plus a few simple syntax tweaks from my side and now DOOM on the Apple Watch has audio. 🤯 It was almost as easy as typing iddqd. To get around lack of SDL support, AVFoundation is used. Check out the GitHub commits.

Categories
DOS Fun Video

Vibe coding like it’s 1999: Flappy Cow

This weekend I vibe coded a tribute to ’90s Gateways, 3Dfx, and DOS with DJGPP, Glide, and Allegro. I originally set up my build environment in DOSBox-X, but then moved to cross-compilation from Linux.

From there I dropped the game into 86Box, which I already had configured with an emulated Voodoo3 3000, for quicker testing.

Finally, I ran it on my Pentium 3 machine with Windows 98 and a real Voodoo3 3000. ChatGPT gets most of the credit for the C code that runs the game, I get credit for the cow voiceover work. 🐄

Categories
Apple Gadgets

Juicy Crumb DockLite G4

Last year, I purchased a DockLite G4 board from Juicy Crumb Systems. It’s a drop-in solution that repurposes the iconic iMac G4 into an HDMI display.

Installation

There are more affordable ways to achieve the same goal if you’ve got the time, but no one else makes it this simple.

In the box I got:

  • the main board
  • an installation manual
  • an HDMI cable
  • a minijack stereo cable
  • a USB-C cable
  • a sticker
  • a legit Australian Tim Tam

Overall, the instructions make sense – pull out the old PPC logic board, pop in the DockLite G4 board, connect power and the display, and you’re kinda done.

The board also features a USB hub, a speaker amplifier, and brightness-adjustment buttons. They all line up nicely with the back of the chassis, even if it’s not airtight, Apple-level industrial design.

Software control

If you’re running a Mac you can install Juicy-Link and connect to the USB hub to set the display brightness and upgrade firmware. It’s simple to install and seems to work well (as long as it’s a Mac).

Things to consider

Limitations

Their software is currently only available on macOS although their website claims Windows support is coming soon. Software control is better than nothing, but still not as nice as DDC/CI. If you find yourself on something other than macOS you’ll need to reach around the back and press the buttons to adjust the brightness.

More disappointing is that the display doesn’t support sleep mode, so turning off the backlight requires pressing the physical power button in the back. This makes the challenge of preserving an old backlight even harder.

The good news is that I think much of this can be fixed with software and firmware updates, so I’d love to see that happen.

It’s still an old LCD

Modern macOS looks great on this design (round all the borders!), but your eyes won’t be fooled coming from HiDPI Retina displays. LCDs have come a long way with higher resolutions and greater pixel densities, uniform and bright LED backlights, wider viewing angles, and higher refresh rates to name a few. This display has none of that. It’s not a suitable daily driver for my eyes, but it works great as a toy.

The drives can go

Juicy Crumb recommends that you leave the CD-ROM and HDD installed so the base retains a proper weight for stability. Interestingly their instructions don’t have you unplug the power from these drives even though they aren’t in use.

I understand keeping things simple, but the type of user who is disassembling an iMac G4 can surely unplug a couple cables to reduce unnecessary power consumption and strain on an old PSU.

I removed both from my 17″ system and I have no concerns about its stability. I recommend doing the same. Plus, you’ll get better airflow with the system fan (although I question if that’s really necessary, too).

Old PSU is old

Although Apple is known for high quality hardware, installing this board doesn’t really lower the risk that the 20+ year old power supply will fail and burn something up. It’d be nice to have an alternative DC input like a barrel pin connector.

The original fan is audible

Do yourself a favor and replace the factory fan with something modern and quiet. Without a hard drive and CD-ROM spinning away, the fan becomes more noticeable. The replacement should be a standard size, though you may want to match the color—anything non-white might be distracting.

Final thoughts

I’m really happy with the purchase and have had a blast playing around with different systems. If you’ve got a compatible iMac G4 and want to do more than Mac OS 9, this is a great solution. Just promise that if you order one and don’t want your Tim Tam biscuit, you’ll send it my way.

Categories
News

It’s time to retire Herp Derp

Thirteen years ago 🤯 I made a silly little browser plugin that got some Internet attention. It was featured on websites and a few big podcasts, and even served as inspiration for a WordPress plugin written by Jamie Zawinski. 😎 I’d occasionally see it pop up as a suggestion by Redditors and even learned of use cases I hadn’t anticipated – like preventing movie spoilers or making the site more suitable for classroom environments.

Reviews on the plugin page were overwhelmingly positive (many of them hilarious – one was even framed in my home) and I was delighted to see that the Internet could take a joke. On a serious note I also want to make it very clear that the term “herp derp” here meant “saying something meaningless”. I later learned there can be different interpretations of the term that don’t carry the same spirit – to be whimsical, silly, and most of all harmless. (this is why we can’t have nice things)

On the technical side the biggest surprise was that Google’s DOM structure stayed extremely stable throughout the whole run and only needed one update. I’m amazed it ran as long as it did with so little effort from my side.

Now that we’re in 2025 I think YouTube has improved comment moderation. Features like “Top comments” seem to help, and in this era of LLMs the accuracy of “quality scoring” should increase as well.

So with that, it’s time to say farewell to Herp Derp for YouTube! I mean, herp derp. 👋

Categories
Arduino Projects

FlipDotMatrix Library

In the past I’ve blogged about my Alfa-Zeta flip dots (one, two) and how I got them working with the Arduino platform. I’ve taken some time to turn the work into a consumable library that should make it much easier for others who want a turn-key way to drive the display.

The library is available in the Arduino IDE as well as the PlatformIO registry and comes with examples to get started. When added to a project it’ll grab all its dependencies so you can get going as quickly as possible.

Most of the heavy lifting is done by the excellent Adafruit GFX Library and it’s been updated to support 7×7 and 14×7 panels in addition to 28×7. Flip on! 🔴

Categories
DOS Fun Projects Retro Computing Video

Merry DOS-mas!

Now I can control my LED Christmas Tree from DOS! 🎄💾 I hope to share more details on the code + dev stack, but basically I used:

GitHub commit.

Categories
AI SwiftUI

ChatGPT SwiftUI Christmas Tree 2024

Over the past couple of years (2022, 2023) I’ve compared the output of the following prompt to ChatGPT:

Draw a Christmas tree using SwiftUI

For 2024 I chose the GPT-4o model, and this is what it output:

import SwiftUI

struct ChristmasTreeView: View {
    var body: some View {
        ZStack {
            // Background color
            Color.blue
                .edgesIgnoringSafeArea(.all)
            
            // Tree
            VStack(spacing: -20) {
                Triangle()
                    .fill(Color.green)
                    .frame(width: 150, height: 100)
                Triangle()
                    .fill(Color.green)
                    .frame(width: 180, height: 120)
                Triangle()
                    .fill(Color.green)
                    .frame(width: 210, height: 140)
                
                // Tree trunk
                Rectangle()
                    .fill(Color.brown)
                    .frame(width: 40, height: 60)
            }
            .offset(y: 50)
            
            // Star on top
            StarShape()
                .fill(Color.yellow)
                .frame(width: 40, height: 40)
                .offset(y: -170)
            
            // Decorations (Ornaments)
            Circle()
                .fill(Color.red)
                .frame(width: 15, height: 15)
                .offset(x: -40, y: -60)
            Circle()
                .fill(Color.yellow)
                .frame(width: 15, height: 15)
                .offset(x: 50, y: -30)
            Circle()
                .fill(Color.white)
                .frame(width: 15, height: 15)
                .offset(x: -30, y: 20)
            Circle()
                .fill(Color.blue)
                .frame(width: 15, height: 15)
                .offset(x: 40, y: 60)
        }
    }
}

// Triangle shape for the tree
struct Triangle: Shape {
    func path(in rect: CGRect) -> Path {
        Path { path in
            path.move(to: CGPoint(x: rect.midX, y: rect.minY))
            path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
            path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
            path.closeSubpath()
        }
    }
}

// Star shape for the top of the tree
struct StarShape: Shape {
    func path(in rect: CGRect) -> Path {
        let points = [
            CGPoint(x: 0.5, y: 0),
            CGPoint(x: 0.61, y: 0.35),
            CGPoint(x: 1, y: 0.35),
            CGPoint(x: 0.68, y: 0.57),
            CGPoint(x: 0.79, y: 0.91),
            CGPoint(x: 0.5, y: 0.7),
            CGPoint(x: 0.21, y: 0.91),
            CGPoint(x: 0.32, y: 0.57),
            CGPoint(x: 0, y: 0.35),
            CGPoint(x: 0.39, y: 0.35)
        ].map { CGPoint(x: $0.x * rect.width, y: $0.y * rect.height) }
        
        return Path { path in
            path.move(to: points[0])
            for point in points.dropFirst() {
                path.addLine(to: point)
            }
            path.closeSubpath()
        }
    }
}

struct ChristmasTreeView_Previews: PreviewProvider {
    static var previews: some View {
        ChristmasTreeView()
    }
}

Which generated:

This is the best one yet! I didn’t have to add or change any code to make it work, it added ornaments and even a (floating 😀) star! 🎄