@@ -1152,3 +1152,65 @@ other file under `src/Sendspin.Player`. The progress bar no longer steps by the
11521152(` Core/MediaSession/AnchoredPosition.cs ` ) and projects from there on each tick, so it is right on a
11531153head whose timer is a quantum late as much as on one whose timer is exact. Phase 5 paces the backdrop
11541154with the same helper at 60 Hz.
1155+
1156+ ### As shipped (reskin phase 3) — Now Playing, Welcome, the backdrop, the prompt
1157+
1158+ ** Now Playing** (` Views/NowPlayingView.axaml ` ) is one tree with two compositions. Below 640 px of view
1159+ width it is the reference's stacked column — the art tile, then centred title / artist / album, then
1160+ the progress row, then the transport; at 640 px and above the art sits on the left with the text
1161+ column, progress row and transport left-aligned beside it, vertically centred. The switch is the
1162+ ` wide ` class on the composition grid, set from ` OnSizeChanged ` in the code-behind, and every
1163+ difference between the two is a style in the view keyed on that class (grid row and column spans,
1164+ alignments, text alignment). It is a class toggle rather than an Avalonia 12 container query because
1165+ the test that pins which composition is active (` Sendspin.Ui.Tests/NowPlayingViewTests ` ) and Phase
1166+ 5's breathing-art work both read the switch off the one control, and because the art's size is a
1167+ computed value a query could not express anyway: the body width minus the 24 px margins when
1168+ stacked, the body height when split, less what the text and transport need, never more than 320 px
1169+ and never less than 96 (` NowPlayingView.ArtSizeFor ` , pinned by a theory). The tile is three borders:
1170+ ` ArtBreath ` , the wrapper Phase 5 animates, carrying nothing today; ` ArtTile ` , which holds the resting
1171+ ` BoxShadow ` (the cheap glow in the effect table above) and must not clip, since a shadow lies outside
1172+ the bounds; and ` ArtClip ` , which clips the picture to the 12 px radius and paints ` ArtPlaceholderBrush `
1173+ behind the ` MusicIcon ` glyph. The shadow is set from the code-behind because a ` BoxShadow ` holds a
1174+ ` Color ` , not a brush, so it reads ` .Color ` off the ` ArtShadowBrush ` token and re-reads it on
1175+ ` ActualThemeVariantChanged ` . The progress row is elapsed / a 4 px bar / duration; for a live stream
1176+ (` MediaSessionState.IsLive ` , the one rule) the bar is hidden and the duration slot reads ` LIVE ` .
1177+ ` MainViewModel ` exposes ` ElapsedText ` , ` DurationText ` , ` HasKnownDuration ` and ` RepeatTooltip ` ;
1178+ ` PositionText ` is gone, nothing else bound it. The idle title is null rather than a failed binding, so
1179+ "Nothing playing" is a ` TargetNullValue ` — the Phase 2 ` FallbackValue ` alone never showed it.
1180+
1181+ ** The blurred backdrop.** ` MainViewModel.LoadArtwork ` decodes the artwork file once per artwork
1182+ change (it already deduplicated by path) and, alongside ` Artwork ` , produces ` ArtBackdrop ` with
1183+ ` Bitmap.CreateScaledBitmap ` to 64×64 — a decoded ` Bitmap ` , which is what that method accepts. Layer 0
1184+ of the window binds it to an ` Image ` with ` Stretch="UniformToFill" ` , a ` BlurEffect ` of radius 32 and
1185+ a −48 px margin so the blur's soft edge falls outside the window instead of fading to the opaque root
1186+ along the border. The blur runs over the 64 px source and is cached — the ` blur-once ` row above —
1187+ so dragging the window does not repaint it. ` HasArtBackdrop ` is connected-and-bitmap-present, set in
1188+ the two places that change either, and the veil follows ` HasBackdrop ` as Phase 2 left it. Both
1189+ bitmaps are unbound before they are disposed, for the render-thread reason in the view model.
1190+
1191+ ** Welcome** (` Views/WelcomeView.axaml ` ) is cards on ` TranslucentSurfaceBrush ` with the settings card's
1192+ 1 px ` SystemControlForegroundBaseLowBrush ` hairline — with no backdrop behind Welcome the surface is
1193+ the root's own colour, and it is the border that makes a card read as one. "This player" holds the
1194+ name as a write-through ` TextBox ` and, while advertising, an accent dot with "Broadcasting, visible
1195+ to servers". The dot pulses at 2 Hz from a ` UiClock ` in the view's code-behind — an Avalonia
1196+ ` Animation ` would spin a core on the Wayland head, per the clock table — and the clock runs only while
1197+ the view is attached, the player is advertising and nothing is connected, which is when the card is on
1198+ screen. Which mode the player is in is read once, at view-model construction, exactly as the player
1199+ service reads it: a mode changed in Settings takes effect after a restart, and the card describes what
1200+ the service is doing now. Discover mode shows "Servers on this network" (a ` ListBox ` of name and
1201+ ` host:port ` , "Searching for servers…" while empty, Connect, or "Connecting…" while ` IsConnecting ` );
1202+ advertise mode shows "Waiting for a server" instead. "Connect by address" is an ` Expander ` beneath
1203+ either, collapsed by default.
1204+
1205+ ** The auto-connect prompt** is an in-window card over the body (` AutoConnectPrompt ` in
1206+ ` MainWindow.axaml ` ), shown once per server: when a connection succeeds in discover mode while
1207+ ` AutoConnect ` is ` Never ` and ` PlayerSettings.AutoConnectPromptedServerId ` is not the server just
1208+ connected to. The id is read from ` LastServerId ` , which the player service writes before it raises
1209+ the connection event. Every answer records the id, so "Not now" is remembered too; "Just once" and
1210+ "Always" write the policy through ` SettingsViewModel.AutoConnect ` — the same
1211+ ` SettingsService.Update ` — so the Settings combo shows the answer rather than the value it loaded at
1212+ startup. The service's start-up auto-connect is untouched: a "just once" connection reverts the policy
1213+ to ` Never ` before it connects, and the server it connects to has already been asked about.
1214+ ` Sendspin.Ui.Tests/AutoConnectPromptTests ` walks each answer and the once-per-server rule.
1215+ Screenshots: ` docs/screenshots/reskin/phase3-{narrow,wide}-{light,dark}.png ` and
1216+ ` phase3-welcome-{advertise,discover}.png ` .
0 commit comments