10 changed files with 256 additions and 10 deletions
@ -0,0 +1,78 @@
@@ -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 @@
@@ -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