mirror of
https://github.com/movie-web/movie-web.git
synced 2024-11-11 00:05:09 +01:00
Add media card flare
This commit is contained in:
parent
88beacde1a
commit
7251b39cc3
@ -1,9 +1,11 @@
|
|||||||
|
import c from "classnames";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
import { TMDBMediaToId } from "@/backend/metadata/tmdb";
|
import { TMDBMediaToId } from "@/backend/metadata/tmdb";
|
||||||
import { MWMediaMeta } from "@/backend/metadata/types/mw";
|
import { MWMediaMeta } from "@/backend/metadata/types/mw";
|
||||||
import { DotList } from "@/components/text/DotList";
|
import { DotList } from "@/components/text/DotList";
|
||||||
|
import { Flare } from "@/components/utils/Flare";
|
||||||
|
|
||||||
import { IconPatch } from "../buttons/IconPatch";
|
import { IconPatch } from "../buttons/IconPatch";
|
||||||
import { Icons } from "../Icon";
|
import { Icons } from "../Icon";
|
||||||
@ -39,19 +41,27 @@ function MediaCardContent({
|
|||||||
if (media.year) dotListContent.push(media.year);
|
if (media.year) dotListContent.push(media.year);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<Flare.Base
|
||||||
className={`group -m-3 mb-2 rounded-xl bg-denim-300 bg-opacity-0 transition-colors duration-100 ${
|
className={`group -m-3 mb-2 rounded-xl bg-background-main transition-colors duration-100 ${
|
||||||
canLink ? "hover:bg-opacity-100" : ""
|
canLink ? "hover:bg-mediaCard-hoverBackground" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<article
|
<Flare.Light
|
||||||
|
flareSize={300}
|
||||||
|
cssColorVar="--colors-mediaCard-hoverAccent"
|
||||||
|
backgroundClass="bg-mediaCard-hoverBackground duration-100"
|
||||||
|
className={c({
|
||||||
|
"rounded-xl bg-background-main group-hover:opacity-100": canLink,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<Flare.Child
|
||||||
className={`pointer-events-auto relative mb-2 p-3 transition-transform duration-100 ${
|
className={`pointer-events-auto relative mb-2 p-3 transition-transform duration-100 ${
|
||||||
canLink ? "group-hover:scale-95" : ""
|
canLink ? "group-hover:scale-95" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={[
|
className={[
|
||||||
"relative mb-4 aspect-[2/3] w-full overflow-hidden rounded-xl bg-denim-500 bg-cover bg-center transition-[border-radius] duration-100",
|
"relative mb-4 aspect-[2/3] w-full overflow-hidden rounded-xl bg-mediaCard-hoverBackground bg-cover bg-center transition-[border-radius] duration-100",
|
||||||
closable ? "" : "group-hover:rounded-lg",
|
closable ? "" : "group-hover:rounded-lg",
|
||||||
].join(" ")}
|
].join(" ")}
|
||||||
style={{
|
style={{
|
||||||
@ -61,13 +71,12 @@ function MediaCardContent({
|
|||||||
{series ? (
|
{series ? (
|
||||||
<div
|
<div
|
||||||
className={[
|
className={[
|
||||||
"absolute right-2 top-2 rounded-md bg-denim-200 px-2 py-1 transition-colors",
|
"absolute right-2 top-2 rounded-md bg-mediaCard-badge px-2 py-1 transition-colors",
|
||||||
closable ? "" : "group-hover:bg-denim-500",
|
|
||||||
].join(" ")}
|
].join(" ")}
|
||||||
>
|
>
|
||||||
<p
|
<p
|
||||||
className={[
|
className={[
|
||||||
"text-center text-xs font-bold text-slate-400 transition-colors",
|
"text-center text-xs font-bold text-mediaCard-badgeText transition-colors",
|
||||||
closable ? "" : "group-hover:text-white",
|
closable ? "" : "group-hover:text-white",
|
||||||
].join(" ")}
|
].join(" ")}
|
||||||
>
|
>
|
||||||
@ -82,19 +91,19 @@ function MediaCardContent({
|
|||||||
{percentage !== undefined ? (
|
{percentage !== undefined ? (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className={`absolute inset-x-0 bottom-0 h-12 bg-gradient-to-t from-denim-300 to-transparent transition-colors ${
|
className={`absolute inset-x-0 bottom-0 h-12 bg-gradient-to-t from-mediaCard-shadow to-transparent transition-colors ${
|
||||||
canLink ? "group-hover:from-denim-100" : ""
|
canLink ? "group-hover:from-mediaCard-hoverShadow" : ""
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className={`absolute inset-x-0 bottom-0 h-12 bg-gradient-to-t from-denim-300 to-transparent transition-colors ${
|
className={`absolute inset-x-0 bottom-0 h-12 bg-gradient-to-t from-mediaCard-shadow to-transparent transition-colors ${
|
||||||
canLink ? "group-hover:from-denim-100" : ""
|
canLink ? "group-hover:from-mediaCard-hoverShadow" : ""
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-x-0 bottom-0 p-3">
|
<div className="absolute inset-x-0 bottom-0 p-3">
|
||||||
<div className="relative h-1 overflow-hidden rounded-full bg-denim-600">
|
<div className="relative h-1 overflow-hidden rounded-full bg-mediaCard-barColor">
|
||||||
<div
|
<div
|
||||||
className="absolute inset-y-0 left-0 rounded-full bg-bink-700"
|
className="absolute inset-y-0 left-0 rounded-full bg-mediaCard-barFillColor"
|
||||||
style={{
|
style={{
|
||||||
width: percentageString,
|
width: percentageString,
|
||||||
}}
|
}}
|
||||||
@ -105,13 +114,13 @@ function MediaCardContent({
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`absolute inset-0 flex items-center justify-center bg-denim-200 bg-opacity-80 transition-opacity duration-200 ${
|
className={`absolute inset-0 flex items-center justify-center bg-mediaCard-badge bg-opacity-80 transition-opacity duration-200 ${
|
||||||
closable ? "opacity-100" : "pointer-events-none opacity-0"
|
closable ? "opacity-100" : "pointer-events-none opacity-0"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<IconPatch
|
<IconPatch
|
||||||
clickable
|
clickable
|
||||||
className="text-2xl text-slate-400"
|
className="text-2xl text-mediaCard-badgeText"
|
||||||
onClick={() => closable && onClose?.()}
|
onClick={() => closable && onClose?.()}
|
||||||
icon={Icons.X}
|
icon={Icons.X}
|
||||||
/>
|
/>
|
||||||
@ -121,8 +130,8 @@ function MediaCardContent({
|
|||||||
<span>{media.title}</span>
|
<span>{media.title}</span>
|
||||||
</h1>
|
</h1>
|
||||||
<DotList className="text-xs" content={dotListContent} />
|
<DotList className="text-xs" content={dotListContent} />
|
||||||
</article>
|
</Flare.Child>
|
||||||
</div>
|
</Flare.Base>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,13 +30,14 @@ function Light(props: FlareProps) {
|
|||||||
function mouseMove(e: MouseEvent) {
|
function mouseMove(e: MouseEvent) {
|
||||||
if (!outerRef.current) return;
|
if (!outerRef.current) return;
|
||||||
const rect = outerRef.current.getBoundingClientRect();
|
const rect = outerRef.current.getBoundingClientRect();
|
||||||
|
const halfSize = size / 2;
|
||||||
outerRef.current.style.setProperty(
|
outerRef.current.style.setProperty(
|
||||||
"--bg-x",
|
"--bg-x",
|
||||||
`${(e.clientX - rect.left - size / 2).toFixed(0)}px`
|
`${(e.clientX - rect.left - halfSize).toFixed(0)}px`
|
||||||
);
|
);
|
||||||
outerRef.current.style.setProperty(
|
outerRef.current.style.setProperty(
|
||||||
"--bg-y",
|
"--bg-y",
|
||||||
`${(e.clientY - rect.top - size / 2).toFixed(0)}px`
|
`${(e.clientY - rect.top - halfSize).toFixed(0)}px`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
document.addEventListener("mousemove", mouseMove);
|
document.addEventListener("mousemove", mouseMove);
|
||||||
@ -58,13 +59,12 @@ function Light(props: FlareProps) {
|
|||||||
backgroundImage: `radial-gradient(circle at center, rgba(var(${cssVar}), 1), rgba(var(${cssVar}), 0) 70%)`,
|
backgroundImage: `radial-gradient(circle at center, rgba(var(${cssVar}), 1), rgba(var(${cssVar}), 0) 70%)`,
|
||||||
backgroundPosition: `var(--bg-x) var(--bg-y)`,
|
backgroundPosition: `var(--bg-x) var(--bg-y)`,
|
||||||
backgroundRepeat: "no-repeat",
|
backgroundRepeat: "no-repeat",
|
||||||
backgroundAttachment: "fixed",
|
|
||||||
backgroundSize: `${size.toFixed(0)}px ${size.toFixed(0)}px`,
|
backgroundSize: `${size.toFixed(0)}px ${size.toFixed(0)}px`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={c(
|
className={c(
|
||||||
"absolute inset-[2px] overflow-hidden",
|
"absolute inset-[1px] overflow-hidden",
|
||||||
props.className,
|
props.className,
|
||||||
props.backgroundClass
|
props.backgroundClass
|
||||||
)}
|
)}
|
||||||
@ -75,7 +75,6 @@ function Light(props: FlareProps) {
|
|||||||
background: `radial-gradient(circle at center, rgba(var(${cssVar}), 1), rgba(var(${cssVar}), 0) 70%)`,
|
background: `radial-gradient(circle at center, rgba(var(${cssVar}), 1), rgba(var(${cssVar}), 0) 70%)`,
|
||||||
backgroundPosition: `var(--bg-x) var(--bg-y)`,
|
backgroundPosition: `var(--bg-x) var(--bg-y)`,
|
||||||
backgroundRepeat: "no-repeat",
|
backgroundRepeat: "no-repeat",
|
||||||
backgroundAttachment: "fixed",
|
|
||||||
backgroundSize: `${size.toFixed(0)}px ${size.toFixed(0)}px`,
|
backgroundSize: `${size.toFixed(0)}px ${size.toFixed(0)}px`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -1,11 +1,24 @@
|
|||||||
.lightbar {
|
.lightbar, .lightbar-visual {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: -25vw;
|
top: 0;
|
||||||
top: 0;
|
width: 150vw;
|
||||||
width: 150vw;
|
|
||||||
height: 800px;
|
height: 800px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbar {
|
||||||
|
left: -25vw;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
--d: 3s;
|
||||||
|
--animation: cubic-bezier(.75,-0.00,.25,1);
|
||||||
|
animation: boot var(--d) var(--animation) forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbar-visual {
|
||||||
|
left: 0;
|
||||||
--top: theme('colors.background.main');
|
--top: theme('colors.background.main');
|
||||||
--bottom: theme('colors.lightBar.light');
|
--bottom: theme('colors.lightBar.light');
|
||||||
--first: conic-gradient(from 90deg at 80% 50%,var(--top),var(--bottom));
|
--first: conic-gradient(from 90deg at 80% 50%,var(--top),var(--bottom));
|
||||||
@ -19,13 +32,30 @@
|
|||||||
transform: rotate(180deg) translateZ(0px) translateY(400px);
|
transform: rotate(180deg) translateZ(0px) translateY(400px);
|
||||||
transform-origin: center center;
|
transform-origin: center center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
display: flex;
|
animation: lightbarBoot var(--d) var(--animation) forwards;
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.lightbar canvas {
|
.lightbar canvas {
|
||||||
width: 40%;
|
width: 40%;
|
||||||
height: 300px;
|
height: 300px;
|
||||||
transform: translateY(-50%) rotate(180deg);
|
transform: translateY(-250px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes boot {
|
||||||
|
from {
|
||||||
|
|
||||||
|
opacity: 0.25;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes lightbarBoot {
|
||||||
|
0% {
|
||||||
|
transform: rotate(180deg) translateZ(0px) translateY(400px) scaleX(0.8);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(180deg) translateZ(0px) translateY(400px) scaleX(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,6 +139,7 @@ export function Lightbar(props: { className?: string }) {
|
|||||||
<div className={props.className}>
|
<div className={props.className}>
|
||||||
<div className="lightbar">
|
<div className="lightbar">
|
||||||
<ParticlesCanvas />
|
<ParticlesCanvas />
|
||||||
|
<div className="lightbar-visual" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -83,6 +83,18 @@ module.exports = {
|
|||||||
placeholder: "#4A4A71",
|
placeholder: "#4A4A71",
|
||||||
icon: "#545476",
|
icon: "#545476",
|
||||||
text: "#FFFFFF"
|
text: "#FFFFFF"
|
||||||
|
},
|
||||||
|
|
||||||
|
// media cards
|
||||||
|
mediaCard: {
|
||||||
|
hoverBackground: "#161622",
|
||||||
|
hoverAccent: "#4D79A8",
|
||||||
|
hoverShadow: "#0A0A10",
|
||||||
|
shadow: "#161622",
|
||||||
|
barColor: "#4B4B63",
|
||||||
|
barFillColor: "#BA7FD6",
|
||||||
|
badge: "#151522",
|
||||||
|
badgeText: "#5F5F7A"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user