// Generates docs/hero.png — the banner at the top of the README. // // Composes the already-generated app icon with the wordmark and tagline rather // than redrawing the coats, so the banner can never drift out of step with the // icon itself. Run generate-icon.swift first. // // Usage: // swift icon/generate-icon.swift icon/AppIcon.iconset // cp icon/AppIcon.iconset/icon_512x512.png icon/shannoncoat.png // iconutil -c icns icon/AppIcon.iconset -o icon/AppIcon.icns // rm -rf icon/AppIcon.iconset // swift icon/generate-hero.swift icon/shannoncoat.png docs/hero.png import Cocoa private func rgb(_ hex: UInt32) -> NSColor { NSColor(srgbRed: CGFloat((hex >> 16) & 255) / 255, green: CGFloat((hex >> 8) & 255) / 255, blue: CGFloat(hex & 255) / 255, alpha: 1) } // Slate ground rather than cloud: GitHub renders READMEs on both a light and a // dark page, and a dark banner sits comfortably on either, where a near-white // one glares against a dark page. It also lets the coral icon carry the colour. private let ground = rgb(0x26_26_25) private let wordmark = rgb(0xF0_EE_E6) private let subtitle = rgb(0xA8_A2_98) private let width = 1200 private let height = 360 let args = CommandLine.arguments let iconPath = args.count > 1 ? args[1] : "icon/shannoncoat.png" let outPath = args.count > 2 ? args[2] : "docs/hero.png" guard let icon = NSImage(contentsOfFile: iconPath) else { fatalError("couldn't read \(iconPath) — run generate-icon.swift first") } guard let rep = NSBitmapImageRep( bitmapDataPlanes: nil, pixelsWide: width, pixelsHigh: height, bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .deviceRGB, bytesPerRow: 0, bitsPerPixel: 0 ) else { fatalError("could not create bitmap rep") } rep.size = NSSize(width: width, height: height) NSGraphicsContext.saveGraphicsState() NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: rep) NSGraphicsContext.current?.imageInterpolation = .high ground.setFill() NSBezierPath(rect: NSRect(x: 0, y: 0, width: width, height: height)).fill() let iconSide: CGFloat = 184 icon.draw(in: NSRect(x: 96, y: (CGFloat(height) - iconSide) / 2, width: iconSide, height: iconSide)) let textX: CGFloat = 96 + iconSide + 56 let name = NSAttributedString(string: "shannoncoat", attributes: [ .font: NSFont.systemFont(ofSize: 76, weight: .semibold), .foregroundColor: wordmark, ]) let tagline = NSAttributedString(string: "which coat will Claude wear today?", attributes: [ .font: NSFont.systemFont(ofSize: 30, weight: .regular), .foregroundColor: subtitle, ]) // Stacked around the vertical centre, using each string's own measured height // so the pair sits optically centred against the icon rather than by guesswork. let nameSize = name.size(), taglineSize = tagline.size() let gap: CGFloat = 14 let blockHeight = nameSize.height + gap + taglineSize.height let top = (CGFloat(height) + blockHeight) / 2 name.draw(at: NSPoint(x: textX, y: top - nameSize.height)) tagline.draw(at: NSPoint(x: textX + 3, y: top - nameSize.height - gap - taglineSize.height)) NSGraphicsContext.restoreGraphicsState() let outURL = URL(fileURLWithPath: outPath) try? FileManager.default.createDirectory( at: outURL.deletingLastPathComponent(), withIntermediateDirectories: true) guard let png = rep.representation(using: .png, properties: [:]) else { fatalError("failed to encode hero PNG") } try! png.write(to: outURL) print("wrote \(outPath)")