139 lines
4.3 KiB
TypeScript
139 lines
4.3 KiB
TypeScript
import React from "react";
|
|
|
|
import type NodeCG from "@nodecg/types";
|
|
import type { AssetFile, BreakSlide } from "../types";
|
|
import { useReplicantValue } from "./lib";
|
|
|
|
const breakSlideReplicant = nodecg.Replicant(
|
|
"breakSlide",
|
|
) as unknown as NodeCG.ServerReplicant<BreakSlide>;
|
|
|
|
// Optional background image (dashboard Assets → "Pauza — pozadinska slika");
|
|
// when present it replaces the drifting blobs.
|
|
const backgroundReplicant = nodecg.Replicant(
|
|
"assets:break-bg",
|
|
) as unknown as NodeCG.ServerReplicant<AssetFile[]>;
|
|
|
|
// Optional band logo (dashboard Assets → "Pauza — logo benda"), shown between
|
|
// the heading and the act name.
|
|
const logoReplicant = nodecg.Replicant(
|
|
"assets:break-logo",
|
|
) as unknown as NodeCG.ServerReplicant<AssetFile[]>;
|
|
|
|
interface Blob {
|
|
size: number;
|
|
left: string;
|
|
top: string;
|
|
color: string;
|
|
animation: string;
|
|
}
|
|
|
|
// Huge blurred radial blobs on slow drift loops — GPU-cheap ambient motion.
|
|
// Colors come from the THEME LAB block in index.css.
|
|
const BLOBS: Blob[] = [
|
|
{
|
|
size: 900,
|
|
left: "-10%",
|
|
top: "-20%",
|
|
color: "var(--color-break-blob-1)",
|
|
animation: "drift-a 90s ease-in-out infinite alternate",
|
|
},
|
|
{
|
|
size: 800,
|
|
left: "55%",
|
|
top: "40%",
|
|
color: "var(--color-break-blob-2)",
|
|
animation: "drift-b 110s ease-in-out infinite alternate",
|
|
},
|
|
{
|
|
size: 700,
|
|
left: "25%",
|
|
top: "55%",
|
|
color: "var(--color-break-blob-3)",
|
|
animation: "drift-c 70s ease-in-out infinite alternate",
|
|
},
|
|
];
|
|
|
|
export function Break() {
|
|
const slide = useReplicantValue<BreakSlide>(breakSlideReplicant);
|
|
const backgroundUrl =
|
|
useReplicantValue<AssetFile[]>(backgroundReplicant)?.[0]?.url ?? null;
|
|
// The slide names which uploaded file to use; fall back to the first one if
|
|
// nothing is picked or the picked file was deleted.
|
|
const logoFiles = useReplicantValue<AssetFile[]>(logoReplicant);
|
|
const logoUrl =
|
|
(logoFiles?.find((f) => f.base === slide?.logoName) ?? logoFiles?.[0])
|
|
?.url ?? null;
|
|
|
|
return (
|
|
<div
|
|
className="relative h-[1080px] w-[1920px] overflow-hidden"
|
|
style={{
|
|
background:
|
|
"linear-gradient(135deg, var(--color-break-bg-a) 0%, var(--color-break-bg-b) 55%, var(--color-break-bg-a) 100%)",
|
|
}}
|
|
>
|
|
{backgroundUrl ? (
|
|
<>
|
|
<img
|
|
src={backgroundUrl}
|
|
alt=""
|
|
className="absolute left-0 top-0 h-full w-full object-cover"
|
|
style={{ animation: "break-bounce 30s ease-in-out infinite alternate" }}
|
|
/>
|
|
{/* scrim keeps the announcement text readable — tune to taste */}
|
|
<div className="absolute inset-0 bg-black/40" />
|
|
</>
|
|
) : (
|
|
BLOBS.map((blob, i) => (
|
|
<div
|
|
key={i}
|
|
className="absolute rounded-full"
|
|
style={{
|
|
width: blob.size,
|
|
height: blob.size,
|
|
left: blob.left,
|
|
top: blob.top,
|
|
background: blob.color,
|
|
filter: "blur(120px)",
|
|
animation: blob.animation,
|
|
}}
|
|
/>
|
|
))
|
|
)}
|
|
|
|
{/*
|
|
Text stays well inside the edges: this same source is shown shrunk to
|
|
70% in the BREAK_AD_* scenes, so extremes would crowd the ad cutout.
|
|
*/}
|
|
<div
|
|
className="font-display absolute inset-0 flex flex-col items-center justify-center gap-10 px-[15%] text-center"
|
|
style={{ animation: "breathe 8s ease-in-out infinite" }}
|
|
>
|
|
<div className="text-6xl font-medium tracking-wide text-white/80">
|
|
{slide?.heading}
|
|
</div>
|
|
{(slide?.logoVisible ?? true) && logoUrl && (
|
|
<img
|
|
src={logoUrl}
|
|
alt=""
|
|
className="max-w-[45%] object-contain"
|
|
style={{
|
|
height: slide?.logoHeightPx ?? 240,
|
|
// invert must come before the shadow so the shadow itself
|
|
// doesn't flip to white
|
|
filter: `${slide?.logoInverted ? "invert(1) " : ""}drop-shadow(0 4px 24px rgba(0,0,0,0.6))`,
|
|
}}
|
|
/>
|
|
)}
|
|
<div className="text-9xl font-extrabold leading-tight text-white drop-shadow-[0_4px_24px_rgba(0,0,0,0.6)]">
|
|
{slide?.nextActName}
|
|
</div>
|
|
<div className="text-5xl font-light tracking-wider text-break-accent">
|
|
{slide?.nextActFrom}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|