10 changed files with 256 additions and 10 deletions
@ -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> |
||||||
|
); |
||||||
|
} |
@ -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> |
||||||
|
); |
||||||
|
} |
Loading…
Reference in new issue