Files
iptvnator/libs/ui/styles/_skeleton-shimmer.scss
T
4grayandClaude Opus 5.5 7d7d691ce4 fix(ui): skeletons that match the M3U header and dashboard rails (#1887)
* fix(ui): skeletons that match the M3U header and dashboard rails

Loading skeletons now take their boxes from the code that draws the
content, so the content lands where the skeleton was.

- M3U channel list: the loading state's headers, divider, groups rail
  and first row share `styles/_channel-list-layout.scss` with the All
  channels, context and groups views. The All channels list moved down
  by 19px when a playlist loaded; the group header was 79px against the
  real 69px (the panel header's 52px minimum plus the padding that
  `.sidebar .sidebar-header` gives it, now one mixin both include).
  Group rows start at the list's top-left like the virtual-scroll rows.
- Dashboard rails: `lib-dashboard-rail-skeleton` replaces six copies of
  the skeleton markup and takes the rail's layout and aspect ratio
  (channel cards for live favorites, 16:9 for sources, posters with a
  title and a meta line otherwise). Its header, track, cards and text
  line boxes come from `_dashboard-rail-geometry.scss`, which the rail
  itself now uses. The skeleton was 328px against the 337.8px poster
  rail and a 2:3 poster in front of the 83.5px channel cards.
- One shimmer in `libs/ui/styles/_skeleton-shimmer.scss`, mixed from
  `--app-on-surface`: the rail sweep between the widget surfaces was
  1.04:1 on the light theme. The dashboard hero and the detail
  skeletons (through a forward) use it too.
- Electron E2E `skeleton-geometry.e2e.ts` holds the IPC channel behind
  each loading state, compares skeleton and content boxes to 0.5px at
  1280x700 and caps the dashboard layout-shift score at 0.01.

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

* fix(dashboard): keep rail heights when a skeleton stands for a label-less or live rail

Review follow-up (Greptile, local):
- Rails that replace a skeleton (Continue Watching, sources, Xtream
  recently added, TMDB) keep an empty meta line under cards without
  labels (`reserveMetaLine`), as the skeleton always draws one; a
  Continue Watching rail of movies with no known duration came in
  shorter.
- The recent-content skeleton follows the recent rail it waits for:
  posters while Continue Watching is still loading with no cards,
  channel cards otherwise.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 08:16:38 +02:00

60 lines
1.7 KiB
SCSS

// The one shimmer recipe for loading placeholders: the dashboard hero and
// rails, and the detail-page hero, actions and episode rows.
//
// A block is a flat fill a step above whatever surface it sits on, with a
// moving highlight a step higher. Both are mixed from the theme's on-surface
// ink rather than from surface tokens: the light theme's widget and header
// surfaces are nearly the same white (1.04:1), so a sweep between them was
// invisible on the light page. On the page background the ink mix gives the
// base about 1.2:1 in both themes and the highlight 1.18:1 (light) and
// 1.26:1 (dark) over the base. Under `prefers-reduced-motion` the highlight
// stays put.
//
// Include `keyframes` once in a component stylesheet that uses `block`:
// Angular scopes keyframes to the component that declares them.
$duration: 1.4s;
$base-ink: 8%;
$highlight-ink: 16%;
@mixin keyframes {
@keyframes skeleton-shimmer {
from {
background-position: 100% 0;
}
to {
background-position: -100% 0;
}
}
}
@mixin block($radius: 4px) {
--skeleton-base: color-mix(
in srgb,
var(--app-on-surface) #{$base-ink},
transparent
);
--skeleton-highlight: color-mix(
in srgb,
var(--app-on-surface) #{$highlight-ink},
transparent
);
display: block;
border-radius: $radius;
background: linear-gradient(
90deg,
var(--skeleton-base) 25%,
var(--skeleton-highlight) 50%,
var(--skeleton-base) 75%
);
background-size: 200% 100%;
animation: skeleton-shimmer $duration ease-in-out infinite;
@media (prefers-reduced-motion: reduce) {
animation: none;
background: var(--skeleton-base);
}
}