Created
August 27, 2026 04:07
-
-
Save timsneath/fdd03729072a56b6147e7a8e48742389 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import Foundation | |
| struct AnimatedBirthdayCake { | |
| let name: String | |
| // Tier character widths | |
| private let topWidth = 21 | |
| private let midWidth = 25 | |
| private let baseWidth = 29 | |
| // ANSI Controls | |
| private let clearScreen = "\u{001B}[2J\u{001B}[H" | |
| private let hideCursor = "\u{001B}[?25l" | |
| private let showCursor = "\u{001B}[?25h" | |
| private let homeCursor = "\u{001B}[H" | |
| // MARK: - Cake Renderer | |
| func render(candleStates: [String], confetti: [String] = []) -> String { | |
| var lines: [String] = [] | |
| // Optional top confetti line | |
| if !confetti.isEmpty { | |
| lines.append(confetti.joined(separator: " ")) | |
| } else { | |
| lines.append("") | |
| } | |
| // 1. Candles (exact centering: 5 spaces indent) | |
| let candlePad = String(repeating: " ", count: 5) | |
| lines.append(candlePad + candleStates.joined(separator: " ")) | |
| // 2. Tier 1 - Top | |
| let topPad = String(repeating: " ", count: (baseWidth - topWidth) / 2) | |
| lines.append(topPad + "┌" + String(repeating: "─", count: topWidth - 2) + "┐") | |
| lines.append(topPad + "│" + center("* H A P P Y *", width: topWidth - 2) + "│") | |
| // 3. Tier 2 - Middle | |
| let midPad = String(repeating: " ", count: (baseWidth - midWidth) / 2) | |
| let step1 = (midWidth - topWidth) / 2 | |
| lines.append(midPad + "┌" + String(repeating: "─", count: step1 - 1) + "┴" + String(repeating: "─", count: topWidth - 2) + "┴" + String(repeating: "─", count: step1 - 1) + "┐") | |
| lines.append(midPad + "│" + center("B I R T H D A Y", width: midWidth - 2) + "│") | |
| // 4. Tier 3 - Base | |
| let step2 = (baseWidth - midWidth) / 2 | |
| let nameTag = "\(name.uppercased())" | |
| lines.append("┌" + String(repeating: "─", count: step2 - 1) + "┴" + String(repeating: "─", count: midWidth - 2) + "┴" + String(repeating: "─", count: step2 - 1) + "┐") | |
| lines.append("│" + center(nameTag, width: baseWidth - 2) + "│") | |
| lines.append("└" + String(repeating: "─", count: baseWidth - 2) + "┘") | |
| return lines.joined(separator: "\n") | |
| } | |
| private func center(_ text: String, width: Int) -> String { | |
| let leftPad = (width - text.count) / 2 | |
| let rightPad = width - text.count - leftPad | |
| return String(repeating: " ", count: leftPad) + text + String(repeating: " ", count: rightPad) | |
| } | |
| // MARK: - Animation Sequence | |
| func animate() async throws { | |
| print(clearScreen + hideCursor, terminator: "") | |
| defer { print(showCursor) } | |
| // Phase 1: Flickering Candles (10 frames) | |
| let flickerVariations = [ | |
| ["🕯️", "🕯️", "🕯️", "🕯️", "🕯️"], | |
| ["✨", "🕯️", "🕯️", "✨", "🕯️"], | |
| ["🕯️", "✨", "🕯️", "🕯️", "✨"], | |
| ["🕯️", "🕯️", "✨", "🕯️", "🕯️"] | |
| ] | |
| for i in 0..<12 { | |
| let state = flickerVariations[i % flickerVariations.count] | |
| print(homeCursor + render(candleStates: state) + "\n\n 🎂 Make a wish, Tim...") | |
| try await Task.sleep(for: .milliseconds(250)) | |
| } | |
| // Phase 2: Blowing out candles one by one (left to right) | |
| var candles = ["🕯️", "🕯️", "🕯️", "🕯️", "🕯️"] | |
| for i in 0..<5 { | |
| candles[i] = "💨" | |
| print(homeCursor + render(candleStates: candles) + "\n\n 💨 *Ffffffffff...* ") | |
| try await Task.sleep(for: .milliseconds(220)) | |
| } | |
| try await Task.sleep(for: .milliseconds(400)) | |
| // Phase 3: Confetti Burst Celebration (15 frames) | |
| let confettiShapes = ["🎉", "✨", "🎈", "⭐️", "🎊", "🥳"] | |
| for _ in 0..<16 { | |
| let row = (0..<6).map { _ in confettiShapes.randomElement()! } | |
| print(homeCursor + render(candleStates: ["💨", "💨", "💨", "💨", "💨"], confetti: row) + "\n\n 🎉 HAPPY BIRTHDAY, TIM! 🥳") | |
| try await Task.sleep(for: .milliseconds(200)) | |
| } | |
| } | |
| } | |
| // MARK: - Run | |
| let cake = AnimatedBirthdayCake(name: "Tim") | |
| try await cake.animate() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment