From 37f6cab256e84d6190b93bd1af993289c33dc190 Mon Sep 17 00:00:00 2001 From: hl-valdemar Date: Mon, 12 Oct 2026 13:46:04 +0200 Subject: [PATCH] feat: add a bundled sparkle application icon --- Makefile | 4 +++- README.md | 2 ++ Resources/Info.plist | 2 ++ Scripts/icon.swift | 55 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 Scripts/icon.swift diff --git a/Makefile b/Makefile index ccc3484..097509f 100644 --- a/Makefile +++ b/Makefile @@ -5,9 +5,11 @@ INSTALL_DIR ?= /Applications build: swift build -c release - mkdir -p $(APP)/Contents/MacOS + mkdir -p $(APP)/Contents/MacOS $(APP)/Contents/Resources cp .build/release/Conjure $(APP)/Contents/MacOS/Conjure cp Resources/Info.plist $(APP)/Contents/Info.plist + swift Scripts/icon.swift .build/Conjure.iconset + iconutil -c icns .build/Conjure.iconset -o $(APP)/Contents/Resources/Conjure.icns codesign --force --sign - $(APP) run: build diff --git a/README.md b/README.md index 7d14c5f..26dee49 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,8 @@ Applications with identical display names show a muted containing-folder hint be The launcher keeps its Raycast-inspired 750 × 474-point, titlebar-free rounded panel, large borderless search field, compact icon rows, right-aligned application labels, and bottom-right launch pill. Its Sunset palette uses a dark brown background, soft amber and rust glows, warm cream text, and orange accents on the caret, selection, and action outline. +The application icon uses the same orange `sparkles` SF Symbol on a self-contained, rounded dark-brown tile. `Scripts/icon.swift` renders every standard and Retina icon size, and `make build` packages the resulting `.icns` in the signed bundle for Finder and other system icon surfaces. Conjure remains an accessory app; adding an application icon does not add a Dock or app-switcher entry. + The brown tint is 35% opaque over an active, behind-window `NSVisualEffectView` using the native HUD material. This exposes macOS's blurred backdrop while retaining the warm palette and glows; the system material supplies an opaque fallback when Reduce Transparency is enabled. Both the panel and launch pill use native rounded alpha masks alongside layer clipping, keeping material inside its rounded outline and making the window shadow follow the same shape. AppKit still provides text editing, table navigation, scrolling, application icons, controls, menus, fonts, and visual-effect materials. The background uses cached native radial gradients that fade to transparent: static soft blobs, without animations, timers, image assets, blur filters, or third-party UI dependencies. Decorative drawing does not intercept clicks. Increased-contrast settings strengthen the selection outline, and text colors are tuned for readability across the glows and selection. diff --git a/Resources/Info.plist b/Resources/Info.plist index 210ae4e..10db57c 100644 --- a/Resources/Info.plist +++ b/Resources/Info.plist @@ -14,6 +14,8 @@ Conjure CFBundleDisplayName Conjure + CFBundleIconFile + Conjure.icns CFBundlePackageType APPL CFBundleShortVersionString diff --git a/Scripts/icon.swift b/Scripts/icon.swift new file mode 100644 index 0000000..6668c33 --- /dev/null +++ b/Scripts/icon.swift @@ -0,0 +1,55 @@ +import AppKit + +// Render the launcher's SF Symbol directly at every macOS icon resolution. +precondition(CommandLine.arguments.count == 2, "Usage: swift Scripts/icon.swift ") +let directory = URL(fileURLWithPath: CommandLine.arguments[1], isDirectory: true) +try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) +let accent = NSColor(srgbRed: 0.95, green: 0.58, blue: 0.23, alpha: 1) +guard let symbol = NSImage(systemSymbolName: "sparkles", accessibilityDescription: nil)? + .withSymbolConfiguration(NSImage.SymbolConfiguration(paletteColors: [accent])) else { + fatalError("The sparkles SF Symbol is unavailable.") +} +let gradient = NSGradient(starting: NSColor(srgbRed: 0.27, green: 0.19, blue: 0.13, alpha: 1), + ending: NSColor(srgbRed: 0.12, green: 0.09, blue: 0.08, alpha: 1))! + +for size in [16, 32, 128, 256, 512] { + for scale in [1, 2] { + let pixels = size * scale + let bitmap = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: pixels, pixelsHigh: pixels, + bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, + isPlanar: false, colorSpaceName: .deviceRGB, + bytesPerRow: 0, bitsPerPixel: 0)! + NSGraphicsContext.saveGraphicsState() + NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: bitmap) + let context = NSGraphicsContext.current!.cgContext + context.scaleBy(x: CGFloat(pixels) / 1024, y: CGFloat(pixels) / 1024) + let tile = NSBezierPath(roundedRect: NSRect(x: 100, y: 100, width: 824, height: 824), + xRadius: 184, yRadius: 184) + NSGraphicsContext.saveGraphicsState() + let shadow = NSShadow() + shadow.shadowColor = NSColor.black.withAlphaComponent(0.28) + shadow.shadowBlurRadius = 24 + shadow.shadowOffset = NSSize(width: 0, height: -12) + shadow.set() + NSColor(srgbRed: 0.12, green: 0.09, blue: 0.08, alpha: 1).setFill() + tile.fill() + NSGraphicsContext.restoreGraphicsState() + gradient.draw(in: tile, angle: -90) + NSColor.white.withAlphaComponent(0.10).setStroke() + tile.lineWidth = 2 + tile.stroke() + + let glyphHeight: CGFloat = 540 + let glyphWidth = glyphHeight * symbol.size.width / symbol.size.height + symbol.draw(in: NSRect(x: (1024 - glyphWidth) / 2, y: (1024 - glyphHeight) / 2, + width: glyphWidth, height: glyphHeight), + from: .zero, operation: .sourceOver, fraction: 1) + NSGraphicsContext.restoreGraphicsState() + let suffix = scale == 2 ? "@2x" : "" + let output = directory.appendingPathComponent("icon_\(size)x\(size)\(suffix).png") + guard let png = bitmap.representation(using: .png, properties: [:]) else { + fatalError("Could not encode \(output.lastPathComponent).") + } + try png.write(to: output) + } +}