diff --git a/app.config.ts b/app.config.ts index d74b729..2b5dca2 100644 --- a/app.config.ts +++ b/app.config.ts @@ -37,7 +37,10 @@ export default defineConfig({ server: { preset: 'bun', prerender: { - routes: ['/sitemaps.xml'], + routes: [ + '/sitemaps.xml', + '/watch/furiosa-a-mad-max-saga-1c829d55201c766641c4aec0346551c6' + ], }, }, }); \ No newline at end of file diff --git a/bun.lock b/bun.lock index 347c56c..e681c89 100644 --- a/bun.lock +++ b/bun.lock @@ -11,7 +11,7 @@ "@solidjs/router": "^0.15.3", "@solidjs/start": "^1.1.4", "better-auth": "^1.2.7", - "better-sqlite3": "^11.10.0", + "bindings": "^1.5.0", "open-props": "^1.7.15", "openapi-fetch": "^0.13.8", "sitemap": "^8.0.0", @@ -21,7 +21,6 @@ }, "devDependencies": { "@testing-library/jest-dom": "^6.6.3", - "@types/better-sqlite3": "^7.6.13", "browserslist": "^4.24.5", "bun-types": "^1.2.13", "lightningcss": "^1.30.1", @@ -391,8 +390,6 @@ "@types/babel__traverse": ["@types/babel__traverse@7.20.6", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg=="], - "@types/better-sqlite3": ["@types/better-sqlite3@7.6.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA=="], - "@types/braces": ["@types/braces@3.0.5", "", {}, "sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w=="], "@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="], @@ -589,7 +586,7 @@ "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], - "chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], + "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], "citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="], @@ -1501,7 +1498,7 @@ "tar-fs": ["tar-fs@2.1.2", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA=="], - "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + "tar-stream": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="], "terracotta": ["terracotta@1.0.6", "", { "dependencies": { "solid-use": "^0.9.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-yVrmT/Lg6a3tEbeYEJH8ksb1PYkR5FA9k5gr1TchaSNIiA2ZWs5a+koEbePXwlBP0poaV7xViZ/v50bQFcMgqw=="], @@ -1789,8 +1786,6 @@ "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "archiver/tar-stream": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="], - "archiver-utils/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], "are-we-there-yet/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], @@ -1937,11 +1932,11 @@ "strip-literal/js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], - "tar/chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], - "tar/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], - "tar-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + "tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], + + "tar-fs/tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], @@ -2017,6 +2012,8 @@ "@netlify/zip-it-and-ship-it/archiver/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + "@netlify/zip-it-and-ship-it/archiver/tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + "@netlify/zip-it-and-ship-it/archiver/zip-stream": ["zip-stream@4.1.1", "", { "dependencies": { "archiver-utils": "^3.0.4", "compress-commons": "^4.1.2", "readable-stream": "^3.6.0" } }, "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ=="], "@netlify/zip-it-and-ship-it/execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], @@ -2125,6 +2122,8 @@ "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "tar-fs/tar-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + "unwasm/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], "unwasm/pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], diff --git a/package.json b/package.json index c1ea803..f63bbbc 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "@solidjs/router": "^0.15.3", "@solidjs/start": "^1.1.4", "better-auth": "^1.2.7", - "better-sqlite3": "^11.10.0", + "bindings": "^1.5.0", "open-props": "^1.7.15", "openapi-fetch": "^0.13.8", "sitemap": "^8.0.0", @@ -31,7 +31,6 @@ }, "devDependencies": { "@testing-library/jest-dom": "^6.6.3", - "@types/better-sqlite3": "^7.6.13", "browserslist": "^4.24.5", "bun-types": "^1.2.13", "lightningcss": "^1.30.1", diff --git a/src/auth.ts b/src/auth.ts index 8b52ecd..f733153 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -2,10 +2,8 @@ import { betterAuth } from "better-auth"; import { genericOAuth } from "better-auth/plugins"; import { createAuthClient } from "better-auth/solid"; import { genericOAuthClient } from "better-auth/client/plugins"; -import Database from "better-sqlite3"; export const auth = betterAuth({ - database: Database('auth.sqlite'), appName: "Streamarr", basePath: "/api/auth", logger: { diff --git a/src/components/hero/hero.module.css b/src/components/hero/hero.module.css index a3aa5b6..46aaf91 100644 --- a/src/components/hero/hero.module.css +++ b/src/components/hero/hero.module.css @@ -1,13 +1,46 @@ +@property --thumb-image { + syntax: ''; + inherits: true; +} + .container { + display: block grid; + grid-auto-flow: column; + grid-auto-columns: 100%; + + overflow: hidden visible; + scroll-snap-type: inline mandatory; + scroll-behavior: smooth; + + scroll-marker-group: after; + + &::scroll-marker-group { + display: block grid; + + grid-auto-flow: column; + grid-auto-columns: 5em; + gap: 1em; + place-content: end center; + + inline-size: 100%; + block-size: 8.333333em; + + z-index: 1; + } +} + +.page { + scroll-snap-align: center; position: relative; display: grid; grid: repeat(3, auto) / 15em 1fr; grid-template-areas: - "thumbnail ." - "thumbnail title" - "thumbnail detail" - "thumbnail summary"; + "thumbnail . ." + "thumbnail title cta" + "thumbnail detail detail" + "thumbnail summary summary"; align-content: end; + align-items: center; gap: 1em; padding: 2em; block-size: 80vh; @@ -20,7 +53,31 @@ position: absolute; inset: 0; display: block; - background: linear-gradient(transparent 50%, #0007 75%); + background: linear-gradient(185deg, transparent 20%, var(--surface-2) 90%), linear-gradient(transparent 50%, #0007 75%); + } + + &::scroll-marker { + display: block; + content: ' '; + + inline-size: 15em; + aspect-ratio: 3 / 5; + + background: var(--thumb-image) center / cover no-repeat; + background-color: cornflowerblue; + border-radius: var(--radius-3); + + transform: scale(.333333); + transition: .3s; + } + + &::scroll-marker:target-current { + /* outline: 1px solid white; */ + position: absolute; + top: -29em; + left: 2em; + + transform: scale(1); } } @@ -31,11 +88,22 @@ filter: contrast(9); } +.cta { + grid-area: cta; + z-index: 1; + border-radius: var(--radius-2); + background-color: var(--gray-2); + color: var(--gray-8); + text-decoration-color: var(--gray-8); + padding: var(--size-3); + font-weight: var(--font-weight-9); +} + .thumbnail { grid-area: thumbnail; inline-size: 15em; aspect-ratio: 3 / 5; - border-radius: 1em; + border-radius: var(--radius-3); object-fit: cover; object-position: center; z-index: 1; diff --git a/src/components/hero/hero.tsx b/src/components/hero/hero.tsx index 6b6fc8b..e4119ef 100644 --- a/src/components/hero/hero.tsx +++ b/src/components/hero/hero.tsx @@ -1,17 +1,38 @@ -import { Index } from "solid-js"; -import { Entry } from "~/features/content"; +import { Component, createEffect, createMemo, For, Index } from "solid-js"; +import { createSlug, Entry } from "~/features/content"; import css from "./hero.module.css"; type HeroProps = { - entry: Entry; + entries: Entry[]; class?: string; }; export function Hero(props: HeroProps) { + const entry = createMemo(() => props.entries.at(0)!); + const slug = createMemo(() => createSlug(entry())); + return (
+ { + entry => + } +
+ ); +} + +const Page: Component<{ entry: Entry }> = (props) => { + const slug = createMemo(() => createSlug(props.entry)); + + createEffect(() => { + console.log(props.entry); + }); + + return ( +

{props.entry.title}

+ + Continue @@ -31,7 +52,7 @@ export function Hero(props: HeroProps) { -

{props.entry.summary}

+

{props.entry.synopsis}

); -} +}; diff --git a/src/features/content/apis/jellyfin.ts b/src/features/content/apis/jellyfin.ts index 3505585..7846770 100644 --- a/src/features/content/apis/jellyfin.ts +++ b/src/features/content/apis/jellyfin.ts @@ -1,12 +1,10 @@ +"use server"; + +import type { paths } from "./jellyfin.generated"; // generated by openapi-typescript import createClient from "openapi-fetch"; -import type { paths, components } from "./jellyfin.generated"; // generated by openapi-typescript import { query } from "@solidjs/router"; import { Entry } from "../types"; -// =============================== -'use server'; -// =============================== - type ItemImageType = "Primary" | "Art" | "Backdrop" | "Banner" | "Logo" | "Thumb" | "Disc" | "Box" | "Screenshot" | "Menu" | "Chapter" | "BoxRear" | "Profile"; const baseUrl = process.env.JELLYFIN_BASE_URL; @@ -58,7 +56,71 @@ export const listUsers = query(async () => { return data ?? []; }, "jellyfin.listUsers"); +export const listItems = query(async (userId: string): Promise => { + const { data, error } = await client.GET("/Items", { + params: { + query: { + userId, + hasTmdbInfo: true, + recursive: true, + includeItemTypes: ["Movie", "Series"], + fields: [ + "ProviderIds", + "Genres", + "DateLastMediaAdded", + "DateCreated", + "MediaSources", + ], + }, + }, + }); + + if (data === undefined) { + return undefined; + } + + return data.Items?.map(item => ({ + id: item.Id!, + title: item.Name!, + thumbnail: new URL(`/Items/${item.Id!}/Images/Primary`, baseUrl), //await getItemImage(data.Id!, 'Primary'), + })) ?? []; +}, "jellyfin.listItems"); + +export const getRandomItem = query(async (userId: string): Promise => getRandomItems(userId, 1).then(items => items?.at(0)), "jellyfin.listRandomItem"); + +export const getRandomItems = query(async (userId: string, limit: number = 10): Promise => { + const { data, error } = await client.GET("/Items", { + params: { + query: { + userId, + hasTmdbInfo: true, + recursive: true, + limit, + sortBy: ["Random"], + includeItemTypes: ["Movie", "Series"], + imageTypes: ["Primary", "Backdrop", "Thumb"], + fields: [ + "ProviderIds", + "Genres", + "DateLastMediaAdded", + "DateCreated", + "MediaSources", + ], + }, + }, + }); + + return data?.Items?.map(item => ({ + id: item.Id!, + title: item.Name!, + thumbnail: new URL(`/Items/${item.Id!}/Images/Primary`, baseUrl), //await getItemImage(data.Id!, 'Primary'), + image: new URL(`/Items/${item.Id!}/Images/Backdrop`, baseUrl), //await getItemImage(data.Id!, 'Primary'), + })) ?? []; +}, "jellyfin.listRandomItems"); + export const getItem = query(async (userId: string, itemId: string): Promise => { + console.log('baseUrl', baseUrl); + const { data, error } = await client.GET("/Items/{itemId}", { params: { path: { @@ -87,7 +149,10 @@ export const getItem = query(async (userId: string, itemId: string): Promise { }, 'jellyfin.queryItems'); -export const getContinueWatching = query( - async (userId: string): Promise => { - const { data, error } = await client.GET("/UserItems/Resume", { - params: { - query: { - userId, - mediaTypes: ["Video"], - // fields: ["ProviderIds", "Genres"], - // includeItemTypes: ["Series", "Movie"] - }, +export const getContinueWatching = query(async (userId: string): Promise => { + const { data, error } = await client.GET("/UserItems/Resume", { + params: { + query: { + userId, + mediaTypes: ["Video"], + // fields: ["ProviderIds", "Genres"], + // includeItemTypes: ["Series", "Movie"] }, - }); + }, + }); - if (Array.isArray(data?.Items) !== true) { - return []; - } + if (Array.isArray(data?.Items) !== true) { + return []; + } - const uniqueIds = new Set(data.Items.map(item => item.Type === 'Episode' ? item.SeriesId! : item.Id)); - const results = await Promise.allSettled(uniqueIds.values().map(id => getItem(userId, id)).toArray()); + const uniqueIds = new Set(data.Items.map(item => item.Type === 'Episode' ? item.SeriesId! : item.Id!)); + const results = await Promise.allSettled(uniqueIds.values().map(id => getItem(userId, id)).toArray()); - assertNoErrors(results); + assertNoErrors(results); - return results.filter((result): result is PromiseFulfilledResult => result.value !== undefined).map(({ value }) => value); - }, - "jellyfin.continueWatching", -); + return results.filter((result): result is PromiseFulfilledResult => result.value !== undefined).map(({ value }) => value); +}, "jellyfin.continueWatching"); function assertNoErrors(results: PromiseSettledResult[]): asserts results is PromiseFulfilledResult[] { if (results.some(({ status }) => status !== 'fulfilled')) { diff --git a/src/features/content/service.ts b/src/features/content/service.ts index ad1b514..0e30484 100644 --- a/src/features/content/service.ts +++ b/src/features/content/service.ts @@ -1,18 +1,19 @@ +"use server"; + import type { Category, Entry } from "./types"; import { query } from "@solidjs/router"; import { entries } from "./data"; -import { getContinueWatching, getItem, TEST } from "./apis/jellyfin"; +import { getContinueWatching, getItem, getRandomItems } from "./apis/jellyfin"; -const jellyfinUserId = "a9c51af8-4bf5-4578-a99a-b4dd0ebf0763"; +const jellyfinUserId = "a9c51af84bf54578a99ab4dd0ebf0763"; + +// export const getHighlights = () => getRandomItems(jellyfinUserId); +export const getHighlights = () => getContinueWatching(jellyfinUserId); export const listCategories = query(async (): Promise => { - "use server"; - - // await TEST() - // console.log(await getItemPlaybackInfo(jellyfinUserId, 'a69c0c0ab66177a7adb671f126335d16')); - return [ - { label: "Continue", entries: await getContinueWatching(jellyfinUserId) }, + // { label: "Continue", entries: await getContinueWatching(jellyfinUserId) }, + { label: "Random", entries: await getRandomItems(jellyfinUserId) }, { label: "Popular", entries: [ @@ -70,11 +71,9 @@ export const listCategories = query(async (): Promise => { export const getEntry = query( async (id: Entry["id"]): Promise => { - "use server"; - return getItem(jellyfinUserId, id); }, "series.get", ); -export { listUsers, getContinueWatching } from "./apis/jellyfin"; +export { listUsers, getContinueWatching, listItems } from "./apis/jellyfin"; diff --git a/src/features/content/types.ts b/src/features/content/types.ts index 8546a5d..a013c47 100644 --- a/src/features/content/types.ts +++ b/src/features/content/types.ts @@ -7,11 +7,13 @@ export interface Category { export interface Entry { id: string; title: string; - summary?: string; + synopsis?: string; releaseDate?: string; sources?: Entry.Source[]; thumbnail?: URL | string; - image?: string; + image?: URL | string; + + [prop: string]: any; } export namespace Entry { diff --git a/src/features/overview/list-item.module.css b/src/features/overview/list-item.module.css index 16a5275..3b2d936 100644 --- a/src/features/overview/list-item.module.css +++ b/src/features/overview/list-item.module.css @@ -22,21 +22,16 @@ box-shadow: var(--shadow-2); background: - /* 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), - /* wave dark part */ + radial-gradient(circle at 25% 30%, #7772, #7774 1em, transparent 1em), + radial-gradient(circle at 85% 15%, #7772, #7774 1em, transparent 1em), + linear-gradient(165deg, transparent 60%, #555 60%, #333), radial-gradient( - ellipse 5em 2.25em at 0.5em calc(50% - 1em) #333 100% transparent 100% + 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% + 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%); + linear-gradient(to bottom, #333 50%, #555 50%); transform-origin: 50% 0; transform: scale(1.1) translateY(calc(-4 * var(--padding))); diff --git a/src/features/overview/list-item.tsx b/src/features/overview/list-item.tsx index 252dd2f..e48a7ed 100644 --- a/src/features/overview/list-item.tsx +++ b/src/features/overview/list-item.tsx @@ -10,6 +10,7 @@ export const ListItem: Component<{ entry: Entry }> = (props) => {
{props.entry.title} +
{props.entry.title} diff --git a/src/features/overview/overview.tsx b/src/features/overview/overview.tsx index 572d7ba..f128ffe 100644 --- a/src/features/overview/overview.tsx +++ b/src/features/overview/overview.tsx @@ -12,16 +12,14 @@ import { Hero } from "~/components/hero"; import css from "./overview.module.css"; type OverviewProps = { - highlight: Entry; + highlights: Entry[]; categories: Category[]; }; export const Overview: Component = (props) => { - const [container, setContainer] = createSignal(); - return ( -
- +
+ {(category) => ( diff --git a/src/features/player/player.tsx b/src/features/player/player.tsx index ffcf797..90bd3f0 100644 --- a/src/features/player/player.tsx +++ b/src/features/player/player.tsx @@ -140,7 +140,7 @@ export const Player: Component = (props) => { console.log("pause", e); }, suspend(e) { - console.log("suspend", e); + // console.log("suspend", e); }, volumechange(e) { @@ -152,7 +152,7 @@ export const Player: Component = (props) => { }, progress(e) { - console.log(e); + // console.log(e); }, // timeupdate(e) { diff --git a/src/routes/(shell)/index.module.css b/src/routes/(shell)/index.module.css deleted file mode 100644 index 6a26acf..0000000 --- a/src/routes/(shell)/index.module.css +++ /dev/null @@ -1,121 +0,0 @@ -.carousel { - display: block grid; - grid: auto 1fr / 100%; - - & > header { - anchor-name: --carousel; - 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; - - overflow: visible auto; - scroll-snap-type: inline mandatory; - overscroll-behavior-inline: contain; - - justify-self: center; - - 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; - } - - /* 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) { - position-area: center span-inline-start; - content: "◄" / "Previous"; - } - - &::scroll-button(inline-end) { - position-area: center span-inline-end; - content: "►" / "Next"; - } - - & > 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 5b4839f..10f62bf 100644 --- a/src/routes/(shell)/index.tsx +++ b/src/routes/(shell)/index.tsx @@ -2,84 +2,28 @@ import { Title } from "@solidjs/meta"; import { createAsync } from "@solidjs/router"; import { Overview } from "~/features/overview"; import { + getHighlights, listCategories, - getEntry, - getContinueWatching, } from "~/features/content"; import { Show } from "solid-js"; -import css from "./index.module.css"; - -const highlightId = 'c97185ed-e0cf-4945-9120-9d15bb8e5998'; export const route = { preload: async () => ({ - highlight: await getEntry(highlightId), + highlight: await getHighlights(), categories: await listCategories(), }), }; export default function Home() { - const highlight = createAsync(() => getEntry(highlightId)); + const highlights = createAsync(() => getHighlights()); const categories = createAsync(() => listCategories()); - + return ( <> Home - {/*
-
some category
- -
    -
  • -
    - Item 1 -
    -
  • -
  • -
    - Item 2 -
    -
  • -
  • -
    - Item 3 -
    -
  • -
  • -
    - Item 4 -
    -
  • -
  • -
    - Item 5 -
    -
  • -
  • -
    - Item 6 -
    -
  • -
  • -
    - Item 7 -
    -
  • -
  • -
    - Item 8 -
    -
  • -
  • -
    - Item 9 -
    -
  • -
-
*/} - - - + + ); diff --git a/src/routes/(shell)/watch/[slug].tsx b/src/routes/(shell)/watch/[slug].tsx index b3aeeb6..3fcdd19 100644 --- a/src/routes/(shell)/watch/[slug].tsx +++ b/src/routes/(shell)/watch/[slug].tsx @@ -1,5 +1,4 @@ import { - createAsync, json, Params, query, @@ -7,10 +6,8 @@ import { RouteDefinition, useParams, } from "@solidjs/router"; -import { createEffect } from "solid-js"; import { createSlug, getEntry } from "~/features/content"; import { Player } from "~/features/player"; -import { toSlug } from "~/utilities"; const healUrl = query(async (slug: string) => { const entry = await getEntry(slug.slice(slug.lastIndexOf("-") + 1)); @@ -34,9 +31,15 @@ interface ItemParams extends Params { export const route = { async preload({ params }) { - await healUrl(params.slug); + const slug = params.slug; - return getEntry(params.slug.slice(params.slug.lastIndexOf("-") + 1)); + if (!slug) { + return; + } + + await healUrl(slug); + + return getEntry(slug.slice(slug.lastIndexOf("-") + 1)); }, } satisfies RouteDefinition; diff --git a/src/utilities.ts b/src/utilities.ts index 340cdd2..3f0e795 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -16,7 +16,7 @@ export const splitAt = ( }; export const toSlug = (subject: string) => - subject.toLowerCase().replaceAll(" ", "-"); + subject.toLowerCase().replaceAll(" ", "-").replaceAll(/[^\w-]/gi, ""); export const toHex = (subject: number) => subject.toString(16).padStart(2, "0"); const encoder = new TextEncoder();