added flares, themes and footer

Co-authored-by: Jip Frijlink <JipFr@users.noreply.github.com>
This commit is contained in:
mrjvs 2023-08-20 16:19:38 +02:00
parent e93644b688
commit eb57f1958f
10 changed files with 256 additions and 10 deletions

View File

@ -10,6 +10,7 @@
"@sentry/integrations": "^7.49.0", "@sentry/integrations": "^7.49.0",
"@sentry/react": "^7.49.0", "@sentry/react": "^7.49.0",
"@use-gesture/react": "^10.2.24", "@use-gesture/react": "^10.2.24",
"classnames": "^2.3.2",
"core-js": "^3.29.1", "core-js": "^3.29.1",
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
"dompurify": "^3.0.1", "dompurify": "^3.0.1",
@ -95,6 +96,7 @@
"prettier-plugin-tailwindcss": "^0.1.7", "prettier-plugin-tailwindcss": "^0.1.7",
"tailwind-scrollbar": "^2.0.1", "tailwind-scrollbar": "^2.0.1",
"tailwindcss": "^3.2.4", "tailwindcss": "^3.2.4",
"tailwindcss-themer": "^3.1.0",
"typescript": "^4.6.4", "typescript": "^4.6.4",
"vite": "^4.0.1", "vite": "^4.0.1",
"vite-plugin-checker": "^0.5.6", "vite-plugin-checker": "^0.5.6",

View File

@ -0,0 +1,78 @@
import { useTranslation } from "react-i18next";
import { Icon, Icons } from "@/components/Icon";
import { BrandPill } from "@/components/layout/BrandPill";
import { WideContainer } from "@/components/layout/WideContainer";
function FooterLink(props: {
href: string;
children: React.ReactNode;
icon: Icons;
}) {
return (
<a
href={props.href}
target="_blank"
className="inline-flex items-center space-x-3 transition-colors duration-200 hover:text-type-emphasis"
rel="noreferrer"
>
<Icon icon={props.icon} className="text-2xl" />
<span className="font-medium">{props.children}</span>
</a>
);
}
export function Footer() {
const { t } = useTranslation();
return (
<footer className="mt-16 border-t border-type-divider py-16 md:py-8">
<WideContainer ultraWide classNames="grid md:grid-cols-2 gap-16 md:gap-8">
<div>
<div className="inline-block">
<BrandPill />
</div>
<p className="mt-4 lg:max-w-[400px]">{t("footer.tagline")}</p>
<div className="mt-8 space-x-[2rem]">
<FooterLink icon={Icons.GITHUB} href="https://github.com/movie-web">
{t("footer.links.github")}
</FooterLink>
<FooterLink
icon={Icons.DISCORD}
href="https://github.com/movie-web"
>
{t("footer.links.github")}
</FooterLink>
</div>
</div>
<div className="md:text-right">
<h3 className="font-semibold text-type-emphasis">
{t("footer.legal.disclaimer")}
</h3>
<p className="mt-3">{t("footer.legal.disclaimerText")}</p>
<div className="mt-8">
<FooterLink icon={Icons.DRAGON} href="https://youtu.be/-WOonkg_ZCo">
{t("footer.links.dmca")}
</FooterLink>
</div>
</div>
</WideContainer>
</footer>
);
}
export function FooterView(props: {
children: React.ReactNode;
className?: string;
}) {
return (
<div
className={["flex min-h-screen flex-col", props.className || ""].join(
" "
)}
>
<div style={{ flex: "1 0 auto" }}>{props.children}</div>
<Footer />
</div>
);
}

View File

@ -3,14 +3,15 @@ import { ReactNode } from "react";
interface WideContainerProps { interface WideContainerProps {
classNames?: string; classNames?: string;
children?: ReactNode; children?: ReactNode;
ultraWide?: boolean;
} }
export function WideContainer(props: WideContainerProps) { export function WideContainer(props: WideContainerProps) {
return ( return (
<div <div
className={`mx-auto w-[700px] max-w-full px-8 sm:px-4 ${ className={`mx-auto max-w-full px-8 ${
props.classNames || "" props.ultraWide ? "w-[1300px] sm:px-16" : "w-[900px] sm:px-8"
}`} } ${props.classNames || ""}`}
> >
{props.children} {props.children}
</div> </div>

View File

@ -7,7 +7,10 @@ interface MediaGridProps {
export const MediaGrid = forwardRef<HTMLDivElement, MediaGridProps>( export const MediaGrid = forwardRef<HTMLDivElement, MediaGridProps>(
(props, ref) => { (props, ref) => {
return ( return (
<div className="grid grid-cols-2 gap-6 sm:grid-cols-3" ref={ref}> <div
className="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4"
ref={ref}
>
{props.children} {props.children}
</div> </div>
); );

View File

@ -0,0 +1,75 @@
import c from "classnames";
import { useEffect, useRef } from "react";
export interface FlareProps {
className?: string;
backgroundClass: string;
flareSize?: number;
cssColorVar?: string;
enabled?: boolean;
}
const SIZE_DEFAULT = 200;
const CSS_VAR_DEFAULT = "--colors-global-accentA";
export function Flare(props: FlareProps) {
const outerRef = useRef<HTMLDivElement>(null);
const size = props.flareSize ?? SIZE_DEFAULT;
const cssVar = props.cssColorVar ?? CSS_VAR_DEFAULT;
useEffect(() => {
function mouseMove(e: MouseEvent) {
if (!outerRef.current) return;
outerRef.current.style.setProperty(
"--bg-x",
`${(e.clientX - size / 2).toFixed(0)}px`
);
outerRef.current.style.setProperty(
"--bg-y",
`${(e.clientY - size / 2).toFixed(0)}px`
);
}
document.addEventListener("mousemove", mouseMove);
return () => document.removeEventListener("mousemove", mouseMove);
}, [size]);
return (
<div
ref={outerRef}
className={c(
"overflow-hidden, pointer-events-none absolute inset-0 hidden",
props.className,
{
"!block": props.enabled ?? false,
}
)}
style={{
backgroundImage: `radial-gradient(circle at center, rgba(var(${cssVar}), 1), rgba(var(${cssVar}), 0) 70%)`,
backgroundPosition: `var(--bg-x) var(--bg-y)`,
backgroundRepeat: "no-repeat",
backgroundAttachment: "fixed",
backgroundSize: "200px 200px",
}}
>
<div
className={c(
"absolute inset-[2px] overflow-hidden",
props.className,
props.backgroundClass
)}
>
<div
className="absolute inset-0 opacity-5"
style={{
background: `radial-gradient(circle at center, rgba(var(${cssVar}), 1), rgba(var(${cssVar}), 0) 70%)`,
backgroundPosition: `var(--bg-x) var(--bg-y)`,
backgroundRepeat: "no-repeat",
backgroundAttachment: "fixed",
backgroundSize: "200px 200px",
}}
/>
</div>
</div>
);
}

View File

@ -131,5 +131,16 @@
}, },
"errors": { "errors": {
"offline": "Check your internet connection" "offline": "Check your internet connection"
},
"footer": {
"tagline": "Watch your favorite shows and movies with this open source streaming app.",
"links": {
"github": "GitHub",
"dmca": "DMCA"
},
"legal": {
"disclaimer": "Disclaimer",
"disclaimerText": "movie-web does not host any files, it merely links to 3rd party services. Legal issues should be taken up with the file hosts and providers. movie-web is not responsible for any media files shown by the video providers."
}
} }
} }

View File

@ -198,7 +198,7 @@ function NewDomainModal() {
export function HomeView() { export function HomeView() {
return ( return (
<div className="mb-16"> <div>
<EmbedMigration /> <EmbedMigration />
<NewDomainModal /> <NewDomainModal />
<Bookmarks /> <Bookmarks />

View File

@ -3,6 +3,7 @@ import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import Sticky from "react-stickynode"; import Sticky from "react-stickynode";
import { FooterView } from "@/components/layout/Footer";
import { Navigation } from "@/components/layout/Navigation"; import { Navigation } from "@/components/layout/Navigation";
import { ThinContainer } from "@/components/layout/ThinContainer"; import { ThinContainer } from "@/components/layout/ThinContainer";
import { WideContainer } from "@/components/layout/WideContainer"; import { WideContainer } from "@/components/layout/WideContainer";
@ -25,7 +26,7 @@ export function SearchView() {
); );
return ( return (
<> <FooterView>
<div className="relative z-10 mb-16 sm:mb-24"> <div className="relative z-10 mb-16 sm:mb-24">
<Helmet> <Helmet>
<title>{t("global.name")}</title> <title>{t("global.name")}</title>
@ -61,6 +62,6 @@ export function SearchView() {
<WideContainer> <WideContainer>
<SearchResultsPartial search={search} /> <SearchResultsPartial search={search} />
</WideContainer> </WideContainer>
</> </FooterView>
); );
} }

View File

@ -1,3 +1,5 @@
const themer = require("tailwindcss-themer");
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
module.exports = { module.exports = {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
@ -42,5 +44,30 @@ module.exports = {
animation: { "loading-pin": "loading-pin 1.8s ease-in-out infinite" } animation: { "loading-pin": "loading-pin 1.8s ease-in-out infinite" }
} }
}, },
plugins: [require("tailwind-scrollbar")] plugins: [
require("tailwind-scrollbar"),
themer({
defaultTheme: {
extend: {
colors: {
background: {
main: "#0A0A10",
accentA: "#6E3B80",
accentB: "#1F1F50"
},
global: {
accentA: "#505DBD",
accentB: "#3440A1"
},
type: {
emphasis: "#FFFFFF",
text: "#73739D",
dimmed: "#926CAD",
divider: "#353549"
}
}
}
}
})
]
}; };

View File

@ -2147,7 +2147,7 @@ chokidar@^3.5.1, chokidar@^3.5.3:
optionalDependencies: optionalDependencies:
fsevents "~2.3.2" fsevents "~2.3.2"
classnames@^2.0.0: classnames@^2.0.0, classnames@^2.3.2:
version "2.3.2" version "2.3.2"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924"
integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==
@ -2184,11 +2184,27 @@ color-name@1.1.3:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
color-name@^1.1.4, color-name@~1.1.4: color-name@^1.0.0, color-name@^1.1.4, color-name@~1.1.4:
version "1.1.4" version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
color-string@^1.9.0:
version "1.9.1"
resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4"
integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==
dependencies:
color-name "^1.0.0"
simple-swizzle "^0.2.2"
color@^4.1.0:
version "4.2.3"
resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a"
integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==
dependencies:
color-convert "^2.0.1"
color-string "^1.9.0"
combined-stream@^1.0.8: combined-stream@^1.0.8:
version "1.0.8" version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
@ -3410,6 +3426,11 @@ is-array-buffer@^3.0.1, is-array-buffer@^3.0.2:
get-intrinsic "^1.2.0" get-intrinsic "^1.2.0"
is-typed-array "^1.1.10" is-typed-array "^1.1.10"
is-arrayish@^0.3.1:
version "0.3.2"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03"
integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==
is-bigint@^1.0.1: is-bigint@^1.0.1:
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3"
@ -3761,6 +3782,11 @@ jsonpointer@^5.0.0:
array-includes "^3.1.5" array-includes "^3.1.5"
object.assign "^4.1.3" object.assign "^4.1.3"
just-unique@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/just-unique/-/just-unique-4.2.0.tgz#80927360b92f5039fad7e7f5f8bc48904d9177b6"
integrity sha512-cxQGGUiit6CGUpuuiezY8N4m1wgF4o7127rXEXDFcxeDUFfdV7gSkwA26Fe2wWBiNQq2SZOgN4gSmMxB/StA8Q==
language-subtag-registry@~0.3.2: language-subtag-registry@~0.3.2:
version "0.3.22" version "0.3.22"
resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz#2e1500861b2e457eba7e7ae86877cbd08fa1fd1d" resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz#2e1500861b2e457eba7e7ae86877cbd08fa1fd1d"
@ -3840,6 +3866,11 @@ lodash.merge@^4.6.2:
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
lodash.mergewith@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55"
integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==
lodash.pick@^4.4.0: lodash.pick@^4.4.0:
version "4.4.0" version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
@ -4859,6 +4890,13 @@ signal-exit@^4.0.1:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.0.2.tgz#ff55bb1d9ff2114c13b400688fa544ac63c36967" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.0.2.tgz#ff55bb1d9ff2114c13b400688fa544ac63c36967"
integrity sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q== integrity sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==
simple-swizzle@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"
integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==
dependencies:
is-arrayish "^0.3.1"
slash@^3.0.0: slash@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
@ -5124,6 +5162,16 @@ tailwind-scrollbar@^2.0.1:
resolved "https://registry.yarnpkg.com/tailwind-scrollbar/-/tailwind-scrollbar-2.1.0.tgz#46e0b8788cef75387f9d163a5ec82b8cacd66c44" resolved "https://registry.yarnpkg.com/tailwind-scrollbar/-/tailwind-scrollbar-2.1.0.tgz#46e0b8788cef75387f9d163a5ec82b8cacd66c44"
integrity sha512-zpvY5mDs0130YzYjZKBiDaw32rygxk5RyJ4KmeHjGnwkvbjm/PszON1m4Bbt2DkMRIXlXsfNevykAESgURN4KA== integrity sha512-zpvY5mDs0130YzYjZKBiDaw32rygxk5RyJ4KmeHjGnwkvbjm/PszON1m4Bbt2DkMRIXlXsfNevykAESgURN4KA==
tailwindcss-themer@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/tailwindcss-themer/-/tailwindcss-themer-3.1.0.tgz#4440d62d7edc54f8f0aaef3404bb71844fa556d6"
integrity sha512-IfgxpCxWm5rRK3Q7aTvVyhQ/7tyyn8EJl5tFak5tS+/n8oXT7OGfv8praYepR7+IsM92waAuBDZng1HgnstrYA==
dependencies:
color "^4.1.0"
just-unique "^4.2.0"
lodash.merge "^4.6.2"
lodash.mergewith "^4.6.2"
tailwindcss@^3.2.4: tailwindcss@^3.2.4:
version "3.3.1" version "3.3.1"
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.1.tgz#b6662fab6a9b704779e48d083a9fef5a81d2b81e" resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.1.tgz#b6662fab6a9b704779e48d083a9fef5a81d2b81e"