Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7de49e575 | ||
|
|
04901b7244 | ||
|
|
d701332485 | ||
|
|
97c34b02e0 | ||
|
|
7ac5eaf833 | ||
|
|
12b6e7d48f | ||
|
|
1e081625d0 |
@@ -89,6 +89,11 @@
|
||||
"faqQuestion1": "Why do text characters sometimes appear as question marks in the preview?",
|
||||
"faqAnswer1": "Because the selected font doesn't support the language of the text. Try changing the font or uploading a custom font that supports the language."
|
||||
},
|
||||
"Effects": {
|
||||
"title": "Effects",
|
||||
"shadowOption": "Shadow",
|
||||
"shadowOptionHelp": "Shadow works only with Pure Color background"
|
||||
},
|
||||
"Footer": {
|
||||
"copyright": "© {year} Screen Designer. All rights reserved."
|
||||
},
|
||||
|
||||
@@ -89,6 +89,11 @@
|
||||
"faqQuestion1": "为什么预览框内的文字内容会显示问号?",
|
||||
"faqAnswer1": "因为该内容的语言没有被选中字体支持,建议更换字体或者上传自定义字体"
|
||||
},
|
||||
"Effects": {
|
||||
"title": "特效",
|
||||
"shadowOption": "阴影",
|
||||
"shadowOptionHelp": "阴影只在纯色背景下有效"
|
||||
},
|
||||
"Footer": {
|
||||
"copyright": "© {year} 3D文字设计工具 版权所有"
|
||||
},
|
||||
|
||||
BIN
src/app/[locale]/blogs/Add-Text-Shadow/1024_576.png
Normal file
BIN
src/app/[locale]/blogs/Add-Text-Shadow/1024_576.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
BIN
src/app/[locale]/blogs/Add-Text-Shadow/512_288.png
Normal file
BIN
src/app/[locale]/blogs/Add-Text-Shadow/512_288.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
9
src/app/[locale]/blogs/Add-Text-Shadow/blog-prompt.txt
Normal file
9
src/app/[locale]/blogs/Add-Text-Shadow/blog-prompt.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
我要写一篇博文,介绍如何设置文字阴影。你是一个专业的SEO优化师,从seo的角度来帮我组织这个blog。
|
||||
1 打开 https://fast3dtext.com/editor
|
||||
2 输入想要的Text
|
||||
3 调整背景、字体、颜色
|
||||
4 打开Effect - Shadow的开关,选择自己喜欢的阴影颜色
|
||||
5 操控三维视角
|
||||
6 下载
|
||||
|
||||
至此,即完成了文字阴影
|
||||
18
src/app/[locale]/blogs/Add-Text-Shadow/data.ts
Normal file
18
src/app/[locale]/blogs/Add-Text-Shadow/data.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import Cover2 from "./512_288.png";
|
||||
import { BlogItem } from "../list";
|
||||
|
||||
export const Blog: BlogItem = {
|
||||
id: "Add-Text-Shadow",
|
||||
date: "2025-08-19",
|
||||
cover: Cover2,
|
||||
en: {
|
||||
title: "How to Add Stunning Text Shadow Effects to 3D Text Online",
|
||||
summary:
|
||||
"Learn how to create eye-catching text shadow effects for your 3D text designs using Fast3DText. This step-by-step guide shows you how to enable shadows, choose shadow colors, and create professional-looking 3D text with depth and dimension. Perfect for designers and content creators.",
|
||||
},
|
||||
zh: {
|
||||
title: "如何为3D文字添加惊艳的阴影效果(在线工具)",
|
||||
summary:
|
||||
"本文教你使用 Fast3DText 为3D文字添加专业的阴影效果。通过开启阴影开关、选择阴影颜色和调整三维视角,你可以轻松创建具有深度感和立体感的3D文字设计。适合设计师和内容创作者使用。",
|
||||
},
|
||||
};
|
||||
89
src/app/[locale]/blogs/Add-Text-Shadow/en.tsx
Normal file
89
src/app/[locale]/blogs/Add-Text-Shadow/en.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import { Box, Heading, Text, Link, Flex } from '@radix-ui/themes';
|
||||
import Image from 'next/image';
|
||||
import img from "./1024_576.png";
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Flex gap={"4"} direction={"column"} justify={"start"} className='text-left'>
|
||||
<Heading as="h1" size="7" mb="4" className='text-center'>How to Add Stunning Text Shadow Effects to 3D Text Online</Heading>
|
||||
|
||||
<Flex justify={"center"}>
|
||||
<Image src={img} alt="3D Text with Shadow Effect Example" width={1024} height={576} />
|
||||
</Flex>
|
||||
|
||||
<Text as="p" mb="4">
|
||||
Want to make your <strong>3D text designs</strong> stand out with professional shadow effects? This comprehensive guide shows you how to easily add <strong>text shadow effects</strong> to your 3D text using <Link href="https://fast3dtext.com/editor" target="_blank" rel="noopener noreferrer">Fast3DText.com</Link> - the best free online 3D text generator with shadow capabilities.
|
||||
</Text>
|
||||
|
||||
<Heading as="h2" size="5" mt="6" mb="3">🎯 Why Add Text Shadow Effects?</Heading>
|
||||
<Text as="p" mb="4">
|
||||
Text shadows add depth, dimension, and professionalism to your 3D designs. They create visual hierarchy, improve readability, and make your text pop against any background. Perfect for social media graphics, presentations, logos, and marketing materials.
|
||||
</Text>
|
||||
|
||||
<Heading as="h2" size="5" mt="6" mb="3">🚀 Step-by-Step Guide to Adding Text Shadows</Heading>
|
||||
|
||||
<Heading as="h3" size="4" mt="4" mb="2">1. Open the 3D Text Editor</Heading>
|
||||
<Text as="p" mb="4">
|
||||
Start by visiting 👉 <Link href="https://fast3dtext.com/editor" target="_blank" rel="noopener noreferrer">https://fast3dtext.com/editor</Link>
|
||||
</Text>
|
||||
|
||||
<Heading as="h3" size="4" mt="4" mb="2">2. Enter Your Text Content</Heading>
|
||||
<Text as="p" mb="4">
|
||||
Type the text you want to transform into 3D with shadow effects. You can enter single words, phrases, or multiple words separated by spaces for individual control.
|
||||
</Text>
|
||||
|
||||
<Heading as="h3" size="4" mt="4" mb="2">3. Customize Basic Settings</Heading>
|
||||
<Text as="p" mb="2">
|
||||
Set up your foundation:
|
||||
</Text>
|
||||
<ul style={{ listStyleType: 'disc', paddingLeft: '20px' }}>
|
||||
<li><strong>Background</strong>: Choose solid colors, gradients, or transparent background</li>
|
||||
<li><strong>Font</strong>: Select from various 3D-compatible fonts</li>
|
||||
<li><strong>Text Color</strong>: Pick your main text color</li>
|
||||
</ul>
|
||||
|
||||
<Heading as="h3" size="4" mt="4" mb="2">4. Enable Shadow Effects</Heading>
|
||||
<Text as="p" mb="4">
|
||||
This is the key step! Navigate to the <strong>Effect panel</strong> and toggle on the <strong>Shadow switch</strong>. This activates the shadow functionality for your 3D text.
|
||||
</Text>
|
||||
|
||||
<Heading as="h3" size="4" mt="4" mb="2">5. Choose Your Shadow Color</Heading>
|
||||
<Text as="p" mb="4">
|
||||
Select the perfect shadow color that complements your text. You can choose:
|
||||
</Text>
|
||||
<ul style={{ listStyleType: 'disc', paddingLeft: '20px' }}>
|
||||
<li><strong>Matching colors</strong> for subtle effects</li>
|
||||
<li><strong>Contrasting colors</strong> for dramatic impact</li>
|
||||
<li><strong>Dark shadows</strong> for traditional depth effects</li>
|
||||
<li><strong>Colored shadows</strong> for creative designs</li>
|
||||
</ul>
|
||||
|
||||
<Heading as="h3" size="4" mt="4" mb="2">6. Adjust 3D Perspective</Heading>
|
||||
<Text as="p" mb="4">
|
||||
Drag and rotate the 3D scene to find the perfect angle that showcases your shadow effect. The shadow will dynamically adjust based on your 3D viewpoint.
|
||||
</Text>
|
||||
|
||||
<Heading as="h3" size="4" mt="4" mb="2">7. Download Your Creation</Heading>
|
||||
<Text as="p" mb="4">
|
||||
Click the <strong>Download button</strong> to save your 3D text with shadow effects as a high-quality PNG image. Ready to use in your projects!
|
||||
</Text>
|
||||
|
||||
<Heading as="h2" size="5" mt="6" mb="3">💡 Pro Tips for Best Results</Heading>
|
||||
<ul style={{ listStyleType: 'disc', paddingLeft: '20px' }}>
|
||||
<li>Use darker shadow colors for more pronounced effects</li>
|
||||
<li>Experiment with different shadow intensities</li>
|
||||
<li>Consider your background color when choosing shadow colors</li>
|
||||
<li>Try multiple angles to find the most flattering shadow presentation</li>
|
||||
</ul>
|
||||
|
||||
<Heading as="h2" size="5" mt="6" mb="3">🎨 Creative Applications</Heading>
|
||||
<Text as="p" mb="4">
|
||||
Text shadow effects are perfect for: logo design, social media graphics, YouTube thumbnails, presentation slides, website headers, marketing materials, and personal projects.
|
||||
</Text>
|
||||
|
||||
<Text as="p" mt="6" style={{ fontWeight: 'bold' }}>
|
||||
Start creating stunning 3D text with professional shadow effects today at <Link href="https://fast3dtext.com/editor" target="_blank" rel="noopener noreferrer">Fast3DText.com</Link>!
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
82
src/app/[locale]/blogs/Add-Text-Shadow/page.tsx
Normal file
82
src/app/[locale]/blogs/Add-Text-Shadow/page.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import Footer from "@/components/Footer";
|
||||
import Header from "@/components/Header";
|
||||
import { Box, Flex, } from "@radix-ui/themes";
|
||||
import { useLocale } from "next-intl";
|
||||
import En from "./en";
|
||||
import Zh from "./zh";
|
||||
import { Locales } from "@/i18n/config";
|
||||
import { Metadata } from "next";
|
||||
import { Blog } from "./data";
|
||||
|
||||
export default function Page() {
|
||||
|
||||
const locale = useLocale() as "en" | "zh";
|
||||
|
||||
return (
|
||||
<Flex direction={"column"} gap={"4"}>
|
||||
<Header />
|
||||
<Flex justify={"center"} >
|
||||
|
||||
<Box className="md:w-2/3 w-full">
|
||||
{locale == "en" && (<En></En>)}
|
||||
{locale == "zh" && (<Zh></Zh>)}
|
||||
</Box>
|
||||
|
||||
</Flex>
|
||||
<Footer />
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
const host = process.env.NEXT_PUBLIC_HOST;
|
||||
|
||||
const locales = Locales;
|
||||
|
||||
export function generateStaticParams() {
|
||||
return locales.map((locale) => ({ locale }));
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ locale: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const { locale } = await params;
|
||||
|
||||
const name = Blog.id;
|
||||
const title = Blog[locale as "en" | "zh"].title;
|
||||
const description = Blog[locale as "en" | "zh"].summary;
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
keywords: [],
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
url: `${host}/${locale}/blogs/${name}`,
|
||||
images: [
|
||||
{
|
||||
url: `${process.env.NEXT_PUBLIC_HOST}/og-image.png`,
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: title,
|
||||
},
|
||||
],
|
||||
locale: locale,
|
||||
type: "website",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title,
|
||||
description,
|
||||
images: [`${process.env.NEXT_PUBLIC_HOST}/og-image.png`],
|
||||
},
|
||||
alternates: {
|
||||
canonical: `${host}/blogs/${name}`,
|
||||
languages: {
|
||||
en: `${host}/en/blogs/${name}`,
|
||||
zh: `${host}/zh/blogs/${name}`,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
89
src/app/[locale]/blogs/Add-Text-Shadow/zh.tsx
Normal file
89
src/app/[locale]/blogs/Add-Text-Shadow/zh.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import { Box, Heading, Text, Link, Flex } from '@radix-ui/themes';
|
||||
import Image from 'next/image';
|
||||
import img from "./1024_576.png";
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Flex gap={"4"} direction={"column"} justify={"start"} className='text-left'>
|
||||
<Heading as="h1" size="7" mb="4" className='text-center'>如何为3D文字添加惊艳的阴影效果(在线工具)</Heading>
|
||||
|
||||
<Flex justify={"center"}>
|
||||
<Image src={img} alt="3D文字阴影效果示例" width={1024} height={576} />
|
||||
</Flex>
|
||||
|
||||
<Text as="p" mb="4">
|
||||
想要让你的<strong>3D文字设计</strong>通过专业的阴影效果脱颖而出吗?本完整指南将教你如何使用<Link href="https://fast3dtext.com/editor" target="_blank" rel="noopener noreferrer">Fast3DText.com</Link>轻松为3D文字添加<strong>阴影效果</strong> - 这是最好的免费在线3D文字生成器,具备阴影功能。
|
||||
</Text>
|
||||
|
||||
<Heading as="h2" size="5" mt="6" mb="3">🎯 为什么要添加文字阴影效果?</Heading>
|
||||
<Text as="p" mb="4">
|
||||
文字阴影能为你的3D设计增添深度、立体感和专业感。它们创建视觉层次,提高可读性,并让你的文字在任何背景下都更加突出。非常适合社交媒体图形、演示文稿、Logo设计和营销材料。
|
||||
</Text>
|
||||
|
||||
<Heading as="h2" size="5" mt="6" mb="3">🚀 添加文字阴影的分步指南</Heading>
|
||||
|
||||
<Heading as="h3" size="4" mt="4" mb="2">1. 打开3D文字编辑器</Heading>
|
||||
<Text as="p" mb="4">
|
||||
首先访问 👉 <Link href="https://fast3dtext.com/editor" target="_blank">https://fast3dtext.com/editor</Link>
|
||||
</Text>
|
||||
|
||||
<Heading as="h3" size="4" mt="4" mb="2">2. 输入你的文字内容</Heading>
|
||||
<Text as="p" mb="4">
|
||||
输入你想要转换为带阴影效果的3D文字。可以输入单个单词、短语或用空格分隔的多个单词以获得单独控制。
|
||||
</Text>
|
||||
|
||||
<Heading as="h3" size="4" mt="4" mb="2">3. 自定义基本设置</Heading>
|
||||
<Text as="p" mb="2">
|
||||
设置基础配置:
|
||||
</Text>
|
||||
<ul style={{ listStyleType: 'disc', paddingLeft: '20px' }}>
|
||||
<li><strong>背景</strong>: 选择纯色、渐变或透明背景</li>
|
||||
<li><strong>字体</strong>: 从各种3D兼容字体中选择</li>
|
||||
<li><strong>文字颜色</strong>: 选择主要文字颜色</li>
|
||||
</ul>
|
||||
|
||||
<Heading as="h3" size="4" mt="4" mb="2">4. 启用阴影效果</Heading>
|
||||
<Text as="p" mb="4">
|
||||
这是关键步骤!导航到<strong>效果面板</strong>并打开<strong>阴影开关</strong>。这将激活3D文字的阴影功能。
|
||||
</Text>
|
||||
|
||||
<Heading as="h3" size="4" mt="4" mb="2">5. 选择阴影颜色</Heading>
|
||||
<Text as="p" mb="4">
|
||||
选择与你的文字完美搭配的阴影颜色。你可以选择:
|
||||
</Text>
|
||||
<ul style={{ listStyleType: 'disc', paddingLeft: '20px' }}>
|
||||
<li><strong>匹配颜色</strong>获得微妙效果</li>
|
||||
<li><strong>对比颜色</strong>获得戏剧性效果</li>
|
||||
<li><strong>深色阴影</strong>获得传统深度效果</li>
|
||||
<li><strong>彩色阴影</strong>获得创意设计</li>
|
||||
</ul>
|
||||
|
||||
<Heading as="h3" size="4" mt="4" mb="2">6. 调整3D视角</Heading>
|
||||
<Text as="p" mb="4">
|
||||
拖动并旋转3D场景,找到展示阴影效果的最佳角度。阴影会根据你的3D视角动态调整。
|
||||
</Text>
|
||||
|
||||
<Heading as="h3" size="4" mt="4" mb="2">7. 下载你的创作</Heading>
|
||||
<Text as="p" mb="4">
|
||||
点击<strong>下载按钮</strong>将带阴影效果的3D文字保存为高质量的PNG图像。随时可用于你的项目!
|
||||
</Text>
|
||||
|
||||
<Heading as="h2" size="5" mt="6" mb="3">💡 最佳效果的专业技巧</Heading>
|
||||
<ul style={{ listStyleType: 'disc', paddingLeft: '20px' }}>
|
||||
<li>使用较深的阴影颜色获得更明显的效果</li>
|
||||
<li>尝试不同的阴影强度</li>
|
||||
<li>选择阴影颜色时考虑背景颜色</li>
|
||||
<li>尝试多个角度找到最合适的阴影展示</li>
|
||||
</ul>
|
||||
|
||||
<Heading as="h2" size="5" mt="6" mb="3">🎨 创意应用场景</Heading>
|
||||
<Text as="p" mb="4">
|
||||
文字阴影效果非常适合:Logo设计、社交媒体图形、YouTube缩略图、演示文稿幻灯片、网站标题、营销材料和个人项目。
|
||||
</Text>
|
||||
|
||||
<Text as="p" mt="6" style={{ fontWeight: 'bold' }}>
|
||||
立即开始在 <Link href="https://fast3dtext.com/editor" target="_blank" rel="noopener noreferrer">Fast3DText.com</Link> 创建带有专业阴影效果的惊艳3D文字!
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -15,8 +15,10 @@ export interface BlogItem {
|
||||
import { StaticImageData } from "next/image";
|
||||
import { Blog as Create3DTextBlog } from "./Create-3D-Text-with-the-Barbie-Font/data";
|
||||
import { Blog as Create3DLetterBlog } from "./Create-3D-Letters/data";
|
||||
import { Blog as AddTextShadowBlog } from "./Add-Text-Shadow/data";
|
||||
|
||||
export const blogs = [
|
||||
Create3DLetterBlog,
|
||||
Create3DTextBlog,
|
||||
AddTextShadowBlog,
|
||||
] satisfies BlogItem[];
|
||||
|
||||
@@ -36,7 +36,7 @@ export default function Page() {
|
||||
<h2 className="text-3xl font-bold text-center mb-12">
|
||||
{t("toolTitle")}
|
||||
</h2>
|
||||
<Editor textProp={text} backgroundProp={undefined}></Editor>
|
||||
<Editor textProp={text} backgroundProp={undefined} effectProp={undefined}></Editor>
|
||||
</section>
|
||||
|
||||
{/* Features Section */}
|
||||
|
||||
@@ -1,16 +1,46 @@
|
||||
import { OnlyPage } from "@/components/editor/OnlyPage";
|
||||
import { decodeText } from "@/lib/utils";
|
||||
import { decode } from "@/lib/utils";
|
||||
import { Metadata } from "next";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
export default async function Page({ params }: { params: Promise<{ data: string }> }) {
|
||||
|
||||
const { data } = await params
|
||||
|
||||
let backgroundProp, textProp, effectProp
|
||||
|
||||
if (data) {
|
||||
try {
|
||||
const { bg, text, effect } = decode(data);
|
||||
|
||||
backgroundProp = bg;
|
||||
textProp = text;
|
||||
effectProp = effect;
|
||||
} catch (error) {
|
||||
console.error("parse data from url error", error)
|
||||
}
|
||||
}
|
||||
|
||||
return (<OnlyPage textProp={textProp} backgroundProp={backgroundProp} effectProp={effectProp}></OnlyPage>)
|
||||
|
||||
}
|
||||
|
||||
const host = process.env.NEXT_PUBLIC_HOST;
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ locale: string, data: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const { locale, data } = await params;
|
||||
const t = await getTranslations({ locale, namespace: "TextEditor" });
|
||||
|
||||
const name = `editor/${data}`;
|
||||
let backgroundProp, textProp
|
||||
|
||||
if (data) {
|
||||
try {
|
||||
const { bg, text } = JSON.parse(decodeText(data));
|
||||
|
||||
const { bg, text } = decode(data);
|
||||
backgroundProp = bg;
|
||||
textProp = text;
|
||||
} catch (error) {
|
||||
@@ -18,6 +48,38 @@ export default async function Page({ params }: { params: Promise<{ data: string
|
||||
}
|
||||
}
|
||||
|
||||
return (<OnlyPage textProp={textProp} backgroundProp={backgroundProp}></OnlyPage>)
|
||||
const description = t("seoDescription") + `bacground: ${JSON.stringify(backgroundProp)}; text: ${JSON.stringify(textProp)})}`;
|
||||
|
||||
}
|
||||
return {
|
||||
title: t("seoTitle"),
|
||||
description,
|
||||
openGraph: {
|
||||
title: t("seoTitle"),
|
||||
description,
|
||||
url: `${host}/${locale}/${name}`,
|
||||
images: [
|
||||
{
|
||||
url: `${process.env.NEXT_PUBLIC_HOST}/og-image.png`,
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: t("seoTitle"),
|
||||
},
|
||||
],
|
||||
locale: locale,
|
||||
type: "website",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: t("seoTitle"),
|
||||
description,
|
||||
images: [`${process.env.NEXT_PUBLIC_HOST}/og-image.png`],
|
||||
},
|
||||
alternates: {
|
||||
canonical: `${host}/${name}`,
|
||||
languages: {
|
||||
en: `${host}/en/${name}`,
|
||||
zh: `${host}/zh/${name}`,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Metadata } from "next";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
const host = process.env.NEXT_PUBLIC_HOST;
|
||||
export default function Page() {
|
||||
return (<OnlyPage textProp={undefined} backgroundProp={undefined}></OnlyPage>)
|
||||
return (<OnlyPage textProp={undefined} backgroundProp={undefined} effectProp={undefined}></OnlyPage>)
|
||||
}
|
||||
|
||||
const locales = Locales;
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import Footer from "@/components/Footer";
|
||||
import Header from "@/components/Header";
|
||||
import { Box, Flex } from "@radix-ui/themes";
|
||||
|
||||
export default function Page() {
|
||||
|
||||
return (
|
||||
|
||||
<Flex direction={"column"} gap={"4"}>
|
||||
<Header />
|
||||
<Box p="4" className="text-center">
|
||||
<iframe src="https://docs.google.com/forms/d/e/1FAIpQLSeFbI-Bu-RsuYg1SP3_-L7wo5OOIfp5XR7H4E7jYgullaCm7g/viewform?embedded=true" className="w-full h-full" >Loading…</iframe>
|
||||
</Box>
|
||||
<Footer />
|
||||
</Flex>
|
||||
|
||||
)
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { routing } from "@/i18n/routing";
|
||||
import { getTranslations, setRequestLocale } from "next-intl/server";
|
||||
import { jsonLdScriptProps } from "react-schemaorg";
|
||||
import { WebSite } from "schema-dts";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
// import { Geist, Geist_Mono } from "next/font/google";
|
||||
import { Analytics } from "@vercel/analytics/react";
|
||||
import { SpeedInsights } from "@vercel/speed-insights/next";
|
||||
import "../globals.css";
|
||||
@@ -12,15 +12,15 @@ import { ThemeProvider } from "next-themes";
|
||||
import { Theme } from "@radix-ui/themes";
|
||||
const host = process.env.NEXT_PUBLIC_HOST;
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
// const geistSans = Geist({
|
||||
// variable: "--font-geist-sans",
|
||||
// subsets: ["latin"],
|
||||
// });
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
// const geistMono = Geist_Mono({
|
||||
// variable: "--font-geist-mono",
|
||||
// subsets: ["latin"],
|
||||
// });
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
@@ -66,7 +66,7 @@ export default async function RootLayout({
|
||||
/>
|
||||
</head>
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
// className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
suppressHydrationWarning
|
||||
>
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ export default function HomePage() {
|
||||
<Heading as="h2" size={"8"}>
|
||||
{t("toolTitle")}
|
||||
</Heading>
|
||||
<Editor textProp={undefined} backgroundProp={undefined}></Editor>
|
||||
<Editor textProp={undefined} backgroundProp={undefined} effectProp={undefined}></Editor>
|
||||
</Flex>
|
||||
|
||||
</Section>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Box, Flex, Heading, Link } from "@radix-ui/themes";
|
||||
import { useLocale, useTranslations } from "next-intl";
|
||||
|
||||
import Image from "next/image";
|
||||
export default function Footer() {
|
||||
const f = useTranslations("Footer");
|
||||
const t = useTranslations("Header");
|
||||
@@ -32,14 +32,6 @@ export default function Footer() {
|
||||
>
|
||||
{t("blogName")}
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/features-form"
|
||||
className="text-sm text-muted-foreground hover:text-primary"
|
||||
>
|
||||
Features Wanted
|
||||
</Link>
|
||||
|
||||
</Flex>
|
||||
|
||||
<Flex gap={"2"} direction={"column"} >
|
||||
@@ -68,6 +60,14 @@ export default function Footer() {
|
||||
>
|
||||
UIUXDECK
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="https://twelve.tools"
|
||||
className="text-sm text-muted-foreground hover:text-primary"
|
||||
target="_blank"
|
||||
>
|
||||
<Image src="https://twelve.tools/badge0-white.svg" alt="Featured on Twelve Tools" width={100} height={28} />
|
||||
</Link>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
|
||||
@@ -7,24 +7,27 @@ import PreviewToolbar from "./common/PreviewToolbar";
|
||||
import { useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import TextSetting, { TextProp } from "./common/TextSetting";
|
||||
import Effects, { EffectProp } from "./common/Effects";
|
||||
|
||||
/**
|
||||
* 全特性工具栏
|
||||
* @returns
|
||||
*/
|
||||
export default function Page({ textProp, backgroundProp }: { textProp: TextProp | undefined, backgroundProp: BackgroundProp | undefined }) {
|
||||
export default function Page({ textProp, backgroundProp, effectProp }: { textProp: TextProp | undefined, backgroundProp: BackgroundProp | undefined, effectProp: EffectProp | undefined }) {
|
||||
|
||||
const t = useTranslations("TextEditor");
|
||||
|
||||
backgroundProp = backgroundProp || {
|
||||
color: "#c4b1b1",
|
||||
color: "#a49494",
|
||||
image: null,
|
||||
} satisfies BackgroundProp;
|
||||
|
||||
textProp = textProp || TextProp.default(t("defaultText"));
|
||||
effectProp = effectProp || { enableShadow: true, shadowColor: "#000000" } satisfies EffectProp;
|
||||
|
||||
const [background, setBackground] = useState<BackgroundProp>(backgroundProp!);
|
||||
const [text, setText] = useState<TextProp>(textProp!);
|
||||
const [effect, setEffect] = useState<EffectProp>(effectProp);
|
||||
|
||||
return (
|
||||
<Flex gap={"2"}>
|
||||
@@ -34,10 +37,12 @@ export default function Page({ textProp, backgroundProp }: { textProp: TextProp
|
||||
setBackground={setBackground}
|
||||
/>
|
||||
<TextSetting text={text} setText={setText} />
|
||||
|
||||
<Effects effect={effect} setEffect={setEffect} background={background} />
|
||||
</Flex>
|
||||
|
||||
<Flex className="w-2/3" direction={"column"} justify={"between"}>
|
||||
<PreviewToolbar background={background} text={text} />
|
||||
<PreviewToolbar background={background} text={text} effect={effect} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -37,10 +37,12 @@ export default function Header() {
|
||||
{t("blogName")}
|
||||
</Link>
|
||||
|
||||
|
||||
</Flex>
|
||||
|
||||
<Flex align="center" gap="4" className="w-1/4">
|
||||
<Link href="https://github.com/wms-why/fast3dtextonline" target="_blank">
|
||||
<svg width="24" height="24" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.49933 0.25C3.49635 0.25 0.25 3.49593 0.25 7.50024C0.25 10.703 2.32715 13.4206 5.2081 14.3797C5.57084 14.446 5.70302 14.2222 5.70302 14.0299C5.70302 13.8576 5.69679 13.4019 5.69323 12.797C3.67661 13.235 3.25112 11.825 3.25112 11.825C2.92132 10.9874 2.44599 10.7644 2.44599 10.7644C1.78773 10.3149 2.49584 10.3238 2.49584 10.3238C3.22353 10.375 3.60629 11.0711 3.60629 11.0711C4.25298 12.1788 5.30335 11.8588 5.71638 11.6732C5.78225 11.205 5.96962 10.8854 6.17658 10.7043C4.56675 10.5209 2.87415 9.89918 2.87415 7.12104C2.87415 6.32925 3.15677 5.68257 3.62053 5.17563C3.54576 4.99226 3.29697 4.25521 3.69174 3.25691C3.69174 3.25691 4.30015 3.06196 5.68522 3.99973C6.26337 3.83906 6.8838 3.75895 7.50022 3.75583C8.1162 3.75895 8.73619 3.83906 9.31523 3.99973C10.6994 3.06196 11.3069 3.25691 11.3069 3.25691C11.7026 4.25521 11.4538 4.99226 11.3795 5.17563C11.8441 5.68257 12.1245 6.32925 12.1245 7.12104C12.1245 9.9063 10.4292 10.5192 8.81452 10.6985C9.07444 10.9224 9.30633 11.3648 9.30633 12.0413C9.30633 13.0102 9.29742 13.7922 9.29742 14.0299C9.29742 14.2239 9.42828 14.4496 9.79591 14.3788C12.6746 13.4179 14.75 10.7025 14.75 7.50024C14.75 3.49593 11.5036 0.25 7.49933 0.25Z" fill="currentColor" fillRule="evenodd" clipRule="evenodd"></path></svg>
|
||||
</Link>
|
||||
<LanguageSwitcher />
|
||||
<ModeToggle />
|
||||
</Flex>
|
||||
|
||||
@@ -83,33 +83,33 @@ export default function BackgroundSelector({
|
||||
};
|
||||
|
||||
return (
|
||||
<Box className="p-4 border rounded-lg min-w-64">
|
||||
<Box className="p-4 border rounded-lg min-w-64 border-t-2 border-t-purple-500 shadow">
|
||||
<Heading as="h2" size="4" className="font-medium text-lg">{t("title")}</Heading>
|
||||
<Flex gap={"2"} p="2" direction={"column"}>
|
||||
<Flex gap="2" align={"center"}>
|
||||
<Checkbox checked={backgroundType.includes("color")} onClick={(e) => handleBackgroundTypeChange("color")} className="cursor-pointer" />
|
||||
<Heading as="h3" size={"3"}>{t("colorOption")}</Heading>
|
||||
<Heading as="h3" size={"3"} className="w-24">{t("colorOption")}</Heading>
|
||||
<Flex gap={"4"} >
|
||||
<input
|
||||
type="color"
|
||||
id="color-picker"
|
||||
value={color || "black"}
|
||||
onChange={handleColorChange}
|
||||
className="w-1/3 h-10 rounded-md cursor-pointer"
|
||||
className="w-1/3 h-8 rounded-md cursor-pointer"
|
||||
/>
|
||||
|
||||
{color && (<input
|
||||
type="text"
|
||||
value={color}
|
||||
onChange={handleColorChange}
|
||||
className="w-1/2 h-10 rounded-md cursor-pointer pl-4"
|
||||
className="w-1/2 h-8 rounded-md cursor-pointer pl-4"
|
||||
/>)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex gap="2" align={"center"}>
|
||||
<Checkbox checked={backgroundType.includes("image")} onClick={(e) => handleBackgroundTypeChange("image")} className="cursor-pointer" />
|
||||
<Heading as="h3" size={"3"}>{t("imageOption")}</Heading>
|
||||
<Heading as="h3" size={"3"} className="w-24">{t("imageOption")}</Heading>
|
||||
<Flex gap={"4"} >
|
||||
<input
|
||||
type="file"
|
||||
@@ -117,7 +117,7 @@ export default function BackgroundSelector({
|
||||
accept="image/*"
|
||||
onChange={handleImageUpload}
|
||||
className="block w-full text-sm text-muted-foreground
|
||||
file:mr-4 file:py-2 file:px-4
|
||||
file:mr-4 file:py-1 file:px-2
|
||||
file:rounded-md file:border-0
|
||||
file:text-sm file:font-semibold
|
||||
file:bg-primary file:text-primary-foreground
|
||||
|
||||
93
src/components/common/Effects.tsx
Normal file
93
src/components/common/Effects.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
'use client'
|
||||
import { Box, Checkbox, Flex, Heading, IconButton, Tooltip } from "@radix-ui/themes";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useEffect, useState } from "react";
|
||||
import { BackgroundProp } from "./BackgroundSelector";
|
||||
import { CircleQuestionMarkIcon } from "lucide-react";
|
||||
export interface EffectProp {
|
||||
enableShadow: boolean;
|
||||
shadowColor: string;
|
||||
}
|
||||
export default function EffectsPage({
|
||||
effect,
|
||||
setEffect,
|
||||
background
|
||||
}: {
|
||||
effect: EffectProp;
|
||||
setEffect: (e: EffectProp) => void;
|
||||
background: BackgroundProp
|
||||
}) {
|
||||
const t = useTranslations("Effects");
|
||||
|
||||
// const [shadowValid, setShadowValid] = useState(true);
|
||||
// useEffect(() => {
|
||||
// setShadowValid(!background.image);
|
||||
// }, [background])
|
||||
|
||||
return (
|
||||
<Box className="p-4 border rounded-lg min-w-64 border-t-2 border-t-purple-500 shadow">
|
||||
<Heading as="h2" size="4" className="font-medium text-lg">{t("title")}</Heading>
|
||||
<Flex gap={"2"} p="2" direction={"column"}>
|
||||
|
||||
<Flex gap="2" align={"center"}>
|
||||
<Checkbox checked={effect.enableShadow} onClick={(e) => setEffect({ ...effect, enableShadow: !effect.enableShadow })} />
|
||||
{t("shadowOption")}
|
||||
<input
|
||||
type="color"
|
||||
id="color-picker"
|
||||
value={effect.shadowColor}
|
||||
onChange={e => setEffect({ ...effect, shadowColor: e.target.value })}
|
||||
className="w-1/3 h-8 rounded-md cursor-pointer"
|
||||
/>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
value={effect.shadowColor}
|
||||
onChange={e => setEffect({ ...effect, shadowColor: e.target.value })}
|
||||
className="w-1/3 h-8 rounded-md cursor-pointer pl-4"
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
{/* <Flex gap="2" align={"center"}>
|
||||
<Checkbox checked={backgroundType.includes("color")} onClick={(e) => handleBackgroundTypeChange("color")} className="cursor-pointer" />
|
||||
<Heading as="h3" size={"3"}>{t("colorOption")}</Heading>
|
||||
<Flex gap={"4"} >
|
||||
<input
|
||||
type="color"
|
||||
id="color-picker"
|
||||
value={color || "black"}
|
||||
onChange={handleColorChange}
|
||||
className="w-1/3 h-10 rounded-md cursor-pointer"
|
||||
/>
|
||||
|
||||
{color && (<input
|
||||
type="text"
|
||||
value={color}
|
||||
onChange={handleColorChange}
|
||||
className="w-1/2 h-10 rounded-md cursor-pointer pl-4"
|
||||
/>)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex gap="2" align={"center"}>
|
||||
<Checkbox checked={backgroundType.includes("image")} onClick={(e) => handleBackgroundTypeChange("image")} className="cursor-pointer" />
|
||||
<Heading as="h3" size={"3"}>{t("imageOption")}</Heading>
|
||||
<Flex gap={"4"} >
|
||||
<input
|
||||
type="file"
|
||||
id="file-upload"
|
||||
accept="image/*"
|
||||
onChange={handleImageUpload}
|
||||
className="block w-full text-sm text-muted-foreground
|
||||
file:mr-4 file:py-2 file:px-4
|
||||
file:rounded-md file:border-0
|
||||
file:text-sm file:font-semibold
|
||||
file:bg-primary file:text-primary-foreground
|
||||
hover:file:bg-primary/90"
|
||||
/>
|
||||
</Flex>
|
||||
</Flex> */}
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -4,15 +4,17 @@ import { useLocale, useTranslations } from "next-intl";
|
||||
import { Eye, Download, Share } from "lucide-react";
|
||||
import { BackgroundProp } from "./BackgroundSelector";
|
||||
import { Text, Flex, Button, Select, AlertDialog, Code, AspectRatio } from "@radix-ui/themes";
|
||||
import { getPicture, resize, init as threeInit, updateBackground, updateTextProps } from "./ThreeTools";
|
||||
import { getPicture, resize, init as threeInit, updateBackground, updateEffectProp, updateTextProp } from "./ThreeTools";
|
||||
import { TextProp } from "./TextSetting";
|
||||
import { encodeText, getShareLink } from "@/lib/utils";
|
||||
import { getShareLink } from "@/lib/utils";
|
||||
import { EffectProp } from "./Effects";
|
||||
|
||||
const Sizes = [
|
||||
"1920x1080",
|
||||
"1024x576",
|
||||
"1024x768",
|
||||
"800x600",
|
||||
"512x288"
|
||||
]
|
||||
interface Size {
|
||||
width: number;
|
||||
@@ -27,9 +29,11 @@ const AspectRatios = Sizes.map(o => {
|
||||
export default function PreviewToolbar({
|
||||
background,
|
||||
text,
|
||||
effect
|
||||
}: {
|
||||
background: BackgroundProp;
|
||||
text: TextProp;
|
||||
effect: EffectProp
|
||||
}) {
|
||||
let host = process.env.NEXT_PUBLIC_HOST?.substring("https://".length);
|
||||
const t = useTranslations("PreviewBar");
|
||||
@@ -70,12 +74,17 @@ export default function PreviewToolbar({
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
updateTextProps(text);
|
||||
updateTextProp(text);
|
||||
|
||||
console.log("text change", text);
|
||||
|
||||
}, [text]);
|
||||
|
||||
useEffect(() => {
|
||||
updateEffectProp(effect);
|
||||
console.log("effect change", effect);
|
||||
}, [effect]);
|
||||
|
||||
const generateImage = async (w: number, h: number): Promise<string> => {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -126,9 +135,6 @@ export default function PreviewToolbar({
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
const handleDownload = async () => {
|
||||
@@ -193,11 +199,8 @@ export default function PreviewToolbar({
|
||||
}
|
||||
|
||||
const bg = { ...background, image: null };
|
||||
let txt = JSON.stringify({ bg, text });
|
||||
txt = encodeText(txt);
|
||||
|
||||
|
||||
const link = getShareLink(txt, locale);
|
||||
const link = getShareLink({ bg, text }, locale);
|
||||
|
||||
setShareLink(link);
|
||||
|
||||
@@ -228,7 +231,7 @@ export default function PreviewToolbar({
|
||||
}, [handleFullScreen]);
|
||||
|
||||
return (
|
||||
<Flex direction={"column"} justify={"center"} align={"center"} p="2" className="rounded-lg border w-full" gap={"2"}>
|
||||
<Flex direction={"column"} justify={"center"} align={"center"} p="2" className="shadow rounded-lg border w-full border-t-2 border-t-purple-500" gap={"2"}>
|
||||
<Flex gap={"4"} >
|
||||
{t("tipsTitle")}:
|
||||
<Text>{t("mouseLeft")}</Text>
|
||||
|
||||
@@ -43,7 +43,7 @@ export class TextProp {
|
||||
}
|
||||
return {
|
||||
text,
|
||||
color: "#8e86fe",
|
||||
color: ["#ce6464", "#63635a"],
|
||||
colorGradientDir: "l2r",
|
||||
font,
|
||||
fontUrl: getOnlineFontPath(font, FontWeight.Regular),
|
||||
@@ -79,9 +79,6 @@ export default function TextSetting({
|
||||
setText: (text: TextProp) => void;
|
||||
}) {
|
||||
const locale = useLocale();
|
||||
|
||||
|
||||
|
||||
const t = useTranslations("TextEditor");
|
||||
const [uploadFonts, setUploadFonts] = useState<UploadFont[]>([]);
|
||||
const isPureColor = !Array.isArray(text.color);
|
||||
@@ -167,16 +164,14 @@ export default function TextSetting({
|
||||
setFontWeightEnabled(map);
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Flex className="p-4 border rounded-lg " gap={"3"} direction={"column"}>
|
||||
<Flex className="p-4 border rounded-lg border-t-2 border-t-purple-500 shadow" gap={"3"} direction={"column"}>
|
||||
<Heading as="h2" size="4" className="font-medium text-lg" >{t("title")}</Heading>
|
||||
<textarea
|
||||
value={text.text}
|
||||
onChange={e => setText({ ...text, text: e.target.value })}
|
||||
className="w-full p-3 border rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
rows={4}
|
||||
rows={2}
|
||||
/>
|
||||
<div className="space-y-1">
|
||||
<Heading as="h3" size={"3"} >{t("textColor")}</Heading>
|
||||
@@ -220,13 +215,13 @@ export default function TextSetting({
|
||||
type="color"
|
||||
value={textGradientColor[0]}
|
||||
onChange={e => setTextGradientColor([e.target.value, text.color[1]])}
|
||||
className="w-1/2 h-10 rounded-md cursor-pointer"
|
||||
className="w-1/2 h-8 rounded-md cursor-pointer"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={textGradientColor[0]}
|
||||
onChange={e => setTextGradientColor([e.target.value, text.color[1]])}
|
||||
className="w-1/2 h-10 rounded-md cursor-pointer pl-4"
|
||||
className="w-1/2 h-8 rounded-md cursor-pointer pl-4"
|
||||
/>
|
||||
</Flex>
|
||||
<Flex gap={"4"}>
|
||||
@@ -235,13 +230,13 @@ export default function TextSetting({
|
||||
value={textGradientColor[1]}
|
||||
onChange={e => setTextGradientColor([text.color[0], e.target.value])}
|
||||
|
||||
className="w-1/2 h-10 rounded-md cursor-pointer"
|
||||
className="w-1/2 h-8 rounded-md cursor-pointer"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={textGradientColor[1]}
|
||||
onChange={e => setTextGradientColor([text.color[0], e.target.value])}
|
||||
className="w-1/2 h-10 rounded-md cursor-pointer pl-4"
|
||||
className="w-1/2 h-8 rounded-md cursor-pointer pl-4"
|
||||
/>
|
||||
</Flex>
|
||||
</Box>
|
||||
|
||||
@@ -2,10 +2,12 @@ import * as THREE from "three";
|
||||
import { BackgroundProp } from "./BackgroundSelector";
|
||||
import { FontLoader } from "three/addons/loaders/FontLoader.js";
|
||||
import { TextGeometry } from "three/addons/geometries/TextGeometry.js";
|
||||
import { ShadowMapViewer } from "three/addons/utils/ShadowMapViewer.js";
|
||||
|
||||
THREE.Cache.enabled = true;
|
||||
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
|
||||
import { ColorGradientDir, TextProp } from "./TextSetting";
|
||||
import { EffectProp } from "./Effects";
|
||||
|
||||
let camera: THREE.PerspectiveCamera,
|
||||
scene: THREE.Scene,
|
||||
@@ -28,9 +30,10 @@ export function init(
|
||||
renderer.setPixelRatio(1);
|
||||
renderer.setSize(width, height, false);
|
||||
renderer.setAnimationLoop(animate);
|
||||
renderer.shadowMap.enabled = true;
|
||||
|
||||
camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 1000);
|
||||
camera.position.set(0, 80, 0);
|
||||
camera.position.set(0, 0, 50);
|
||||
|
||||
// const domWidth = container.clientWidth;
|
||||
// const domHeight = container.clientHeight;
|
||||
@@ -49,9 +52,10 @@ export function init(
|
||||
// controls
|
||||
|
||||
controls = new OrbitControls(camera, renderer.domElement);
|
||||
controls.screenSpacePanning = false;
|
||||
// controls.screenSpacePanning = false;
|
||||
|
||||
controls.enabled = true;
|
||||
// controls.enablePan = false;
|
||||
|
||||
//controls.addEventListener( 'change', render ); // call this only in static scenes (i.e., if there is no animation loop)
|
||||
|
||||
@@ -63,8 +67,6 @@ export function init(
|
||||
controls.minDistance = 0.1;
|
||||
controls.maxDistance = 50000;
|
||||
|
||||
controls.maxPolarAngle = Math.PI / 2;
|
||||
|
||||
// lights
|
||||
|
||||
const dirLight1 = new THREE.DirectionalLight(0xffffff, 3);
|
||||
@@ -72,9 +74,25 @@ export function init(
|
||||
scene.add(dirLight1);
|
||||
|
||||
const dirLight2 = new THREE.DirectionalLight(0x002288, 3);
|
||||
dirLight2.position.set(-1, -1, -1);
|
||||
dirLight2.position.set(-10, 10, 50);
|
||||
dirLight2.castShadow = true;
|
||||
dirLight2.shadow.camera.left = -100;
|
||||
dirLight2.shadow.camera.top = 100;
|
||||
dirLight2.shadow.camera.bottom = -100;
|
||||
dirLight2.shadow.camera.right = 100;
|
||||
dirLight2.shadow.camera.near = 0;
|
||||
dirLight2.shadow.camera.far = 200;
|
||||
dirLight2.shadow.bias = -0.000222;
|
||||
dirLight2.shadow.mapSize.width = 2048;
|
||||
dirLight2.shadow.mapSize.height = 2048;
|
||||
scene.add(dirLight2);
|
||||
|
||||
// const helper = new THREE.DirectionalLightHelper(dirLight2, 50);
|
||||
// scene.add(helper);
|
||||
|
||||
// const helper2 = new THREE.CameraHelper(dirLight2.shadow.camera);
|
||||
// scene.add(helper2);
|
||||
|
||||
const ambientLight = new THREE.AmbientLight(0x555555);
|
||||
scene.add(ambientLight);
|
||||
}
|
||||
@@ -113,7 +131,7 @@ export function resize(
|
||||
|
||||
let textMesh: THREE.Mesh;
|
||||
let lastTextProps: TextProp | null = null;
|
||||
export async function updateTextProps(textProps: TextProp) {
|
||||
export async function updateTextProp(textProps: TextProp) {
|
||||
// const mirror = true;
|
||||
// const plane = new THREE.Mesh(
|
||||
// new THREE.PlaneGeometry(10000, 10000),
|
||||
@@ -143,8 +161,10 @@ export async function updateTextProps(textProps: TextProp) {
|
||||
let size = new THREE.Vector3();
|
||||
geo.boundingBox?.getSize(size);
|
||||
let textMesh1 = new THREE.Mesh(geo, mat);
|
||||
textMesh1.rotateX(-Math.PI / 2);
|
||||
textMesh1.scale.multiplyScalar(100 / size.x);
|
||||
// textMesh1.rotateX(-Math.PI / 2);
|
||||
textMesh1.scale.multiplyScalar(50).divideScalar(size.x);
|
||||
textMesh1.castShadow = true;
|
||||
textMesh1.receiveShadow = true;
|
||||
|
||||
scene.add(textMesh1);
|
||||
|
||||
@@ -159,6 +179,9 @@ export async function updateTextProps(textProps: TextProp) {
|
||||
let geo = await getTextGeometry(textProps);
|
||||
textMesh.geometry.dispose();
|
||||
textMesh.geometry = geo;
|
||||
let size = new THREE.Vector3();
|
||||
geo.boundingBox?.getSize(size);
|
||||
textMesh.scale.set(1, 1, 1).multiplyScalar(50).divideScalar(size.x);
|
||||
|
||||
if (Array.isArray(textProps.color)) {
|
||||
setGradient(
|
||||
@@ -271,6 +294,7 @@ async function getTextGeometry(textProps: TextProp) {
|
||||
|
||||
textGeo.computeBoundingBox();
|
||||
textGeo.center();
|
||||
textGeo.translate(0, size / 2, depth / 2);
|
||||
textGeo.computeVertexNormals();
|
||||
return textGeo;
|
||||
|
||||
@@ -334,3 +358,28 @@ export function getPicture(width: number, height: number) {
|
||||
renderer.setSize(lastWidth, lastHeight, false);
|
||||
return img;
|
||||
}
|
||||
|
||||
let shadowPlane: THREE.Mesh | null = null;
|
||||
export function updateEffectProp(effect: EffectProp) {
|
||||
// && background.color && !background.image
|
||||
if (effect.enableShadow) {
|
||||
if (!shadowPlane) {
|
||||
shadowPlane = new THREE.Mesh(
|
||||
new THREE.PlaneGeometry(100, 100, 10, 10),
|
||||
new THREE.ShadowMaterial({
|
||||
color: new THREE.Color(effect.shadowColor),
|
||||
opacity: 0.3,
|
||||
})
|
||||
);
|
||||
shadowPlane.receiveShadow = true;
|
||||
}
|
||||
scene.add(shadowPlane);
|
||||
|
||||
(shadowPlane.material as THREE.ShadowMaterial).color.set(
|
||||
effect.shadowColor
|
||||
);
|
||||
shadowPlane.visible = true;
|
||||
} else {
|
||||
shadowPlane && (shadowPlane.visible = false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@ import Header from "@/components/Header";
|
||||
import { Box, Container, Flex, Heading, Text, Card } from "@radix-ui/themes";
|
||||
import { HelpCircle } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { EffectProp } from "../common/Effects";
|
||||
|
||||
export function OnlyPage({ textProp, backgroundProp }: {
|
||||
export function OnlyPage({ textProp, backgroundProp, effectProp }: {
|
||||
textProp: TextProp | undefined;
|
||||
backgroundProp: BackgroundProp | undefined
|
||||
backgroundProp: BackgroundProp | undefined;
|
||||
effectProp: EffectProp | undefined;
|
||||
}) {
|
||||
const t = useTranslations('TextEditor');
|
||||
|
||||
@@ -20,6 +22,7 @@ export function OnlyPage({ textProp, backgroundProp }: {
|
||||
<FullEditor
|
||||
textProp={textProp}
|
||||
backgroundProp={backgroundProp}
|
||||
effectProp={effectProp}
|
||||
/>
|
||||
</Container>
|
||||
<Container p="4">
|
||||
|
||||
@@ -2,18 +2,21 @@ import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import CryptoJS from "crypto-js";
|
||||
import LZString from "lz-string";
|
||||
import { BackgroundProp } from "@/components/common/BackgroundSelector";
|
||||
import { EffectProp } from "@/components/common/Effects";
|
||||
import { TextProp } from "@/components/common/TextSetting";
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
const SECRET_KEY = "fast3dtext-ymk";
|
||||
export function encodeText(text: string) {
|
||||
function encodeText(text: string) {
|
||||
const compressed = LZString.compressToEncodedURIComponent(text);
|
||||
const encrypted = CryptoJS.AES.encrypt(compressed, SECRET_KEY).toString();
|
||||
return encodeURIComponent(encrypted);
|
||||
}
|
||||
|
||||
export function decodeText(encodedText: string) {
|
||||
function decodeText(encodedText: string) {
|
||||
const decoded = decodeURIComponent(encodedText);
|
||||
const decrypted = CryptoJS.AES.decrypt(decoded, SECRET_KEY).toString(
|
||||
CryptoJS.enc.Utf8
|
||||
@@ -22,6 +25,16 @@ export function decodeText(encodedText: string) {
|
||||
return decompressed;
|
||||
}
|
||||
|
||||
export function getShareLink(data: string, locale: string) {
|
||||
return `${window.location.origin}/${locale}/editor/${data}`;
|
||||
export interface ShareObj {
|
||||
bg: BackgroundProp;
|
||||
text: TextProp;
|
||||
effect?: EffectProp;
|
||||
}
|
||||
export function getShareLink(data: ShareObj, locale: string) {
|
||||
const dataStr = JSON.stringify(data);
|
||||
return `${window.location.origin}/${locale}/editor/${encodeText(dataStr)}`;
|
||||
}
|
||||
export function decode(data: string) {
|
||||
const decoded = decodeText(data);
|
||||
return JSON.parse(decoded) as ShareObj;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user