Skip to content

[PC TV] Apple-TV-style depth on focused cards#4580

Draft
david-gonzalez-a8c wants to merge 17 commits into
trunkfrom
pc-tv-focused-card-depth
Draft

[PC TV] Apple-TV-style depth on focused cards#4580
david-gonzalez-a8c wants to merge 17 commits into
trunkfrom
pc-tv-focused-card-depth

Conversation

@david-gonzalez-a8c

Copy link
Copy Markdown
Contributor

Summary

Adds depth to focused cards across the tvOS app: each focused surface gets a soft drop shadow and a focus highlight tuned to its content (full diagonal sheen for artwork cards, edge-stroke highlight for text-heavy ones). A subtle top-to-bottom page gradient replaces the flat background so the shadows have something to read against, and pcBackgroundActive is stepped back from pure white/black so the focused surface reads as a silvery/charcoal material instead of raw brightness.

What changed

  • New focusedCardDepth(...) modifier (FocusedCardDepth.swift) with two styles — .surface (full diagonal specular sheen for artwork) and .content (1.5pt inner-stroke highlight for cover-plus-text cards). Both share the same drop shadow. Accessibility-aware: respects accessibilityReduceTransparency (drops the sheen, keeps the shadow) and accessibilityReduceMotion (drops the easing).
  • Applied to discover category / featured / video / podcast-row cells, the Your Podcasts grid (covers + folders), PlaylistCell, EpisodeRow, NowPlayingRow.
  • RootView background: flat pcBackgroundSurface → top→bottom LinearGradient via new pcBackgroundTop / pcBackgroundBottom tokens.
  • pcBackgroundActive retuned: dark #FBFBFC#D4D6DB, light #161718#2A2D31. Global focus-highlight token — touches every focused surface in the TV app.
  • Rounded corners added to DiscoverCategoryCell and PlaylistCell (16pt) where they were previously .clipped() rectangles, so the depth overlay matches a real card shape.

To test

  1. Home → focus through Now Playing, Up Next, Made for TV, You might like, playlists, category cards. Each should pick up shadow + highlight on focus.
  2. Podcasts tab → covers and folders both lift on focus.
  3. Discover → featured carousel and category grid both lift.
  4. Toggle light/dark appearance; toggle Reduce Motion / Reduce Transparency in Settings.

Checklist

  • I have considered if this change warrants user-facing release notes and have added them to CHANGELOG.md if necessary. (Visual refinement only; no behavioural change.)
  • I have considered adding unit tests for my changes. (UI-only modifier; verified by #Preview in FocusedCardDepth.swift.)
  • I have updated (or requested that someone edit) the Event Horizon schema to reflect any new or changed analytics. (No analytics changes.)

david-gonzalez-a8c and others added 10 commits June 19, 2026 18:02
Three pieces that work together to give focused cards more depth, in the
spirit of the Apple TV home screen carousels:

- New `focusedCardDepth(isFocused:cornerRadius:)` view modifier that
  layers a soft drop shadow and a diagonal "lit from above" sheen on a
  focused card surface.
- RootView background swapped from a flat fill to a subtle top→bottom
  gradient (new `pcBackgroundTop` / `pcBackgroundBottom` tokens). The
  gradient is a touch lighter than `pcBackgroundSurface` at the top so
  the new shadows have something to read against.
- Applied the modifier to `DiscoverCategoryCell`, `PlaylistCell`, and
  `DiscoverFeaturedPodcastCell`, with explicit `RoundedRectangle` clips
  on the first two (the existing `.clipped()` was a bare rectangle).

Co-Authored-By: Claude <noreply@anthropic.com>
- pcBackgroundActive steps back from the white/black extremes
  (#FBFBFC → #D4D6DB in dark, #161718 → #2A2D31 in light) so focused
  cards read as silvery/charcoal and let the depth treatment do the
  lifting instead of raw brightness.
- Sheen gradient stops bumped (top white .22 → .45, bottom black .10 →
  .28) and blend mode switched from .plusLighter to .overlay; the old
  mode was silently swallowing the dark side of the gradient.
- Shadow opacity .45 → .6, radius 32 → 44, y-offset 20 → 26.

Co-Authored-By: Claude <noreply@anthropic.com>
…ills

Extend the depth treatment to the three pill types that were missing it
from the Home screen:

- `DiscoverVideoEpisodeCell` (Made for TV row)
- `NowPlayingRowLabel` (currently-playing pill)
- `EpisodeRow` (Up Next pills, also picked up wherever EpisodeRow is
  used elsewhere)

Also push the inner sheen harder: highlight stop bumped to .85 and the
gradient now ramps through .45 → .15 → clear → .45 black so the lit
edge has a more pronounced glint rather than a uniform glow.

Co-Authored-By: Claude <noreply@anthropic.com>
The full diagonal sheen looks great on artwork cards but muddies the
cover and text on content-heavy cards. Split the modifier into two
styles sharing the same drop shadow:

- `.surface` (default): the existing full diagonal sheen, for cards
  that are mostly artwork or a flat colour.
- `.content`: a thin inner stroke that lights the top edge and lays a
  subtle rim line at the bottom, leaving the body of the card alone.

`DiscoverVideoEpisodeCell`, `NowPlayingRow`, and `EpisodeRow` switch to
`.content`. The other three (DiscoverCategoryCell, PlaylistCell,
DiscoverFeaturedPodcastCell) keep `.surface` via the default.

Co-Authored-By: Claude <noreply@anthropic.com>
…on bare covers

Three tweaks following on-device review:

- PlaylistCell switched to .content — the diagonal sheen was washing the
  "New playlist" / "Smart playlist" / episode-count text on the left
  side of the pill.
- .surface highlight rebuilt as two stacked passes: a `.plusLighter`
  top-leading glint that's purely additive (always brightens, never
  darkens, so it pops on busy artwork) plus an `.overlay`-blended
  bottom-trailing falloff. The single `.overlay` pass was capping the
  highlight intensity against artwork; splitting makes it specular.
- DiscoverPodcastRow's bare cover thumbnails now get the .surface
  treatment via a small `FocusedPodcastCover` wrapper that reads the
  `.card` button's `\.isFocused`.

Co-Authored-By: Claude <noreply@anthropic.com>
The Podcasts tab grid was using `NavigationLink + PodcastImage +
.buttonStyle(.card)` with no depth treatment, so its covers felt flat
next to Discover rows like "You might like" (which I'd already wired
up with the depth modifier).

Added a parameterless `focusedCardDepth(cornerRadius:style:)` overload
that reads `\.isFocused` from the environment, so any view sitting
inside a Button/NavigationLink label can opt in without a wrapper
struct. Used it for the Your Podcasts grid, and refactored the
DiscoverPodcastRow wrapper away to share the same call shape.

Co-Authored-By: Claude <noreply@anthropic.com>
The .surface specular was reading as too aggressive in the on-device
review. Halved the plus-lighter highlight stops (.55/.25/.08 → .28/.12/
.04) and roughly halved the overlay falloff (.30/.55 → .16/.30) so it
reads as a subtle gleam rather than a metallic sheen.

In return, push the shared drop shadow harder across both styles —
opacity .6 → .85, radius 44 → 60, y 26 → 36 — so cards feel more
clearly lifted off the page now that the highlight is doing less of
the lifting itself.

Co-Authored-By: Claude <noreply@anthropic.com>
- Respect `accessibilityReduceTransparency`: skip the blend-mode sheen
  (the shadow still lifts the card, just without the translucent
  overlay).
- Respect `accessibilityReduceMotion`: drop the easeInOut timing curve
  on focus state changes.
- `FolderCardView` now also picks up `.surface` depth so folder cells
  match the podcasts they sit next to in the Podcasts grid.
- Added a `#Preview` showing both styles side by side for future
  tuning.

Co-Authored-By: Claude <noreply@anthropic.com>
`EpisodeRow` paints its own background, clip, and focused-card depth
when used standalone (Up Next, podcast detail list, player button).
Inside `EpisodeRowWithActions`, the outer container needs to be the
single card surface so the depth treatment renders across both the row
and the more-button gutter — previously the inner row's shadow got
clipped by the wider outer clipShape, leaving the depth half-applied.

Adds a `providesCardSurface` flag (default true) on `EpisodeRow` with
a `fileprivate` init for same-file callers, and moves the card surface
onto the outer container in `EpisodeRowWithActions`.

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

Copy link
Copy Markdown
Collaborator
1 Warning
⚠️ View files have been modified, but no screenshot or video is included in the pull request. Consider adding some for clarity.
1 Message
📖 This PR is still a Draft: some checks will be skipped.

Generated by 🚫 Danger

david-gonzalez-a8c and others added 7 commits June 19, 2026 18:58
The shared `radius: 60, y: 36` shadow pools nicely below roughly-square
`.surface` cards but spills out the sides of wide `.content` pills,
where the blur radius is comparable to the card's height. The shadow
ends up reading as a horizontal halo rather than a downward pool.

Split the shadow tuning by style: `.surface` keeps its existing
values; `.content` gets a tighter, more directional shadow
(opacity .7, radius 26, y 22) that stays under the card.

Co-Authored-By: Claude <noreply@anthropic.com>
The focused-card drop shadow was being clipped at the bottom of these
ScrollViews, leaving pills with a hard cut edge instead of the soft
pool below that covers (which already had `.scrollClipDisabled()` via
DiscoverPodcastRow) display. Match that pattern here.

Co-Authored-By: Claude <noreply@anthropic.com>
Same root cause as the HomeView fix: every horizontal `ScrollView`
that hosts a focused card needs `.scrollClipDisabled()` or its drop
shadow gets sliced off at the scroll boundary. Audited the TV app for
all `ScrollView(.horizontal)` callsites and added the modifier to the
four remaining rows that were missing it:

- DiscoverVideoEpisodesRow (Made for TV)
- DiscoverFeaturedPodcastsRow (featured carousel)
- DiscoverCategoriesRow (category cards)
- DiscoverSinglePodcastRow (single-podcast content rows)

`DiscoverPodcastRow` already had it from the start.

Co-Authored-By: Claude <noreply@anthropic.com>
DiscoverVideoEpisodeCell scales 1.1x on focus (its own state-machine
zoom), but Up Next / currently-playing pills scale only 1.02x via
EpisodeRowButtonStyle. When the depth modifier sits before the
scaleEffect, its shadow gets scaled along with the card — so the
video cell's shadow renders ~10% larger than every other pill's,
which read as visually oversized.

Move `.focusedCardDepth(...)` after the `.scaleEffect(...)` on the
video cell so the shadow renders at a consistent native size
regardless of how the card itself scales on focus.

Co-Authored-By: Claude <noreply@anthropic.com>
Shadow tuned down across the board: opacity .85 → .7, radius 60 → 36,
y 36 → 22. At the old values the shadow read as oversized on
larger-content cards (the video pill in particular), where the soft
60pt halo spilled well past the card's bottom edge.

`.surface` highlight stops roughly halved again (top glint .28 → .16,
bottom falloff .30 → .20) so the sheen on covers reads as a gentle
gleam rather than a pronounced specular.

Co-Authored-By: Claude <noreply@anthropic.com>
The previous `#2B2E32 → #171819` gradient sat very close to black, so
the drop shadow on focused cards had little tonal headroom to fall
into. Push the dark-mode stops up to `#3D4045 → #22252A` — still a
dark surface, but bright enough for the shadow to read as a clear
pool below the card. Light mode stops unchanged (already had
plenty of contrast).

Co-Authored-By: Claude <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.

2 participants