kaas
This commit is contained in:
parent
873677ea04
commit
d683b051b6
17 changed files with 244 additions and 273 deletions
|
@ -37,7 +37,10 @@ export default defineConfig({
|
||||||
server: {
|
server: {
|
||||||
preset: 'bun',
|
preset: 'bun',
|
||||||
prerender: {
|
prerender: {
|
||||||
routes: ['/sitemaps.xml'],
|
routes: [
|
||||||
|
'/sitemaps.xml',
|
||||||
|
'/watch/furiosa-a-mad-max-saga-1c829d55201c766641c4aec0346551c6'
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
21
bun.lock
21
bun.lock
|
@ -11,7 +11,7 @@
|
||||||
"@solidjs/router": "^0.15.3",
|
"@solidjs/router": "^0.15.3",
|
||||||
"@solidjs/start": "^1.1.4",
|
"@solidjs/start": "^1.1.4",
|
||||||
"better-auth": "^1.2.7",
|
"better-auth": "^1.2.7",
|
||||||
"better-sqlite3": "^11.10.0",
|
"bindings": "^1.5.0",
|
||||||
"open-props": "^1.7.15",
|
"open-props": "^1.7.15",
|
||||||
"openapi-fetch": "^0.13.8",
|
"openapi-fetch": "^0.13.8",
|
||||||
"sitemap": "^8.0.0",
|
"sitemap": "^8.0.0",
|
||||||
|
@ -21,7 +21,6 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"@types/better-sqlite3": "^7.6.13",
|
|
||||||
"browserslist": "^4.24.5",
|
"browserslist": "^4.24.5",
|
||||||
"bun-types": "^1.2.13",
|
"bun-types": "^1.2.13",
|
||||||
"lightningcss": "^1.30.1",
|
"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/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/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=="],
|
"@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=="],
|
"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=="],
|
"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-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=="],
|
"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=="],
|
"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=="],
|
"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=="],
|
"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=="],
|
"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/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=="],
|
"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/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/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=="],
|
"@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=="],
|
"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/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
|
||||||
|
|
||||||
"unwasm/pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
"unwasm/pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
"@solidjs/router": "^0.15.3",
|
"@solidjs/router": "^0.15.3",
|
||||||
"@solidjs/start": "^1.1.4",
|
"@solidjs/start": "^1.1.4",
|
||||||
"better-auth": "^1.2.7",
|
"better-auth": "^1.2.7",
|
||||||
"better-sqlite3": "^11.10.0",
|
"bindings": "^1.5.0",
|
||||||
"open-props": "^1.7.15",
|
"open-props": "^1.7.15",
|
||||||
"openapi-fetch": "^0.13.8",
|
"openapi-fetch": "^0.13.8",
|
||||||
"sitemap": "^8.0.0",
|
"sitemap": "^8.0.0",
|
||||||
|
@ -31,7 +31,6 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"@types/better-sqlite3": "^7.6.13",
|
|
||||||
"browserslist": "^4.24.5",
|
"browserslist": "^4.24.5",
|
||||||
"bun-types": "^1.2.13",
|
"bun-types": "^1.2.13",
|
||||||
"lightningcss": "^1.30.1",
|
"lightningcss": "^1.30.1",
|
||||||
|
|
|
@ -2,10 +2,8 @@ import { betterAuth } from "better-auth";
|
||||||
import { genericOAuth } from "better-auth/plugins";
|
import { genericOAuth } from "better-auth/plugins";
|
||||||
import { createAuthClient } from "better-auth/solid";
|
import { createAuthClient } from "better-auth/solid";
|
||||||
import { genericOAuthClient } from "better-auth/client/plugins";
|
import { genericOAuthClient } from "better-auth/client/plugins";
|
||||||
import Database from "better-sqlite3";
|
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
database: Database('auth.sqlite'),
|
|
||||||
appName: "Streamarr",
|
appName: "Streamarr",
|
||||||
basePath: "/api/auth",
|
basePath: "/api/auth",
|
||||||
logger: {
|
logger: {
|
||||||
|
|
|
@ -1,13 +1,46 @@
|
||||||
|
@property --thumb-image {
|
||||||
|
syntax: '<image>';
|
||||||
|
inherits: true;
|
||||||
|
}
|
||||||
|
|
||||||
.container {
|
.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;
|
position: relative;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid: repeat(3, auto) / 15em 1fr;
|
grid: repeat(3, auto) / 15em 1fr;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"thumbnail ."
|
"thumbnail . ."
|
||||||
"thumbnail title"
|
"thumbnail title cta"
|
||||||
"thumbnail detail"
|
"thumbnail detail detail"
|
||||||
"thumbnail summary";
|
"thumbnail summary summary";
|
||||||
align-content: end;
|
align-content: end;
|
||||||
|
align-items: center;
|
||||||
gap: 1em;
|
gap: 1em;
|
||||||
padding: 2em;
|
padding: 2em;
|
||||||
block-size: 80vh;
|
block-size: 80vh;
|
||||||
|
@ -20,7 +53,31 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
display: block;
|
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);
|
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 {
|
.thumbnail {
|
||||||
grid-area: thumbnail;
|
grid-area: thumbnail;
|
||||||
inline-size: 15em;
|
inline-size: 15em;
|
||||||
aspect-ratio: 3 / 5;
|
aspect-ratio: 3 / 5;
|
||||||
border-radius: 1em;
|
border-radius: var(--radius-3);
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
object-position: center;
|
object-position: center;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
|
@ -1,17 +1,38 @@
|
||||||
import { Index } from "solid-js";
|
import { Component, createEffect, createMemo, For, Index } from "solid-js";
|
||||||
import { Entry } from "~/features/content";
|
import { createSlug, Entry } from "~/features/content";
|
||||||
import css from "./hero.module.css";
|
import css from "./hero.module.css";
|
||||||
|
|
||||||
type HeroProps = {
|
type HeroProps = {
|
||||||
entry: Entry;
|
entries: Entry[];
|
||||||
class?: string;
|
class?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Hero(props: HeroProps) {
|
export function Hero(props: HeroProps) {
|
||||||
|
const entry = createMemo(() => props.entries.at(0)!);
|
||||||
|
const slug = createMemo(() => createSlug(entry()));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={`${css.container} ${props.class ?? ''}`}>
|
<div class={`${css.container} ${props.class ?? ''}`}>
|
||||||
|
<For each={props.entries}>{
|
||||||
|
entry => <Page entry={entry} />
|
||||||
|
}</For>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Page: Component<{ entry: Entry }> = (props) => {
|
||||||
|
const slug = createMemo(() => createSlug(props.entry));
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
console.log(props.entry);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class={`${css.page}`} style={{ '--thumb-image': `url(${props.entry.thumbnail})` }}>
|
||||||
|
|
||||||
<h2 class={css.title}>{props.entry.title}</h2>
|
<h2 class={css.title}>{props.entry.title}</h2>
|
||||||
|
|
||||||
|
<a class={css.cta} href={`/watch/${slug()}`}>Continue</a>
|
||||||
|
|
||||||
<img src={props.entry.thumbnail} class={css.thumbnail} />
|
<img src={props.entry.thumbnail} class={css.thumbnail} />
|
||||||
<img src={props.entry.image} class={css.background} />
|
<img src={props.entry.image} class={css.background} />
|
||||||
|
@ -31,7 +52,7 @@ export function Hero(props: HeroProps) {
|
||||||
</Index>
|
</Index>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<p class={css.summary}>{props.entry.summary}</p>
|
<p class={css.summary}>{props.entry.synopsis}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
|
"use server";
|
||||||
|
|
||||||
|
import type { paths } from "./jellyfin.generated"; // generated by openapi-typescript
|
||||||
import createClient from "openapi-fetch";
|
import createClient from "openapi-fetch";
|
||||||
import type { paths, components } from "./jellyfin.generated"; // generated by openapi-typescript
|
|
||||||
import { query } from "@solidjs/router";
|
import { query } from "@solidjs/router";
|
||||||
import { Entry } from "../types";
|
import { Entry } from "../types";
|
||||||
|
|
||||||
// ===============================
|
|
||||||
'use server';
|
|
||||||
// ===============================
|
|
||||||
|
|
||||||
type ItemImageType = "Primary" | "Art" | "Backdrop" | "Banner" | "Logo" | "Thumb" | "Disc" | "Box" | "Screenshot" | "Menu" | "Chapter" | "BoxRear" | "Profile";
|
type ItemImageType = "Primary" | "Art" | "Backdrop" | "Banner" | "Logo" | "Thumb" | "Disc" | "Box" | "Screenshot" | "Menu" | "Chapter" | "BoxRear" | "Profile";
|
||||||
|
|
||||||
const baseUrl = process.env.JELLYFIN_BASE_URL;
|
const baseUrl = process.env.JELLYFIN_BASE_URL;
|
||||||
|
@ -58,7 +56,71 @@ export const listUsers = query(async () => {
|
||||||
return data ?? [];
|
return data ?? [];
|
||||||
}, "jellyfin.listUsers");
|
}, "jellyfin.listUsers");
|
||||||
|
|
||||||
|
export const listItems = query(async (userId: string): Promise<Entry[] | undefined> => {
|
||||||
|
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<Entry | undefined> => getRandomItems(userId, 1).then(items => items?.at(0)), "jellyfin.listRandomItem");
|
||||||
|
|
||||||
|
export const getRandomItems = query(async (userId: string, limit: number = 10): Promise<Entry[]> => {
|
||||||
|
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<Entry | undefined> => {
|
export const getItem = query(async (userId: string, itemId: string): Promise<Entry | undefined> => {
|
||||||
|
console.log('baseUrl', baseUrl);
|
||||||
|
|
||||||
const { data, error } = await client.GET("/Items/{itemId}", {
|
const { data, error } = await client.GET("/Items/{itemId}", {
|
||||||
params: {
|
params: {
|
||||||
path: {
|
path: {
|
||||||
|
@ -87,7 +149,10 @@ export const getItem = query(async (userId: string, itemId: string): Promise<Ent
|
||||||
return {
|
return {
|
||||||
id: data.Id!,
|
id: data.Id!,
|
||||||
title: data.Name!,
|
title: data.Name!,
|
||||||
|
synopsis: data.Overview!,
|
||||||
thumbnail: new URL(`/Items/${itemId}/Images/Primary`, baseUrl), //await getItemImage(data.Id!, 'Primary'),
|
thumbnail: new URL(`/Items/${itemId}/Images/Primary`, baseUrl), //await getItemImage(data.Id!, 'Primary'),
|
||||||
|
image: new URL(`/Items/${itemId}/Images/Backdrop`, baseUrl),
|
||||||
|
// ...data,
|
||||||
};
|
};
|
||||||
}, "jellyfin.getItem");
|
}, "jellyfin.getItem");
|
||||||
|
|
||||||
|
@ -142,32 +207,29 @@ export const queryItems = query(async () => {
|
||||||
|
|
||||||
}, 'jellyfin.queryItems');
|
}, 'jellyfin.queryItems');
|
||||||
|
|
||||||
export const getContinueWatching = query(
|
export const getContinueWatching = query(async (userId: string): Promise<Entry[]> => {
|
||||||
async (userId: string): Promise<Entry[]> => {
|
const { data, error } = await client.GET("/UserItems/Resume", {
|
||||||
const { data, error } = await client.GET("/UserItems/Resume", {
|
params: {
|
||||||
params: {
|
query: {
|
||||||
query: {
|
userId,
|
||||||
userId,
|
mediaTypes: ["Video"],
|
||||||
mediaTypes: ["Video"],
|
// fields: ["ProviderIds", "Genres"],
|
||||||
// fields: ["ProviderIds", "Genres"],
|
// includeItemTypes: ["Series", "Movie"]
|
||||||
// includeItemTypes: ["Series", "Movie"]
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (Array.isArray(data?.Items) !== true) {
|
if (Array.isArray(data?.Items) !== true) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const uniqueIds = new Set<string>(data.Items.map(item => item.Type === 'Episode' ? item.SeriesId! : item.Id));
|
const uniqueIds = new Set<string>(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 results = await Promise.allSettled(uniqueIds.values().map(id => getItem(userId, id)).toArray());
|
||||||
|
|
||||||
assertNoErrors(results);
|
assertNoErrors(results);
|
||||||
|
|
||||||
return results.filter((result): result is PromiseFulfilledResult<Entry> => result.value !== undefined).map(({ value }) => value);
|
return results.filter((result): result is PromiseFulfilledResult<Entry> => result.value !== undefined).map(({ value }) => value);
|
||||||
},
|
}, "jellyfin.continueWatching");
|
||||||
"jellyfin.continueWatching",
|
|
||||||
);
|
|
||||||
|
|
||||||
function assertNoErrors<T>(results: PromiseSettledResult<T>[]): asserts results is PromiseFulfilledResult<T>[] {
|
function assertNoErrors<T>(results: PromiseSettledResult<T>[]): asserts results is PromiseFulfilledResult<T>[] {
|
||||||
if (results.some(({ status }) => status !== 'fulfilled')) {
|
if (results.some(({ status }) => status !== 'fulfilled')) {
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
|
"use server";
|
||||||
|
|
||||||
import type { Category, Entry } from "./types";
|
import type { Category, Entry } from "./types";
|
||||||
import { query } from "@solidjs/router";
|
import { query } from "@solidjs/router";
|
||||||
import { entries } from "./data";
|
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<Category[]> => {
|
export const listCategories = query(async (): Promise<Category[]> => {
|
||||||
"use server";
|
|
||||||
|
|
||||||
// await TEST()
|
|
||||||
// console.log(await getItemPlaybackInfo(jellyfinUserId, 'a69c0c0ab66177a7adb671f126335d16'));
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{ label: "Continue", entries: await getContinueWatching(jellyfinUserId) },
|
// { label: "Continue", entries: await getContinueWatching(jellyfinUserId) },
|
||||||
|
{ label: "Random", entries: await getRandomItems(jellyfinUserId) },
|
||||||
{
|
{
|
||||||
label: "Popular",
|
label: "Popular",
|
||||||
entries: [
|
entries: [
|
||||||
|
@ -70,11 +71,9 @@ export const listCategories = query(async (): Promise<Category[]> => {
|
||||||
|
|
||||||
export const getEntry = query(
|
export const getEntry = query(
|
||||||
async (id: Entry["id"]): Promise<Entry | undefined> => {
|
async (id: Entry["id"]): Promise<Entry | undefined> => {
|
||||||
"use server";
|
|
||||||
|
|
||||||
return getItem(jellyfinUserId, id);
|
return getItem(jellyfinUserId, id);
|
||||||
},
|
},
|
||||||
"series.get",
|
"series.get",
|
||||||
);
|
);
|
||||||
|
|
||||||
export { listUsers, getContinueWatching } from "./apis/jellyfin";
|
export { listUsers, getContinueWatching, listItems } from "./apis/jellyfin";
|
||||||
|
|
|
@ -7,11 +7,13 @@ export interface Category {
|
||||||
export interface Entry {
|
export interface Entry {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
summary?: string;
|
synopsis?: string;
|
||||||
releaseDate?: string;
|
releaseDate?: string;
|
||||||
sources?: Entry.Source[];
|
sources?: Entry.Source[];
|
||||||
thumbnail?: URL | string;
|
thumbnail?: URL | string;
|
||||||
image?: string;
|
image?: URL | string;
|
||||||
|
|
||||||
|
[prop: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace Entry {
|
export namespace Entry {
|
||||||
|
|
|
@ -22,21 +22,16 @@
|
||||||
|
|
||||||
box-shadow: var(--shadow-2);
|
box-shadow: var(--shadow-2);
|
||||||
background:
|
background:
|
||||||
/* Dot */
|
radial-gradient(circle at 25% 30%, #7772, #7774 1em, transparent 1em),
|
||||||
radial-gradient(circle at 25% 30% #7772 #7774 1em transparent 1em),
|
radial-gradient(circle at 85% 15%, #7772, #7774 1em, transparent 1em),
|
||||||
/* Dot */
|
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(
|
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(
|
radial-gradient(
|
||||||
ellipse 5em 2.25em at calc(100% - 0.5em) calc(50% + 1em) #555 100%
|
ellipse 5em 2.25em at calc(100% - 0.5em) calc(50% + 1em), #555 100%, transparent 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-origin: 50% 0;
|
||||||
transform: scale(1.1) translateY(calc(-4 * var(--padding)));
|
transform: scale(1.1) translateY(calc(-4 * var(--padding)));
|
||||||
|
|
|
@ -10,6 +10,7 @@ export const ListItem: Component<{ entry: Entry }> = (props) => {
|
||||||
<figure class={css.listItem} data-id={props.entry.id}>
|
<figure class={css.listItem} data-id={props.entry.id}>
|
||||||
<img src={props.entry.thumbnail ?? ''} alt={props.entry.title} />
|
<img src={props.entry.thumbnail ?? ''} alt={props.entry.title} />
|
||||||
|
|
||||||
|
|
||||||
<figcaption>
|
<figcaption>
|
||||||
<strong>{props.entry.title}</strong>
|
<strong>{props.entry.title}</strong>
|
||||||
|
|
||||||
|
|
|
@ -12,16 +12,14 @@ import { Hero } from "~/components/hero";
|
||||||
import css from "./overview.module.css";
|
import css from "./overview.module.css";
|
||||||
|
|
||||||
type OverviewProps = {
|
type OverviewProps = {
|
||||||
highlight: Entry;
|
highlights: Entry[];
|
||||||
categories: Category[];
|
categories: Category[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Overview: Component<OverviewProps> = (props) => {
|
export const Overview: Component<OverviewProps> = (props) => {
|
||||||
const [container, setContainer] = createSignal<HTMLElement>();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={setContainer} class={css.container}>
|
<div class={css.container}>
|
||||||
<Hero class={css.hero} entry={props.highlight}></Hero>
|
<Hero class={css.hero} entries={props.highlights}></Hero>
|
||||||
|
|
||||||
<Index each={props.categories}>
|
<Index each={props.categories}>
|
||||||
{(category) => (
|
{(category) => (
|
||||||
|
|
|
@ -140,7 +140,7 @@ export const Player: Component<PlayerProps> = (props) => {
|
||||||
console.log("pause", e);
|
console.log("pause", e);
|
||||||
},
|
},
|
||||||
suspend(e) {
|
suspend(e) {
|
||||||
console.log("suspend", e);
|
// console.log("suspend", e);
|
||||||
},
|
},
|
||||||
|
|
||||||
volumechange(e) {
|
volumechange(e) {
|
||||||
|
@ -152,7 +152,7 @@ export const Player: Component<PlayerProps> = (props) => {
|
||||||
},
|
},
|
||||||
|
|
||||||
progress(e) {
|
progress(e) {
|
||||||
console.log(e);
|
// console.log(e);
|
||||||
},
|
},
|
||||||
|
|
||||||
// timeupdate(e) {
|
// timeupdate(e) {
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,84 +2,28 @@ import { Title } from "@solidjs/meta";
|
||||||
import { createAsync } from "@solidjs/router";
|
import { createAsync } from "@solidjs/router";
|
||||||
import { Overview } from "~/features/overview";
|
import { Overview } from "~/features/overview";
|
||||||
import {
|
import {
|
||||||
|
getHighlights,
|
||||||
listCategories,
|
listCategories,
|
||||||
getEntry,
|
|
||||||
getContinueWatching,
|
|
||||||
} from "~/features/content";
|
} from "~/features/content";
|
||||||
import { Show } from "solid-js";
|
import { Show } from "solid-js";
|
||||||
import css from "./index.module.css";
|
|
||||||
|
|
||||||
const highlightId = 'c97185ed-e0cf-4945-9120-9d15bb8e5998';
|
|
||||||
|
|
||||||
export const route = {
|
export const route = {
|
||||||
preload: async () => ({
|
preload: async () => ({
|
||||||
highlight: await getEntry(highlightId),
|
highlight: await getHighlights(),
|
||||||
categories: await listCategories(),
|
categories: await listCategories(),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const highlight = createAsync(() => getEntry(highlightId));
|
const highlights = createAsync(() => getHighlights());
|
||||||
const categories = createAsync(() => listCategories());
|
const categories = createAsync(() => listCategories());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Title>Home</Title>
|
<Title>Home</Title>
|
||||||
|
|
||||||
{/* <div class={css.carousel}>
|
<Show when={highlights() && categories()}>
|
||||||
<header>some category</header>
|
<Overview highlights={highlights()!} categories={categories()!} />
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<figure>
|
|
||||||
<img src="https://assets.codepen.io/2585/1.jpg" alt="Item 1" />
|
|
||||||
</figure>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<figure>
|
|
||||||
<img src="https://assets.codepen.io/2585/2.avif" alt="Item 2" />
|
|
||||||
</figure>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<figure>
|
|
||||||
<img src="https://assets.codepen.io/2585/3.avif" alt="Item 3" />
|
|
||||||
</figure>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<figure>
|
|
||||||
<img src="https://assets.codepen.io/2585/4.avif" alt="Item 4" />
|
|
||||||
</figure>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<figure>
|
|
||||||
<img src="https://assets.codepen.io/2585/5.avif" alt="Item 5" />
|
|
||||||
</figure>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<figure>
|
|
||||||
<img src="https://assets.codepen.io/2585/6.avif" alt="Item 6" />
|
|
||||||
</figure>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<figure>
|
|
||||||
<img src="https://assets.codepen.io/2585/7.avif" alt="Item 7" />
|
|
||||||
</figure>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<figure>
|
|
||||||
<img src="https://assets.codepen.io/2585/8.avif" alt="Item 8" />
|
|
||||||
</figure>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<figure>
|
|
||||||
<img src="https://assets.codepen.io/2585/9.avif" alt="Item 9" />
|
|
||||||
</figure>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div> */}
|
|
||||||
|
|
||||||
<Show when={highlight() && categories()}>
|
|
||||||
<Overview highlight={highlight()!} categories={categories()!} />
|
|
||||||
</Show>
|
</Show>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import {
|
import {
|
||||||
createAsync,
|
|
||||||
json,
|
json,
|
||||||
Params,
|
Params,
|
||||||
query,
|
query,
|
||||||
|
@ -7,10 +6,8 @@ import {
|
||||||
RouteDefinition,
|
RouteDefinition,
|
||||||
useParams,
|
useParams,
|
||||||
} from "@solidjs/router";
|
} from "@solidjs/router";
|
||||||
import { createEffect } from "solid-js";
|
|
||||||
import { createSlug, getEntry } from "~/features/content";
|
import { createSlug, getEntry } from "~/features/content";
|
||||||
import { Player } from "~/features/player";
|
import { Player } from "~/features/player";
|
||||||
import { toSlug } from "~/utilities";
|
|
||||||
|
|
||||||
const healUrl = query(async (slug: string) => {
|
const healUrl = query(async (slug: string) => {
|
||||||
const entry = await getEntry(slug.slice(slug.lastIndexOf("-") + 1));
|
const entry = await getEntry(slug.slice(slug.lastIndexOf("-") + 1));
|
||||||
|
@ -34,9 +31,15 @@ interface ItemParams extends Params {
|
||||||
|
|
||||||
export const route = {
|
export const route = {
|
||||||
async preload({ params }) {
|
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;
|
} satisfies RouteDefinition;
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ export const splitAt = (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toSlug = (subject: string) =>
|
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");
|
export const toHex = (subject: number) => subject.toString(16).padStart(2, "0");
|
||||||
|
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue