feat(ads): client banner rotation, fade UX & live toggle (PR2)
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 50s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 58s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 50s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 58s
Consume the server-driven banner block (PR1) in the UI and retire the gate. - banner.ts: createScheduler — a smooth weighted round-robin over campaigns (each appears its weight share per cycle, evenly interleaved) with round-robin over a campaign's messages; the rotator drives fade-in -> hold/scroll -> fade-out -> gap -> fade-in, a lone message stays put, reduce-motion swaps instantly without scroll. - model.ts/codec.ts: Profile.banner (Banner/BannerCampaign/BannerTimings) decoded from the fbs block. - Screen.svelte: drop the compile-time SHOW_AD_BANNER; render AdBanner from app.profile.banner (campaigns + timings + reduceMotion). - AdBanner.svelte: opacity-driven fades + scroll host; the rotator is recreated when the campaigns/timings change (a `banner` notify re-fetch swaps them in place). - app.svelte.ts: on the `notify` `banner` sub-kind, refreshProfile() so the banner shows/hides in place. - tests: scheduler distribution + round-robin, the fade sequence, single-message, reduce-motion, stop(); codec banner decode. UI_DESIGN.md + trackers updated.
This commit is contained in:
@@ -276,6 +276,11 @@ function openStream(): void {
|
||||
if (e.sub === 'admin_reply') {
|
||||
app.feedbackReplyUnread = true;
|
||||
}
|
||||
// The viewer's ad-banner eligibility may have changed (paid/hints/no_banner): re-fetch
|
||||
// the profile, which carries the banner block, so the banner shows or hides in place.
|
||||
if (e.sub === 'banner') {
|
||||
void refreshProfile();
|
||||
}
|
||||
void refreshNotifications();
|
||||
}
|
||||
},
|
||||
@@ -300,6 +305,20 @@ function scheduleReconnect(): void {
|
||||
}, 4000);
|
||||
}
|
||||
|
||||
/**
|
||||
* refreshProfile re-fetches the account profile (which carries the ad-banner block),
|
||||
* after a `banner` notify signals the viewer's banner eligibility changed. Best-effort:
|
||||
* a transient failure leaves the previous profile in place.
|
||||
*/
|
||||
export async function refreshProfile(): Promise<void> {
|
||||
if (!app.session) return;
|
||||
try {
|
||||
app.profile = await gateway.profileGet();
|
||||
} catch {
|
||||
// Best-effort; the banner just stays as it was until the next fetch.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* refreshNotifications recomputes the badge count (incoming friend requests).
|
||||
* Authoritative poll, complementing the live 'notify' push. Game invitations have
|
||||
|
||||
Reference in New Issue
Block a user