Skip to content

Polish: album-art accent + liquid-glass clock + blurred backdrop - #10

Open
tirthfx wants to merge 18 commits into
phase-9-volume-gesturefrom
polish-accent-glass-blur
Open

Polish: album-art accent + liquid-glass clock + blurred backdrop#10
tirthfx wants to merge 18 commits into
phase-9-volume-gesturefrom
polish-accent-glass-blur

Conversation

@tirthfx

@tirthfx tirthfx commented Jul 10, 2026

Copy link
Copy Markdown
Owner

What changed

Folds in the album-art accent color polish item and reworks the lock-screen expanded/lyrics states to match the Apple-Music reference (image 2).

Album-art accent color (both surfaces)

  • New AlbumArtColor samples a vibrant tone from the current art — downsample to 24×24, saturation-weighted average that discounts near-black/near-white areas, then normalized (min saturation/brightness) so it always reads as an accent, never muddy. Falls back to white.
  • MediaMetadataService publishes it as accentColor (recomputed via didSet only when the art actually changes).
  • It tints: the current lyric line on both the notch and the lock screen, the album-art bloom, and the glass rims on the widget / control pill.

Lyrics state → image 2

  • Blur instead of dim. Expanding now frosts the wallpaper (blur + a light 0.34 tint) rather than fading it to near-black, so the wallpaper stays present as a frosted backdrop.
  • Liquid-glass clock. Frosted, material-filled numerals over the blur with a soft light bloom — compact at the top in the expanded/lyrics states, large/centered at the widget level. Content sits below it so the clock no longer overlaps the lyrics (the overlap visible in "before").
  • Layout. Clock small top-center, album art + control pill on the left, synced lyrics on the right — the Apple-Music arrangement.

Base

Stacked on phase-9-volume-gesture (open PR #9). The lock-screen stack is now 5 → 6 → 7 → 8 → 9 → this.

Notes for review — needs your eyes in Xcode

  • Can't verify visuals headlessly. Confirmed: builds clean, launches without crashing, notch unaffected.
  • Please check on the Mac: ⌃⌘L → expand → swipe to lyrics. Expect: wallpaper frosted (not black), a small glassy clock at top, the current lyric line tinted with the album's accent color, and a matching bloom around the art.
  • Two tuning knobs if needed: backdropBlur / backdropTint in LockScreenBackdropView (how frosted/dark), and the accent normalization floors in AlbumArtColor (how strong the tint is). The glass numerals use .regularMaterial as a text fill — if that reads too faint on a bright wallpaper, bump the white overlay opacity in GlassClockView.glassNumerals.

🤖 Generated with Claude Code

…kdrop

Folds in the creative polish items and reworks the lock-screen expanded/lyrics
states to match the Apple-Music reference (image 2).

- Album-art accent color: AlbumArtColor samples a vibrant tone from the current
  art (downsample + saturation-weighted average, normalized to always pop);
  MediaMetadataService publishes it as accentColor. It tints the current lyric
  line (both notch and lock screen), the album-art bloom, and the glass rims.
- Liquid-glass clock: frosted material-filled numerals over the blurred
  wallpaper with a soft light bloom; compact at the top in the expanded/lyrics
  states, large/centered at the widget level.
- Blur instead of dim: expanding now frosts the wallpaper (blur + a light tint)
  rather than fading it to near-black, and the lyrics layout places the clock
  small at top with album art + controls on the left and lyrics on the right.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces dynamic album-art accent color extraction and integrates it across various UI components, including the lyrics panel, control pills, and lock-screen widgets. It also refactors the lock-screen backdrop to support a frosted wallpaper blur and a liquid-glass clock style. The review feedback highlights critical performance and safety improvements: optimizing the album art downsampling algorithm to prevent main-thread UI stutters, replacing expensive wallpaper blur animations with hardware-accelerated material overlays, and safely unwrapping the optional graphics context during image drawing.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +14 to +31
let side = 24
guard let rep = downsampledBitmap(side: side) else { return .white }

var rSum = 0.0, gSum = 0.0, bSum = 0.0, weight = 0.0
for y in 0..<side {
for x in 0..<side {
guard let pixel = rep.colorAt(x: x, y: y)?.usingColorSpace(.sRGB) else { continue }
let s = Double(pixel.saturationComponent)
let v = Double(pixel.brightnessComponent)
// Favor colorful, mid-bright pixels; heavily discount flat
// dark/bright areas (letterboxing, white sleeves, shadows).
let w = s * ((v > 0.12 && v < 0.97) ? 1.0 : 0.08)
rSum += Double(pixel.redComponent) * w
gSum += Double(pixel.greenComponent) * w
bSum += Double(pixel.blueComponent) * w
weight += w
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Downsampling to a 24x24 grid requires 576 iterations of colorAt(x:y:) and usingColorSpace(.sRGB) on the main thread, which is highly inefficient and can cause noticeable UI micro-stutters during track changes.

By reducing the downsample size to 12 (144 pixels) and removing the redundant .usingColorSpace(.sRGB) call (since the bitmap is already initialized in deviceRGB), we can achieve a virtually identical accent color with a fraction of the CPU overhead.

Suggested change
let side = 24
guard let rep = downsampledBitmap(side: side) else { return .white }
var rSum = 0.0, gSum = 0.0, bSum = 0.0, weight = 0.0
for y in 0..<side {
for x in 0..<side {
guard let pixel = rep.colorAt(x: x, y: y)?.usingColorSpace(.sRGB) else { continue }
let s = Double(pixel.saturationComponent)
let v = Double(pixel.brightnessComponent)
// Favor colorful, mid-bright pixels; heavily discount flat
// dark/bright areas (letterboxing, white sleeves, shadows).
let w = s * ((v > 0.12 && v < 0.97) ? 1.0 : 0.08)
rSum += Double(pixel.redComponent) * w
gSum += Double(pixel.greenComponent) * w
bSum += Double(pixel.blueComponent) * w
weight += w
}
}
let side = 12
guard let rep = downsampledBitmap(side: side) else { return .white }
var rSum = 0.0, gSum = 0.0, bSum = 0.0, weight = 0.0
for y in 0..<side {
for x in 0..<side {
guard let pixel = rep.colorAt(x: x, y: y) else { continue }
let s = Double(pixel.saturationComponent)
let v = Double(pixel.brightnessComponent)
// Favor colorful, mid-bright pixels; heavily discount flat
// dark/bright areas (letterboxing, white sleeves, shadows).
let w = s * ((v > 0.12 && v < 0.97) ? 1.0 : 0.08)
rSum += Double(pixel.redComponent) * w
gSum += Double(pixel.greenComponent) * w
bSum += Double(pixel.blueComponent) * w
weight += w
}
}

Comment on lines +24 to +25
WallpaperView(blurRadius: backdropBlur)
Color.black.opacity(backdropTint).ignoresSafeArea()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Animating .blur(radius:) on a high-resolution desktop wallpaper image is extremely expensive on macOS and typically causes severe frame drops during the transition.

Using SwiftUI's native material overlays (like .ultraThinMaterial) is hardware-accelerated, performs beautifully at 60 FPS, and achieves the same frosted aesthetic without the performance penalty.

                WallpaperView()
                if isExpanded {
                    Rectangle()
                        .fill(.ultraThinMaterial)
                        .overlay(Color.black.opacity(0.2))
                        .ignoresSafeArea()
                        .transition(.opacity)
                }

Comment on lines +55 to +59
NSGraphicsContext.saveGraphicsState()
NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: rep)
draw(in: NSRect(x: 0, y: 0, width: side, height: side),
from: .zero, operation: .copy, fraction: 1)
NSGraphicsContext.restoreGraphicsState()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In AppKit, NSGraphicsContext(bitmapImageRep:) returns an optional context. Setting NSGraphicsContext.current directly without checking if the context is non-nil can lead to silent drawing failures or unexpected behavior if context creation fails.

Suggested change
NSGraphicsContext.saveGraphicsState()
NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: rep)
draw(in: NSRect(x: 0, y: 0, width: side, height: side),
from: .zero, operation: .copy, fraction: 1)
NSGraphicsContext.restoreGraphicsState()
guard let context = NSGraphicsContext(bitmapImageRep: rep) else { return nil }
NSGraphicsContext.saveGraphicsState()
NSGraphicsContext.current = context
draw(in: NSRect(x: 0, y: 0, width: side, height: side),
from: .zero, operation: .copy, fraction: 1)
NSGraphicsContext.restoreGraphicsState()

@tirthfx
tirthfx force-pushed the polish-accent-glass-blur branch from af4b942 to 53a512c Compare July 11, 2026 09:31
tirthfx and others added 17 commits July 11, 2026 15:16
Match the macOS lock-screen reference: much larger heavy frosted clock
(132pt), compact minimal now-playing card (300pt, smaller art/text/
transport, hairline edge instead of the accent rim), and a cosmetic
"Touch ID or Enter Password" hint at the bottom.

Add "Set Lock Screen Wallpaper…" to the menu bar: pick any image as the
lock-screen backdrop (persisted in UserDefaults), with "Use Desktop
Wallpaper" to revert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
LiquidGlassPanel gains a .clear style: untinted .glassEffect(.clear) on
macOS 26 (material-only approximation pre-26). The lock-screen
now-playing card now uses it — fully transparent glass, no dark slab —
with soft text shadows keeping white content legible.

Clock numerals drop from heavy to bold rounded (reference glyphs are
thick, not bubbly) and switch to .thinMaterial with a lighter white
overlay so they read as clearer glass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Clicking the widget now zooms: the expanded art+pill cluster springs
  up from 0.3 scale while the compact card swells away (asymmetric
  transitions replace the inert matched-geometry morph on L1<->L2; the
  L2<->L3 hero morph stays).
- Top-left glass droplet button reveals a slider adjusting the idle
  wallpaper blur (0-60, persisted in UserDefaults).
- Clock switches from rounded to SF Pro Display Bold.
- Expanded control pill width matches the 320pt album cover.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… shadow

- Album art 320 -> 360 with tighter 12pt corners; art-to-pill gap 18.
- Control pill shrunk to a reference-style mini-player: 260pt wide
  (clearly narrower than the art), 14/11pt text, small transport row,
  hairline edge replacing the accent rim; accent bloom on the art
  softened to a whisper (reference shadow is neutral).
- Accent param dropped from the pill (lyrics highlight still uses it).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Control pill switches to the fully transparent LiquidGlassPanel
  (.clear) treatment, matching the compact widget.
- Album cover 360 -> 480 (the reference art fills ~half the screen
  height); pill widened to 300 to stay proportional.
- Clock drops the AM/PM suffix (reference is bare hour:minute) and the
  compact variant grows to 78pt with a 15pt date.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Clock: hardcoded "h:mm" (locale AM/PM omission zero-padded the hour
  to "04:00"; reference is "9:24") and proportional digits — tabular
  figures read too wide and boxy.
- Album cover corners 12 -> 6, matching the reference's near-square
  edges (widget placeholder too).
- Grow-on-click now actually animates: the expanded cluster springs
  from 35% scale via onAppear state inside LockScreenExpandedView. The
  previous .transition approach never fired — the whole switch-case
  subtree swaps, so nested transitions are ignored.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…onstants

Rebuild the lock-screen L1/L2 states per the measured spec:

- New LockScreenLayout enum holds every size/radius/gap/spring constant
  in one place, tagged measured vs estimated (compact numbers came from
  an angled photo and are meant to be nudged on-device).
- L1 compact state is now clock -> small centered art (215pt, r16) ->
  condensed mini pill (160x85, r10), replacing the old horizontal
  widget card. Pill content condenses (smaller type, thinner scrubber,
  small transport) via isCompact; ScrubberView gains a compact variant.
- L1<->L2 is ONE persistent view tree: the same art/pill animate their
  frames, radii, and gaps in a single coordinated spring (response
  0.45, damping 0.75) — no more crossfade, no out-of-sync morph.
  matchedGeometryEffect remains only for the L2<->L3 lyrics hero.
- Clock font size fixed at 96pt in both states; expanding only shifts
  it down 12pt (spec: translateY only, never resizes).
- Tap art or track info toggles expand/collapse (rapid re-taps retarget
  the spring safely); Esc/click-away still step down.
- LockScreenExpandedView deleted — superseded by the cluster.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per notchbeat-lockscreen-fix-prompt.md (pixel-analyzed ratios, scale to
any screen; all in LockScreenLayout):

- Idle no longer rests in the expanded-card layout (the ref-3 bug). It
  now shows the Large Clock (digits ~23.5% of screen height) with a
  small glass chip in the bottom-left corner: album thumbnail + track
  title/artist only — no scrubber or transport at chip size.
- Tapping the chip grows it into the centered card: clock digits scale
  down to 9.8% H (date keeps its size), album cover is a true centered
  square (43.3% H, top edge 33% down), pill 21.3% W x 16.9% H starting
  5.4% H below the art.
- The morph is two persistent views (glass container + album art)
  animating frames/positions/radii in the one spec spring — the cover
  visibly flies out of the chip's thumbnail slot to screen center, so
  the expand reads as growing, never a crossfade or snap. Container
  content (chip line vs full controls) crossfades inside.
- Clock renders at idle size and scales down for the card state, so
  the big resting clock stays crisp.
- LockScreenControlPill reduced to the fixed lyrics-state (L3) pill.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… card

Per notchbeat-lockscreen-fix-round2-prompt.md; animation untouched.

- Idle clock 1.17x (digit block ~21.4% of H); expanded clock 1.48x
  (~10.5% of H) — both high-confidence pixel measurements.
- Idle now-playing: the bottom-left chip becomes a small CENTERED
  mini-player (280x112 at 60% down): thumbnail + title/artist, thin
  scrubber, small transport row — matching the target-3 reference photo
  (its visual layout won over the doc's off-angle "bottom-left" pill
  numbers; confirmed with the user).
- Expanded card: album pulled up (top edge 33% -> 22% of H) and grown
  to 47% of H (~31% of W); pill width now matches the art exactly and
  slides down under the repositioned cover, same gap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per notchbeat-lockscreen-fix-round3-prompt.md; animation untouched.

- Idle clock font ratio 0.275 -> 0.34: digit INK is ~63% of font size,
  so the previous ratio rendered 17.3% ink against the 21.4% target.
  Expanded clock keeps its correct absolute size (ratio math preserved).
- Idle now-playing reshaped: the centered mini-player becomes a compact
  left-aligned chip (240x64 at 53% down, 28pt left margin) holding only
  the album thumbnail + title/artist — scrubber and transport removed
  from idle entirely; they belong to the expanded pill.
- Expanded pill height ratio 0.169 -> 0.21 (+30%, read cramped);
  clock/album untouched (measured correct).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per notchbeat-lockscreen-fix-round4-prompt.md (absolute targets, not
deltas). Album/pill morph animation untouched.

- Clock ratios corrected against SF Pro's real cap height (~71% of font
  size, not 63%): idle font 0.30 -> 21.4% ink (round-3's 0.34 overshot
  to mid-20s), card font 0.147 -> 10.5% ink.
- Idle chip locked to the absolute spec: 18% W x 14% H (ratio-based
  now), 56pt thumb, 14/12pt text; still left-aligned at 53% down,
  thumbnail + title/artist only.
- Expanded pill: system full-intensity Liquid Glass (.regular Glass,
  new LiquidGlassStyle case) instead of clear — real blur/vibrancy —
  plus a specular top-edge rim (white gradient hairline) on the
  container in both states. Expanded album/pill geometry confirmed
  in-spec, unchanged.
- Clock size change now SNAPS between states (transaction strips the
  spring); the cover/pill morph keeps animating as before.

Duplicate-widget report investigated: the idle tree renders exactly one
now-playing element — the "stray mid-screen thumbnail" matches the
morphing art view captured mid-flight during collapse, not a leftover
instance.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reverts 7e3270a, ce3a247, b02d699 — back to the round-3 state
(bigger idle clock, minimal left-aligned idle chip, taller pill).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant