From 3142ac6185a56af4a26f61c8970b26e458ba326a Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Thu, 17 Apr 2025 00:03:37 +0200 Subject: [PATCH] applied the cool new carousel css feature! --- src/components/list/list.module.css | 54 ++++++++- src/components/list/list.tsx | 6 +- src/features/content/apis/jellyfin.ts | 5 +- src/features/content/service.ts | 4 + src/features/overview/list-item.module.css | 26 +++-- src/features/overview/list-item.tsx | 10 +- src/routes/(shell)/index.module.css | 128 +++++++++++++++++---- src/routes/(shell)/index.tsx | 75 ++++++++---- 8 files changed, 233 insertions(+), 75 deletions(-) diff --git a/src/components/list/list.module.css b/src/components/list/list.module.css index 177f8b3..9e1bea0 100644 --- a/src/components/list/list.module.css +++ b/src/components/list/list.module.css @@ -9,6 +9,9 @@ } .list { + list-style-type: none; + + container-type: inline-size; display: grid; grid-auto-flow: column; @@ -18,14 +21,53 @@ margin: -10em -4em 0em; overflow: visible auto; - scroll-snap-type: inline proximity; + scroll-snap-type: inline mandatory; + overscroll-behavior-inline: contain; - @media (hover: none) { - padding: 5em; - margin: 0; + @media (prefers-reduced-motion: no-preference) { + scroll-behavior: smooth; + } + + /* the before and afters have unsnappable elements that create bouncy edges to the scroll */ + &::before, + &::after { + content: ""; + display: block; + } + + &::before { + order: 0; + inline-size: 15cqi; + } + + &::after { + order: 11; + inline-size: 50cqi; + } + + & > li { + scroll-snap-align: start; + container-type: scroll-state; + padding: 0; + position: relative; + + order: calc(var(--sibling-count) - var(--sibling-index)); + z-index: var(--sibling-index); & > * { - scroll-snap-align: start; + @supports (animation-timeline: view()) { + @media (prefers-reduced-motion: no-preference) { + animation: slide-in linear both; + animation-timeline: view(inline); + animation-range: cover -100cqi contain 15cqi; + } + } } } -} \ No newline at end of file +} + +@keyframes slide-in { + from { + transform: translateX(-100cqi) scale(0.5); + } +} diff --git a/src/components/list/list.tsx b/src/components/list/list.tsx index 2cff1ad..4a52acf 100644 --- a/src/components/list/list.tsx +++ b/src/components/list/list.tsx @@ -10,13 +10,15 @@ interface ListProps { export function List(props: ListProps) { return ( -
+
{props.label}
    - {(item) => props.children(item)} + + {(item) =>
  • {props.children(item)}
  • } +
); diff --git a/src/features/content/apis/jellyfin.ts b/src/features/content/apis/jellyfin.ts index 4e449b6..ce67776 100644 --- a/src/features/content/apis/jellyfin.ts +++ b/src/features/content/apis/jellyfin.ts @@ -13,8 +13,7 @@ const client = createClient({ export const listUsers = query(async () => { const { data, error } = await client.GET("/Users", { - params: { - }, + params: {}, }); return data ?? []; @@ -62,7 +61,7 @@ export const getContinueWatching = query( const items = (data?.Items ?? []).map(({ Id, Name }) => ({ id: Id, title: Name, - thumbnail: `${baseUrl}Items/${Id}/Images/Primary`, + thumbnail: `${baseUrl}/Items/${Id}/Images/Primary`, })); return items; diff --git a/src/features/content/service.ts b/src/features/content/service.ts index bcabb8f..05c9786 100644 --- a/src/features/content/service.ts +++ b/src/features/content/service.ts @@ -1,11 +1,15 @@ import type { Category, Entry } from "./types"; import { query } from "@solidjs/router"; import { entries } from "./data"; +import { getContinueWatching } from "./apis/jellyfin"; export const listCategories = query(async (): Promise => { "use server"; + const jellyfinUserId = "a9c51af84bf54578a99ab4dd0ebf0763"; + return [ + { label: "Continue", entries: await getContinueWatching(jellyfinUserId) }, { label: "Popular", entries: [ diff --git a/src/features/overview/list-item.module.css b/src/features/overview/list-item.module.css index 4105fa8..16a5275 100644 --- a/src/features/overview/list-item.module.css +++ b/src/features/overview/list-item.module.css @@ -25,15 +25,18 @@ /* Dot */ radial-gradient(circle at 25% 30% #7772 #7774 1em transparent 1em), /* Dot */ - radial-gradient(circle at 85% 15% #7772 #7774 1em transparent 1em), - /* Bottom fade */ - linear-gradient(165deg transparent 60% #555 60% #333), + radial-gradient(circle at 85% 15% #7772 #7774 1em transparent 1em), + /* Bottom fade */ linear-gradient(165deg transparent 60% #555 60% #333), /* wave dark part */ - radial-gradient(ellipse 5em 2.25em at 0.5em calc(50% - 1em) #333 100% transparent 100%), + radial-gradient( + ellipse 5em 2.25em at 0.5em calc(50% - 1em) #333 100% transparent 100% + ), /* wave light part */ - radial-gradient(ellipse 5em 2.25em at calc(100% - 0.5em) calc(50% + 1em) #555 100% transparent 100%), - /* Base */ - linear-gradient(to bottom #333 50% #555 50%); + radial-gradient( + ellipse 5em 2.25em at calc(100% - 0.5em) calc(50% + 1em) #555 100% + transparent 100% + ), + /* Base */ linear-gradient(to bottom #333 50% #555 50%); transform-origin: 50% 0; transform: scale(1.1) translateY(calc(-4 * var(--padding))); @@ -41,7 +44,7 @@ user-select: none; } - & > main { + & > figcaption { --offset: calc(1.5 * var(--padding)); grid-area: 1/ 1; display: grid; @@ -98,11 +101,10 @@ will-change: transform; } - & > main { + & > figcaption { clip-path: inset(40%); } } - @media (prefers-reduced-motion: no-preference) { & { transition: transform var(--duration-moderate-1) linear; @@ -112,7 +114,7 @@ transition: transform var(--duration-moderate-1) ease-in-out; } - & > main { + & > figcaption { transition: clip-path var(--duration-moderate-1) ease-in-out; } @@ -126,4 +128,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/features/overview/list-item.tsx b/src/features/overview/list-item.tsx index 5c73a9e..000bcf9 100644 --- a/src/features/overview/list-item.tsx +++ b/src/features/overview/list-item.tsx @@ -7,14 +7,14 @@ export const ListItem: Component<{ entry: Entry }> = (props) => { const slug = createMemo(() => createSlug(props.entry)); return ( -
- +
+ {props.entry.title} -
+
{props.entry.title} Watch now -
-
+ + ); }; diff --git a/src/routes/(shell)/index.module.css b/src/routes/(shell)/index.module.css index cdd3e8c..6a26acf 100644 --- a/src/routes/(shell)/index.module.css +++ b/src/routes/(shell)/index.module.css @@ -1,39 +1,121 @@ -.list { +.carousel { + display: block grid; + grid: auto 1fr / 100%; + + & > header { anchor-name: --carousel; - overflow: auto; - scroll-snap-type: inline mandatory; + padding-inline: 3rem; + font-size: 1.75rem; + font-weight: 900; + } + + & > ul { + list-style-type: none; + + container-type: size; + inline-size: 100%; + block-size: min(60svh, 720px); display: grid; grid-auto-flow: column; - inline-size: 80%; + overflow: visible auto; + scroll-snap-type: inline mandatory; + overscroll-behavior-inline: contain; + justify-self: center; - & > li { - inline-size: 30vw; - list-style: none; - scroll-snap-align: start; + gap: 1em; + padding-inline: 2em; + scroll-padding-inline: 2em; + padding-block: 2em 4em; + margin-block-end: 5em; + + @media (prefers-reduced-motion: no-preference) { + scroll-behavior: smooth; } - &::scroll-button(inline-start), - &::scroll-button(inline-end) { - position: fixed; - position-anchor: --carousel; + /* the before and afters have unsnappable elements that create bouncy edges to the scroll */ + &::before, + &::after { + content: ""; + display: block; + } + + &::before { + order: 0; + inline-size: 15cqi; + } + + &::after { + order: 11; + inline-size: 50cqi; + } + + &::scroll-button(*) { + z-index: 20; + background: oklch(from var(--surface-1) l c h / 50%); + backdrop-filter: blur(10px); } &::scroll-button(inline-start) { - --_inner: center span-inline-end; - --_outer: inline-start center; - - position-area: var(--_outer); - content: 'arrow_back' / 'Previous'; + position-area: center span-inline-start; + content: "◄" / "Previous"; } &::scroll-button(inline-end) { - --_inner: center span-inline-start; - --_outer: inline-end center; - - position-area: var(--_outer); - content: 'arrow_forward' / 'Next'; + position-area: center span-inline-end; + content: "►" / "Next"; } -} \ No newline at end of file + + & > li { + scroll-snap-align: start; + container-type: scroll-state; + padding: 0; + position: relative; + + order: calc(var(--sibling-count) - var(--sibling-index)); + z-index: var(--sibling-index); + + & > figure { + @supports (animation-timeline: view()) { + @media (prefers-reduced-motion: no-preference) { + animation: slide-in linear both; + animation-timeline: view(inline); + animation-range: cover -100cqi contain 25cqi; + } + } + @container scroll-state(snapped: inline) { + outline: 1px solid var(--gray-1); + outline-offset: 10px; + } + + flex-shrink: 0; + block-size: 100cqb; + aspect-ratio: 9/16; + background: light-dark(#ccc, #444); + box-shadow: var(--shadow-5); + border-radius: 20px; + overflow: clip; + + display: flex; + + @container (width < 480px) { + block-size: 50cqb; + } + + & > img { + inline-size: 100%; + block-size: 100%; + object-fit: cover; + } + } + } + } +} + +@keyframes slide-in { + from { + transform: translateX(-100cqi) scale(0.75); + } +} diff --git a/src/routes/(shell)/index.tsx b/src/routes/(shell)/index.tsx index a130454..9f49c50 100644 --- a/src/routes/(shell)/index.tsx +++ b/src/routes/(shell)/index.tsx @@ -7,47 +7,74 @@ import { getContinueWatching, } from "~/features/content"; import { Show } from "solid-js"; -import { List } from "~/components/list"; -import { ListItem } from "~/features/overview/list-item"; -import css from './index.module.css'; +import css from "./index.module.css"; export const route = { preload: async () => ({ highlight: await getEntry("14"), categories: await listCategories(), - continue: await getContinueWatching("a9c51af84bf54578a99ab4dd0ebf0763"), }), }; export default function Home() { const highlight = createAsync(() => getEntry("14")); const categories = createAsync(() => listCategories()); - const continueWatching = createAsync(() => - getContinueWatching("a9c51af84bf54578a99ab4dd0ebf0763"), - ); return ( <> Home -
    -
  • Item 0
  • -
  • Item 1
  • -
  • Item 2
  • -
  • Item 3
  • -
  • Item 4
  • -
  • Item 5
  • -
  • Item 6
  • -
  • Item 7
  • -
  • Item 8
  • -
  • Item 9
  • -
+ {/*
+
some category
- {/* { - entries => - {(item) => } - - } */} +
    +
  • +
    + Item 1 +
    +
  • +
  • +
    + Item 2 +
    +
  • +
  • +
    + Item 3 +
    +
  • +
  • +
    + Item 4 +
    +
  • +
  • +
    + Item 5 +
    +
  • +
  • +
    + Item 6 +
    +
  • +
  • +
    + Item 7 +
    +
  • +
  • +
    + Item 8 +
    +
  • +
  • +
    + Item 9 +
    +
  • +
+
*/}