docs: 加上应用图标和社交预览图

- docs/icon.png       满幅方图,去掉 icns 里四周 100px 的透明留白 ——
                      GitHub 会自己加圆角,留白会让图标缩成小小一颗
- docs/social-preview.png  1280×640。仓库被分享到 Twitter / Slack / 飞书时
                      唯一会被看到的东西;没有它链接展开是一片灰底加一行小字
- README 头部放上图标

社交预览图由 Resources/make-social.swift 生成,图标直接读 AppIcon.icns
而不是重画一遍 —— 两处各画一遍必然漂移,"两个地方的图标不一样"
是最廉价也最刺眼的破绽。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
wuguanganandClaude Opus 5 committed 2026-08-14 23:24:16 +08:00
1 parent 3af92f27cf
commit 3e23d1efba
4 files changed
+148 -2

No files matched your search

+6 -2
View File
@@ -1,6 +1,10 @@
# Vibe Foreman Free
<p align="center">
<img src="docs/icon.png" width="128" alt="Vibe Foreman">
</p>
把 MacBook 的刘海变成本地 AI 编程 agent 的**工头**。
<h1 align="center">Vibe Foreman Free</h1>
<p align="center">把 MacBook 的刘海变成本地 AI 编程 agent 的<b>工头</b>。</p>
它不只是让你看见 agent 在干嘛 —— 它盯着它们**有没有真的把活干完**。
+142
View File
@@ -0,0 +1,142 @@
import AppKit
import CoreGraphics
import Foundation
import ImageIO
// GitHub 的社交预览图(1280×640)。
//
// 这张图是仓库被分享到 Twitter / Slack / 飞书时唯一会被看到的东西 ——
// 没有它,链接展开后是一片灰底加一行小字,看起来像个废弃项目。
//
// 沿用 app 图标的视觉语言:近黑底 + 青→蓝→紫→品红→橙的渐变胶囊。
// 图标本身直接读 AppIcon.icns 里那张 1024,不重画一遍 ——
// 两处各画一遍必然漂移,而"两个地方的图标不一样"是最廉价也最刺眼的破绽。
let W: CGFloat = 1280
let H: CGFloat = 640
let here = URL(fileURLWithPath: #filePath).deletingLastPathComponent()
let iconURL = here.appendingPathComponent("AppIcon.icns")
let outURL = here.deletingLastPathComponent()
.appendingPathComponent("docs/social-preview.png")
guard let iconImage = NSImage(contentsOf: iconURL) else {
FileHandle.standardError.write(Data("找不到 AppIcon.icns\n".utf8))
exit(1)
}
guard let ctx = CGContext(
data: nil, width: Int(W), height: Int(H),
bitsPerComponent: 8, bytesPerRow: 0,
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
) else { exit(1) }
ctx.setAllowsAntialiasing(true)
// ── 底:近黑,带一点从左下往右上的提亮,别做成死板的纯色块。
let space = CGColorSpaceCreateDeviceRGB()
if let bg = CGGradient(
colorsSpace: space,
colors: [
CGColor(red: 0.055, green: 0.055, blue: 0.075, alpha: 1),
CGColor(red: 0.098, green: 0.094, blue: 0.129, alpha: 1),
] as CFArray,
locations: [0, 1]
) {
ctx.drawLinearGradient(
bg, start: CGPoint(x: 0, y: 0), end: CGPoint(x: W, y: H), options: []
)
}
// ── 图标背后的光晕。图标是深色的,直接压在深色底上会糊成一团。
ctx.saveGState()
let glowCenter = CGPoint(x: 300, y: H / 2)
if let glow = CGGradient(
colorsSpace: space,
colors: [
CGColor(red: 0.45, green: 0.35, blue: 0.95, alpha: 0.32),
CGColor(red: 0.45, green: 0.35, blue: 0.95, alpha: 0),
] as CFArray,
locations: [0, 1]
) {
ctx.drawRadialGradient(
glow, startCenter: glowCenter, startRadius: 0,
endCenter: glowCenter, endRadius: 300, options: []
)
}
ctx.restoreGState()
// ── 图标
let iconSide: CGFloat = 300
let iconRect = CGRect(
x: glowCenter.x - iconSide / 2, y: H / 2 - iconSide / 2,
width: iconSide, height: iconSide
)
var proposed = iconRect
if let cg = iconImage.cgImage(forProposedRect: &proposed, context: nil, hints: nil) {
ctx.draw(cg, in: iconRect)
}
// ── 文字。用 AppKit 画,CoreText 手搓一遍不值得。
let gfx = NSGraphicsContext(cgContext: ctx, flipped: false)
NSGraphicsContext.saveGraphicsState()
NSGraphicsContext.current = gfx
func draw(_ text: String, at point: CGPoint, size: CGFloat, weight: NSFont.Weight,
color: NSColor, tracking: CGFloat = 0) {
let style = NSMutableParagraphStyle()
style.lineHeightMultiple = 1.1
let attrs: [NSAttributedString.Key: Any] = [
.font: NSFont.systemFont(ofSize: size, weight: weight),
.foregroundColor: color,
.kern: tracking,
.paragraphStyle: style,
]
NSAttributedString(string: text, attributes: attrs).draw(at: point)
}
let textX: CGFloat = 520
draw("Vibe Foreman", at: CGPoint(x: textX, y: 372), size: 76, weight: .bold,
color: .white, tracking: -1.5)
draw("给 vibe coding 当工头", at: CGPoint(x: textX, y: 312), size: 30, weight: .medium,
color: NSColor(calibratedRed: 0.72, green: 0.70, blue: 0.85, alpha: 1))
// 这两行是全图唯一在说"它和别的监控工具有什么不同"的地方。
draw("不只看见 AI agent 在干嘛,", at: CGPoint(x: textX, y: 244), size: 26,
weight: .regular, color: NSColor(white: 0.62, alpha: 1))
draw("还盯着它有没有真的把活干完。", at: CGPoint(x: textX, y: 206), size: 26,
weight: .regular, color: NSColor(white: 0.62, alpha: 1))
NSGraphicsContext.restoreGraphicsState()
// ── 底部那道渐变细线:把图标上的配色再点一次,收住整张图。
if let line = CGGradient(
colorsSpace: space,
colors: [
CGColor(red: 0.30, green: 0.78, blue: 0.95, alpha: 1),
CGColor(red: 0.45, green: 0.45, blue: 0.95, alpha: 1),
CGColor(red: 0.85, green: 0.35, blue: 0.75, alpha: 1),
CGColor(red: 0.98, green: 0.55, blue: 0.35, alpha: 1),
] as CFArray,
locations: [0, 0.35, 0.7, 1]
) {
ctx.saveGState()
ctx.clip(to: CGRect(x: 0, y: 0, width: W, height: 8))
ctx.drawLinearGradient(
line, start: CGPoint(x: 0, y: 0), end: CGPoint(x: W, y: 0), options: []
)
ctx.restoreGState()
}
guard let image = ctx.makeImage(),
let dest = CGImageDestinationCreateWithURL(
outURL as CFURL, "public.png" as CFString, 1, nil
)
else { exit(1) }
CGImageDestinationAddImage(dest, image, nil)
guard CGImageDestinationFinalize(dest) else { exit(1) }
print("已生成 \(outURL.path)")
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 611 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 777 KiB