mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 09:28:11 +00:00
ai tools section videos for live portrait and video style transfer (#656)
* vst video block added * vst video and poster update * ai tools live portrait video * touch start for mobile and ai tools page update
This commit is contained in:
1 parent
a0dac5ec29
commit
c5cb07d391
14 files changed
+162
-30
No files matched your search
@@ -77,7 +77,7 @@
|
||||
/terms* /website/index.html 200
|
||||
/testing* /website/index.html 200
|
||||
/text-to-image* /website/index.html 200
|
||||
/tools* /website/index.html 200
|
||||
/ai-tools* /website/index.html 200
|
||||
/tts* /website/index.html 200
|
||||
/tts_configs* /website/index.html 200
|
||||
/twitch* /website/index.html 200
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
/terms* /website/index.html 200
|
||||
/testing* /website/index.html 200
|
||||
/text-to-image* /website/index.html 200
|
||||
/tools* /website/index.html 200
|
||||
/ai-tools* /website/index.html 200
|
||||
/tts* /website/index.html 200
|
||||
/tts_configs* /website/index.html 200
|
||||
/twitch* /website/index.html 200
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
/terms* /index.html 200
|
||||
/testing* /index.html 200
|
||||
/text-to-image* /index.html 200
|
||||
/tools* /index.html 200
|
||||
/ai-tools* /index.html 200
|
||||
/tts* /index.html 200
|
||||
/tts_configs* /index.html 200
|
||||
/twitch* /index.html 200
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 78 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 108 KiB |
Binary file not shown.
Binary file not shown.
@@ -458,7 +458,7 @@ export default function TopNav({
|
||||
<NavItem
|
||||
icon={faScrewdriverWrench}
|
||||
label="Creator Tools"
|
||||
link="/tools"
|
||||
link="/ai-tools"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -605,7 +605,7 @@ export default function TopNav({
|
||||
<li>
|
||||
<NavLink
|
||||
exact={true}
|
||||
to="/tools"
|
||||
to="/ai-tools"
|
||||
activeClassName="active-link"
|
||||
onClick={handleNavLinkClick}
|
||||
>
|
||||
|
||||
@@ -40,7 +40,8 @@ export default function AITools() {
|
||||
to: "/style-video",
|
||||
title: t("productVideoStyleTransferTitle"),
|
||||
text: t("productVideoStyleTransferText"),
|
||||
imgSrc: "/images/landing/select-vst.webp",
|
||||
videoSrc: "/videos/ai-tools/vst_video.mp4",
|
||||
videoPosterSrc: "/images/ai-tools/vst_video_poster.jpg",
|
||||
imgAlt: "Video Style Transfer",
|
||||
badgeContent: {
|
||||
type: "new",
|
||||
@@ -52,7 +53,8 @@ export default function AITools() {
|
||||
to: "/ai-face-mirror",
|
||||
title: t("productLivePortraitTitle"),
|
||||
text: t("productLivePortraitText"),
|
||||
imgSrc: "/images/landing/select-fm.webp",
|
||||
videoSrc: "/videos/ai-tools/lp_video.mp4",
|
||||
videoPosterSrc: "/images/ai-tools/lp_video_poster.jpg",
|
||||
imgAlt: "Live Portrait",
|
||||
badgeContent: {
|
||||
type: "new",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
import { faArrowRight } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { faLongArrowRight } from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
interface BadgeContent {
|
||||
type: string;
|
||||
@@ -14,9 +14,11 @@ interface AIToolsItemProps {
|
||||
to: string;
|
||||
title: string;
|
||||
text?: string;
|
||||
imgSrc: string;
|
||||
imgSrc?: string;
|
||||
imgAlt: string;
|
||||
badgeContent?: BadgeContent;
|
||||
videoSrc?: string;
|
||||
videoPosterSrc?: string;
|
||||
}
|
||||
|
||||
export function AIToolsItem({
|
||||
@@ -26,14 +28,69 @@ export function AIToolsItem({
|
||||
imgSrc,
|
||||
imgAlt,
|
||||
badgeContent,
|
||||
videoSrc,
|
||||
videoPosterSrc,
|
||||
}: AIToolsItemProps) {
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [showPoster, setShowPoster] = useState(true);
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
if (videoRef.current && !isPlaying) {
|
||||
setShowPoster(false);
|
||||
videoRef.current.play();
|
||||
setIsPlaying(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTouchStart = () => {
|
||||
if (videoRef.current && !isPlaying) {
|
||||
setShowPoster(false);
|
||||
videoRef.current.play();
|
||||
setIsPlaying(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleVideoEnded = () => {
|
||||
if (videoRef.current) {
|
||||
videoRef.current.pause();
|
||||
setShowPoster(true);
|
||||
setTimeout(() => {
|
||||
if (videoRef.current) {
|
||||
videoRef.current.currentTime = 0;
|
||||
}
|
||||
setIsPlaying(false);
|
||||
}, 200);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTimeUpdate = () => {
|
||||
if (videoRef.current) {
|
||||
const timeRemaining =
|
||||
videoRef.current.duration - videoRef.current.currentTime;
|
||||
if (timeRemaining <= 0.2) {
|
||||
setShowPoster(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const videoElement = videoRef.current;
|
||||
if (videoElement) {
|
||||
videoElement.addEventListener("timeupdate", handleTimeUpdate);
|
||||
return () => {
|
||||
videoElement.removeEventListener("timeupdate", handleTimeUpdate);
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="col-12 col-md-6 col-lg-4">
|
||||
<Link
|
||||
to={to}
|
||||
className="panel panel-select d-flex flex-column align-items-center"
|
||||
>
|
||||
<div className="d-flex px-4 pt-4 align-items-start w-100">
|
||||
<div className="d-flex px-3 pt-3 px-xl-4 pt-xl-4 align-items-start w-100">
|
||||
<div className="flex-grow-1">
|
||||
{badgeContent && (
|
||||
<div className="mb-1">
|
||||
@@ -57,10 +114,63 @@ export function AIToolsItem({
|
||||
)}
|
||||
</div>
|
||||
<Link to={to} className="btn btn-square mt-1">
|
||||
<FontAwesomeIcon icon={faArrowRight} />
|
||||
<FontAwesomeIcon
|
||||
icon={faLongArrowRight}
|
||||
className="btn-icon fs-5"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<img className="img-fluid" src={imgSrc} alt={imgAlt} />
|
||||
{imgSrc && <img className="img-fluid" src={imgSrc} alt={imgAlt} />}
|
||||
{videoSrc && (
|
||||
<div
|
||||
className="w-100 mt-3 px-3 px-xl-4 overflow-hidden"
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onTouchStart={handleTouchStart}
|
||||
>
|
||||
<div
|
||||
className="w-100 h-100 position-relative overflow-hidden"
|
||||
style={{
|
||||
borderTopLeftRadius: "0.5rem",
|
||||
borderTopRightRadius: "0.5rem",
|
||||
}}
|
||||
>
|
||||
{videoPosterSrc && (
|
||||
<div
|
||||
className={`h-100 w-100 ${
|
||||
showPoster ? "opacity-100" : "opacity-0"
|
||||
}`}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
opacity: 0,
|
||||
transition: "opacity 0.2s ease-in-out",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={videoPosterSrc}
|
||||
className="h-100 w-100 object-fit-cover"
|
||||
alt={imgAlt}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<video
|
||||
ref={videoRef}
|
||||
muted={true}
|
||||
playsInline={true}
|
||||
className="w-100 h-100 object-fit-cover"
|
||||
style={{
|
||||
borderTopLeftRadius: "0.5rem",
|
||||
borderTopRightRadius: "0.5rem",
|
||||
}}
|
||||
onEnded={handleVideoEnded}
|
||||
>
|
||||
<source src={videoSrc} type="video/mp4" />
|
||||
</video>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1567,7 +1567,7 @@ styles with important tags:
|
||||
transition: 0.2s all;
|
||||
border-radius: 0.5rem !important;
|
||||
border-width: 2px !important;
|
||||
height: 320px;
|
||||
height: 330px;
|
||||
overflow: hidden;
|
||||
z-index: 2;
|
||||
|
||||
@@ -1590,6 +1590,18 @@ styles with important tags:
|
||||
transition: 0.2s all;
|
||||
background-color: lighten($primary, 5%);
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
transition: 0.2s all;
|
||||
}
|
||||
|
||||
&:hover .btn .btn-icon {
|
||||
transform: translateX(3px);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
height: 280px;
|
||||
}
|
||||
}
|
||||
|
||||
.fs-14 {
|
||||
|
||||
@@ -990,7 +990,7 @@ class PageContainer extends React.Component<
|
||||
<BetaKeysListPage />
|
||||
</Route>
|
||||
|
||||
<Route path="/tools">
|
||||
<Route path="/ai-tools">
|
||||
<CreatorToolsPage
|
||||
sessionWrapper={this.props.sessionWrapper}
|
||||
/>
|
||||
|
||||
@@ -94,7 +94,7 @@ function FooterNav(props: Props) {
|
||||
},
|
||||
{ link: "/explore", text: "Explore Videos", icon: null },
|
||||
...(domain.titlePart === "FakeYou"
|
||||
? [{ link: "/tools", text: "AI Tools", icon: null }]
|
||||
? [{ link: "/ai-tools", text: "AI Tools", icon: null }]
|
||||
: []),
|
||||
// { link: "/upload-assets", text: "Upload Assets", icon: null },
|
||||
],
|
||||
|
||||
+22
-14
@@ -4,7 +4,6 @@ import {
|
||||
faScrewdriverWrench,
|
||||
faSparkles,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
|
||||
import {
|
||||
GetWebsite,
|
||||
@@ -17,6 +16,7 @@ import { useLocalize } from "hooks";
|
||||
import React from "react";
|
||||
import { AIToolsRow } from "components/marketing";
|
||||
import "./CreatorTools.scss";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
interface CreatorToolsPageProps {
|
||||
sessionWrapper: SessionWrapper;
|
||||
@@ -28,13 +28,6 @@ export default function CreatorToolsPage(props: CreatorToolsPageProps) {
|
||||
usePrefixedDocumentTitle("Creator Tools");
|
||||
|
||||
const videoProducts = [
|
||||
{
|
||||
to: "/face-animator",
|
||||
title: t("productLipsyncTitle"),
|
||||
text: t("productLipsyncText"),
|
||||
imgSrc: "/images/landing/select-fa.webp",
|
||||
imgAlt: "Lipsync",
|
||||
},
|
||||
// {
|
||||
// to: "/text-to-image",
|
||||
// title: t("productImageGenTitle"),
|
||||
@@ -51,7 +44,8 @@ export default function CreatorToolsPage(props: CreatorToolsPageProps) {
|
||||
to: "/style-video",
|
||||
title: t("productVideoStyleTransferTitle"),
|
||||
text: t("productVideoStyleTransferText"),
|
||||
imgSrc: "/images/landing/select-vst.webp",
|
||||
videoSrc: "/videos/ai-tools/vst_video.mp4",
|
||||
videoPosterSrc: "/images/ai-tools/vst_video_poster.jpg",
|
||||
imgAlt: "Video Style Transfer",
|
||||
badgeContent: {
|
||||
type: "new",
|
||||
@@ -64,7 +58,8 @@ export default function CreatorToolsPage(props: CreatorToolsPageProps) {
|
||||
to: "/ai-face-mirror",
|
||||
title: t("productLivePortraitTitle"),
|
||||
text: t("productLivePortraitText"),
|
||||
imgSrc: "/images/landing/select-fm.webp",
|
||||
videoSrc: "/videos/ai-tools/lp_video.mp4",
|
||||
videoPosterSrc: "/images/ai-tools/lp_video_poster.jpg",
|
||||
imgAlt: "Live Portrait",
|
||||
badgeContent: {
|
||||
type: "new",
|
||||
@@ -72,6 +67,14 @@ export default function CreatorToolsPage(props: CreatorToolsPageProps) {
|
||||
label: "NEW",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
to: "/face-animator",
|
||||
title: t("productLipsyncTitle"),
|
||||
text: t("productLipsyncText"),
|
||||
imgSrc: "/images/landing/select-fa.webp",
|
||||
imgAlt: "Lipsync",
|
||||
},
|
||||
];
|
||||
|
||||
const voiceProducts = [
|
||||
@@ -111,10 +114,15 @@ export default function CreatorToolsPage(props: CreatorToolsPageProps) {
|
||||
return (
|
||||
<Container type="panel">
|
||||
<Panel clear={true} className="mt-5">
|
||||
<h1 className="fw-bold mb-4">
|
||||
<FontAwesomeIcon icon={faScrewdriverWrench} className="me-3" />
|
||||
Creator Tools
|
||||
</h1>
|
||||
<div className="mb-4">
|
||||
<h1 className="fw-bold mb-1 d-flex align-items-center">
|
||||
<FontAwesomeIcon icon={faScrewdriverWrench} className="me-3 fs-2" />
|
||||
Creator Tools
|
||||
</h1>
|
||||
<h5 className="opacity-75">
|
||||
AI-powered tools for video and voice creation.
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
<div className="d-flex flex-column gap-5">
|
||||
<div>
|
||||
|
||||
Reference in new issue
Block a user