background gallery + many other styling and animation transition updates
No files matched your search
@@ -8,6 +8,7 @@
|
||||
"name": "storyteller-board",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@devnomic/marquee": "^1.0.3",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.6.0",
|
||||
"@headlessui/react": "^2.1.3",
|
||||
"@preact/signals-react": "^2.2.0",
|
||||
@@ -381,6 +382,21 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@devnomic/marquee": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@devnomic/marquee/-/marquee-1.0.3.tgz",
|
||||
"integrity": "sha512-q8QUtNO8ff+UvfrKJsHBtxh4fbOfhGk6SSdK0Uu/XZFdEzrmiGJ/l0FuVHRYsPAmzgnhVVnz1dGNB5CWGLMVvw==",
|
||||
"dependencies": {
|
||||
"clsx": "^2.1.0",
|
||||
"tailwind-merge": "^2.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^18.2.47",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/is-prop-valid": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.0.tgz",
|
||||
@@ -1510,14 +1526,12 @@
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
|
||||
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.4.tgz",
|
||||
"integrity": "sha512-J7W30FTdfCxDDjmfRM+/JqLHBIyl7xUIp9kwK637FGmY7+mkSFSe6L4jpZzhj5QMfLssSDP4/i75AKkrdC7/Jw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.0.2"
|
||||
@@ -1527,7 +1541,6 @@
|
||||
"version": "18.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz",
|
||||
"integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
@@ -2364,8 +2377,7 @@
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
|
||||
},
|
||||
"node_modules/data-view-buffer": {
|
||||
"version": "1.0.1",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@devnomic/marquee": "^1.0.3",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.6.0",
|
||||
"@headlessui/react": "^2.1.3",
|
||||
"@preact/signals-react": "^2.2.0",
|
||||
|
||||
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 276 KiB |
|
After Width: | Height: | Size: 320 KiB |
|
After Width: | Height: | Size: 565 KiB |
|
After Width: | Height: | Size: 366 KiB |
|
After Width: | Height: | Size: 452 KiB |
|
After Width: | Height: | Size: 337 KiB |
|
After Width: | Height: | Size: 379 KiB |
|
After Width: | Height: | Size: 302 KiB |
|
After Width: | Height: | Size: 446 KiB |
|
After Width: | Height: | Size: 238 KiB |
|
After Width: | Height: | Size: 344 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 299 KiB |
|
After Width: | Height: | Size: 396 KiB |
|
After Width: | Height: | Size: 318 KiB |
|
After Width: | Height: | Size: 521 KiB |
|
After Width: | Height: | Size: 318 KiB |
|
After Width: | Height: | Size: 396 KiB |
|
After Width: | Height: | Size: 316 KiB |
@@ -0,0 +1,145 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Marquee } from "@devnomic/marquee";
|
||||
import "@devnomic/marquee/dist/index.css";
|
||||
|
||||
function getRandomImages(count: number) {
|
||||
const images = Array.from({ length: count }, (_, i) => ({
|
||||
id: i + 1,
|
||||
src: `/images/generate/image${i + 1}.png`,
|
||||
alt: `Generated image ${i + 1}`,
|
||||
}));
|
||||
|
||||
// Shuffle the array
|
||||
return [...images].sort(() => Math.random() - 0.5);
|
||||
}
|
||||
|
||||
interface ImageColumnProps {
|
||||
images: Array<{ id: number; src: string; alt: string }>;
|
||||
delay: number;
|
||||
reverse?: boolean;
|
||||
isLoaded: boolean;
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
function ImageColumn({
|
||||
images,
|
||||
delay,
|
||||
reverse = false,
|
||||
duration = 100,
|
||||
isLoaded,
|
||||
}: ImageColumnProps) {
|
||||
return (
|
||||
<div className="flex h-screen flex-col gap-6">
|
||||
<Marquee
|
||||
fade={false}
|
||||
direction="up"
|
||||
reverse={reverse}
|
||||
pauseOnHover={false}
|
||||
className={`h-full`}
|
||||
style={{ "--duration": `${duration}s` } as React.CSSProperties}
|
||||
innerClassName="gap-6 [--gap:24px]"
|
||||
numberOfCopies={2}
|
||||
>
|
||||
{images.map((image, i) => (
|
||||
<div
|
||||
key={`${image.id}-${i}`}
|
||||
className={`relative aspect-[4/5] w-full overflow-hidden rounded-lg shadow-lg transition-all duration-700 ease-out ${
|
||||
isLoaded
|
||||
? "translate-y-0 opacity-100"
|
||||
: "translate-y-12 opacity-0"
|
||||
}`}
|
||||
style={{
|
||||
transitionDelay: `${delay + i * 50}ms`,
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={image.src}
|
||||
alt={image.alt}
|
||||
className="w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</Marquee>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function GalleryBackground() {
|
||||
const [images, _setImages] = useState(() => getRandomImages(20));
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// delay for smooth animation
|
||||
const timer = setTimeout(() => {
|
||||
setIsLoaded(true);
|
||||
}, 100);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
// Columns for masonry layout
|
||||
const columns = {
|
||||
col1: images.filter((_, i) => i % 5 === 0),
|
||||
col2: images.filter((_, i) => i % 5 === 1),
|
||||
col3: images.filter((_, i) => i % 5 === 2),
|
||||
col4: images.filter((_, i) => i % 5 === 3),
|
||||
col5: images.filter((_, i) => i % 5 === 4),
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="fixed inset-0 z-[1] overflow-hidden bg-[radial-gradient(50%_50%_at_50%_50%,rgba(0,0,0,0.00)_49%,rgba(0,0,0,0.60)_100%)]" />
|
||||
<div className="fixed inset-0 z-0 overflow-hidden">
|
||||
<div
|
||||
className={`h-full w-full transition-opacity duration-1000 ${isLoaded ? "opacity-50" : "opacity-0"}`}
|
||||
>
|
||||
<div className="absolute inset-0 grid grid-cols-5 gap-6 px-6 opacity-35">
|
||||
<ImageColumn
|
||||
images={columns.col1}
|
||||
delay={0}
|
||||
isLoaded={isLoaded}
|
||||
duration={100}
|
||||
/>
|
||||
<ImageColumn
|
||||
images={columns.col2}
|
||||
delay={columns.col1.length * 50}
|
||||
isLoaded={isLoaded}
|
||||
reverse={true}
|
||||
duration={100}
|
||||
/>
|
||||
<ImageColumn
|
||||
images={columns.col3}
|
||||
delay={(columns.col1.length + columns.col2.length) * 50}
|
||||
isLoaded={isLoaded}
|
||||
duration={125}
|
||||
/>
|
||||
<ImageColumn
|
||||
images={columns.col4}
|
||||
delay={
|
||||
(columns.col1.length +
|
||||
columns.col2.length +
|
||||
columns.col3.length) *
|
||||
50
|
||||
}
|
||||
isLoaded={isLoaded}
|
||||
reverse={true}
|
||||
duration={160}
|
||||
/>
|
||||
<ImageColumn
|
||||
images={columns.col5}
|
||||
delay={
|
||||
(columns.col1.length +
|
||||
columns.col2.length +
|
||||
columns.col3.length +
|
||||
columns.col4.length) *
|
||||
50
|
||||
}
|
||||
isLoaded={isLoaded}
|
||||
duration={110}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -3,7 +3,10 @@ import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { Button, LoadingSpinner } from "~/components/ui";
|
||||
import { useRenderCounter } from "~/hooks/useRenderCounter";
|
||||
import { GenerationLoadingState, generationSignal } from "~/signals";
|
||||
import { useRef } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
import BackgroundGallery from "./BackgroundGallery";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { Transition } from "@headlessui/react";
|
||||
|
||||
export const GenerationRootComponent = () => {
|
||||
// This is a hook that will log the number of times the component has rerendered
|
||||
@@ -12,22 +15,29 @@ export const GenerationRootComponent = () => {
|
||||
useSignals();
|
||||
|
||||
const generationState = generationSignal.value;
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||
const [isInputFocused, setIsInputFocused] = useState(false);
|
||||
|
||||
const handleGenerate = () => {
|
||||
if (!inputRef.current) {
|
||||
return
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: For testing only!!! Remove when finished
|
||||
if (generationState.loadingState === GenerationLoadingState.GENERATING) {
|
||||
generationSignal.value = { loadingState: GenerationLoadingState.INIT, prompt: "" };
|
||||
generationSignal.value = {
|
||||
loadingState: GenerationLoadingState.INIT,
|
||||
prompt: "",
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const prompt = inputRef.current.value;
|
||||
generationSignal.value = { loadingState: GenerationLoadingState.GENERATING, prompt };
|
||||
|
||||
generationSignal.value = {
|
||||
loadingState: GenerationLoadingState.GENERATING,
|
||||
prompt,
|
||||
};
|
||||
|
||||
// TODO: Write the signal change effect in generationSignals.ts
|
||||
// TODO: Call the server to generate the image
|
||||
};
|
||||
@@ -35,15 +45,17 @@ export const GenerationRootComponent = () => {
|
||||
let contentElement;
|
||||
switch (generationState.loadingState) {
|
||||
case GenerationLoadingState.INIT:
|
||||
contentElement =
|
||||
<div className="flex flex-col items-center justify-center mb-32">
|
||||
<span className="text-9xl font-bold">Generate Image</span>
|
||||
<span className="text-3xl pt-2">Imagine, Describe, Generate.</span>
|
||||
</div>
|
||||
break;
|
||||
contentElement = null;
|
||||
break;
|
||||
case GenerationLoadingState.GENERATING:
|
||||
case GenerationLoadingState.GENERATED:
|
||||
contentElement = <GenerationContent loadingState={generationState.loadingState} imageB64={generationState.imageB64} prompt={generationState.prompt} />
|
||||
contentElement = (
|
||||
<GenerationContent
|
||||
loadingState={generationState.loadingState}
|
||||
imageB64={generationState.imageB64}
|
||||
prompt={generationState.prompt}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -52,50 +64,129 @@ export const GenerationRootComponent = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="fixed flex flex-col items-center justify-center w-full h-full gap-y-12 transition-all duration-1000">
|
||||
<div className="fixed z-10 flex h-full w-full flex-col items-center justify-center transition-all duration-500">
|
||||
<Transition
|
||||
show={generationState.loadingState !== GenerationLoadingState.INIT}
|
||||
enter="transition-opacity duration-500"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="transition-opacity duration-500"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
as="div"
|
||||
>
|
||||
{contentElement}
|
||||
<div className="glass p-3 rounded-xl flex w-1/2 border-ui-panel border-2">
|
||||
<input ref={inputRef} type="text" placeholder="Describe what you want to see..." className="flex-1 text-lg rounded-md bg-transparent focus:outline-none" />
|
||||
<Button icon={faSparkles} variant="primary" className="text-lg" onClick={handleGenerate} disabled={isButtonDisabled}>Generate</Button>
|
||||
</Transition>
|
||||
<div
|
||||
className={twMerge(
|
||||
"absolute flex w-full flex-col items-center justify-center gap-16 transition-all duration-500",
|
||||
generationState.loadingState === GenerationLoadingState.INIT
|
||||
? "bottom-1/2 translate-y-1/2 transform"
|
||||
: "bottom-8",
|
||||
)}
|
||||
>
|
||||
<Transition
|
||||
show={generationState.loadingState === GenerationLoadingState.INIT}
|
||||
enter="transition-opacity duration-200"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="transition-opacity duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
as="div"
|
||||
>
|
||||
<div className="flex flex-col items-center justify-center text-center drop-shadow-xl">
|
||||
<span className="text-8xl font-bold">Generate Image</span>
|
||||
<span className="pt-2 text-2xl opacity-80">
|
||||
Imagine, Describe, Generate.
|
||||
</span>
|
||||
</div>
|
||||
</Transition>
|
||||
<div
|
||||
className={twMerge(
|
||||
"glass flex min-h-[56px] w-full max-w-[900px] items-end rounded-xl border-2 p-3 shadow-xl transition-all duration-[400ms] ease-in-out",
|
||||
isInputFocused ? "border-primary-400/60" : "border-ui-panel",
|
||||
)}
|
||||
>
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
rows={1}
|
||||
placeholder="Describe what you want to see..."
|
||||
className="max-h-[120px] min-h-[40px] flex-1 resize-none overflow-y-auto rounded-md bg-transparent px-3 py-2 text-lg leading-normal focus:outline-none"
|
||||
style={{ lineHeight: "24px" }}
|
||||
onInput={(e) => {
|
||||
const target = e.target as HTMLTextAreaElement;
|
||||
target.style.height = "auto";
|
||||
target.style.height = `${target.scrollHeight}px`;
|
||||
}}
|
||||
onFocus={() => setIsInputFocused(true)}
|
||||
onBlur={() => setIsInputFocused(false)}
|
||||
/>
|
||||
<Button
|
||||
icon={faSparkles}
|
||||
variant="primary"
|
||||
className="text-md ml-2 self-end"
|
||||
onClick={handleGenerate}
|
||||
disabled={isButtonDisabled}
|
||||
>
|
||||
Generate
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Transition
|
||||
show={generationState.loadingState === GenerationLoadingState.INIT}
|
||||
enter="transition-opacity duration-700"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="transition-opacity duration-700"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
as="div"
|
||||
>
|
||||
<BackgroundGallery />
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const GenerationContent = (generationState : {
|
||||
const GenerationContent = (generationState: {
|
||||
loadingState: GenerationLoadingState;
|
||||
imageB64?: string;
|
||||
prompt: string;
|
||||
}) => {
|
||||
|
||||
const imgBoxStyle = {
|
||||
width: "1024px",
|
||||
height: "1024px",
|
||||
}
|
||||
|
||||
let imgBoxContent;
|
||||
if (generationState.loadingState === GenerationLoadingState.GENERATING) {
|
||||
imgBoxContent = (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<LoadingSpinner isShowing={true} message="Generating image..." />
|
||||
</div>
|
||||
)
|
||||
} else if (generationState.loadingState === GenerationLoadingState.GENERATED) {
|
||||
);
|
||||
} else if (
|
||||
generationState.loadingState === GenerationLoadingState.GENERATED
|
||||
) {
|
||||
imgBoxContent = (
|
||||
<img src={generationState.imageB64} alt="Generated Image" />
|
||||
)
|
||||
<img
|
||||
src={generationState.imageB64}
|
||||
alt="Generated Image"
|
||||
className="h-full w-full object-contain"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center" >
|
||||
<div style={imgBoxStyle} className="bg-ui-border rounded-lg">
|
||||
{imgBoxContent}
|
||||
<>
|
||||
<div className="absolute inset-0 flex h-full w-full flex-col items-center justify-center">
|
||||
<div className="aspect-[1/1] w-[min(80vw,68vh)] rounded-t-lg bg-[#29292D]/60">
|
||||
{imgBoxContent}
|
||||
</div>
|
||||
<div className="mb-16 flex w-[min(80vw,68vh)] rounded-b-lg bg-[#29292D]">
|
||||
<span className="p-3.5 text-sm opacity-50">
|
||||
{generationState.prompt || "No prompt provided"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-ui-panel w-full min-h-12 flex items-center justify-center">
|
||||
<span className="text-lg p-4">{generationState.prompt}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -27,7 +27,9 @@ export const ToolbarTopLeft = () => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={twMerge("z-20 flex h-fit w-fit items-center gap-8 pl-1 pr-4")}
|
||||
className={twMerge(
|
||||
"relative z-50 flex h-fit w-fit items-center gap-8 pl-1 pr-4",
|
||||
)}
|
||||
>
|
||||
<img
|
||||
src="/brand/mira-logo.png"
|
||||
|
||||
@@ -22,7 +22,9 @@ export const ToolbarTopRight = () => {
|
||||
const [aspectRatio, setAspectRatio] = useState("1:1");
|
||||
|
||||
return (
|
||||
<div className={twMerge("z-20 flex h-fit w-fit items-center gap-2")}>
|
||||
<div
|
||||
className={twMerge("relative z-50 flex h-fit w-fit items-center gap-2")}
|
||||
>
|
||||
{import.meta.env.DEV && ( // only show in dev mode for now
|
||||
<ToggleButton
|
||||
label="Ratio"
|
||||
|
||||
@@ -200,6 +200,14 @@
|
||||
"@babel/helper-validator-identifier" "^7.24.7"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@devnomic/marquee@^1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.npmjs.org/@devnomic/marquee/-/marquee-1.0.3.tgz"
|
||||
integrity sha512-q8QUtNO8ff+UvfrKJsHBtxh4fbOfhGk6SSdK0Uu/XZFdEzrmiGJ/l0FuVHRYsPAmzgnhVVnz1dGNB5CWGLMVvw==
|
||||
dependencies:
|
||||
clsx "^2.1.0"
|
||||
tailwind-merge "^2.2.0"
|
||||
|
||||
"@emotion/is-prop-valid@^1.1.0":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.0.tgz"
|
||||
@@ -910,14 +918,14 @@
|
||||
resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz"
|
||||
integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==
|
||||
|
||||
"@types/react-dom@^18.3.0":
|
||||
"@types/react-dom@^18.2.18", "@types/react-dom@^18.3.0":
|
||||
version "18.3.0"
|
||||
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz"
|
||||
integrity sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react@*", "@types/react@^18.3.3":
|
||||
"@types/react@*", "@types/react@^18.2.47", "@types/react@^18.3.3":
|
||||
version "18.3.4"
|
||||
resolved "https://registry.npmjs.org/@types/react/-/react-18.3.4.tgz"
|
||||
integrity sha512-J7W30FTdfCxDDjmfRM+/JqLHBIyl7xUIp9kwK637FGmY7+mkSFSe6L4jpZzhj5QMfLssSDP4/i75AKkrdC7/Jw==
|
||||
@@ -1416,7 +1424,7 @@ cli-columns@^4.0.0:
|
||||
string-width "^4.2.3"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
clsx@^2.0.0:
|
||||
clsx@^2.0.0, clsx@^2.1.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz"
|
||||
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
|
||||
@@ -3635,7 +3643,7 @@ react-colorful@^5.6.1:
|
||||
resolved "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz"
|
||||
integrity sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==
|
||||
|
||||
"react-dom@^16.8.0 || ^17.0.0 || ^18.0.0", react-dom@^18, react-dom@^18.0.0, "react-dom@^18.0.0 || ^19.0.0 || ^19.0.0-rc", react-dom@^18.3.1, "react-dom@>= 16.8.0", react-dom@>=16.4.0, react-dom@>=16.8, react-dom@>=16.8.0:
|
||||
"react-dom@^16.8.0 || ^17.0.0 || ^18.0.0", react-dom@^18, react-dom@^18.0.0, "react-dom@^18.0.0 || ^19.0.0 || ^19.0.0-rc", react-dom@^18.2.0, react-dom@^18.3.1, "react-dom@>= 16.8.0", react-dom@>=16.4.0, react-dom@>=16.8, react-dom@>=16.8.0:
|
||||
version "18.3.1"
|
||||
resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz"
|
||||
integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
|
||||
@@ -3684,7 +3692,7 @@ react-router@6.26.1:
|
||||
dependencies:
|
||||
"@remix-run/router" "1.19.1"
|
||||
|
||||
"react@^16.14.0 || 17.x || 18.x", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0", react@^18, react@^18.0.0, "react@^18.0.0 || ^19.0.0 || ^19.0.0-rc", react@^18.3.1, "react@>= 16.8.0", react@>=16.3, react@>=16.4.0, react@>=16.8, react@>=16.8.0:
|
||||
"react@^16.14.0 || 17.x || 18.x", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0", react@^18, react@^18.0.0, "react@^18.0.0 || ^19.0.0 || ^19.0.0-rc", react@^18.2.0, react@^18.3.1, "react@>= 16.8.0", react@>=16.3, react@>=16.4.0, react@>=16.8, react@>=16.8.0:
|
||||
version "18.3.1"
|
||||
resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz"
|
||||
integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
|
||||
@@ -4180,7 +4188,7 @@ tabbable@^6.0.0:
|
||||
resolved "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz"
|
||||
integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==
|
||||
|
||||
tailwind-merge@^2.5.2:
|
||||
tailwind-merge@^2.2.0, tailwind-merge@^2.5.2:
|
||||
version "2.5.2"
|
||||
resolved "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.2.tgz"
|
||||
integrity sha512-kjEBm+pvD+6eAwzJL2Bi+02/9LFLal1Gs61+QB7HvTfQQ0aXwC5LGT8PEt1gS0CWKktKe6ysPTAy3cBC5MeiIg==
|
||||
|
||||