Skip to content

Commit e85f2cf

Browse files
committed
v1.0.3: neo-brutalist icon (blush + wine)
Replace the gradient bolt with a flat sticker mark — blush card, deep wine V monogram, chunky offset shadow. Tonal palette (same hue family) keeps it soft while still high-contrast at small sizes.
1 parent 60bb173 commit e85f2cf

3 files changed

Lines changed: 68 additions & 62 deletions

File tree

Resources/AppIcon.icns

119 KB
Binary file not shown.

Resources/Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<key>CFBundleVersion</key>
1010
<string>1</string>
1111
<key>CFBundleShortVersionString</key>
12-
<string>1.0.2</string>
12+
<string>1.0.3</string>
1313
<key>CFBundleIconFile</key>
1414
<string>AppIcon</string>
1515
<key>LSUIElement</key>

Scripts/make_icon.swift

Lines changed: 67 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
#!/usr/bin/env swift
22

3-
// Generates Veil's app icon: a white bolt on a purple→indigo gradient with
4-
// a macOS-style rounded squircle. Renders all sizes Apple expects in an
5-
// .iconset, then `iconutil` packages it (called from the Makefile).
3+
// Generates Veil's app icon: a soft-pastel neo-brutalist sticker — a
4+
// rounded square with a heavy black border and chunky offset shadow,
5+
// centered on a thick "V" mark.
66
//
77
// Run: swift Scripts/make_icon.swift
8-
//
9-
// Output: Resources/AppIcon.iconset/icon_<size>.png × N
108

119
import AppKit
1210

@@ -16,7 +14,6 @@ let outputDir = projectRoot + "/Resources/AppIcon.iconset"
1614
try? FileManager.default.removeItem(atPath: outputDir)
1715
try! FileManager.default.createDirectory(atPath: outputDir, withIntermediateDirectories: true)
1816

19-
/// macOS .iconset expected entries: (size, scale, suffix).
2017
let entries: [(size: Int, scale: Int, suffix: String)] = [
2118
(16, 1, "16x16"),
2219
(16, 2, "16x16@2x"),
@@ -30,69 +27,78 @@ let entries: [(size: Int, scale: Int, suffix: String)] = [
3027
(512, 2, "512x512@2x"),
3128
]
3229

30+
// Tonal palette — dusty blush card paired with a deep wine ink.
31+
// Same hue family (red), light tint vs dark shade. Reads soft at a glance,
32+
// holds high contrast at every size.
33+
let card = NSColor(srgbRed: 0.96, green: 0.84, blue: 0.84, alpha: 1) // dusty blush
34+
let ink = NSColor(srgbRed: 0.27, green: 0.10, blue: 0.16, alpha: 1) // deep wine
35+
36+
func roundedRectPath(_ rect: CGRect, radius: CGFloat) -> CGPath {
37+
CGPath(roundedRect: rect, cornerWidth: radius, cornerHeight: radius, transform: nil)
38+
}
39+
3340
func render(size: CGFloat) -> NSImage {
3441
let image = NSImage(size: NSSize(width: size, height: size))
3542
image.lockFocus()
3643
guard let ctx = NSGraphicsContext.current?.cgContext else {
3744
fatalError("No graphics context")
3845
}
3946

40-
// Rounded squircle background. macOS Big Sur+ uses ~22% corner radius.
41-
let radius = size * 0.225
42-
let rect = CGRect(x: 0, y: 0, width: size, height: size)
43-
let path = CGPath(roundedRect: rect, cornerWidth: radius, cornerHeight: radius, transform: nil)
44-
45-
ctx.saveGState()
46-
ctx.addPath(path)
47-
ctx.clip()
48-
49-
// Vertical gradient: indigo → violet.
50-
let colors = [
51-
NSColor(srgbRed: 0.36, green: 0.31, blue: 0.78, alpha: 1).cgColor, // indigo
52-
NSColor(srgbRed: 0.59, green: 0.34, blue: 0.91, alpha: 1).cgColor, // violet
53-
] as CFArray
54-
let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: colors, locations: [0, 1])!
55-
ctx.drawLinearGradient(
56-
gradient,
57-
start: CGPoint(x: 0, y: size),
58-
end: CGPoint(x: 0, y: 0),
59-
options: []
47+
// Sticker fills the canvas. Card sits top-left, shadow peeks bottom-right.
48+
// No backdrop fill — area outside the sticker stays transparent so the
49+
// dock / Finder show whatever's behind.
50+
let shadowOffset = size * 0.07
51+
let cardRect = CGRect(
52+
x: 0,
53+
y: shadowOffset,
54+
width: size - shadowOffset,
55+
height: size - shadowOffset
6056
)
61-
62-
// Subtle inner highlight along the top edge.
63-
let highlight = NSColor.white.withAlphaComponent(0.18).cgColor
64-
ctx.setFillColor(highlight)
65-
let highlightRect = CGRect(x: 0, y: size * 0.7, width: size, height: size * 0.3)
66-
ctx.fill(highlightRect)
67-
68-
ctx.restoreGState()
69-
70-
// Draw the bolt symbol centered, white, ~55% of canvas.
71-
let symbolSize = size * 0.58
72-
if let bolt = NSImage(systemSymbolName: "bolt.fill", accessibilityDescription: nil) {
73-
let config = NSImage.SymbolConfiguration(pointSize: symbolSize, weight: .heavy)
74-
let configured = bolt.withSymbolConfiguration(config) ?? bolt
75-
76-
let originX = (size - symbolSize) / 2
77-
let originY = (size - symbolSize) / 2
78-
let symbolRect = NSRect(x: originX, y: originY, width: symbolSize, height: symbolSize)
79-
80-
// Render the symbol white via NSGraphicsContext composite mode.
81-
ctx.setFillColor(NSColor.white.cgColor)
82-
let cgImage = configured.cgImage(forProposedRect: nil, context: nil, hints: nil)
83-
if let cg = cgImage {
84-
ctx.saveGState()
85-
ctx.translateBy(x: 0, y: size)
86-
ctx.scaleBy(x: 1, y: -1)
87-
let flippedRect = NSRect(x: originX, y: size - originY - symbolSize, width: symbolSize, height: symbolSize)
88-
ctx.clip(to: flippedRect, mask: cg)
89-
ctx.fill(rect)
90-
ctx.restoreGState()
91-
} else {
92-
// Fallback — draw the symbol normally (will be tinted by template handling).
93-
configured.draw(in: symbolRect)
94-
}
95-
}
57+
let shadowRect = cardRect.offsetBy(dx: shadowOffset, dy: -shadowOffset)
58+
let cardRadius = size * 0.20
59+
let borderWidth = size * 0.05
60+
61+
// Chunky offset shadow.
62+
ctx.setFillColor(ink.cgColor)
63+
ctx.addPath(roundedRectPath(shadowRect, radius: cardRadius))
64+
ctx.fillPath()
65+
66+
// Card fill.
67+
ctx.setFillColor(card.cgColor)
68+
ctx.addPath(roundedRectPath(cardRect, radius: cardRadius))
69+
ctx.fillPath()
70+
71+
// Card border.
72+
let borderInset = borderWidth / 2
73+
let borderRect = cardRect.insetBy(dx: borderInset, dy: borderInset)
74+
let borderRadius = max(0, cardRadius - borderInset)
75+
ctx.setStrokeColor(ink.cgColor)
76+
ctx.setLineWidth(borderWidth)
77+
ctx.addPath(roundedRectPath(borderRect, radius: borderRadius))
78+
ctx.strokePath()
79+
80+
// V monogram — two stroked diagonals meeting at the apex. Centered on
81+
// the card, not the canvas, so the offset shadow doesn't cause a shift.
82+
let vWidth = cardRect.width * 0.62
83+
let vHeight = cardRect.height * 0.50
84+
let strokeWidth = size * 0.13
85+
let vOriginX = cardRect.midX - vWidth / 2
86+
let vOriginY = cardRect.midY - vHeight / 2
87+
let topY = vOriginY + vHeight
88+
let bottomY = vOriginY
89+
90+
let vPath = CGMutablePath()
91+
vPath.move(to: CGPoint(x: vOriginX, y: topY))
92+
vPath.addLine(to: CGPoint(x: vOriginX + vWidth / 2, y: bottomY))
93+
vPath.addLine(to: CGPoint(x: vOriginX + vWidth, y: topY))
94+
95+
ctx.setStrokeColor(ink.cgColor)
96+
ctx.setLineWidth(strokeWidth)
97+
ctx.setLineJoin(.miter)
98+
ctx.setLineCap(.butt)
99+
ctx.setMiterLimit(20)
100+
ctx.addPath(vPath)
101+
ctx.strokePath()
96102

97103
image.unlockFocus()
98104
return image

0 commit comments

Comments
 (0)