Skip to content

Commit 143ca06

Browse files
akhil-gautamclaude
andcommitted
fix(bloatmac): drop sidebar BrandMark halo + show real version
The PNG-backed BrandMark introduced a faint white halo at the rounded-square edges from rasterisation anti-aliasing. Redrawing as native SwiftUI shapes (RoundedRectangle gradient + a custom Path "B" made of a stem and two D-loops + a sparkle Shape) gives crisp edges at any size with no halo, and stays resolution-independent so the Onboarding splash's 2.4× scaleEffect remains pixel-aligned. Sparkle hides below 36 pt to avoid reading as noise at sidebar scale — matches the SVG's behaviour at small sizes. Also fixes the hardcoded "v 2.4.1" prototype version string in the sidebar header — it now pulls CFBundleShortVersionString from the running bundle so the sidebar always reflects the actual build. Removes the now-unused BrandLogo.imageset (saves ~100 KB from the bundle). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4f61453 commit 143ca06

5 files changed

Lines changed: 128 additions & 16 deletions

File tree

bloatmac/bloatmac/Assets.xcassets/BrandLogo.imageset/Contents.json

Lines changed: 0 additions & 8 deletions
This file was deleted.
Binary file not shown.
-68.9 KB
Binary file not shown.
-5.75 KB
Binary file not shown.

bloatmac/bloatmac/Shell/Sidebar.swift

Lines changed: 128 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ struct Sidebar: View {
7777
BrandMark()
7878
VStack(alignment: .leading, spacing: 1) {
7979
Text("BloatMac").font(.system(size: 14, weight: .bold)).tracking(-0.3).foregroundStyle(Tokens.text)
80-
Text("v 2.4.1").font(.system(size: 10, weight: .medium)).foregroundStyle(Tokens.text3)
80+
Text(appVersionLabel).font(.system(size: 10, weight: .medium)).foregroundStyle(Tokens.text3)
8181
}
8282
}
8383
.padding(.horizontal, 14).padding(.bottom, 14)
@@ -108,15 +108,135 @@ struct Sidebar: View {
108108
}
109109
}
110110

111+
/// Reads the bundle's CFBundleShortVersionString so the sidebar always
112+
/// reflects the actual build, not a hardcoded prototype string. Falls
113+
/// back to the build number if the short version key is missing.
114+
var appVersionLabel: String {
115+
let info = Bundle.main.infoDictionary
116+
let short = info?["CFBundleShortVersionString"] as? String
117+
let build = info?["CFBundleVersion"] as? String
118+
return "v " + (short ?? build ?? "?")
119+
}
120+
121+
/// Native SwiftUI redraw of the brand logo. We rasterise from PNG assets
122+
/// for the AppIcon (Apple requires `.icns` from PNGs there), but for
123+
/// in-app surfaces the bitmap import introduces a faint halo at the
124+
/// rounded-square edges from anti-aliasing. Drawing as live shapes
125+
/// stays crisp at any scale and ditches the halo entirely.
111126
struct BrandMark: View {
112-
/// Defaults to the sidebar's 26 pt; callers like `Onboarding` scale via
113-
/// `.scaleEffect()` so we don't need a `size` parameter.
127+
/// Defaults to the sidebar's 26 pt. `Onboarding` scales via
128+
/// `.scaleEffect()` so the geometry inside remains pixel-aligned.
129+
var size: CGFloat = 26
130+
131+
private var corner: CGFloat { size * (232.0 / 1024.0) }
132+
114133
var body: some View {
115-
Image("BrandLogo")
116-
.resizable()
117-
.interpolation(.high)
118-
.frame(width: 26, height: 26)
119-
.shadow(color: Color(hex: 0x0A84FF).opacity(0.32), radius: 4, x: 0, y: 2)
134+
ZStack {
135+
// Badge gradient — matches Logo.svg's #0A84FF → #5E5CE6 stops.
136+
RoundedRectangle(cornerRadius: corner, style: .continuous)
137+
.fill(LinearGradient(colors: [Color(hex: 0x0A84FF), Color(hex: 0x5E5CE6)],
138+
startPoint: .topLeading, endPoint: .bottomTrailing))
139+
// Subtle radial highlight near the top-left so the badge has dimension.
140+
RoundedRectangle(cornerRadius: corner, style: .continuous)
141+
.fill(RadialGradient(colors: [.white.opacity(0.20), .clear],
142+
center: UnitPoint(x: 0.28, y: 0.22),
143+
startRadius: 0, endRadius: size * 0.95))
144+
// Hairline inner stroke — matches the SVG's white-12% inset rect.
145+
RoundedRectangle(cornerRadius: corner - 0.5, style: .continuous)
146+
.stroke(.white.opacity(0.12), lineWidth: 1)
147+
.padding(0.5)
148+
// Stylized "B" — vertical stem + two stacked D-loops, baked
149+
// into one path so it fills as one shape. Coords are
150+
// normalised against the SVG's 1024-unit canvas, then scaled
151+
// to the runtime size.
152+
BLetterShape()
153+
.fill(LinearGradient(
154+
colors: [.white, Color(hex: 0xF0E9FF)],
155+
startPoint: UnitPoint(x: 0.3, y: 0.05),
156+
endPoint: UnitPoint(x: 0.6, y: 1.0)
157+
))
158+
// Smart-care sparkle — fades out below the sidebar threshold so
159+
// it doesn't read as noise at 26 pt.
160+
if size >= 36 {
161+
SparkleShape()
162+
.fill(.white.opacity(0.94))
163+
.frame(width: size * 0.16, height: size * 0.16)
164+
.offset(x: size * 0.275, y: -size * 0.275)
165+
}
166+
}
167+
.frame(width: size, height: size)
168+
.compositingGroup()
169+
.shadow(color: Color(hex: 0x0A84FF).opacity(0.32), radius: size * 0.18, x: 0, y: size * 0.08)
170+
}
171+
}
172+
173+
/// Filled "B": vertical stem + two D-loops in a single path. All
174+
/// coordinates expressed against the SVG's 1024-unit canvas, then
175+
/// scaled to the runtime rect.
176+
private struct BLetterShape: Shape {
177+
func path(in rect: CGRect) -> Path {
178+
let scale = rect.width / 1024.0
179+
func p(_ x: CGFloat, _ y: CGFloat) -> CGPoint {
180+
CGPoint(x: rect.minX + x * scale, y: rect.minY + y * scale)
181+
}
182+
var path = Path()
183+
// Stem: rounded rect 288..394 × 268..756.
184+
path.addRoundedRect(
185+
in: CGRect(origin: p(288, 268),
186+
size: CGSize(width: 106 * scale, height: 488 * scale)),
187+
cornerSize: CGSize(width: 22 * scale, height: 22 * scale)
188+
)
189+
// Top D-loop: from (390,268) down to (390,504), arc back via right bulge.
190+
path.move(to: p(390, 268))
191+
path.addLine(to: p(390, 504))
192+
path.addArc(
193+
tangent1End: p(514, 504),
194+
tangent2End: p(514, 268),
195+
radius: 124 * scale
196+
)
197+
path.addArc(
198+
tangent1End: p(514, 268),
199+
tangent2End: p(390, 268),
200+
radius: 124 * scale
201+
)
202+
path.closeSubpath()
203+
// Bottom D-loop: slightly larger per typographic convention.
204+
path.move(to: p(390, 520))
205+
path.addLine(to: p(390, 756))
206+
path.addArc(
207+
tangent1End: p(528, 756),
208+
tangent2End: p(528, 520),
209+
radius: 138 * scale
210+
)
211+
path.addArc(
212+
tangent1End: p(528, 520),
213+
tangent2End: p(390, 520),
214+
radius: 138 * scale
215+
)
216+
path.closeSubpath()
217+
return path
218+
}
219+
}
220+
221+
/// Four-point sparkle: a vertical bar + horizontal bar (rounded) + a
222+
/// faint 45°-rotated rounded square between them.
223+
private struct SparkleShape: Shape {
224+
func path(in rect: CGRect) -> Path {
225+
var path = Path()
226+
let cx = rect.midX, cy = rect.midY
227+
let arm = rect.width * 0.5
228+
let thick = rect.width * 0.16
229+
// Vertical bar
230+
path.addRoundedRect(
231+
in: CGRect(x: cx - thick / 2, y: cy - arm, width: thick, height: arm * 2),
232+
cornerSize: CGSize(width: thick / 2, height: thick / 2)
233+
)
234+
// Horizontal bar
235+
path.addRoundedRect(
236+
in: CGRect(x: cx - arm, y: cy - thick / 2, width: arm * 2, height: thick),
237+
cornerSize: CGSize(width: thick / 2, height: thick / 2)
238+
)
239+
return path
120240
}
121241
}
122242

0 commit comments

Comments
 (0)