feat: enhance image handling and layout adjustments

- Added image quality settings and device sizes in next.config.ts for better image optimization.
- Updated package dependencies, including cheerio and baseline-browser-mapping.
- Improved global styles for blog content images to ensure responsiveness.
- Refactored button components for consistent sizing and styling across the application.
- Enhanced blog post card layout with new variants and improved author name formatting.
- Adjusted hero section and content layout for better visual appeal and user experience.
This commit is contained in:
alirez.br
2026-06-17 21:35:35 +03:30
parent a9ee6659cc
commit ff2632f8db
47 changed files with 1050 additions and 648 deletions
+9 -12
View File
@@ -1,3 +1,6 @@
import { Button } from "@/components/ui/button";
import Link from "next/link";
export default function AboutPage() {
return (
<>
@@ -242,18 +245,12 @@ export default function AboutPage() {
با ما تماس بگیرید تا درباره نیازهای شما صحبت کنیم و بهترین راهکار را پیشنهاد دهیم
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<a
href="/pricing"
className="inline-flex items-center justify-center rounded-lg bg-primary px-8 py-3 text-base font-medium text-white hover:bg-accent hover:text-foreground transition-colors"
>
مشاهده قیمت‌ها
</a>
<a
href="#contact"
className="inline-flex items-center justify-center rounded-lg border border-primary px-8 py-3 text-base font-medium text-primary hover:bg-accent hover:text-foreground hover:border-accent transition-colors"
>
تماس با ما
</a>
<Button asChild size="lg">
<Link href="/pricing">مشاهده قیمت‌ها</Link>
</Button>
<Button asChild size="lg" variant="outline">
<Link href="#contact">تماس با ما</Link>
</Button>
</div>
</div>
</div>
+5
View File
@@ -295,4 +295,9 @@
.wp-block-image {
@apply rounded-md overflow-hidden shadow-sm;
}
.blog-content img {
@apply w-full h-auto;
image-rendering: auto;
}
-1
View File
@@ -1,7 +1,6 @@
import ContentSection from "@/components/content-1";
import Features from "@/components/features-12";
import HeroSection from "@/components/hero-section";
import Image from "next/image";
export default function Home() {
return (
+1 -1
View File
@@ -25,7 +25,7 @@ export function BlogBlockquote({ payload }: BlogBlockquoteProps) {
{citation ? (
<footer className="mt-4 border-t border-border/60 pt-4">
<cite className="text-sm text-muted-foreground not-italic">
— {citation}
{citation}
</cite>
</footer>
) : null}
+54 -4
View File
@@ -4,12 +4,13 @@ import { ImageIcon, User } from "lucide-react";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { getFeaturedImageUrl, type WpPost } from "@/lib/wp-api";
import { formatDateShamsi } from "@/lib/format-date";
import { formatAuthorName } from "@/lib/blog-utils";
function stripHtml(html: string): string {
return html.replace(/<[^>]*>/g, "").trim();
}
type BlogPostCardVariant = "default" | "tiny";
type BlogPostCardVariant = "default" | "tiny" | "related";
export function BlogPostCard({
post,
@@ -20,9 +21,57 @@ export function BlogPostCard({
}) {
const excerpt = stripHtml(post.excerpt.rendered) || stripHtml(post.content.rendered).slice(0, 160);
const modifiedDate = post.modified ? formatDateShamsi(post.modified) : null;
const authorName = post._embedded?.author?.[0]?.name ?? null;
const authorName = formatAuthorName(post._embedded?.author?.[0]?.name);
const thumbnailUrl = getFeaturedImageUrl(post, "full");
if (variant === "related") {
return (
<Link href={`/blog/${encodeURIComponent(post.slug)}`} className="block h-full min-w-0">
<Card className="h-full overflow-hidden transition-all rounded-md duration-300 hover:shadow-md flex flex-col p-0 gap-0">
<CardContent className="p-3 sm:p-4 grid grid-cols-3 gap-3 sm:gap-4 min-w-0">
{thumbnailUrl ? (
<div className="relative rounded-md aspect-[4/3] w-full overflow-hidden bg-muted shrink-0 col-span-1 min-w-0">
<Image
src={thumbnailUrl}
alt=""
fill
className="object-cover"
quality={100}
sizes="(max-width: 640px) 33vw, (max-width: 1024px) 20vw, 200px"
/>
</div>
) : (
<div
className="relative rounded-md flex aspect-[4/3] w-full items-center justify-center overflow-hidden bg-muted shrink-0 col-span-1"
aria-hidden
>
<ImageIcon className="size-10 text-muted-foreground/50" />
</div>
)}
<div className="col-span-2 min-w-0 flex flex-col justify-center">
<h4 className="text-base font-semibold leading-snug line-clamp-3">
{post.title.rendered}
</h4>
<div className="flex flex-wrap w-full items-center gap-x-4 gap-y-1 text-sm text-muted-foreground bg-muted inline-flex p-1.5 mt-3 rounded-sm">
{authorName && (
<span className="inline-flex flex-1 items-center gap-1.5 min-w-0">
<User className="size-4 shrink-0" aria-hidden />
<span className="truncate">{authorName}</span>
</span>
)}
{modifiedDate && (
<time dateTime={post.modified} className="shrink-0">
{modifiedDate}
</time>
)}
</div>
</div>
</CardContent>
</Card>
</Link>
);
}
if (variant === "tiny") {
return (
<Link href={`/blog/${encodeURIComponent(post.slug)}`} className="block h-full">
@@ -35,7 +84,8 @@ export function BlogPostCard({
alt=""
fill
className="object-cover"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
quality={100}
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 400px"
/>
</div>
) : (
@@ -82,7 +132,7 @@ export function BlogPostCard({
className="object-cover"
quality={100}
priority
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 350px"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 400px"
/>
</div>
) : (
-2
View File
@@ -140,7 +140,6 @@ export function BlogPostMeta({
<Button
variant="ghost"
size="icon-sm"
className="size-8 text-muted-foreground hover:text-foreground"
onClick={copyLink}
aria-label="کپی لینک"
>
@@ -160,7 +159,6 @@ export function BlogPostMeta({
<Button
variant="ghost"
size="icon-sm"
className="size-8 text-muted-foreground hover:text-foreground"
onClick={handleShare}
aria-label="اشتراک‌گذاری"
>
+1 -1
View File
@@ -20,7 +20,7 @@ export function BlogPullquote({ payload }: BlogPullquoteProps) {
{citation ? (
<footer className="mt-4">
<cite className="text-sm text-muted-foreground not-italic">
— {citation}
{citation}
</cite>
</footer>
) : null}
+17 -6
View File
@@ -11,24 +11,35 @@ export default function ContentSection() {
<div className="relative mb-6 sm:mb-0">
<div className="bg-linear-to-b aspect-76/59 relative rounded-2xl from-zinc-300 to-transparent p-px dark:from-zinc-700">
<Image
src="/assets/Echosystem.png"
className="rounded-[15px] object-cover"
alt="اکوسیستم یکپارچه فیپ"
src="/assets/main-pic/intelligent-workflow-control-center.jpg"
className="rounded-[15px] object-cover dark:hidden"
alt="مرکز کنترل گردش کار هوشمند"
width={1207}
height={929}
quality={100}
sizes="(max-width: 640px) 100vw, 50vw"
/>
<Image
src="/assets/main-pic/intelligent-workflow-control-center-dark.jpg"
className="hidden rounded-[15px] object-cover dark:block"
alt="مرکز کنترل گردش کار هوشمند"
width={1207}
height={929}
quality={100}
sizes="(max-width: 640px) 100vw, 50vw"
/>
</div>
</div>
<div className="relative space-y-4">
<p className="text-muted-foreground leading-relaxed tracking-normal">
<p className="text-muted-foreground leading-relaxed tracking-normal text-justify sm:text-start">
فیپ یک گروه متخصص است که در حوزه اینتگریشن و روش‌های نوین یکپارچه‌سازی فعالیت می‌کند.{" "}
<span className="text-accent-foreground font-bold">
<span className="text-primary font-bold">
ما با استفاده از تکنولوژی‌های پیشرفته و رویکردهای نوآورانه
</span>{" "}
به کسب‌وکارها کمک می‌کنیم تا سیستم‌های خود را به شکلی کارآمد و قابل‌اعتماد بهم متصل کنند.
</p>
<p className="text-muted-foreground leading-relaxed tracking-normal">
<p className="text-muted-foreground leading-relaxed tracking-normal text-justify sm:text-start">
ما در زمینه یکپارچه‌سازی سیستم‌ها، اتوماسیون فرآیندها، ارتباطات چندکاناله و مدیریت داده‌ها تخصص داریم. با استفاده از روش‌های نوین و ابزارهای پیشرفته، راهکارهای سفارشی برای هر کسب‌وکار طراحی و پیاده‌سازی می‌کنیم که به آن‌ها امکان می‌دهد تا از قدرت اتوماسیون و ارتباطات هوشمند بهره‌مند شوند.
</p>
+56 -34
View File
@@ -20,22 +20,29 @@ export default function Features() {
type ImageKey = "item-1" | "item-2" | "item-3" | "item-4";
const [activeItem, setActiveItem] = useState<ImageKey>("item-1");
const sectionImage = (filename: string) =>
`/assets/main-pic/4section/${encodeURIComponent(filename)}`;
const images = {
"item-1": {
image: "/assets/main-pic/4section/Systems Integration.jpg",
alt: "System integration",
image: sectionImage("Systems Integration.jpg"),
imageDark: sectionImage("Systems Integration-dark.jpg"),
alt: "یکپارچه‌سازی سیستم‌ها",
},
"item-2": {
image: "/assets/main-pic/4section/Powerful Management panels.jpg",
alt: "Management panels",
image: sectionImage("Powerful Management panels.jpg"),
imageDark: sectionImage("Powerful Management panels-dark.jpg"),
alt: "پنل‌های مدیریتی قدرتمند",
},
"item-3": {
image: "/assets/main-pic/4section/Interaction with Social Networks.jpg",
alt: "Social media integration",
image: sectionImage("Interaction with Social Networks.jpg"),
imageDark: sectionImage("Interaction with Social Networks_dark.jpg"),
alt: "تعامل با شبکه‌های اجتماعی",
},
"item-4": {
image: "/assets/main-pic/4section/Process Automation.jpg",
alt: "Process automation",
image: sectionImage("Process Automation.jpg"),
imageDark: sectionImage("Process Automation-dark.jpg"),
alt: "اتوماسیون فرآیندها",
},
};
@@ -113,33 +120,48 @@ export default function Features() {
</AccordionItem>
</Accordion>
<div className="bg-background relative flex overflow-hidden rounded-3xl border p-0 m-0">
<div className="w-15 absolute inset-0 right-0 ml-auto border-l bg-[repeating-linear-gradient(-45deg,var(--color-border),var(--color-border)_1px,transparent_1px,transparent_8px)]"></div>
<div className="aspect-76/59 bg-background relative w-full rounded-3xl m-0 p-0">
<AnimatePresence mode="wait">
<motion.div
key={`${activeItem}-id`}
initial={{ opacity: 0, y: 6, scale: 0.98 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 6, scale: 0.98 }}
transition={{ duration: 0.2 }}
className="size-full overflow-hidden rounded-3xl m-0 p-0"
>
<Image
src={images[activeItem].image}
className="size-full object-cover dark:mix-blend-lighten"
alt={images[activeItem].alt}
width={1207}
height={929}
/>
</motion.div>
</AnimatePresence>
<div className="relative rounded-3xl bg-linear-to-br from-primary/50 via-accent/40 to-secondary/50 p-px shadow-lg shadow-primary/10">
<div className="bg-background relative flex overflow-hidden rounded-[calc(1.5rem-1px)] m-0 p-0">
<div className="w-15 absolute inset-0 right-0 ml-auto border-l border-primary/15 bg-[repeating-linear-gradient(-45deg,color-mix(in_oklab,var(--color-primary)_25%,var(--color-border)),color-mix(in_oklab,var(--color-primary)_25%,var(--color-border))_1px,transparent_1px,transparent_8px)]"></div>
<div className="aspect-76/59 bg-background relative w-full rounded-[calc(1.5rem-1px)] m-0 p-0">
<AnimatePresence mode="wait">
<motion.div
key={`${activeItem}-id`}
initial={{ opacity: 0, y: 6, scale: 0.98 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 6, scale: 0.98 }}
transition={{ duration: 0.2 }}
className="size-full overflow-hidden rounded-[calc(1.5rem-1px)] m-0 p-0"
>
<Image
src={images[activeItem].image}
className="size-full object-cover dark:hidden"
alt={images[activeItem].alt}
width={1207}
height={929}
quality={100}
sizes="(max-width: 768px) 100vw, 50vw"
/>
<Image
src={images[activeItem].imageDark}
className="hidden size-full object-cover dark:block"
alt={images[activeItem].alt}
width={1207}
height={929}
quality={100}
sizes="(max-width: 768px) 100vw, 50vw"
/>
</motion.div>
</AnimatePresence>
</div>
<BorderBeam
duration={6}
size={200}
borderWidth={1}
colorFrom="#FCC61D"
colorTo="#01796F"
/>
</div>
<BorderBeam
duration={6}
size={200}
className="from-transparent via-yellow-700 to-transparent dark:via-white/50"
/>
</div>
</div>
</div>
+68 -61
View File
@@ -72,9 +72,11 @@ export default function HeroSection() {
<Image
src="/assets/main-pic/main-pic.jpg"
alt="background"
className="hidden size-full dark:block"
width="1150"
height="613"
className="hidden size-full object-cover dark:block"
width={1150}
height={613}
quality={100}
sizes="100vw"
/>
</AnimatedGroup>
@@ -120,7 +122,7 @@ export default function HeroSection() {
as="p"
className="mx-auto mt-8 max-w-2xl text-balance text-base md:text-lg leading-relaxed tracking-normal"
>
ما مجموعه‌ای از ابزارهای یکپارچه‌سازی، اتوماسیون و ارتباطات ارائه می‌کنیم که به شما کمک می‌کند داده‌ها، کاربران و فرآیندهای کسب‌وکارتان را از هر سیستم یا پلتفرمی — از وب‌سایت و CRM گرفته تا تلگرام و سرویس‌های سفارشی — به شکلی دقیق، سریع و قابل‌اعتماد بهم متصل کنید.
با ابزارهای یکپارچه‌سازی، اتوماسیون و ارتباطات ما، داده‌ها، کاربران و فرآیندهای کسب‌وکارتان را از هر سیستم یا پلتفرمی، از وب‌سایت و CRM تا تلگرام و سرویس‌های سفارشی، دقیق، سریع و قابل اعتماد به هم متصل کنید.
</TextEffect>
<AnimatedGroup
@@ -141,11 +143,7 @@ export default function HeroSection() {
key={1}
className="bg-foreground/10 rounded-[calc(var(--radius-xl)+0.125rem)] border p-0.5"
>
<Button
asChild
size="lg"
className="rounded-xl px-5 text-base"
>
<Button asChild size="lg">
<Link href="#link">
<span className="text-nowrap">شروع کنید</span>
</Link>
@@ -168,78 +166,87 @@ export default function HeroSection() {
...transitionVariants,
}}
>
<div className="relative mt-8 overflow-hidden px-2 sm:mt-12 md:mt-20 p-2">
<div className="ring-background bg-background relative max-w-5xl mx-auto overflow-hidden rounded-2xl border ring-1 aspect-[3/2]">
<Image
src="/assets/main-pic/Untitled-1 copy-min.jpg"
alt="یکپارچه‌سازی سیستم‌ها و شبکه دیجیتال"
fill
className="object-cover rounded-2xl inline-block my-0"
quality={100}
priority
/>
<div className="relative mt-8 overflow-visible px-2 sm:mt-12 md:mt-20 p-2">
<div className="relative max-w-4xl mx-auto flex items-center justify-center">
<div className="relative rounded-2xl overflow-hidden">
{/* Glow effect */}
<div className="absolute inset-0 rounded-2xl bg-gradient-to-r from-blue-500/20 via-purple-500/20 to-pink-500/20 blur-3xl -z-10 scale-110 animate-pulse" />
<div className="absolute inset-0 rounded-2xl bg-gradient-to-r from-blue-400/30 via-purple-400/30 to-pink-400/30 blur-2xl -z-10 scale-105" />
<div className="relative bg-background rounded-2xl border ring-1 ring-border/50 shadow-2xl shadow-blue-500/10 dark:shadow-blue-500/20">
<Image
src="/assets/main-pic/1.jpg"
alt="یکپارچه‌سازی سیستم‌ها و شبکه دیجیتال"
width={1920}
height={1280}
className="object-contain rounded-2xl w-full h-auto max-h-[600px] md:max-h-[700px] dark:hidden"
quality={100}
priority
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 90vw, 1200px"
/>
<Image
src="/assets/main-pic/1dark.jpg"
alt="یکپارچه‌سازی سیستم‌ها و شبکه دیجیتال"
width={1920}
height={1280}
className="hidden object-contain rounded-2xl w-full h-auto max-h-[600px] md:max-h-[700px] dark:block"
quality={100}
priority
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 90vw, 1200px"
/>
</div>
</div>
</div>
</div>
</AnimatedGroup>
</div>
</section>
<section className="bg-background pb-16 pt-16 md:pb-32">
<div className="group relative m-auto max-w-5xl px-6">
<div className="absolute inset-0 z-10 flex scale-95 items-center justify-center opacity-0 duration-500 group-hover:scale-100 group-hover:opacity-100">
<Link
href="/"
className="block text-sm duration-150 hover:text-accent transition-colors"
>
<span> اتصال به سایر سیستم ها </span>
<ChevronRight className="ml-1 inline-block size-3" />
</Link>
<div className="m-auto max-w-5xl px-6">
<div className="text-center">
<p className="text-sm font-medium text-muted-foreground">
یکپارچه با ابزارهای محبوب
</p>
<h2 className="mt-2 text-lg font-semibold md:text-xl">
اتصال به سایر سیستم‌ها
</h2>
</div>
<div className="group-hover:blur-xs mx-auto mt-12 grid max-w-4xl grid-cols-4 gap-x-12 gap-y-8 transition-all duration-500 group-hover:opacity-50 sm:gap-x-16 sm:gap-y-14">
<LogoItem
icon="logos:telegram"
alt="Telegram Logo"
size="md"
className="translate-x-1"
/>
<div className="mx-auto mt-10 flex max-w-3xl flex-wrap items-center justify-center gap-4 sm:gap-5">
<LogoItem icon="logos:telegram" alt="Telegram Logo" size="md" />
<LogoItem
icon="simple-icons:n8n"
alt="n8n Logo"
size="lg"
className="translate-x-4 "
iconClassName="text-black"
/>
<LogoItem
icon="skill-icons:wordpress"
alt="WordPress Logo"
size="md"
className="-translate-x-2"
/>
<LogoItem icon="logos:google-icon" alt="Google Logo" size="md" />
<LogoItem
icon="logos:whatsapp-icon"
alt="WhatsApp Logo"
size="md"
/>
<LogoItem
icon="logos:google-icon"
alt="Google Logo"
icon="logos:microsoft-windows-icon"
alt="Microsoft Windows Logo"
size="md"
className="translate-x-5"
/>
<div className="col-span-4 flex justify-center gap-x-12 sm:gap-x-40">
<LogoItem
icon="logos:whatsapp-icon"
alt="WhatsApp Logo"
size="md"
className="translate-x-2"
/>
<LogoItem
icon="logos:microsoft-windows-icon"
alt="Microsoft Windows Logo"
size="md"
className="translate-x-2"
/>
<LogoItem
icon="skill-icons:github-dark"
alt="github Logo"
size="md"
className="translate-x-4"
/>
</div>
<LogoItem icon="logos:github-icon" alt="GitHub Logo" size="md" />
</div>
<div className="mt-8 text-center">
<Link
href="/"
className="inline-flex items-center text-sm text-muted-foreground transition-colors hover:text-accent"
>
<span>مشاهده همه یکپارچه‌سازی‌ها</span>
<ChevronRight className="ml-1 inline-block size-3" />
</Link>
</div>
</div>
</section>
+2 -9
View File
@@ -61,10 +61,7 @@ export function LoginForm({
/>
</Field>
<Field>
<Button
type="submit"
className="w-full h-10 sm:h-11 text-sm sm:text-base"
>
<Button type="submit" size="lg" className="w-full">
ورود
</Button>
</Field>
@@ -72,11 +69,7 @@ export function LoginForm({
یا ادامه با
</FieldSeparator>
<Field className="">
<Button
variant="outline"
type="button"
className="w-full h-10 sm:h-11 text-sm sm:text-base"
>
<Button variant="outline" type="button" size="lg" className="w-full">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
+2 -2
View File
@@ -174,10 +174,10 @@ export default function Pricing() {
ما آماده‌ایم تا بر اساس نیازهای شما بهترین پیشنهاد را ارائه دهیم.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<Button asChild size="lg" className="text-base px-8 py-6">
<Button asChild size="lg">
<Link href="#chat">شروع گفتگو با تیم</Link>
</Button>
<Button asChild size="lg" variant="outline" className="text-base px-8 py-6">
<Button asChild size="lg" variant="outline">
<Link href="/about">درباره ما</Link>
</Button>
</div>
+2 -2
View File
@@ -41,11 +41,11 @@ export function ScrollToTop({ className }: { className?: string }) {
)}
>
<Button
size="icon"
size="icon-lg"
variant="outline"
onClick={scrollToTop}
aria-label="برگشت به بالا"
className="size-10 rounded-md shadow-md"
className="shadow-md"
>
<ChevronUp className="size-5" />
</Button>
+2 -4
View File
@@ -18,8 +18,7 @@ export function ThemeToggle() {
return (
<Button
variant="ghost"
size="sm"
className="size-9 rounded-lg"
size="icon"
aria-label="Toggle theme"
>
<Sun className="size-4" />
@@ -30,9 +29,8 @@ export function ThemeToggle() {
return (
<Button
variant="ghost"
size="sm"
size="icon"
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
className="size-9 rounded-lg transition-all duration-300 hover:bg-muted"
aria-label="Toggle theme"
>
<Sun className="size-4 rotate-0 scale-100 transition-all duration-300 dark:-rotate-90 dark:scale-0" />
+1 -1
View File
@@ -57,7 +57,7 @@ export const BorderBeam = ({
delay = 0,
duration = 6,
colorFrom = "#FCC61D",
colorTo = "#0046FF",
colorTo = "#01796F",
transition,
style,
reverse = false,
+12 -10
View File
@@ -5,25 +5,27 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg font-medium transition-colors disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-accent hover:text-foreground dark:hover:bg-[#FCC61D] dark:hover:text-foreground transition-colors",
default:
"bg-primary text-primary-foreground hover:bg-accent hover:text-foreground dark:hover:bg-[#FCC61D] dark:hover:text-foreground",
destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-[#FCC61D] dark:hover:text-foreground transition-colors",
"border border-primary text-primary bg-transparent hover:bg-accent hover:text-foreground hover:border-accent dark:bg-input/30 dark:border-input dark:text-foreground dark:hover:bg-[#FCC61D] dark:hover:text-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-accent hover:text-foreground dark:hover:bg-[#FCC61D] dark:hover:text-foreground transition-colors",
"bg-secondary text-secondary-foreground hover:bg-accent hover:text-foreground dark:hover:bg-[#FCC61D] dark:hover:text-foreground",
ghost:
"hover:bg-accent hover:text-foreground dark:hover:bg-[#FCC61D] dark:hover:text-foreground transition-colors",
link: "text-primary underline-offset-4 hover:text-accent hover:underline dark:hover:text-[#FCC61D] transition-colors",
"hover:bg-accent hover:text-foreground dark:hover:bg-[#FCC61D] dark:hover:text-foreground",
link:
"text-primary underline-offset-4 hover:text-accent hover:underline dark:hover:text-[#FCC61D]",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
default: "h-9 px-4 text-sm has-[>svg]:px-3",
sm: "h-8 gap-1.5 px-3 text-sm has-[>svg]:px-2.5",
lg: "h-11 px-8 text-base has-[>svg]:px-6",
icon: "size-9",
"icon-sm": "size-8",
"icon-lg": "size-10",
@@ -51,7 +53,7 @@ function Button({
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
className={cn(buttonVariants({ variant, size }), className)}
{...props}
/>
)
+31 -13
View File
@@ -6,26 +6,44 @@ interface LogoItemProps {
alt: string;
size?: "sm" | "md" | "lg";
className?: string;
iconClassName?: string;
}
const sizeMap = {
sm: "h-8",
md: "h-10",
lg: "h-12",
const iconSizeMap = {
sm: "size-6",
md: "size-7",
lg: "size-8",
};
export function LogoItem({ icon, alt, size = "md", className }: LogoItemProps) {
const tileSizeMap = {
sm: "size-12",
md: "size-14",
lg: "size-16",
};
export function LogoItem({
icon,
alt,
size = "md",
className,
iconClassName,
}: LogoItemProps) {
return (
<div className={cn("flex", className)}>
<Icon
icon={icon}
<div className={cn("flex items-center justify-center", className)}>
<div
className={cn(
"mx-auto w-fit dark:invert",
sizeMap[size]
"flex items-center justify-center rounded-2xl border border-border/50 bg-white shadow-sm transition-all duration-300",
"hover:-translate-y-1 hover:shadow-md",
"dark:border-white/10 dark:bg-white/95 dark:shadow-lg dark:shadow-black/25",
tileSizeMap[size]
)}
aria-label={alt}
/>
>
<Icon
icon={icon}
className={cn("shrink-0", iconSizeMap[size], iconClassName)}
aria-label={alt}
/>
</div>
</div>
);
}
+204 -5
View File
@@ -34,7 +34,7 @@ export interface TipBlockPayload {
content: string;
}
/** Payload بلاک code — بعداً پیاده‌سازی می‌شود. */
/** Payload بلاک code؛ بعداً پیاده‌سازی می‌شود. */
// export interface CodeBlockPayload { ... }
/** یونیون همهٔ payloadها برای تایپ‌امن رندر. */
@@ -122,6 +122,195 @@ function extractAccordionPayload(
return { title: "", items };
}
const FAQ_HEADING_RE = /س[وؤ]الات\s*متداول|^faq$/i;
function isFaqHeading(text: string): boolean {
return FAQ_HEADING_RE.test(text.replace(/<[^>]*>/g, "").trim());
}
function isDescendantOfExcluded(el: Element, excluded: Set<Element>): boolean {
let parent = el.parent;
while (parent && isElement(parent)) {
if (excluded.has(parent)) return true;
parent = parent.parent;
}
return false;
}
function nextElementSibling(node: unknown): Element | null {
let next = (node as { next?: unknown })?.next;
while (next) {
if (isElement(next)) return next;
next = (next as { next?: unknown }).next;
}
return null;
}
function collectH3QuestionPairs(
$: cheerio.CheerioAPI,
$container: cheerio.Cheerio<Element>,
items: { question: string; answer: string }[]
): void {
$container.find("> h3").each((_: number, h3: Element) => {
const question = $(h3).text().trim();
const answers: string[] = [];
let next = nextElementSibling(h3);
while (next) {
const tag = next.tagName?.toLowerCase();
if (tag === "h2" || tag === "h3") break;
if (tag === "p") {
const html = $(next).html()?.trim();
if (html) answers.push(html);
next = nextElementSibling(next);
continue;
}
break;
}
const answer = answers.join("");
if (question || answer) items.push({ question, answer });
});
}
/**
* استخراج سوالات متداول از گروه alignwide وردپرس (چیدمان ستونی h3 + p).
*/
function extractFaqGroupPayload(
$: cheerio.CheerioAPI,
el: Element
): CollapseBlockPayload | null {
const $el = $(el);
const $h2 = $el.children("h2").first();
if (!$h2.length || !isFaqHeading($h2.text())) return null;
const title = $h2.text().trim();
const items: { question: string; answer: string }[] = [];
$el.find(".wp-block-column").each((_: number, col: Element) => {
const $col = $(col);
const question = $col.find("h3").first().text().trim();
const $p = $col.find("p").first();
const answer = ($p.html() ?? "").trim();
if (question || answer) items.push({ question, answer });
});
if (!items.length) collectH3QuestionPairs($, $el, items);
return items.length ? { title, items } : null;
}
type FaqBlockEntry = {
el: Element;
blockType: "collapse";
blockId: number;
payload: CollapseBlockPayload;
/** برای FAQهای بدون wrapper؛ همهٔ نودهای h2 + h3 + p حذف می‌شوند. */
removeNodes?: Element[];
};
/**
* کشف بخش‌های «سوالات متداول» که به‌صورت h2 + جفت‌های h3/p در جریان محتوا آمده‌اند.
*/
function discoverStandaloneFaqSections(
$: cheerio.CheerioAPI,
excluded: Set<Element>
): FaqBlockEntry[] {
const results: FaqBlockEntry[] = [];
$("h2")
.toArray()
.filter(isElement)
.forEach((h2El) => {
if (excluded.has(h2El) || isDescendantOfExcluded(h2El, excluded)) return;
if (!isFaqHeading($(h2El).text())) return;
if ($(h2El).closest("div.wp-block-group.alignwide").length) return;
const title = $(h2El).text().trim();
const items: { question: string; answer: string }[] = [];
const removeNodes: Element[] = [h2El];
let next = nextElementSibling(h2El);
while (next) {
const tag = next.tagName?.toLowerCase();
if (tag === "h2") break;
if (tag === "h3") {
const question = $(next).text().trim();
removeNodes.push(next);
const answers: string[] = [];
let after = nextElementSibling(next);
while (after) {
const afterTag = after.tagName?.toLowerCase();
if (afterTag === "h2" || afterTag === "h3") break;
if (afterTag === "p") {
const html = $(after).html()?.trim();
removeNodes.push(after);
if (html) answers.push(html);
after = nextElementSibling(after);
continue;
}
break;
}
if (question || answers.length) {
items.push({ question, answer: answers.join("") });
}
next = nextElementSibling(removeNodes[removeNodes.length - 1]);
continue;
}
if (tag === "p" && !$(next).text().trim()) {
removeNodes.push(next);
next = nextElementSibling(next);
continue;
}
break;
}
if (items.length) {
results.push({
el: h2El,
blockType: "collapse",
blockId: -1,
payload: { title, items },
removeNodes,
});
}
});
return results;
}
function discoverFaqCollapseBlocks(
$: cheerio.CheerioAPI,
excluded: Set<Element>,
blocksByType: Record<string, BlockPayload[]>
): FaqBlockEntry[] {
const faqEntries: FaqBlockEntry[] = [];
$("div.wp-block-group.alignwide")
.toArray()
.filter(isElement)
.forEach((el) => {
if (excluded.has(el) || isDescendantOfExcluded(el, excluded)) return;
const payload = extractFaqGroupPayload($, el);
if (!payload) return;
const blockId = blocksByType.collapse.length;
blocksByType.collapse.push(payload);
faqEntries.push({ el, blockType: "collapse", blockId, payload });
excluded.add(el);
});
for (const entry of discoverStandaloneFaqSections($, excluded)) {
const blockId = blocksByType.collapse.length;
blocksByType.collapse.push(entry.payload);
faqEntries.push({ ...entry, blockId });
excluded.add(entry.el);
for (const node of entry.removeNodes ?? []) excluded.add(node);
}
return faqEntries;
}
function extractBlockquotePayload(
$: cheerio.CheerioAPI,
el: Element
@@ -236,6 +425,7 @@ export function discoverBlocks(html: string): DiscoveredContent {
blockType: BlockType;
blockId: number;
payload: BlockPayload;
removeNodes?: Element[];
};
const entries: BlockEntry[] = [];
const blocksByType: Record<string, BlockPayload[]> = {
@@ -261,15 +451,24 @@ export function discoverBlocks(html: string): DiscoveredContent {
}
}
const excluded = new Set(entries.map((entry) => entry.el));
entries.push(
...discoverFaqCollapseBlocks($, excluded, blocksByType)
);
// مرتب‌سازی بر اساس ترتیب در سند (از آخر به اول جایگزین می‌کنیم تا ایندکس‌ها جابجا نشوند)
entries.sort((a, b) => orderMap.get(a.el)! - orderMap.get(b.el)!);
// جایگزینی با placeholder از آخر به اول
for (let i = entries.length - 1; i >= 0; i--) {
const { el, blockType, blockId } = entries[i];
$(el).replaceWith(
`<div data-blog-block-type="${blockType}" data-blog-block-id="${blockId}"></div>`
);
const { el, blockType, blockId, removeNodes } = entries[i];
const placeholder = `<div data-blog-block-type="${blockType}" data-blog-block-id="${blockId}"></div>`;
if (removeNodes?.length) {
$(el).before(placeholder);
for (const node of removeNodes) $(node).remove();
} else {
$(el).replaceWith(placeholder);
}
}
const htmlWithPlaceholders = $.html() ?? html;
+30 -1
View File
@@ -3,9 +3,21 @@ import {
mergeHeadingIdsIntoContentSegments,
type ContentSegment,
} from "@/lib/blog-blocks";
import { getFullSizeWpImageUrl } from "@/lib/wp-api";
export type { ContentSegment } from "@/lib/blog-blocks";
const AUTHOR_NAME_OVERRIDES: Record<string, string> = {
adm: "احمدرضا بدیعی",
};
/** Maps WordPress author display names to site-facing labels. */
export function formatAuthorName(name: string | null | undefined): string | null {
if (!name?.trim()) return null;
const normalized = name.trim().toLowerCase();
return AUTHOR_NAME_OVERRIDES[normalized] ?? name.trim();
}
/**
* Creates a URL-safe slug from heading text (strip HTML, normalize, Persian-friendly).
*/
@@ -43,7 +55,9 @@ export function parseContentHeadings(html: string): {
toc: TocItem[];
segments: ContentSegment[];
} {
const { htmlWithPlaceholders, segments: rawSegments } = discoverBlocks(html);
const { htmlWithPlaceholders, segments: rawSegments } = discoverBlocks(
enhanceContentImages(html)
);
usedIds.clear();
const toc: TocItem[] = [];
@@ -94,3 +108,18 @@ export function getReadingTimeMinutes(html: string): number {
return Math.max(1, Math.ceil(wordCount / WPM));
}
/** Use full-size WordPress uploads and drop responsive srcset that picks smaller variants. */
export function enhanceContentImages(html: string): string {
return html.replace(/<img\b([^>]*)>/gi, (tag, attrs: string) => {
let next = attrs
.replace(/\s+srcset=(["'])[^"']*\1/gi, "")
.replace(/\s+sizes=(["'])[^"']*\1/gi, "")
.replace(/\s+src=(["'])([^"']+)\1/gi, (_m, quote: string, src: string) => {
return ` src=${quote}${getFullSizeWpImageUrl(src)}${quote}`;
});
if (!/\bloading=/i.test(next)) next += ' loading="lazy"';
if (!/\bdecoding=/i.test(next)) next += ' decoding="async"';
return `<img${next}>`;
});
}
+8 -2
View File
@@ -40,16 +40,22 @@ export interface WpPost {
};
}
/** Returns thumbnail URL for list/card (medium) or full for single post. */
/** WordPress scaled filenames: photo-1024x768.jpg → photo.jpg */
export function getFullSizeWpImageUrl(url: string): string {
return url.replace(/-\d+x\d+(?=\.(jpe?g|png|gif|webp|avif)(\?|$))/i, "");
}
/** Returns thumbnail URL for list/card (medium) or full original for single post. */
export function getFeaturedImageUrl(
post: WpPost,
size: "medium" | "medium_large" | "full" = "medium"
): string | null {
const media = post._embedded?.["wp:featuredmedia"]?.[0];
if (!media?.source_url) return null;
if (size === "full") return media.source_url;
const sizes = media.media_details?.sizes;
if (sizes?.[size]?.source_url) return sizes[size].source_url;
if (size !== "full" && sizes?.medium?.source_url) return sizes.medium.source_url;
if (sizes?.medium?.source_url) return sizes.medium.source_url;
return media.source_url;
}
+2
View File
@@ -3,6 +3,8 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: 'standalone',
images: {
qualities: [100, 75],
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
remotePatterns: [
{
protocol: "https",
+303 -3
View File
@@ -36,10 +36,12 @@
"@radix-ui/react-toggle": "^1.1.10",
"@radix-ui/react-toggle-group": "^1.1.11",
"@radix-ui/react-tooltip": "^1.2.8",
"cheerio": "^1.0.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"date-fns": "^4.1.0",
"date-fns-jalali": "^4.1.0-0",
"embla-carousel-react": "^8.6.0",
"input-otp": "^1.4.2",
"lucide-react": "^0.555.0",
@@ -62,6 +64,7 @@
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"baseline-browser-mapping": "^2.9.19",
"eslint": "^9",
"eslint-config-next": "16.0.6",
"tailwindcss": "^4",
@@ -4230,15 +4233,21 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
"version": "2.8.32",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.32.tgz",
"integrity": "sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==",
"version": "2.9.19",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
"integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"baseline-browser-mapping": "dist/cli.js"
}
},
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
"license": "ISC"
},
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
@@ -4394,6 +4403,48 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/cheerio": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.0.tgz",
"integrity": "sha512-+0hMx9eYhJvWbgpKV9hN7jg0JcwydpopZE4hgi+KvQtByZXPp04NiCWU0LzcAbP63abZckIHkTQaXVF52mX3xQ==",
"license": "MIT",
"dependencies": {
"cheerio-select": "^2.1.0",
"dom-serializer": "^2.0.0",
"domhandler": "^5.0.3",
"domutils": "^3.2.2",
"encoding-sniffer": "^0.2.0",
"htmlparser2": "^10.0.0",
"parse5": "^7.3.0",
"parse5-htmlparser2-tree-adapter": "^7.1.0",
"parse5-parser-stream": "^7.1.2",
"undici": "^7.10.0",
"whatwg-mimetype": "^4.0.0"
},
"engines": {
"node": ">=18.17"
},
"funding": {
"url": "https://github.com/cheeriojs/cheerio?sponsor=1"
}
},
"node_modules/cheerio-select": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
"integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
"license": "BSD-2-Clause",
"dependencies": {
"boolbase": "^1.0.0",
"css-select": "^5.1.0",
"css-what": "^6.1.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/class-variance-authority": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
@@ -4486,6 +4537,34 @@
"node": ">= 8"
}
},
"node_modules/css-select": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
"integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
"license": "BSD-2-Clause",
"dependencies": {
"boolbase": "^1.0.0",
"css-what": "^6.1.0",
"domhandler": "^5.0.2",
"domutils": "^3.0.1",
"nth-check": "^2.0.1"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/css-what": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
"integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
"license": "BSD-2-Clause",
"engines": {
"node": ">= 6"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
@@ -4796,6 +4875,61 @@
"csstype": "^3.0.2"
}
},
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"license": "MIT",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
"entities": "^4.2.0"
},
"funding": {
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
}
},
"node_modules/domelementtype": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"license": "BSD-2-Clause"
},
"node_modules/domhandler": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"license": "BSD-2-Clause",
"dependencies": {
"domelementtype": "^2.3.0"
},
"engines": {
"node": ">= 4"
},
"funding": {
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/domutils": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
"license": "BSD-2-Clause",
"dependencies": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3"
},
"funding": {
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -4853,6 +4987,19 @@
"dev": true,
"license": "MIT"
},
"node_modules/encoding-sniffer": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz",
"integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==",
"license": "MIT",
"dependencies": {
"iconv-lite": "^0.6.3",
"whatwg-encoding": "^3.1.1"
},
"funding": {
"url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
}
},
"node_modules/enhanced-resolve": {
"version": "5.18.3",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
@@ -4867,6 +5014,18 @@
"node": ">=10.13.0"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/es-abstract": {
"version": "1.24.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz",
@@ -5995,6 +6154,49 @@
"hermes-estree": "0.25.1"
}
},
"node_modules/htmlparser2": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz",
"integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==",
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"license": "MIT",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.2.2",
"entities": "^7.0.1"
}
},
"node_modules/htmlparser2/node_modules/entities": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -7223,6 +7425,18 @@
"dev": true,
"license": "MIT"
},
"node_modules/nth-check": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
"license": "BSD-2-Clause",
"dependencies": {
"boolbase": "^1.0.0"
},
"funding": {
"url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -7426,6 +7640,55 @@
"node": ">=6"
}
},
"node_modules/parse5": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
"license": "MIT",
"dependencies": {
"entities": "^6.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/parse5-htmlparser2-tree-adapter": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
"integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
"license": "MIT",
"dependencies": {
"domhandler": "^5.0.3",
"parse5": "^7.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/parse5-parser-stream": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
"integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
"license": "MIT",
"dependencies": {
"parse5": "^7.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/parse5/node_modules/entities": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -7950,6 +8213,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/scheduler": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
@@ -8698,6 +8967,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/undici": {
"version": "7.21.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.21.0.tgz",
"integrity": "sha512-Hn2tCQpoDt1wv23a68Ctc8Cr/BHpUSfaPYrkajTXOS9IKpxVRx/X5m1K2YkbK2ipgZgxXSgsUinl3x+2YdSSfg==",
"license": "MIT",
"engines": {
"node": ">=20.18.1"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
@@ -8868,6 +9146,28 @@
"d3-timer": "^3.0.1"
}
},
"node_modules/whatwg-encoding": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
"deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
"license": "MIT",
"dependencies": {
"iconv-lite": "0.6.3"
},
"engines": {
"node": ">=18"
}
},
"node_modules/whatwg-mimetype": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+2 -1
View File
@@ -9,7 +9,6 @@
"lint": "eslint"
},
"dependencies": {
"cheerio": "^1.0.0",
"@hookform/resolvers": "^5.2.2",
"@iconify/react": "^6.0.2",
"@radix-ui/react-accordion": "^1.2.12",
@@ -38,6 +37,7 @@
"@radix-ui/react-toggle": "^1.1.10",
"@radix-ui/react-toggle-group": "^1.1.11",
"@radix-ui/react-tooltip": "^1.2.8",
"cheerio": "^1.0.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
@@ -65,6 +65,7 @@
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"baseline-browser-mapping": "^2.9.19",
"eslint": "^9",
"eslint-config-next": "16.0.6",
"tailwindcss": "^4",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

-7
View File
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 150 150">
<!-- Generator: Adobe Illustrator 29.0.1, SVG Export Plug-In . SVG Version: 2.1.0 Build 192) -->
<path d="M119.76,40.76c14.06,5.02,20.23,21.27,16.93,34.39-2.4,9.52-11.4,19.28-22.36,20.11-2.99.22-6.4-.92-8.35,1.7-1.4,1.89-.67,5.33-.85,7.57-.82,10.41-10.8,19.56-21.99,14.3-8.84-4.16-8.66-12.16-10.4-19.92-1.91-8.48-5.87-19.14-15.59-22.18-2.61-.82-6.37-1.39-9.27-.79-.5.1-3.17.99-4.02,1.3-9.23,3.29-15.03,14.73-15.7,23.38-.19,2.4-.38,8.5.01,10.69.49,2.72,4.59,3.72,5.78.66,1.04-2.69.2-8.01.44-11.09.64-8.23,6.38-17.59,15.99-18.41,1.73-.15,3.48-.11,5.58-.29.32-.03.89-.02,1.51-.72.21-.24,2.96,1.71,3.39,2.05.12.1,1.23,1.09,1.28,1.15.52.59-.3,1.28-.76,1.67-3.35,2.8-5.51,2.29-9.58,2.65-5.9.54-9.62,4.59-10.82,9.86-1.28,5.64,1.22,14.4-3.22,18.71-5.81,5.63-14.96,1.85-15.79-5.49-1.16-10.3.66-21.78,6.77-30.52,8.81-12.6,26.31-16.97,38.92-6.16,5.68,4.87,9,13.22,10.74,20.12,1.21,4.78,1.23,12.99,5.45,16.2,1.55,1.18,3.89,2.1,5.87,2.11,2.47.02,5.82-1.92,7.17-3.85,4.21-6-.55-13.27,5.69-18.77,4.41-3.89,9.58-1.44,14.5-3.02,19.57-6.27,18.54-36.54-1.41-41.74-1.88-.49-17.19-.55-18.8,0s-4.48,2.35-5.75,3.62c-8.09,8.1-2.66,24-4.4,34.08-.2,1.14-1.68,3.64-2.16,5.06-.76,2.2-1.05,4.41-1.57,6.65-.06.25-.1.78-.49.72l-1.99-8.48c.6-9.62-.76-20,0-29.53.68-8.48,8.18-18.14,17.7-18.92,4.86-.4,17.23-.42,21.56,1.13Z"/>
<path d="M23.51,55.93c0-.07,0-.14.01-.21,1.01-8.29,8.79-17.27,18-18.02s19.66.15,29.09.24c2.16.02,4.28.71,5.79,2.14,5.11,4.83,3.72,14.33-3.67,16.98-.16.06-.33.09-.5.11-9.03.75-19.04-.67-27.93.08-3.38.29-2.33,4.31-2.22,6.91.03.66-.44,1.25-1.12,1.47-.47.15-1,.39-1.27.54-.72.38-1.89,1.09-2.7,1.59q-.5.3-1.3.74c1.11-6.07-2.25-15.41,6.43-17.45.14-.03.29-.05.44-.05h27.7c.23,0,.47-.06.68-.14.47-.19.68-.34,1.08-.67,1.21-1,1.64-2.9.96-4.22s-1.88-1.91-3.46-2.03c-6.9-.54-18.8-.44-25.76.02-16.22,1.07-14.22,17.86-14.03,28.9,0,.31-.09.61-.28.87l-2.72,3.77s-.06.09-.09.14h0c-.8,1.41-3.12.89-3.12-.71v-20.98Z"/>
<path d="M92.89,58.27c.36-2.47,2.89-5.76,5.64-6.28,2.58-.49,11.71-.35,14.6-.13,7.15.57,12.08,8.33,12.04,14.51-.04,6.93-3.74,12.3-10.84,14.25-.23.06-5.8.52-8.25.79-8.04.87-12.93,9.48-13.47,16.3-.22,2.82,1.21,9.15-4.04,8.16-2.64-.5-2.54-4.13-2.47-6.07.41-10.52,6.14-22,17.75-24.93,2.78-.7,6.34.09,9.25-.56,5.76-1.3,7.26-9.22,3.96-13.27-.2-.24-2.45-2.03-2.73-2.18-1.87-.96-10.11-.89-12.53-.74-2.03.13-2.71.5-2.96,2.49-.22,1.74.32,11.34-.04,11.85-2.22.84-4.06,2.72-6.19,3.43-.14-.1.28-.33.28-.39,0-5-.68-12.58,0-17.24Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 452 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 421 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 476 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 924 KiB

+238 -466
View File
File diff suppressed because it is too large Load Diff