mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 09:28:11 +00:00
new landing completed (#583)
new landing new landing update styling updates update more updates marquee section updates buttons update landing email sign up update mobile update hanashi hide small screen removed unused package updates mobile video size fix mobile remove laptop because video too small cta for logged in users with access
This commit is contained in:
1 parent
ee2226a16d
commit
dde3dfa2df
31 files changed
+978
-43
No files matched your search
+1
-1
@@ -33,7 +33,7 @@
|
||||
"dependencies": {
|
||||
"@react-spring/types": "^9.7.3",
|
||||
"@typeform/embed": "^4.8.1",
|
||||
"@typeform/embed-react": "^3.15.0",
|
||||
"framer-motion": "6.5.1",
|
||||
"local-storage": "^2.0.0",
|
||||
"moment": "^2.29.4",
|
||||
"universal-cookie": "^6.1.1",
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"@testing-library/react": "^9.5.0",
|
||||
"@testing-library/user-event": "^7.2.1",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@typeform/embed-react": "^3.17.0",
|
||||
"@types/axios": "^0.14.0",
|
||||
"@types/deep-equal": "^1.0.3",
|
||||
"@types/google.analytics": "^0.0.42",
|
||||
@@ -64,6 +65,7 @@
|
||||
"react-device-detect": "^2.2.3",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-drag-drop-files": "^2.3.9",
|
||||
"react-fast-marquee": "^1.6.4",
|
||||
"react-i18next": "^11.15.4",
|
||||
"react-iframe": "^1.8.5",
|
||||
"react-infinite-scroll-component": "^6.1.0",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 602 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 542 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 437 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -55,7 +55,6 @@ $headings-font-family: "Fira Sans", sans-serif;
|
||||
#main {
|
||||
position: relative; // Footer attachment.
|
||||
min-height: calc(100vh - 66px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.social {
|
||||
|
||||
@@ -113,6 +113,10 @@
|
||||
border: none;
|
||||
}
|
||||
|
||||
.button-font-large {
|
||||
font-size: 18px !important;
|
||||
}
|
||||
|
||||
.button-link {
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
|
||||
@@ -24,6 +24,7 @@ export default function Button({
|
||||
isLoading = false,
|
||||
isActive,
|
||||
type = "button",
|
||||
fontLarge,
|
||||
...rest
|
||||
}: ButtonProps) {
|
||||
let variant = variantProps;
|
||||
@@ -60,6 +61,8 @@ export default function Button({
|
||||
|
||||
const commonProps = {
|
||||
className: `${externalClass} button ${small ? "button-small" : ""} ${
|
||||
fontLarge ? "button-font-large" : ""
|
||||
} ${
|
||||
square ? (small ? "button-square-small" : "button-square") : ""
|
||||
} button-${variant} ${full ? "w-100" : ""}`,
|
||||
disabled: disabled || isLoading,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { ButtonHTMLAttributes } from "react";
|
||||
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
|
||||
export default interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
export default interface ButtonProps
|
||||
extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
buttonRef?: any;
|
||||
label?: string | null;
|
||||
icon?: IconDefinition;
|
||||
@@ -19,4 +20,5 @@ export default interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElem
|
||||
disabled?: boolean;
|
||||
isLoading?: boolean;
|
||||
isActive?: boolean;
|
||||
}
|
||||
fontLarge?: boolean;
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
@import "scss/colours.scss";
|
||||
|
||||
.topbar-bg-transparent {
|
||||
background-color: transparent;
|
||||
background-color: transparent !important;
|
||||
border-bottom: 1px solid transparent !important;
|
||||
}
|
||||
|
||||
.topbar-bg-dark {
|
||||
|
||||
@@ -215,9 +215,9 @@ export default function TopNav({
|
||||
domain.titlePart === "Storyteller AI" &&
|
||||
isOnLandingPage
|
||||
) {
|
||||
topBarWrapper.classList.add("topbar-hide-top");
|
||||
topBarWrapper.classList.add("topbar-bg-transparent");
|
||||
} else {
|
||||
topBarWrapper?.classList.remove("topbar-hide-top");
|
||||
topBarWrapper?.classList.remove("topbar-bg-transparent");
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@@ -227,10 +227,10 @@ export default function TopNav({
|
||||
domain.titlePart === "Storyteller AI" &&
|
||||
isOnLandingPage
|
||||
) {
|
||||
if (window.scrollY > 100) {
|
||||
topBarWrapper.classList.remove("topbar-hide-top");
|
||||
if (window.scrollY > 500) {
|
||||
topBarWrapper.classList.remove("topbar-bg-transparent");
|
||||
} else {
|
||||
topBarWrapper.classList.add("topbar-hide-top");
|
||||
topBarWrapper.classList.add("topbar-bg-transparent");
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -349,7 +349,7 @@ export default function TopNav({
|
||||
|
||||
return (
|
||||
<>
|
||||
{domain.titlePart === "Storyteller AI" &&
|
||||
{/* {domain.titlePart === "Storyteller AI" &&
|
||||
isOnLandingPage &&
|
||||
!isScrolled && (
|
||||
<div
|
||||
@@ -394,7 +394,7 @@ export default function TopNav({
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
)} */}
|
||||
<div
|
||||
id="topbar-wrapper"
|
||||
className={`position-fixed ${
|
||||
@@ -478,17 +478,18 @@ export default function TopNav({
|
||||
</div>
|
||||
|
||||
<div className="topbar-nav-right">
|
||||
{domain.titlePart === "Storyteller AI" && (
|
||||
<div className="d-none d-lg-block">
|
||||
<Button
|
||||
icon={faPortalEnter}
|
||||
label="Enter Storyteller Studio"
|
||||
href="https://studio.storyteller.ai/"
|
||||
small={true}
|
||||
className="me-2"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{domain.titlePart === "Storyteller AI" &&
|
||||
sessionWrapper.canAccessStudio() && (
|
||||
<div className="d-none d-lg-block">
|
||||
<Button
|
||||
icon={faPortalEnter}
|
||||
label="Enter Storyteller Studio"
|
||||
href="https://studio.storyteller.ai/"
|
||||
small={true}
|
||||
className="me-2"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="d-flex align-items-center gap-2">
|
||||
<div className="d-none d-lg-flex gap-2">
|
||||
|
||||
@@ -298,4 +298,8 @@ $topbar-height: 66px;
|
||||
.sidebar-nav {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#page-content-wrapper {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
@@ -91,9 +91,9 @@ styles with important tags:
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow-y: scroll;
|
||||
}
|
||||
// body {
|
||||
// overflow-y: scroll;
|
||||
// }
|
||||
|
||||
p {
|
||||
font-weight: 400;
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
faSignOutAlt,
|
||||
faBook,
|
||||
faStar,
|
||||
faWandMagicSparkles
|
||||
faWandMagicSparkles,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { faDiscord } from "@fortawesome/free-brands-svg-icons";
|
||||
import { Logout } from "@storyteller/components/src/api/session/Logout";
|
||||
@@ -160,7 +160,7 @@ function TopNav(props: Props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="d-none d-lg-block">
|
||||
<div className="d-none d-lg-block overflow-hidden vw-100">
|
||||
<nav
|
||||
className="navbar navbar-expand-lg navbar-dark pt-3"
|
||||
aria-label="Offcanvas navbar large"
|
||||
|
||||
@@ -18,7 +18,8 @@ import {
|
||||
} from "@storyteller/components/src/env/GetWebsite";
|
||||
// import OnboardingSelection from "./storyteller/OnboardingSelection";
|
||||
// import TtsDemoSection from "./components/TtsDemoSection/TtsDemoSection";
|
||||
import PrelaunchLanding from "./storyteller/PrelaunchLanding/PrelaunchLanding";
|
||||
// import PrelaunchLanding from "./storyteller/PrelaunchLanding/PrelaunchLanding";
|
||||
import PostlaunchLanding from "./storyteller/PostlaunchLanding/PostlaunchLanding";
|
||||
|
||||
interface Props {
|
||||
sessionWrapper: SessionWrapper;
|
||||
@@ -54,7 +55,8 @@ function LandingPage(props: Props) {
|
||||
<>
|
||||
{domain.website === Website.StorytellerAi && (
|
||||
// <LandingVideoReel sessionWrapper={props.sessionWrapper} />
|
||||
<PrelaunchLanding sessionWrapper={props.sessionWrapper} />
|
||||
// <PrelaunchLanding sessionWrapper={props.sessionWrapper} />
|
||||
<PostlaunchLanding sessionWrapper={props.sessionWrapper} />
|
||||
)}
|
||||
<Container type="panel">
|
||||
{
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
import React, { useState } from "react";
|
||||
import { Widget } from "@typeform/embed-react";
|
||||
|
||||
interface EmailSignUpProps {
|
||||
mobile?: boolean;
|
||||
}
|
||||
|
||||
export default function EmailSignUp({ mobile }: EmailSignUpProps) {
|
||||
const [isHanashiHovered, setIsHanashiHovered] = useState(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-center mb-5 fw-bold display-5">
|
||||
Join the waitlist today!
|
||||
</h1>
|
||||
<div
|
||||
style={{
|
||||
paddingTop: mobile ? "0px" : "60px",
|
||||
backgroundColor: "#242433",
|
||||
borderRadius: "1rem",
|
||||
borderTop: "3px solid #e66462",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
{!mobile && (
|
||||
<img
|
||||
src={
|
||||
isHanashiHovered
|
||||
? "/images/landing/hanashi-demo-2.webp"
|
||||
: "/images/landing/hanashi-demo-1.webp"
|
||||
}
|
||||
alt="Hanashi Demo"
|
||||
onMouseEnter={() => setIsHanashiHovered(true)}
|
||||
onMouseLeave={() => setIsHanashiHovered(false)}
|
||||
draggable="false"
|
||||
style={{
|
||||
top: "-49%",
|
||||
right: "3%",
|
||||
width: "390px",
|
||||
position: "absolute",
|
||||
}}
|
||||
className="d-none d-xxl-block"
|
||||
/>
|
||||
)}
|
||||
|
||||
<Widget id="TKSc5ImN" style={{ width: "100%", height: "300px" }} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
import React from "react";
|
||||
import { useFeatureStore } from "./store";
|
||||
|
||||
interface FeatureCardProps extends CardProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
interface CardProps {
|
||||
id: string;
|
||||
}
|
||||
|
||||
function FeatureCard({ children, id }: FeatureCardProps) {
|
||||
const inViewFeature = useFeatureStore((state: any) => state.inViewFeature);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="h-100 w-100 overflow-hidden"
|
||||
style={{
|
||||
opacity: inViewFeature === id ? 1 : 0,
|
||||
transition: "all 0.25s",
|
||||
borderRadius: "1rem",
|
||||
border: "2px solid rgba(255, 255, 255, 0.06)",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function BuildScene({ id }: CardProps) {
|
||||
return (
|
||||
<FeatureCard id={id}>
|
||||
<video
|
||||
src="/videos/landing/build_scene.mp4"
|
||||
className="object-fit-contain w-100 h-100"
|
||||
preload="metadata"
|
||||
muted={true}
|
||||
autoPlay={true}
|
||||
controls={false}
|
||||
loop={true}
|
||||
playsInline={true}
|
||||
/>
|
||||
</FeatureCard>
|
||||
);
|
||||
}
|
||||
|
||||
export function Animate({ id }: CardProps) {
|
||||
return (
|
||||
<FeatureCard id={id}>
|
||||
<video
|
||||
src="/videos/landing/animate_scene.mp4"
|
||||
className="object-fit-contain w-100 h-100"
|
||||
preload="metadata"
|
||||
muted={true}
|
||||
autoPlay={true}
|
||||
controls={false}
|
||||
loop={true}
|
||||
playsInline={true}
|
||||
/>
|
||||
</FeatureCard>
|
||||
);
|
||||
}
|
||||
|
||||
export function SelectStyle({ id }: CardProps) {
|
||||
return (
|
||||
<FeatureCard id={id}>
|
||||
<video
|
||||
src="/videos/landing/select_style.mp4"
|
||||
className="object-fit-contain w-100 h-100"
|
||||
preload="metadata"
|
||||
muted={true}
|
||||
autoPlay={true}
|
||||
controls={false}
|
||||
loop={true}
|
||||
playsInline={true}
|
||||
/>
|
||||
</FeatureCard>
|
||||
);
|
||||
}
|
||||
|
||||
export function GenerateMovie({ id }: CardProps) {
|
||||
return (
|
||||
<FeatureCard id={id}>
|
||||
<video
|
||||
src="/videos/landing/generate_movie.mp4"
|
||||
className="object-fit-contain w-100 h-100"
|
||||
preload="metadata"
|
||||
muted={true}
|
||||
autoPlay={true}
|
||||
controls={false}
|
||||
loop={true}
|
||||
playsInline={true}
|
||||
/>
|
||||
</FeatureCard>
|
||||
);
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { useInView } from "framer-motion";
|
||||
import { IconDefinition } from "@fortawesome/fontawesome-common-types";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useFeatureStore } from "./store";
|
||||
|
||||
interface FeatureTitleProps {
|
||||
title: string;
|
||||
description: string;
|
||||
icon: IconDefinition;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export default function FeatureTitle({
|
||||
title,
|
||||
description,
|
||||
icon,
|
||||
id,
|
||||
}: FeatureTitleProps) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const isInView = useInView(ref, { margin: "-50% 0px -50% 0px" });
|
||||
const setInViewFeature = useFeatureStore(
|
||||
(state: any) => state.setInViewFeature
|
||||
);
|
||||
const inViewFeature = useFeatureStore((state: any) => state.inViewFeature);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInView) {
|
||||
setInViewFeature(id);
|
||||
}
|
||||
if (!isInView && inViewFeature === id) {
|
||||
setInViewFeature(null);
|
||||
}
|
||||
}, [isInView, id, setInViewFeature, inViewFeature]);
|
||||
|
||||
return (
|
||||
<div ref={ref} style={{ paddingBottom: "120px", position: "relative" }}>
|
||||
<div
|
||||
style={{
|
||||
padding: "2.5rem",
|
||||
transition: "all 0.25s",
|
||||
opacity: !isInView ? 0.2 : 1,
|
||||
transform: !isInView ? "scale(0.90)" : "scale(1)",
|
||||
transformOrigin: "right center",
|
||||
backgroundColor: "#242433",
|
||||
border: !isInView
|
||||
? "2px solid rgba(255, 255, 255, 0.04)"
|
||||
: "2px solid #e66462",
|
||||
borderRadius: "1rem",
|
||||
position: "relative",
|
||||
zIndex: 2,
|
||||
}}
|
||||
>
|
||||
<h2 className="fs-2 fw-bold mb-4">
|
||||
<FontAwesomeIcon icon={icon} className="me-3" />
|
||||
{title}
|
||||
</h2>
|
||||
<p className="opacity-75" style={{ fontSize: "18px" }}>
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
{/* <div
|
||||
style={{
|
||||
width: "10px",
|
||||
height: "140px",
|
||||
position: "absolute",
|
||||
left: "50%",
|
||||
bottom: "-10px",
|
||||
backgroundColor: "rgba(255, 255, 255, 0.05)",
|
||||
zIndex: 1,
|
||||
display: isLastChild ? "none" : "block", // Conditionally show/hide based on being the last child
|
||||
}}
|
||||
/> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+483
@@ -0,0 +1,483 @@
|
||||
import {
|
||||
Website,
|
||||
WebsiteConfig,
|
||||
} from "@storyteller/components/src/env/GetWebsite";
|
||||
import { usePrefixedDocumentTitle } from "common/UsePrefixedDocumentTitle";
|
||||
import { Button } from "components/common";
|
||||
import { useDomainConfig } from "context/DomainConfigContext";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useScroll, useTransform, motion } from "framer-motion";
|
||||
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
|
||||
import FeatureTitle from "./FeatureTitle";
|
||||
import { Animate, BuildScene, GenerateMovie, SelectStyle } from "./FeatureCard";
|
||||
import {
|
||||
faArrowRight,
|
||||
faCube,
|
||||
faFilm,
|
||||
faPaintbrushPencil,
|
||||
faPersonRunning,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import ScrollingSceneCarousel from "./ScrollingSceneCarousel";
|
||||
import EmailSignUp from "./EmailSignUp";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
interface PostlaunchLandingProps {
|
||||
sessionWrapper: SessionWrapper;
|
||||
}
|
||||
|
||||
export default function PostlaunchLanding(props: PostlaunchLandingProps) {
|
||||
const [imageHeight, setImageHeight] = useState("100vh");
|
||||
const domain: WebsiteConfig = useDomainConfig();
|
||||
|
||||
const webpageTitle =
|
||||
domain.website === Website.FakeYou
|
||||
? "FakeYou Celebrity Voice Generator"
|
||||
: "AI Creation Engine";
|
||||
|
||||
usePrefixedDocumentTitle(webpageTitle);
|
||||
|
||||
const [isMobile, setIsMobile] = useState(window.innerWidth <= 992);
|
||||
|
||||
const handleResize = () => {
|
||||
setIsMobile(window.innerWidth <= 992);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("resize", handleResize);
|
||||
return () => {
|
||||
window.removeEventListener("resize", handleResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const updateImageSize = () => {
|
||||
const headerHeight =
|
||||
document.querySelector(".header-container")?.clientHeight || 0;
|
||||
const topPadding = 20;
|
||||
const bottomPadding = 50;
|
||||
const windowHeight = window.innerHeight;
|
||||
const availableHeight =
|
||||
windowHeight - headerHeight - topPadding - bottomPadding;
|
||||
setImageHeight(`${availableHeight}px`);
|
||||
};
|
||||
|
||||
window.addEventListener("resize", updateImageSize);
|
||||
|
||||
updateImageSize();
|
||||
|
||||
return () => window.removeEventListener("resize", updateImageSize);
|
||||
}, []);
|
||||
|
||||
const [isSmallScreen, setIsSmallScreen] = useState(window.innerHeight > 1000);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
setIsSmallScreen(window.innerHeight > 1000);
|
||||
};
|
||||
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, []);
|
||||
|
||||
let scaleFactor;
|
||||
if (isSmallScreen) {
|
||||
scaleFactor = 3.5;
|
||||
} else {
|
||||
scaleFactor = 4.5;
|
||||
}
|
||||
|
||||
const container = useRef(null);
|
||||
const { scrollYProgress } = useScroll({
|
||||
target: container,
|
||||
offset: ["start start", "end end"],
|
||||
});
|
||||
|
||||
const scaleFull = useTransform(scrollYProgress, [0, 1], [1, scaleFactor]);
|
||||
const opacityLaptop = useTransform(scrollYProgress, [0, 0.5], [1, 0]);
|
||||
const opacityTitle = useTransform(scrollYProgress, [0, 0.2], [1, 0]);
|
||||
const opacityOverlay = useTransform(scrollYProgress, [0.6, 1], [0, 1]);
|
||||
|
||||
const ctaButton = props.sessionWrapper.canAccessStudio() ? (
|
||||
<div className="d-flex">
|
||||
<Button
|
||||
label="Enter Storyteller Studio"
|
||||
className="mt-4"
|
||||
fontLarge={true}
|
||||
icon={faArrowRight}
|
||||
iconFlip={true}
|
||||
href="https://studio.storyteller.ai/"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="d-flex">
|
||||
<Button
|
||||
label="Join the Waitlist"
|
||||
className="mt-4"
|
||||
fontLarge={true}
|
||||
icon={faArrowRight}
|
||||
iconFlip={true}
|
||||
href="https://7mjlxvmjq8u.typeform.com/to/ZQTkv9ha"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isMobile ? (
|
||||
// MOBILE VIEW
|
||||
<div className="container">
|
||||
<div
|
||||
className="header-container text-center d-flex flex-column align-items-center justify-content-center mb-5"
|
||||
style={{ paddingTop: "80px" }}
|
||||
>
|
||||
<h1 className="display-1 fw-bold mt-4">{firstTitle}</h1>
|
||||
<p className="lead fw-medium fs-4 opacity-75">{firstSubtext}</p>
|
||||
{ctaButton}
|
||||
</div>
|
||||
<div
|
||||
className="d-flex justify-content-center align-items-center position-relative"
|
||||
style={{
|
||||
width: "100%",
|
||||
zIndex: 10,
|
||||
}}
|
||||
>
|
||||
<video
|
||||
src="/videos/landing/hero_video.mp4"
|
||||
preload="metadata"
|
||||
style={{
|
||||
width: "100%",
|
||||
maxWidth: "900px",
|
||||
borderRadius: "0.75rem",
|
||||
overflow: "hidden",
|
||||
border: "2px solid rgba(255, 255, 255, 0.1)",
|
||||
}}
|
||||
autoPlay={true}
|
||||
controls={false}
|
||||
muted={true}
|
||||
loop={true}
|
||||
playsInline={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: "60px", marginBottom: "60px" }}>
|
||||
<ScrollingSceneCarousel small={true} />
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="container d-flex flex-column text-center justify-content-center align-items-center w-100"
|
||||
style={{ marginBottom: "70px" }}
|
||||
>
|
||||
<div className="d-flex flex-column align-items-center">
|
||||
<h2 className="display-4 fw-bold mt-4">{secondTitle}</h2>
|
||||
<p className="lead fw-normal fs-5 opacity-75">{secondSubtext}</p>
|
||||
{ctaButton}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="container d-flex flex-column gap-5">
|
||||
{features.map(feature => (
|
||||
<li key={feature.id} className="list-unstyled d-flex flex-column">
|
||||
<video
|
||||
src={feature.video}
|
||||
className="object-fit-contain w-100 h-100 mb-4"
|
||||
preload="metadata"
|
||||
muted={true}
|
||||
autoPlay={true}
|
||||
controls={false}
|
||||
loop={true}
|
||||
playsInline={true}
|
||||
style={{ borderRadius: "1rem" }}
|
||||
/>
|
||||
<h2 className="fs-2 fw-bold mb-3">
|
||||
<FontAwesomeIcon icon={feature.icon} className="me-3" />
|
||||
{feature.title}
|
||||
</h2>
|
||||
<p className="opacity-75 mb-5">{feature.description}</p>
|
||||
</li>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="container d-flex flex-column align-items-center"
|
||||
style={{ marginTop: "60px" }}
|
||||
>
|
||||
<h1 className="display-5 fw-bold">Showcase</h1>
|
||||
<p className="lead text-center fw-medium opacity-75 fs-5 mb-5">
|
||||
Videos created with Storyteller Studio.
|
||||
</p>
|
||||
<video
|
||||
src="/videos/landing/landing_reel.mp4"
|
||||
poster="/images/landing/storyteller/Landing_Reel_Poster.png"
|
||||
preload="metadata"
|
||||
style={{
|
||||
width: "100%",
|
||||
maxWidth: "900px",
|
||||
borderRadius: "0.75rem",
|
||||
overflow: "hidden",
|
||||
border: "2px solid rgba(255, 255, 255, 0.1)",
|
||||
}}
|
||||
autoPlay={true}
|
||||
controls={false}
|
||||
muted={true}
|
||||
loop={true}
|
||||
playsInline={true}
|
||||
/>
|
||||
{props.sessionWrapper.isLoggedIn() && ctaButton}
|
||||
</div>
|
||||
|
||||
{!props.sessionWrapper.canAccessStudio() ? (
|
||||
<div style={{ marginTop: "100px" }}>
|
||||
<EmailSignUp mobile={true} />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : (
|
||||
// DESKTOP VIEW
|
||||
<div>
|
||||
<div
|
||||
ref={container}
|
||||
style={{
|
||||
height: "150vh",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="vh-100 w-100"
|
||||
style={{ position: "sticky", top: 0, overflow: "hidden" }}
|
||||
>
|
||||
<motion.div
|
||||
className="header-container text-center d-flex flex-column align-items-center justify-content-center"
|
||||
style={{ paddingTop: "50px", opacity: opacityTitle }}
|
||||
>
|
||||
<h1 className="display-1 fw-bold mt-4">{firstTitle}</h1>
|
||||
<p className="lead fw-medium fs-4 opacity-75">{firstSubtext}</p>
|
||||
{ctaButton}
|
||||
</motion.div>
|
||||
<div
|
||||
style={{
|
||||
flexGrow: 1,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: "32px 0",
|
||||
}}
|
||||
>
|
||||
<motion.div
|
||||
className="d-flex justify-content-center align-items-center position-relative"
|
||||
style={{
|
||||
width: "100%",
|
||||
maxHeight: imageHeight,
|
||||
scale: scaleFull,
|
||||
zIndex: 10,
|
||||
}}
|
||||
>
|
||||
<video
|
||||
src="/videos/landing/hero_video.mp4"
|
||||
className="position-absolute"
|
||||
preload="metadata"
|
||||
style={{
|
||||
height: "52%",
|
||||
top: "20.55%",
|
||||
zIndex: 10,
|
||||
borderRadius: "0.25rem",
|
||||
}}
|
||||
autoPlay={true}
|
||||
controls={false}
|
||||
muted={true}
|
||||
loop={true}
|
||||
playsInline={true}
|
||||
/>
|
||||
<motion.div
|
||||
className="position-absolute"
|
||||
style={{
|
||||
width: "40%",
|
||||
height: "52%",
|
||||
top: "20.55%",
|
||||
zIndex: 11,
|
||||
backgroundColor: "rgba(0, 0, 0, 0.7)",
|
||||
opacity: opacityOverlay,
|
||||
borderRadius: "0.25rem",
|
||||
}}
|
||||
/>
|
||||
<motion.img
|
||||
src="/images/landing/storyteller/Laptop_Storyteller_2.png"
|
||||
alt="Laptop"
|
||||
style={{
|
||||
maxHeight: imageHeight,
|
||||
objectFit: "contain",
|
||||
width: "auto",
|
||||
opacity: opacityLaptop,
|
||||
userSelect: "none",
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
/>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="position-absolute w-100"
|
||||
style={{ bottom: isSmallScreen ? "240px" : "150px" }}
|
||||
>
|
||||
<div className="d-flex flex-column justify-content-center align-items-center w-100">
|
||||
<div>
|
||||
<h2
|
||||
className="display-4 fw-bold mt-4"
|
||||
style={{ textShadow: "2px 2px 10px rgba(0, 0, 0, 0.3)" }}
|
||||
>
|
||||
{secondTitle}
|
||||
</h2>
|
||||
<p
|
||||
className="lead fw-medium fs-5 opacity-75"
|
||||
style={{ textShadow: "2px 2px 10px rgba(0, 0, 0, 0.3)" }}
|
||||
>
|
||||
{secondSubtext}
|
||||
</p>
|
||||
{ctaButton}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* SCENE CAROUSEL SECTION */}
|
||||
<ScrollingSceneCarousel />
|
||||
|
||||
<div className="container">
|
||||
<div className="text-center">
|
||||
<h1 className="fw-bold display-4" style={{ marginTop: "140px" }}>
|
||||
{thirdTitle}
|
||||
</h1>
|
||||
<p
|
||||
className="lead fw-medium opacity-75 fs-4"
|
||||
style={{ marginBottom: !isSmallScreen ? "-10%" : "-22%" }}
|
||||
>
|
||||
{thirdSubtext}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className="d-flex w-100 align-items-start"
|
||||
style={{ gap: "40px" }}
|
||||
>
|
||||
<div className="w-100">
|
||||
<ul
|
||||
className="list-unstyled"
|
||||
style={{ paddingTop: "50vh", paddingBottom: "33vh" }}
|
||||
>
|
||||
{features.map(feature => (
|
||||
<li key={feature.id}>
|
||||
<FeatureTitle
|
||||
id={feature.id}
|
||||
title={feature.title}
|
||||
icon={feature.icon}
|
||||
description={feature.description}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div
|
||||
className="w-100 position-sticky top-0 d-flex align-items-center justify-content-center"
|
||||
style={{ height: "100vh", marginTop: "120px" }}
|
||||
>
|
||||
<div
|
||||
className="ratio ratio-1x1"
|
||||
style={{
|
||||
width: !isSmallScreen ? "600px" : "650px",
|
||||
backgroundColor: "#242433",
|
||||
borderRadius: "1rem",
|
||||
}}
|
||||
>
|
||||
{features.map(feature => (
|
||||
<feature.card id={feature.id} key={feature.id} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="container d-flex flex-column align-items-center"
|
||||
style={{ marginTop: "-50px" }}
|
||||
>
|
||||
<h1 className="display-5 fw-bold">Showcase</h1>
|
||||
<p className="lead fw-medium opacity-75 fs-4 mb-5">
|
||||
Videos created with Storyteller Studio.
|
||||
</p>
|
||||
<video
|
||||
src="/videos/landing/landing_reel.mp4"
|
||||
poster="/images/landing/storyteller/Landing_Reel_Poster.png"
|
||||
preload="metadata"
|
||||
style={{
|
||||
width: "100%",
|
||||
maxWidth: "900px",
|
||||
borderRadius: "1rem",
|
||||
overflow: "hidden",
|
||||
border: "2px solid rgba(255, 255, 255, 0.1)",
|
||||
}}
|
||||
autoPlay={true}
|
||||
controls={false}
|
||||
muted={true}
|
||||
loop={true}
|
||||
playsInline={true}
|
||||
/>
|
||||
{props.sessionWrapper.isLoggedIn() && ctaButton}
|
||||
</div>
|
||||
|
||||
{!props.sessionWrapper.canAccessStudio() ? (
|
||||
<div style={{ marginTop: "260px" }}>
|
||||
<EmailSignUp />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const firstTitle = "Control Your Movie";
|
||||
const firstSubtext =
|
||||
"Effortlessly create your movie in any style you want with AI.";
|
||||
const secondTitle = "Your AI 3D Creation Engine";
|
||||
const secondSubtext =
|
||||
"Turn your creative ideas into stunning visuals, by simply building a 3D scene and generate.";
|
||||
const thirdTitle = "Filmmaking Made Easy";
|
||||
const thirdSubtext = "Just a few simple steps to creating your movie.";
|
||||
|
||||
const features = [
|
||||
{
|
||||
title: "Build your 3D scene",
|
||||
icon: faCube,
|
||||
description:
|
||||
" Storyteller Studio allows you to create and customize your 3D environment. Add characters, objects, and fine-tune details to craft the perfect scene for your movie.",
|
||||
id: "build-scene",
|
||||
card: BuildScene,
|
||||
video: "/videos/landing/build_scene.mp4",
|
||||
},
|
||||
{
|
||||
title: "Animate your scene",
|
||||
icon: faPersonRunning,
|
||||
description:
|
||||
"Bring your scene to life by adding animations to your characters and objects. Control movements to create dynamic visuals that engage your audience.",
|
||||
id: "animate-scene",
|
||||
card: Animate,
|
||||
video: "/videos/landing/animate_scene.mp4",
|
||||
},
|
||||
{
|
||||
title: "Select a style",
|
||||
icon: faPaintbrushPencil,
|
||||
description:
|
||||
"Choose from a variety of artistic styles to transform your 3D scene. Whether you prefer a realistic look or a more abstract approach, our AI can apply the style seamlessly.",
|
||||
id: "select-style",
|
||||
card: SelectStyle,
|
||||
video: "/videos/landing/select_style.mp4",
|
||||
},
|
||||
{
|
||||
title: "Generate your movie",
|
||||
icon: faFilm,
|
||||
description:
|
||||
"Let Storyteller Studio's AI process your scene and selected style to produce a stunning video. Sit back and watch as your 3D creation comes to life with the chosen visual art style.",
|
||||
id: "generate-movie",
|
||||
card: GenerateMovie,
|
||||
video: "/videos/landing/generate_movie.mp4",
|
||||
},
|
||||
];
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
import { faArrowRight } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { Button } from "components/common";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import React, { useState } from "react";
|
||||
|
||||
interface SceneCardProps {
|
||||
image: string;
|
||||
alt: string;
|
||||
title?: string;
|
||||
token?: string;
|
||||
small?: boolean;
|
||||
}
|
||||
|
||||
export default function SceneCard({
|
||||
image,
|
||||
alt,
|
||||
title,
|
||||
token,
|
||||
small,
|
||||
}: SceneCardProps) {
|
||||
const [showOverlay, setShowOverlay] = useState(false);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className="position-relative overflow-hidden rounded"
|
||||
style={{ width: "max-content", marginRight: small ? "24px" : "32px" }}
|
||||
onHoverStart={() => setShowOverlay(true)}
|
||||
onHoverEnd={() => setShowOverlay(false)}
|
||||
>
|
||||
<AnimatePresence>
|
||||
{showOverlay && (
|
||||
<motion.div
|
||||
className="position-absolute w-100 h-100 p-2"
|
||||
style={{ backgroundColor: "rgba(0, 0, 0, 0.3)" }}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<div className="d-flex justify-content-end w-100">
|
||||
<Button
|
||||
label="View"
|
||||
variant="action"
|
||||
small={true}
|
||||
icon={faArrowRight}
|
||||
iconFlip={true}
|
||||
to={`/media/${token}`}
|
||||
/>
|
||||
</div>
|
||||
<motion.p
|
||||
className="position-absolute bottom-0 left-0 ps-2 pb-2 fw-medium"
|
||||
style={{
|
||||
textShadow: "2px 2px 10px rgba(0, 0, 0, 0.7)",
|
||||
maxWidth: "240px",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
initial={{ y: 10 }}
|
||||
animate={{ y: 0 }}
|
||||
exit={{ y: 10 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
{title}
|
||||
</motion.p>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<img src={image} alt={alt} width={small ? 180 : 340} />
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import SceneCard from "./SceneCard";
|
||||
import Marquee from "react-fast-marquee";
|
||||
|
||||
interface ScrollingSceneCarouselProps {
|
||||
small?: boolean;
|
||||
}
|
||||
|
||||
interface MediaItem {
|
||||
token: string;
|
||||
public_bucket_path: string;
|
||||
maybe_creator?: {
|
||||
username: string;
|
||||
};
|
||||
maybe_title?: string;
|
||||
}
|
||||
|
||||
export default function ScrollingSceneCarousel({
|
||||
small,
|
||||
}: ScrollingSceneCarouselProps) {
|
||||
const [mediaItems, setMediaItems] = useState<MediaItem[]>([]);
|
||||
const bucketConfig = new BucketConfig();
|
||||
|
||||
useEffect(() => {
|
||||
fetchMediaItems();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const fetchMediaItems = async () => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
"https://api.storyteller.ai/v1/media_files/list_featured?page_size=100&filter_media_classes=video"
|
||||
);
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
setMediaItems(shuffleArray(data.results));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching media items:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const shuffleArray = (array: any[]) => {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
}
|
||||
return array;
|
||||
};
|
||||
|
||||
const halfLength = Math.ceil(mediaItems.length / 2);
|
||||
const firstHalf = mediaItems.slice(0, halfLength);
|
||||
const secondHalf = mediaItems.slice(halfLength);
|
||||
|
||||
return (
|
||||
<div className="d-flex gap-5 mt-5 overflow-hidden flex-column">
|
||||
<Marquee
|
||||
gradient={true}
|
||||
gradientColor="#1a1a27"
|
||||
gradientWidth={small ? 80 : 200}
|
||||
speed={small ? 100 : 50}
|
||||
pauseOnHover={true}
|
||||
direction="left"
|
||||
>
|
||||
{firstHalf.map((item, index) => (
|
||||
<div key={index}>
|
||||
<SceneCard
|
||||
image={bucketConfig.getCdnUrl(
|
||||
item.public_bucket_path + "-thumb.gif",
|
||||
360,
|
||||
20
|
||||
)}
|
||||
alt={`Scene ${index}`}
|
||||
title={item.maybe_title || "Scene"}
|
||||
token={item.token}
|
||||
small={small}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</Marquee>
|
||||
{!small && (
|
||||
<Marquee
|
||||
gradient={true}
|
||||
gradientColor="#1a1a27"
|
||||
gradientWidth={small ? 80 : 200}
|
||||
speed={small ? 100 : 50}
|
||||
pauseOnHover={true}
|
||||
direction="right"
|
||||
>
|
||||
{secondHalf.map((item, index) => (
|
||||
<div key={index}>
|
||||
<SceneCard
|
||||
image={bucketConfig.getCdnUrl(
|
||||
item.public_bucket_path + "-thumb.gif",
|
||||
360,
|
||||
20
|
||||
)}
|
||||
alt={`Scene ${index}`}
|
||||
title={item.maybe_title || "Scene"}
|
||||
token={item.token}
|
||||
small={small}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</Marquee>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { create } from "zustand";
|
||||
|
||||
type FeatureStore = {
|
||||
inViewFeature: string | null;
|
||||
setInViewFeature: (feature: string | null) => void;
|
||||
};
|
||||
|
||||
export const useFeatureStore = create<FeatureStore>(set => ({
|
||||
inViewFeature: null,
|
||||
setInViewFeature: (feature: string | null) => set({ inViewFeature: feature }),
|
||||
}));
|
||||
+2
-2
@@ -160,7 +160,7 @@ function PrelaunchLanding(props: Props) {
|
||||
) : mediaItem ? (
|
||||
<img
|
||||
onClick={() =>
|
||||
window.location.href = `/media/${mediaItem.token}`
|
||||
(window.location.href = `/media/${mediaItem.token}`)
|
||||
}
|
||||
src={gifUrl}
|
||||
className="w-100 h-100 object-fit-cover"
|
||||
@@ -174,6 +174,6 @@ function PrelaunchLanding(props: Props) {
|
||||
};
|
||||
|
||||
return <div className="prelaunch-grid-container">{renderGridItems()}</div>;
|
||||
};
|
||||
}
|
||||
|
||||
export default PrelaunchLanding;
|
||||
@@ -4399,6 +4399,7 @@ __metadata:
|
||||
"@testing-library/react": ^9.5.0
|
||||
"@testing-library/user-event": ^7.2.1
|
||||
"@tippyjs/react": ^4.2.6
|
||||
"@typeform/embed-react": ^3.17.0
|
||||
"@types/axios": ^0.14.0
|
||||
"@types/deep-equal": ^1.0.3
|
||||
"@types/google.analytics": ^0.0.42
|
||||
@@ -4444,6 +4445,7 @@ __metadata:
|
||||
react-device-detect: ^2.2.3
|
||||
react-dom: ^16.13.1
|
||||
react-drag-drop-files: ^2.3.9
|
||||
react-fast-marquee: ^1.6.4
|
||||
react-i18next: ^11.15.4
|
||||
react-iframe: ^1.8.5
|
||||
react-infinite-scroll-component: ^6.1.0
|
||||
@@ -4474,9 +4476,9 @@ __metadata:
|
||||
dependencies:
|
||||
"@react-spring/types": ^9.7.3
|
||||
"@typeform/embed": ^4.8.1
|
||||
"@typeform/embed-react": ^3.15.0
|
||||
craco: ^0.0.3
|
||||
cross-env: ^7.0.3
|
||||
framer-motion: 6.5.1
|
||||
husky: ^8.0.3
|
||||
lerna: ^4.0.0
|
||||
local-storage: ^2.0.0
|
||||
@@ -4937,22 +4939,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typeform/embed-react@npm:^3.15.0":
|
||||
version: 3.15.0
|
||||
resolution: "@typeform/embed-react@npm:3.15.0"
|
||||
"@typeform/embed-react@npm:^3.17.0":
|
||||
version: 3.17.0
|
||||
resolution: "@typeform/embed-react@npm:3.17.0"
|
||||
dependencies:
|
||||
"@typeform/embed": 4.8.1
|
||||
"@typeform/embed": 4.10.0
|
||||
fast-deep-equal: ^3.1.3
|
||||
peerDependencies:
|
||||
react: ">=16.8.0"
|
||||
checksum: d5e5d4b1cdfc128de2ce6c17ba648adbe98bfecc5a2c296fbedc8fb32da0c302e813baa6ce3ecc3de9dc84f36ccfd42250a4693f846d84ce2574e6fedcaa86e5
|
||||
checksum: 90d58df3124de46eda7db5f71fd478d4b57ee952b7da04b3eb64c74036e4830a8e5ea7f4bce67eb55827af91cdda0fa107fc2aa13801b0890c8c611af742584a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typeform/embed@npm:4.8.1, @typeform/embed@npm:^4.8.1":
|
||||
version: 4.8.1
|
||||
resolution: "@typeform/embed@npm:4.8.1"
|
||||
checksum: 1e7ac3d0d95c1ea823160f1478a973f871550a4bab7de1501f557b1e18966ab5a740b6a913d9436b5ee59d0f25d707b66582a8c41dea195383cca3afb1955b23
|
||||
"@typeform/embed@npm:4.10.0":
|
||||
version: 4.10.0
|
||||
resolution: "@typeform/embed@npm:4.10.0"
|
||||
checksum: 388c409428af05c8db7066b07ab85b78ce2ed3de9ed9f020c464a92d5fc2c7fb4c6081cb9311d93dd18d251381401be6ab427f1ec44aeca548e27d9b5f454780
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4963,6 +4965,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typeform/embed@npm:^4.8.1":
|
||||
version: 4.8.1
|
||||
resolution: "@typeform/embed@npm:4.8.1"
|
||||
checksum: 1e7ac3d0d95c1ea823160f1478a973f871550a4bab7de1501f557b1e18966ab5a740b6a913d9436b5ee59d0f25d707b66582a8c41dea195383cca3afb1955b23
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/aria-query@npm:^4.2.0":
|
||||
version: 4.2.2
|
||||
resolution: "@types/aria-query@npm:4.2.2"
|
||||
@@ -11084,7 +11093,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"framer-motion@npm:^6.5.1":
|
||||
"framer-motion@npm:6.5.1, framer-motion@npm:^6.5.1":
|
||||
version: 6.5.1
|
||||
resolution: "framer-motion@npm:6.5.1"
|
||||
dependencies:
|
||||
@@ -17800,6 +17809,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-fast-marquee@npm:^1.6.4":
|
||||
version: 1.6.4
|
||||
resolution: "react-fast-marquee@npm:1.6.4"
|
||||
peerDependencies:
|
||||
react: ">= 16.8.0 || 18.0.0"
|
||||
react-dom: ">= 16.8.0 || 18.0.0"
|
||||
checksum: 30bb2c967b5162e7de130cccfcf51654ac1e29ecbf82116225a13958a05a79bc0ec11d72c49f172fa8c4d1f226b51cc93f9db5cb595c5a3529de8b41f60cbc2c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-i18next@npm:^11.15.4":
|
||||
version: 11.15.4
|
||||
resolution: "react-i18next@npm:11.15.4"
|
||||
|
||||
Reference in new issue
Block a user