feat: blog section

This commit is contained in:
2026-02-05 14:54:19 +03:30
parent 5e6c788b54
commit 6dc068076d
18 changed files with 1917 additions and 93 deletions
+1
View File
@@ -0,0 +1 @@
{}
+133
View File
@@ -0,0 +1,133 @@
import type { Metadata } from "next";
import Link from "next/link";
import Image from "next/image";
import { notFound } from "next/navigation";
import { getPostBySlug, getFeaturedImageUrl } from "@/lib/wp-api";
import { formatDateShamsi } from "@/lib/format-date";
import { ChevronRight } from "lucide-react";
import { parseContentHeadings, getReadingTimeMinutes } from "@/lib/blog-utils";
import { BlogToc } from "@/components/blog-toc";
import { BlogPostMeta } from "@/components/blog-post-meta";
import { BlogCollapseSection } from "@/components/blog-collapse-section";
import { ScrollToTop } from "@/components/scroll-to-top";
interface BlogPostPageProps {
params: Promise<{ slug: string }>;
}
export async function generateMetadata({
params,
}: BlogPostPageProps): Promise<Metadata> {
const { slug } = await params;
const post = await getPostBySlug(slug);
if (!post) return { title: "پست یافت نشد" };
const title = post.title?.rendered ?? "پست";
const excerpt = post.excerpt?.rendered?.replace(/<[^>]*>/g, "").trim().slice(0, 160) ?? "";
return {
title,
description: excerpt,
};
}
export default async function BlogPostPage({ params }: BlogPostPageProps) {
const { slug } = await params;
let post;
try {
post = await getPostBySlug(slug);
} catch {
notFound();
}
if (!post) notFound();
const date = post.date ? formatDateShamsi(post.date) : null;
const modifiedDate = post.modified
? formatDateShamsi(post.modified)
: null;
const authorName = post._embedded?.author?.[0]?.name ?? null;
const thumbnailUrl = getFeaturedImageUrl(post, "full");
const media = post._embedded?.["wp:featuredmedia"]?.[0];
const altText = media?.alt_text || post.title?.rendered?.replace(/<[^>]*>/g, "") || "";
const { toc, segments } = parseContentHeadings(post.content.rendered);
const readingTimeMinutes = getReadingTimeMinutes(post.content.rendered);
const postTitle = post.title?.rendered?.replace(/<[^>]*>/g, "") ?? "";
const postPath = `/blog/${slug}`;
return (
<>
{/* تصویر شاخص تمام‌عرض بالای صفحه */}
{thumbnailUrl && (
<div className="pt-28 md:pt-36">
<div className="relative w-full aspect-[21/5] min-h-[10rem] md:min-h-[10rem] bg-muted">
<Image
src={thumbnailUrl}
alt={altText}
fill
className="object-cover"
sizes="100vw"
priority
/>
</div>
</div>
)}
<section className="bg-background pt-8 pb-20 md:pt-12 md:pb-28">
<div className="mx-auto max-w-6xl px-6">
{/* Single grid so sticky TOC stays for whole page scroll */}
<div className="lg:grid lg:grid-cols-[16rem_1fr] lg:gap-10">
<BlogToc items={toc} className="mb-6 lg:mb-0" />
<div className="min-w-0 max-w-3xl space-y-0">
<div className="pb-8 md:pb-12">
<nav className="mb-6" aria-label="مسیر">
<Link
href="/blog"
className="text-muted-foreground hover:text-foreground text-sm inline-flex items-center gap-1 transition-colors"
>
<ChevronRight className="size-4" aria-hidden />
بلاگ
</Link>
</nav>
<h1
className="text-2xl font-bold md:text-3xl leading-tight tracking-normal"
dangerouslySetInnerHTML={{ __html: post.title.rendered }}
/>
<BlogPostMeta
url={postPath}
title={postTitle}
readingTimeMinutes={readingTimeMinutes}
date={date}
dateTime={post.date}
modifiedDate={modifiedDate}
authorName={authorName}
className="mt-4"
/>
</div>
<article className="blog-content text-justify space-y-6 text-foreground [&_h2]:text-2xl [&_h2]:font-bold [&_h2]:mt-8 [&_h2]:mb-4 [&_h3]:text-xl [&_h3]:font-bold [&_h3]:mt-6 [&_h3]:mb-3 [&_p]:leading-relaxed [&_p]:text-muted-foreground [&_a]:text-primary [&_a]:no-underline hover:[&_a]:underline [&_ul]:list-disc [&_ul]:pr-6 [&_ol]:list-decimal [&_ol]:pr-6 [&_li]:my-1 [&_h2]:scroll-mt-24 [&_h3]:scroll-mt-24 [&_h4]:scroll-mt-24">
{segments.map((seg, i) =>
seg.type === "html" ? (
<div
key={i}
dangerouslySetInnerHTML={{ __html: seg.content }}
/>
) : seg.blockType === "collapse" ? (
<BlogCollapseSection
key={i}
section={seg.payload}
idPrefix={`blog-${slug}-${i}`}
/>
) : (
/* سایر بلاک‌ها (blockquote, code, ...) بعداً با کامپوننت خودشان رندر می‌شوند */
null
)
)}
</article>
</div>
</div>
</div>
</section>
<ScrollToTop />
</>
);
}
+202
View File
@@ -0,0 +1,202 @@
import type { Metadata } from "next";
import Link from "next/link";
import { getPosts, getCategories } from "@/lib/wp-api";
import { BlogPostCard } from "@/components/blog-post-card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { ChevronRight, ChevronLeft } from "lucide-react";
const PER_PAGE = 9;
export const metadata: Metadata = {
title: "بلاگ",
description: "مقالات و مطالب فیپ در زمینه یکپارچه‌سازی، اتوماسیون و بهره‌وری سازمانی.",
};
interface BlogPageProps {
searchParams: Promise<{ search?: string; category?: string; page?: string }>;
}
export default async function BlogPage({ searchParams }: BlogPageProps) {
const params = await searchParams;
const search = params.search ?? "";
const categoryId = params.category ? parseInt(params.category, 10) : undefined;
const page = Math.max(1, parseInt(params.page ?? "1", 10) || 1);
let postsResult;
let categories: Awaited<ReturnType<typeof getCategories>> = [];
try {
[postsResult, categories] = await Promise.all([
getPosts({
page,
perPage: PER_PAGE,
search: search || undefined,
categoryId: Number.isFinite(categoryId) ? categoryId : undefined,
}),
getCategories(),
]);
} catch (e) {
console.log(e)
return (
<section className="bg-background pt-32 pb-20 md:pt-40 md:pb-28">
<div className="mx-auto max-w-4xl px-6 text-center">
<h1 className="text-4xl font-bold md:text-5xl">بلاگ</h1>
<p className="mt-4 text-muted-foreground">
در حال حاضر امکان بارگذاری مطالب وجود ندارد. لطفاً بعداً تلاش کنید.
</p>
</div>
</section>
);
}
const { posts, total, totalPages } = postsResult;
const hasFilters = search || categoryId;
function buildQuery(updates: { search?: string; category?: string; page?: number }) {
const q = new URLSearchParams();
if (updates.search !== undefined && updates.search !== "") q.set("search", updates.search);
if (updates.category !== undefined && updates.category !== "") q.set("category", updates.category);
if (updates.page !== undefined && updates.page > 1) q.set("page", String(updates.page));
const s = q.toString();
return s ? `?${s}` : "";
}
return (
<>
{/* Hero + search & filters in one section */}
<section className="bg-background pt-32 pb-10 md:pt-40 md:pb-14">
<div className="mx-auto max-w-6xl px-6">
<div className="text-center space-y-8">
<div className="space-y-3">
<h1 className="text-4xl font-bold md:text-5xl lg:text-6xl leading-tight tracking-normal">
بلاگ
</h1>
<p className="text-lg md:text-xl leading-relaxed text-muted-foreground max-w-2xl mx-auto">
مقالات و مطالب فیپ در زمینه یکپارچه‌سازی، اتوماسیون و بهره‌وری سازمانی.
</p>
</div>
<div className="flex flex-col gap-5 max-w-2xl mx-auto">
<form
method="get"
action="/blog"
className="flex flex-col sm:flex-row gap-2 sm:gap-2 w-full"
>
<input type="hidden" name="category" value={categoryId ?? ""} />
<Input
type="search"
name="search"
placeholder="جستجو در مطالب..."
defaultValue={search}
className="flex-1 w-full sm:min-w-[14rem] text-base"
aria-label="جستجو در مطالب"
/>
<div className="flex gap-2 flex-shrink-0">
<Button type="submit" size="default" className="flex-1 sm:flex-none">
جستجو
</Button>
{hasFilters && (
<Link href="/blog" className="flex-1 sm:flex-none">
<Button type="button" variant="outline" size="default" className="w-full">
پاک کردن فیلتر
</Button>
</Link>
)}
</div>
</form>
{categories.length > 0 && (
<div className="flex flex-wrap items-center justify-center gap-2">
<Link
href="/blog"
className={`text-sm rounded-full px-4 py-2 transition-colors ${!categoryId
? "bg-primary text-primary-foreground shadow-sm"
: "bg-muted/80 hover:bg-muted text-muted-foreground hover:text-foreground"
}`}
>
همه
</Link>
{categories.map((cat) => (
<Link
key={cat.id}
href={`/blog${buildQuery({ search, category: String(cat.id), page: 1 })}`}
className={`text-sm rounded-full px-4 py-2 transition-colors ${categoryId === cat.id
? "bg-primary text-primary-foreground shadow-sm"
: "bg-muted/80 hover:bg-muted text-muted-foreground hover:text-foreground"
}`}
>
{cat.name}
</Link>
))}
</div>
)}
</div>
</div>
</div>
</section>
<section className="bg-background pb-20 md:pb-28">
<div className="mx-auto max-w-6xl px-6 pt-2">
{posts.length === 0 ? (
<div className="text-center py-16 text-muted-foreground">
<p>مطلبی یافت نشد.</p>
<Link href="/blog" className="mt-4 inline-block">
<Button variant="outline" size="sm">
نمایش همه مطالب
</Button>
</Link>
</div>
) : (
<>
<ul className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{posts.map((post) => (
<li key={post.id}>
<BlogPostCard post={post} />
</li>
))}
</ul>
{totalPages > 1 && (
<nav
className="mt-12 flex flex-wrap items-center justify-center gap-2"
aria-label="صفحه‌بندی"
>
{page > 1 && (
<Link
href={`/blog${buildQuery({
search,
category: categoryId ? String(categoryId) : undefined,
page: page - 1,
})}`}
>
<Button variant="outline" size="icon" aria-label="صفحه قبل">
<ChevronRight className="size-4" />
</Button>
</Link>
)}
<span className="text-muted-foreground text-sm px-2">
صفحه {page} از {totalPages} ({total} مطلب)
</span>
{page < totalPages && (
<Link
href={`/blog${buildQuery({
search,
category: categoryId ? String(categoryId) : undefined,
page: page + 1,
})}`}
>
<Button variant="outline" size="icon" aria-label="صفحه بعد">
<ChevronLeft className="size-4" />
</Button>
</Link>
)}
</nav>
)}
</>
)}
</div>
</section>
</>
);
}
+3
View File
@@ -190,6 +190,9 @@
}
@layer base {
html {
scroll-behavior: smooth;
}
* {
@apply border-border outline-ring/50;
}
+48
View File
@@ -0,0 +1,48 @@
"use client";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import type { CollapseBlockPayload } from "@/lib/blog-blocks";
interface BlogCollapseSectionProps {
section: CollapseBlockPayload;
/** Optional prefix for accordion item ids (e.g. for multiple sections on one page). */
idPrefix?: string;
}
export function BlogCollapseSection({
section,
idPrefix = "collapse",
}: BlogCollapseSectionProps) {
if (!section.items.length) return null;
return (
<div className="my-8 space-y-4 border rounded-md p-6">
{section.title ? (
<h2 className="text-2xl font-bold !mt-0 mb-4 scroll-mt-24">
{section.title}
</h2>
) : null}
<Accordion type="single" collapsible className="w-full">
{section.items.map((item, i) => (
<AccordionItem
key={i}
value={`${idPrefix}-${i}`}
className="border-b last:border-b-0 !m-0"
>
<AccordionTrigger className="text-right hover:no-underline py-4 [&>svg]:shrink-0">
{item.question}
</AccordionTrigger>
<AccordionContent className="text-muted-foreground leading-relaxed text-justify">
{item.answer}
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</div>
);
}
+65
View File
@@ -0,0 +1,65 @@
import Link from "next/link";
import Image from "next/image";
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";
function stripHtml(html: string): string {
return html.replace(/<[^>]*>/g, "").trim();
}
export function BlogPostCard({ post }: { post: WpPost }) {
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 thumbnailUrl = getFeaturedImageUrl(post, "medium");
return (
<Link href={`/blog/${encodeURIComponent(post.slug)}`} className="block h-full">
<Card className="h-full overflow-hidden transition-all duration-300 hover:-translate-y-1 hover:shadow-lg pt-0 gap-1">
{thumbnailUrl ? (
<div className="relative aspect-video w-full overflow-hidden bg-muted">
<Image
src={thumbnailUrl}
alt=""
fill
className="object-cover"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
/>
</div>
) : (
<div
className="relative flex aspect-video w-full items-center justify-center overflow-hidden bg-muted"
aria-hidden
>
<ImageIcon className="size-12 text-muted-foreground/50" />
</div>
)}
<CardHeader className="pb-2">
<CardTitle className="text-xl font-bold leading-tight line-clamp-2 mt-2">
{post.title.rendered}
</CardTitle>
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground bg-muted inline-flex p-1 rounded-sm">
{authorName && (
<span className="inline-flex flex-1 items-center gap-1.5">
<User className="size-3.5 shrink-0" aria-hidden />
{authorName}
</span>
)}
{modifiedDate && (
<time dateTime={post.modified}>
{modifiedDate}
</time>
)}
</div>
</CardHeader>
<CardContent className="pt-0">
<p className="text-muted-foreground text-sm leading-relaxed line-clamp-3">
{excerpt}
</p>
</CardContent>
</Card>
</Link>
);
}
+182
View File
@@ -0,0 +1,182 @@
"use client";
import { useState, useCallback, useEffect } from "react";
import {
Link2,
Clock,
Calendar,
User,
Share2,
Check,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
export interface BlogPostMetaProps {
/** Path (e.g. /blog/slug). Full URL is built on client for share/copy. */
url: string;
title: string;
readingTimeMinutes: number;
/** Shamsi publish date (e.g. "۱۴۰۳/۰۸/۱۵") */
date?: string | null;
/** ISO date for <time dateTime> */
dateTime?: string | null;
modifiedDate?: string | null;
authorName?: string | null;
className?: string;
}
export function BlogPostMeta({
url,
title,
readingTimeMinutes,
date,
dateTime,
modifiedDate,
authorName,
className,
}: BlogPostMetaProps) {
const [copied, setCopied] = useState(false);
const [shared, setShared] = useState(false);
const [fullUrl, setFullUrl] = useState(url);
const [canNativeShare, setCanNativeShare] = useState(false);
useEffect(() => {
if (url.startsWith("/")) {
setFullUrl(window.location.origin + url);
} else {
setFullUrl(url);
}
setCanNativeShare(Boolean(typeof navigator !== "undefined" && navigator.share));
}, [url]);
const copyLink = useCallback(async () => {
try {
await navigator.clipboard.writeText(fullUrl);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch {
setCopied(false);
}
}, [fullUrl]);
const handleShare = useCallback(async () => {
if (typeof navigator !== "undefined" && navigator.share) {
try {
await navigator.share({
title,
url: fullUrl,
text: title,
});
setShared(true);
setTimeout(() => setShared(false), 2000);
} catch (err) {
if ((err as Error).name !== "AbortError") copyLink();
}
} else {
copyLink();
}
}, [title, fullUrl, copyLink]);
return (
<TooltipProvider delayDuration={300}>
<div
className={cn(
"flex flex-wrap items-center gap-x-5 gap-y-2 text-sm text-muted-foreground",
className
)}
>
{/* Reading time */}
<span className="inline-flex items-center gap-1.5">
<Clock className="size-4 shrink-0" aria-hidden />
<span>
{readingTimeMinutes === 1
? "۱ دقیقه مطالعه"
: `${readingTimeMinutes.toLocaleString("fa-IR")} دقیقه مطالعه`}
</span>
</span>
{/* Publish date */}
{date && (
<time
className="inline-flex items-center gap-1.5"
dateTime={dateTime ?? undefined}
>
<Calendar className="size-4 shrink-0" aria-hidden />
<span>انتشار: {date}</span>
</time>
)}
{/* Last updated */}
{modifiedDate && (
<span className="inline-flex items-center gap-1.5">
<Calendar className="size-4 shrink-0" aria-hidden />
<span>آخرین بروزرسانی: {modifiedDate}</span>
</span>
)}
{/* Author */}
{authorName && (
<span className="inline-flex items-center gap-1.5">
<User className="size-4 shrink-0" aria-hidden />
<span>{authorName}</span>
</span>
)}
{/* Divider + actions */}
<span className="mr-auto inline-flex items-center gap-1">
<span
className="hidden sm:inline h-4 w-px bg-border ml-2"
aria-hidden
/>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon-sm"
className="size-8 text-muted-foreground hover:text-foreground"
onClick={copyLink}
aria-label="کپی لینک"
>
{copied ? (
<Check className="size-4 text-green-600" />
) : (
<Link2 className="size-4" />
)}
</Button>
</TooltipTrigger>
<TooltipContent side="bottom">
{copied ? "کپی شد" : "کپی لینک"}
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon-sm"
className="size-8 text-muted-foreground hover:text-foreground"
onClick={handleShare}
aria-label="اشتراک‌گذاری"
>
{shared ? (
<Check className="size-4 text-green-600" />
) : (
<Share2 className="size-4" />
)}
</Button>
</TooltipTrigger>
<TooltipContent side="bottom">
{shared ? "اشتراک گذاشته شد" : canNativeShare ? "اشتراک‌گذاری" : "کپی لینک"}
</TooltipContent>
</Tooltip>
</span>
</div>
</TooltipProvider>
);
}
+222
View File
@@ -0,0 +1,222 @@
"use client";
import { useEffect, useMemo, useState } from "react";
import Link from "next/link";
import { ChevronDown, List } from "lucide-react";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { ScrollArea } from "@/components/ui/scroll-area";
import { cn } from "@/lib/utils";
import type { TocItem } from "@/lib/blog-utils";
interface TocGroup {
heading: TocItem;
children: TocItem[];
}
function groupByLevel2(items: TocItem[]): TocGroup[] {
const groups: TocGroup[] = [];
let current: TocGroup | null = null;
for (const item of items) {
if (item.level === 2) {
current = { heading: item, children: [] };
groups.push(current);
} else if (current) {
current.children.push(item);
}
}
return groups;
}
interface BlogTocProps {
items: TocItem[];
className?: string;
}
const tocLinkClass = (
level: number,
isActive: boolean
) =>
cn(
"block rounded-md border-r-2 rounded-s-none border-transparent py-1.5 text-sm transition-colors hover:text-foreground hover:bg-muted/60",
level === 2 && "pr-3",
level === 3 && "pr-5",
level === 4 && "pr-7",
isActive
? "font-medium text-foreground border-primary bg-muted/50"
: "text-muted-foreground"
);
export function BlogToc({ items, className }: BlogTocProps) {
const [activeId, setActiveId] = useState<string | null>(items[0]?.id ?? null);
const [mobileOpen, setMobileOpen] = useState(false);
const [openSections, setOpenSections] = useState<Set<string>>(() => new Set());
const groups = useMemo(() => groupByLevel2(items), [items]);
useEffect(() => {
setOpenSections(new Set(groups.map((g) => g.heading.id)));
}, [groups]);
useEffect(() => {
if (items.length === 0) return;
const observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
const id = entry.target.getAttribute("id");
if (id) setActiveId(id);
}
}
},
{ rootMargin: "-80px 0% -70% 0%", threshold: 0 }
);
items.forEach(({ id }) => {
const el = document.getElementById(id);
if (el) observer.observe(el);
});
return () => observer.disconnect();
}, [items]);
useEffect(() => {
if (activeId && groups.length > 0) {
const group = groups.find(
(g) => g.heading.id === activeId || g.children.some((c) => c.id === activeId)
);
if (group) {
setOpenSections((prev) => new Set(prev).add(group.heading.id));
}
}
}, [activeId, groups]);
if (items.length === 0) return null;
const renderTocLink = (item: TocItem, onNav?: () => void) => (
<Link
key={item.id}
href={`#${item.id}`}
onClick={() => {
setMobileOpen(false);
onNav?.();
}}
className={tocLinkClass(item.level, activeId === item.id)}
>
<span className="line-clamp-2">{item.text}</span>
</Link>
);
const tocList = (
<nav aria-label="فهرست مطالب" className="space-y-1">
{groups.map(({ heading, children }) => {
const hasChildren = children.length > 0;
const isOpen = openSections.has(heading.id);
if (!hasChildren) {
return renderTocLink(heading);
}
return (
<Collapsible
key={heading.id}
open={isOpen}
onOpenChange={(open) => {
setOpenSections((prev) => {
const next = new Set(prev);
if (open) next.add(heading.id);
else next.delete(heading.id);
return next;
});
}}
>
<div
className={cn(
"flex items-center gap-0.5 rounded-md border-r-2 rounded-s-none py-1.5 pr-3",
activeId === heading.id ? "border-primary bg-muted/50" : "border-transparent"
)}
>
<Link
href={`#${heading.id}`}
onClick={() => setMobileOpen(false)}
className={cn(
"min-w-0 flex-1 text-sm transition-colors hover:text-foreground hover:bg-muted/60 rounded-md py-0.5 -my-0.5",
activeId === heading.id ? "font-medium text-foreground" : "text-muted-foreground"
)}
>
<span className="line-clamp-2">{heading.text}</span>
</Link>
<CollapsibleTrigger
className="shrink-0 rounded p-1 text-muted-foreground hover:bg-muted/60 hover:text-foreground transition-colors"
aria-label={isOpen ? "بستن" : "باز کردن"}
>
<ChevronDown
className={cn("size-4 transition-transform", isOpen && "rotate-180")}
/>
</CollapsibleTrigger>
</div>
<CollapsibleContent>
<div className="space-y-1 pt-0.5 pr-2 border-r border-border/50">
{children.map((item) => renderTocLink(item))}
</div>
</CollapsibleContent>
</Collapsible>
);
})}
</nav>
);
return (
<>
{/* Desktop: sticky sidebar left */}
<aside
className={cn(
"hidden lg:block shrink-0",
"sticky top-24 self-start max-h-[calc(100vh-6rem)]",
className
)}
>
<p className="mb-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
فهرست مطالب
</p>
<ScrollArea className="h-full pb-8" dir="rtl">
{tocList}
</ScrollArea>
</aside>
{/* Mobile: collapsible, sticky when scrolling */}
<Collapsible
open={mobileOpen}
onOpenChange={setMobileOpen}
className={cn(
"lg:hidden w-full sticky top-20 z-10 bg-background",
className
)}
>
<CollapsibleTrigger
className={cn(
"flex w-full items-center justify-between gap-2 rounded-lg border border-border bg-muted/30 px-4 py-3 text-sm font-medium transition-colors hover:bg-muted/50",
mobileOpen && "rounded-b-none border-b-0"
)}
>
<span className="flex items-center gap-2">
<List className="size-4 text-muted-foreground" />
فهرست مطالب
</span>
<ChevronDown
className={cn("size-4 text-muted-foreground transition-transform", mobileOpen && "rotate-180")}
/>
</CollapsibleTrigger>
<CollapsibleContent>
<div className="max-h-64 overflow-y-auto rounded-b-lg border border-t-0 border-border bg-muted/20 p-3">
{tocList}
</div>
</CollapsibleContent>
</Collapsible>
</>
);
}
+1 -3
View File
@@ -16,9 +16,7 @@ export default function FooterSection() {
return (
<footer className="py-16 md:py-32">
<div className="mx-auto max-w-5xl px-6">
<Link href="/" aria-label="go home" className="mx-auto block size-fit">
<Logo />
</Link>
<Logo />
<div className="my-8 flex flex-wrap justify-center gap-6 text-sm">
{links.map((link, index) => (
+47 -27
View File
@@ -1,5 +1,6 @@
'use client'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { Logo } from '@/components/logo'
import { Menu, X } from 'lucide-react'
import { Button } from '@/components/ui/button'
@@ -11,10 +12,17 @@ const menuItems = [
{ name: 'صفحه اصلی', href: '/' },
{ name: 'درباره ما', href: '/about' },
{ name: 'راه حل‌ها', href: '/solutions' },
{ name: 'بلاگ', href: '/blog' },
{ name: 'قیمت‌گذاری', href: '/pricing' },
]
function isLinkActive(pathname: string, href: string): boolean {
if (href === '/') return pathname === '/'
return pathname === href || pathname.startsWith(href + '/')
}
export const HeroHeader = () => {
const pathname = usePathname()
const [menuState, setMenuState] = React.useState(false)
const [isScrolled, setIsScrolled] = React.useState(false)
@@ -33,13 +41,7 @@ export const HeroHeader = () => {
<div className={cn('mx-auto mt-2 max-w-6xl px-6 transition-all duration-300 lg:px-12 bg-background/80 backdrop-blur-md rounded-2xl border border-border/50', isScrolled && 'bg-background/95 max-w-4xl shadow-lg lg:px-5')}>
<div className="relative flex flex-wrap items-center justify-between gap-6 py-2 lg:gap-0 lg:py-3">
<div className="flex w-full justify-between lg:w-auto">
<Link
href="/"
aria-label="home"
className="flex items-center space-x-2">
<Logo />
</Link>
<Logo />
<button
onClick={() => setMenuState(!menuState)}
aria-label={menuState == true ? 'بستن منو' : 'باز کردن منو'}
@@ -51,16 +53,25 @@ export const HeroHeader = () => {
<div className="absolute inset-0 m-auto hidden size-fit lg:flex items-center gap-8">
<ul className="flex gap-8 text-sm">
{menuItems.map((item, index) => (
<li key={index}>
<Link
href={item.href}
className="text-muted-foreground hover:text-foreground transition-all duration-300 relative group">
<span className="relative z-10">{item.name}</span>
<span className="absolute bottom-0 right-0 w-0 h-0.5 bg-accent transition-all duration-300 group-hover:w-full"></span>
</Link>
</li>
))}
{menuItems.map((item, index) => {
const active = isLinkActive(pathname ?? '', item.href)
return (
<li key={index}>
<Link
href={item.href}
className={cn(
'text-muted-foreground hover:text-foreground transition-all duration-300 relative group',
active && 'text-foreground'
)}>
<span className="relative z-10">{item.name}</span>
<span className={cn(
'absolute bottom-0 right-0 h-0.5 bg-accent transition-all duration-300 group-hover:w-full',
active ? 'w-full' : 'w-0'
)} />
</Link>
</li>
)
})}
</ul>
<ThemeToggle />
</div>
@@ -68,16 +79,25 @@ export const HeroHeader = () => {
<div className="bg-background in-data-[state=active]:block lg:in-data-[state=active]:flex mb-6 hidden w-full flex-wrap items-center justify-end space-y-8 rounded-3xl border p-6 shadow-2xl shadow-zinc-300/20 md:flex-nowrap lg:m-0 lg:flex lg:w-fit lg:gap-6 lg:space-y-0 lg:border-transparent lg:bg-transparent lg:p-0 lg:shadow-none dark:shadow-none dark:lg:bg-transparent">
<div className="lg:hidden w-full">
<ul className="space-y-6 text-base">
{menuItems.map((item, index) => (
<li key={index}>
<Link
href={item.href}
className="text-muted-foreground hover:text-foreground transition-all duration-300 relative group block">
<span className="relative z-10">{item.name}</span>
<span className="absolute bottom-0 right-0 w-0 h-0.5 bg-accent transition-all duration-300 group-hover:w-full"></span>
</Link>
</li>
))}
{menuItems.map((item, index) => {
const active = isLinkActive(pathname ?? '', item.href)
return (
<li key={index}>
<Link
href={item.href}
className={cn(
'text-muted-foreground hover:text-foreground transition-all duration-300 relative group block',
active && 'text-foreground'
)}>
<span className="relative z-10">{item.name}</span>
<span className={cn(
'absolute bottom-0 right-0 h-0.5 bg-accent transition-all duration-300 group-hover:w-full',
active ? 'w-full' : 'w-0'
)} />
</Link>
</li>
)
})}
</ul>
<div className="mt-6 flex justify-end">
<ThemeToggle />
+54
View File
@@ -0,0 +1,54 @@
"use client";
import { useEffect, useState } from "react";
import { ChevronUp } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
const SCROLL_THRESHOLD = 0.4; // show after 40% scroll
export function ScrollToTop({ className }: { className?: string }) {
const [visible, setVisible] = useState(false);
useEffect(() => {
function onScroll() {
const maxScroll =
document.documentElement.scrollHeight - window.innerHeight;
if (maxScroll <= 0) {
setVisible(false);
return;
}
const ratio = window.scrollY / maxScroll;
setVisible(ratio >= SCROLL_THRESHOLD);
}
onScroll();
window.addEventListener("scroll", onScroll, { passive: true });
return () => window.removeEventListener("scroll", onScroll);
}, []);
const scrollToTop = () => {
window.scrollTo({ top: 0, behavior: "smooth" });
};
if (!visible) return null;
return (
<div
className={cn(
"fixed bottom-6 start-6 z-50 transition-opacity duration-300",
className
)}
>
<Button
size="icon"
variant="outline"
onClick={scrollToTop}
aria-label="برگشت به بالا"
className="size-10 rounded-md shadow-md"
>
<ChevronUp className="size-5" />
</Button>
</div>
);
}
+235
View File
@@ -0,0 +1,235 @@
/**
* زیرساخت کشف بلاک‌های محتوای بلاگ و تبدیل به JSON.
* مرحله ۱: کشف بلاک‌های شناخته‌شده در HTML (collapse, و بعداً blockquote, code, ...)
* مرحله ۲: استخراج payload به صورت JSON برای هر بلاک
* مرحله ۳: لایه بیرونی بر اساس blockType و payload کامپوننت مناسب را رندر می‌کند.
*
* برای افزودن بلاک جدید: یک تعریف در BLOCK_DEFINITIONS اضافه کنید و در رندر blockType را هندل کنید.
*/
import * as cheerio from "cheerio";
import type { Element } from "domhandler";
// ─── انواع بلاک (برای گسترش بعدی) ───────────────────────────────────────────
/** نام نوع بلاک؛ با افزودن بلاک جدید این یونیون را گسترش دهید. */
export type BlockType = "collapse" | "blockquote" | "code";
/** Payload بلاک collapse (سوالات متداول / accordion). */
export interface CollapseBlockPayload {
title: string;
items: { question: string; answer: string }[];
}
/** Payload بلاک blockquote — بعداً پیاده‌سازی می‌شود. */
// export interface BlockquoteBlockPayload { ... }
/** Payload بلاک code — بعداً پیاده‌سازی می‌شود. */
// export interface CodeBlockPayload { ... }
/** یونیون همهٔ payloadها برای تایپ‌امن رندر. */
export type BlockPayload = CollapseBlockPayload; // | BlockquoteBlockPayload | CodeBlockPayload
// ─── سگمنت خروجی برای لایه رندر ─────────────────────────────────────────────
/** یک سگمنت HTML خام. */
export interface HtmlSegment {
type: "html";
content: string;
}
/** یک بلاک با نوع و payload (JSON) برای رندر با کامپوننت مناسب. */
export interface BlockSegment {
type: "block";
blockType: BlockType;
payload: BlockPayload;
}
/** هر سگمنت محتوا: یا HTML یا یک بلاک با JSON. */
export type ContentSegment = HtmlSegment | BlockSegment;
/** خروجی کشف بلاک: HTML با placeholderها و آرایهٔ سگمنت‌ها (با payload توکار). */
export interface DiscoveredContent {
/** HTML بعد از جایگزینی بلاک‌ها با placeholder (برای ادامهٔ پردازش مثلاً تزریق id سرنویس). */
htmlWithPlaceholders: string;
/** سگمنت‌ها به ترتیب برای رندر؛ هر بلاک شامل blockType و payload است. */
segments: ContentSegment[];
}
// ─── تعریف بلاک‌ها (ثبت برای کشف و استخراج) ────────────────────────────────
export interface BlockDefinition<T extends BlockPayload = BlockPayload> {
blockType: BlockType;
/** سلکتور Cheerio برای پیدا کردن المنت ریشهٔ بلاک. */
selector: string;
/** استخراج payload (JSON) از المنت. */
extract: ($: cheerio.CheerioAPI, el: Element) => T;
}
function extractCollapsePayload(
$: cheerio.CheerioAPI,
el: Element
): CollapseBlockPayload {
const $el = $(el);
const innerHtml = $el.html() ?? "";
const $inner = cheerio.load(innerHtml, null, false);
const title = $inner("h2").first().text().trim();
const items: { question: string; answer: string }[] = [];
$inner("ul").each((_: number, ul: Element) => {
const question = $inner(ul).find("li strong").first().text().trim();
const answer = $inner(ul).nextAll("p").first().text().trim();
if (question || answer) items.push({ question, answer });
});
return { title, items };
}
/**
* ثبت همهٔ بلاک‌های شناخته‌شده.
* برای بلاک‌های جدید (مثلاً blockquote, code) فقط یک مورد جدید به این آرایه اضافه کنید.
*/
export const BLOCK_DEFINITIONS: BlockDefinition[] = [
{
blockType: "collapse",
selector: ".collpase-section",
extract: extractCollapsePayload as BlockDefinition["extract"],
},
// بعداً:
// { blockType: "blockquote", selector: "blockquote.wp-block-quote", extract: extractBlockquotePayload },
// { blockType: "code", selector: "pre.wp-block-code", extract: extractCodePayload },
];
// ─── کشف بلاک و ساخت سگمنت‌ها ─────────────────────────────────────────────
const PLACEHOLDER_REGEX =
/<div data-blog-block-type="([^"]+)" data-blog-block-id="(\d+)"[^>]*><\/div>/g;
function isElement(node: unknown): node is Element {
return typeof node === "object" && node !== null && (node as Element).type === "tag";
}
/**
* ترتیب قرارگیری المنت در سند را با یک مپ نگه می‌دارد.
*/
function buildDocumentOrderMap($: cheerio.CheerioAPI): Map<Element, number> {
const map = new Map<Element, number>();
let index = 0;
$("*")
.toArray()
.filter(isElement)
.forEach((el) => {
map.set(el, index++);
});
return map;
}
/**
* مرحله ۱ و ۲: از روی HTML محتوا بلاک‌های شناخته‌شده را کشف می‌کند،
* به JSON (payload) تبدیل می‌کند، و خروجی را به صورت HTML با placeholder و
* آرایهٔ segments (با payload توکار) به لایه بیرونی می‌دهد.
*/
export function discoverBlocks(html: string): DiscoveredContent {
const $ = cheerio.load(html, null, false);
const orderMap = buildDocumentOrderMap($);
type BlockEntry = {
el: Element;
blockType: BlockType;
blockId: number;
payload: BlockPayload;
};
const entries: BlockEntry[] = [];
const blocksByType: Record<string, BlockPayload[]> = {
collapse: [],
blockquote: [],
code: [],
};
for (const def of BLOCK_DEFINITIONS) {
const elements = $('.collpase-section').toArray();
for (const el of elements) {
const payload = def.extract($, el) as BlockPayload;
const blockId = blocksByType[def.blockType].length;
blocksByType[def.blockType].push(payload);
entries.push({
el,
blockType: def.blockType,
blockId,
payload,
});
}
}
// مرتب‌سازی بر اساس ترتیب در سند (از آخر به اول جایگزین می‌کنیم تا ایندکس‌ها جابجا نشوند)
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 htmlWithPlaceholders = $.html() ?? html;
// ساخت segments: تقسیم با placeholder و قرار دادن payload در هر بلاک
const segments: ContentSegment[] = [];
let lastEnd = 0;
const regex = new RegExp(PLACEHOLDER_REGEX.source, "g");
let m: RegExpExecArray | null;
while ((m = regex.exec(htmlWithPlaceholders)) !== null) {
if (m.index > lastEnd) {
segments.push({
type: "html",
content: htmlWithPlaceholders.slice(lastEnd, m.index),
});
}
const blockType = m[1] as BlockType;
const blockId = parseInt(m[2], 10);
const list = blocksByType[blockType];
if (list && blockId >= 0 && blockId < list.length) {
segments.push({
type: "block",
blockType,
payload: list[blockId],
});
}
lastEnd = regex.lastIndex;
}
if (lastEnd < htmlWithPlaceholders.length) {
segments.push({
type: "html",
content: htmlWithPlaceholders.slice(lastEnd),
});
}
return { htmlWithPlaceholders, segments };
}
/**
* بعد از تزریق id سرنویس‌ها در HTML، محتوای هر سگمنت html را با نسخهٔ به‌روز (شامل idها) جایگزین می‌کند.
* لایهٔ blog-utils بعد از inject کردن idها این تابع را صدا می‌زند.
*/
export function mergeHeadingIdsIntoContentSegments(
contentWithIds: string,
segments: ContentSegment[]
): ContentSegment[] {
const parts: string[] = [];
let last = 0;
let m: RegExpExecArray | null;
const regex = new RegExp(PLACEHOLDER_REGEX.source, "g");
while ((m = regex.exec(contentWithIds)) !== null) {
parts.push(contentWithIds.slice(last, m.index));
last = regex.lastIndex;
}
parts.push(contentWithIds.slice(last));
let j = 0;
return segments.map((seg) => {
if (seg.type === "html") {
return { ...seg, content: parts[j++] ?? seg.content };
}
return seg;
});
}
+96
View File
@@ -0,0 +1,96 @@
import {
discoverBlocks,
mergeHeadingIdsIntoContentSegments,
type ContentSegment,
} from "@/lib/blog-blocks";
export type { ContentSegment } from "@/lib/blog-blocks";
/**
* Creates a URL-safe slug from heading text (strip HTML, normalize, Persian-friendly).
*/
function slugify(text: string): string {
const stripped = text.replace(/<[^>]*>/g, "").trim();
return stripped
.replace(/\s+/g, "-")
.replace(/[^\p{L}\p{N}-]/gu, "")
.replace(/-+/g, "-")
.replace(/^-|-$/g, "")
.toLowerCase() || "section";
}
export interface TocItem {
id: string;
text: string;
level: number; // 2 = h2, 3 = h3, 4 = h4
}
const HEADING_REGEX = /<(h[234])[^>]*>([\s\S]*?)<\/\1>/gi;
const usedIds = new Map<string, number>();
function ensureUniqueId(baseId: string): string {
const count = usedIds.get(baseId) ?? 0;
usedIds.set(baseId, count + 1);
return count === 0 ? baseId : `${baseId}-${count}`;
}
/**
* پارس محتوا: ابتدا بلاک‌ها کشف و به JSON تبدیل می‌شوند (blog-blocks)،
* سپس id سرنویس‌ها تزریق می‌شود و سگمنت‌های نهایی برای رندر برمی‌گردند.
*/
export function parseContentHeadings(html: string): {
contentWithIds: string;
toc: TocItem[];
segments: ContentSegment[];
} {
const { htmlWithPlaceholders, segments: rawSegments } = discoverBlocks(html);
usedIds.clear();
const toc: TocItem[] = [];
let lastIndex = 0;
let match: RegExpExecArray | null;
const parts: string[] = [];
const regex = new RegExp(HEADING_REGEX.source, "gi");
while ((match = regex.exec(htmlWithPlaceholders)) !== null) {
const [fullMatch, tag, inner] = match;
const level = parseInt(tag.charAt(1), 10) as 2 | 3 | 4;
const text = inner.replace(/<[^>]*>/g, "").trim();
const baseId = slugify(text);
const id = ensureUniqueId(baseId);
toc.push({ id, text, level });
const openTag = fullMatch.slice(0, fullMatch.indexOf(">") + 1);
const hasId = /^\s*<h[234][^>]*\bid\s*=/i.test(openTag);
const newOpenTag = hasId
? openTag
: openTag.replace(/^(<\s*h[234])(\s|>)/i, `$1 id="${id}"$2`);
parts.push(htmlWithPlaceholders.slice(lastIndex, match.index));
parts.push(newOpenTag + inner + `</${tag}>`);
lastIndex = regex.lastIndex;
}
parts.push(htmlWithPlaceholders.slice(lastIndex));
const contentWithIds = parts.join("");
const segments = mergeHeadingIdsIntoContentSegments(
contentWithIds,
rawSegments
);
return { contentWithIds, toc, segments };
}
/** Average reading speed in words per minute (Persian/English mix). */
const WPM = 200;
/**
* Estimates reading time in minutes from HTML content.
*/
export function getReadingTimeMinutes(html: string): number {
const text = html.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
const wordCount = text.split(/\s+/).filter(Boolean).length;
return Math.max(1, Math.ceil(wordCount / WPM));
}
+15
View File
@@ -0,0 +1,15 @@
import { format, newDate } from "date-fns-jalali";
import { faIR } from "date-fns-jalali/locale";
/**
* Format a Gregorian date (Date or ISO string) to Shamsi (Jalali) Persian string.
* Example: "۱۴ بهمن ۱۴۰۴"
*/
export function formatDateShamsi(
date: Date | string,
formatStr: string = "d MMMM yyyy"
): string {
const d = typeof date === "string" ? new Date(date) : date;
if (Number.isNaN(d.getTime())) return "";
return format(d, formatStr, { locale: faIR });
}
+145
View File
@@ -0,0 +1,145 @@
const WP_API_BASE = "https://wpa.feep.ir/wp-json/wp/v2";
const FETCH_TIMEOUT_MS = 15_000;
export interface WpMediaSize {
source_url: string;
width?: number;
height?: number;
}
export interface WpFeaturedMedia {
source_url: string;
alt_text?: string;
media_details?: {
width?: number;
height?: number;
sizes?: Record<string, WpMediaSize>;
};
}
export interface WpAuthor {
id: number;
name: string;
slug?: string;
}
export interface WpPost {
id: number;
date: string;
modified?: string;
slug: string;
title: { rendered: string };
content: { rendered: string };
excerpt: { rendered: string };
featured_media: number;
categories: number[];
author?: number;
_embedded?: {
"wp:featuredmedia"?: WpFeaturedMedia[];
author?: WpAuthor[];
};
}
/** Returns thumbnail URL for list/card (medium) or full 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;
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;
return media.source_url;
}
export interface WpCategory {
id: number;
name: string;
slug: string;
count: number;
}
export interface GetPostsResult {
posts: WpPost[];
total: number;
totalPages: number;
}
export interface GetPostsOptions {
page?: number;
perPage?: number;
search?: string;
categoryId?: number;
}
async function fetchWithTimeout(
url: string,
options?: RequestInit
): Promise<Response> {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
try {
const res = await fetch(url, {
...options,
signal: controller.signal,
next: { revalidate: 0 },
});
clearTimeout(timeout);
return res;
} catch (e) {
clearTimeout(timeout);
throw e;
}
}
export async function getPosts(
options: GetPostsOptions = {}
): Promise<GetPostsResult> {
const { page = 1, perPage = 9, search, categoryId } = options;
const params = new URLSearchParams();
params.set("page", String(page));
params.set("per_page", String(perPage));
if (search?.trim()) params.set("search", search.trim());
if (categoryId) params.set("categories", String(categoryId));
params.set("_embed", "1");
const url = `${WP_API_BASE}/posts?${params.toString()}`;
const res = await fetchWithTimeout(url);
if (!res.ok) {
throw new Error(`WP API error: ${res.status} ${res.statusText}`);
}
const total = parseInt(res.headers.get("x-wp-total") ?? "0", 10);
const totalPages = parseInt(res.headers.get("x-wp-totalpages") ?? "1", 10);
const posts: WpPost[] = await res.json();
return { posts, total, totalPages };
}
export async function getPostBySlug(slug: string): Promise<WpPost | null> {
const decodedSlug = decodeURIComponent(slug);
const params = new URLSearchParams({ slug: decodedSlug, _embed: "1" });
const url = `${WP_API_BASE}/posts?${params.toString()}`;
const res = await fetchWithTimeout(url);
if (!res.ok) {
throw new Error(`WP API error: ${res.status} ${res.statusText}`);
}
const posts: WpPost[] = await res.json();
return posts.length > 0 ? posts[0] : null;
}
export async function getCategories(): Promise<WpCategory[]> {
const url = `${WP_API_BASE}/categories?per_page=100`;
const res = await fetchWithTimeout(url);
if (!res.ok) {
throw new Error(`WP API error: ${res.status} ${res.statusText}`);
}
const data: WpCategory[] = await res.json();
return data.filter((c) => c.count > 0);
}
+4
View File
@@ -12,6 +12,10 @@ const nextConfig: NextConfig = {
protocol: "https",
hostname: "images.unsplash.com",
},
{
protocol: "https",
hostname: "wpa.feep.ir",
},
],
},
typescript: {
+2
View File
@@ -9,6 +9,7 @@
"lint": "eslint"
},
"dependencies": {
"cheerio": "^1.0.0",
"@hookform/resolvers": "^5.2.2",
"@iconify/react": "^6.0.2",
"@radix-ui/react-accordion": "^1.2.12",
@@ -41,6 +42,7 @@
"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",
+462 -63
View File
@@ -21,7 +21,7 @@
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz"
integrity sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==
"@babel/core@^7.0.0", "@babel/core@^7.24.4":
"@babel/core@^7.24.4":
version "7.28.5"
resolved "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz"
integrity sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==
@@ -164,6 +164,14 @@
"@emnapi/wasi-threads" "1.1.0"
tslib "^2.4.0"
"@emnapi/core@^1.6.0", "@emnapi/core@^1.7.1":
version "1.8.1"
resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.8.1.tgz#fd9efe721a616288345ffee17a1f26ac5dd01349"
integrity sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==
dependencies:
"@emnapi/wasi-threads" "1.1.0"
tslib "^2.4.0"
"@emnapi/runtime@^1.4.3":
version "1.7.1"
resolved "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz"
@@ -171,9 +179,16 @@
dependencies:
tslib "^2.4.0"
"@emnapi/wasi-threads@1.1.0":
"@emnapi/runtime@^1.6.0", "@emnapi/runtime@^1.7.0", "@emnapi/runtime@^1.7.1":
version "1.8.1"
resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.8.1.tgz#550fa7e3c0d49c5fb175a116e8cd70614f9a22a5"
integrity sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==
dependencies:
tslib "^2.4.0"
"@emnapi/wasi-threads@1.1.0", "@emnapi/wasi-threads@^1.1.0":
version "1.1.0"
resolved "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz"
resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz#60b2102fddc9ccb78607e4a3cf8403ea69be41bf"
integrity sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==
dependencies:
tslib "^2.4.0"
@@ -320,6 +335,143 @@
resolved "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz"
integrity sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==
"@img/sharp-darwin-arm64@0.34.5":
version "0.34.5"
resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz#6e0732dcade126b6670af7aa17060b926835ea86"
integrity sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==
optionalDependencies:
"@img/sharp-libvips-darwin-arm64" "1.2.4"
"@img/sharp-darwin-x64@0.34.5":
version "0.34.5"
resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz#19bc1dd6eba6d5a96283498b9c9f401180ee9c7b"
integrity sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==
optionalDependencies:
"@img/sharp-libvips-darwin-x64" "1.2.4"
"@img/sharp-libvips-darwin-arm64@1.2.4":
version "1.2.4"
resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz#2894c0cb87d42276c3889942e8e2db517a492c43"
integrity sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==
"@img/sharp-libvips-darwin-x64@1.2.4":
version "1.2.4"
resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz#e63681f4539a94af9cd17246ed8881734386f8cc"
integrity sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==
"@img/sharp-libvips-linux-arm64@1.2.4":
version "1.2.4"
resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz#b1b288b36864b3bce545ad91fa6dadcf1a4ad318"
integrity sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==
"@img/sharp-libvips-linux-arm@1.2.4":
version "1.2.4"
resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz#b9260dd1ebe6f9e3bdbcbdcac9d2ac125f35852d"
integrity sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==
"@img/sharp-libvips-linux-ppc64@1.2.4":
version "1.2.4"
resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz#4b83ecf2a829057222b38848c7b022e7b4d07aa7"
integrity sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==
"@img/sharp-libvips-linux-riscv64@1.2.4":
version "1.2.4"
resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz#880b4678009e5a2080af192332b00b0aaf8a48de"
integrity sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==
"@img/sharp-libvips-linux-s390x@1.2.4":
version "1.2.4"
resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz#74f343c8e10fad821b38f75ced30488939dc59ec"
integrity sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==
"@img/sharp-libvips-linux-x64@1.2.4":
version "1.2.4"
resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz#df4183e8bd8410f7d61b66859a35edeab0a531ce"
integrity sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==
"@img/sharp-libvips-linuxmusl-arm64@1.2.4":
version "1.2.4"
resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz#c8d6b48211df67137541007ee8d1b7b1f8ca8e06"
integrity sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==
"@img/sharp-libvips-linuxmusl-x64@1.2.4":
version "1.2.4"
resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz#be11c75bee5b080cbee31a153a8779448f919f75"
integrity sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==
"@img/sharp-linux-arm64@0.34.5":
version "0.34.5"
resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz#7aa7764ef9c001f15e610546d42fce56911790cc"
integrity sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==
optionalDependencies:
"@img/sharp-libvips-linux-arm64" "1.2.4"
"@img/sharp-linux-arm@0.34.5":
version "0.34.5"
resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz#5fb0c3695dd12522d39c3ff7a6bc816461780a0d"
integrity sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==
optionalDependencies:
"@img/sharp-libvips-linux-arm" "1.2.4"
"@img/sharp-linux-ppc64@0.34.5":
version "0.34.5"
resolved "https://registry.yarnpkg.com/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz#9c213a81520a20caf66978f3d4c07456ff2e0813"
integrity sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==
optionalDependencies:
"@img/sharp-libvips-linux-ppc64" "1.2.4"
"@img/sharp-linux-riscv64@0.34.5":
version "0.34.5"
resolved "https://registry.yarnpkg.com/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz#cdd28182774eadbe04f62675a16aabbccb833f60"
integrity sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==
optionalDependencies:
"@img/sharp-libvips-linux-riscv64" "1.2.4"
"@img/sharp-linux-s390x@0.34.5":
version "0.34.5"
resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz#93eac601b9f329bb27917e0e19098c722d630df7"
integrity sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==
optionalDependencies:
"@img/sharp-libvips-linux-s390x" "1.2.4"
"@img/sharp-linux-x64@0.34.5":
version "0.34.5"
resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz#55abc7cd754ffca5002b6c2b719abdfc846819a8"
integrity sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==
optionalDependencies:
"@img/sharp-libvips-linux-x64" "1.2.4"
"@img/sharp-linuxmusl-arm64@0.34.5":
version "0.34.5"
resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz#d6515ee971bb62f73001a4829b9d865a11b77086"
integrity sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==
optionalDependencies:
"@img/sharp-libvips-linuxmusl-arm64" "1.2.4"
"@img/sharp-linuxmusl-x64@0.34.5":
version "0.34.5"
resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz#d97978aec7c5212f999714f2f5b736457e12ee9f"
integrity sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==
optionalDependencies:
"@img/sharp-libvips-linuxmusl-x64" "1.2.4"
"@img/sharp-wasm32@0.34.5":
version "0.34.5"
resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz#2f15803aa626f8c59dd7c9d0bbc766f1ab52cfa0"
integrity sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==
dependencies:
"@emnapi/runtime" "^1.7.0"
"@img/sharp-win32-arm64@0.34.5":
version "0.34.5"
resolved "https://registry.yarnpkg.com/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz#3706e9e3ac35fddfc1c87f94e849f1b75307ce0a"
integrity sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==
"@img/sharp-win32-ia32@0.34.5":
version "0.34.5"
resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz#0b71166599b049e032f085fb9263e02f4e4788de"
integrity sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==
"@img/sharp-win32-x64@0.34.5":
version "0.34.5"
resolved "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz"
@@ -359,6 +511,24 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@napi-rs/wasm-runtime@^0.2.11":
version "0.2.12"
resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz#3e78a8b96e6c33a6c517e1894efbd5385a7cb6f2"
integrity sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==
dependencies:
"@emnapi/core" "^1.4.3"
"@emnapi/runtime" "^1.4.3"
"@tybys/wasm-util" "^0.10.0"
"@napi-rs/wasm-runtime@^1.0.7":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz#c3705ab549d176b8dc5172723d6156c3dc426af2"
integrity sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==
dependencies:
"@emnapi/core" "^1.7.1"
"@emnapi/runtime" "^1.7.1"
"@tybys/wasm-util" "^0.10.1"
"@next/env@16.0.6":
version "16.0.6"
resolved "https://registry.npmjs.org/@next/env/-/env-16.0.6.tgz"
@@ -371,6 +541,41 @@
dependencies:
fast-glob "3.3.1"
"@next/swc-darwin-arm64@16.0.6":
version "16.0.6"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.0.6.tgz#e43c59c193a27a10f299c15c7305560226680493"
integrity sha512-AGzKiPlDiui+9JcPRHLI4V9WFTTcKukhJTfK9qu3e0tz+Y/88B7vo5yZoO7UaikplJEHORzG3QaBFQfkjhnL0Q==
"@next/swc-darwin-x64@16.0.6":
version "16.0.6"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-16.0.6.tgz#083292f12c7fbc31658b5c7a581be1ec1c0bb04e"
integrity sha512-LlLLNrK9WCIUkq2GciWDcquXYIf7vLxX8XE49gz7EncssZGL1vlHwgmURiJsUZAvk0HM1a8qb1ABDezsjAE/jw==
"@next/swc-linux-arm64-gnu@16.0.6":
version "16.0.6"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.0.6.tgz#2495a4dc0d6ff6fba7bd5edfbec72c79b3221c29"
integrity sha512-r04NzmLSGGfG8EPXKVK72N5zDNnq9pa9el78LhdtqIC3zqKh74QfKHnk24DoK4PEs6eY7sIK/CnNpt30oc59kg==
"@next/swc-linux-arm64-musl@16.0.6":
version "16.0.6"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.0.6.tgz#06b6a22512a7f721f4075166177c965902d9a735"
integrity sha512-hfB/QV0hA7lbD1OJxp52wVDlpffUMfyxUB5ysZbb/pBC5iuhyLcEKSVQo56PFUUmUQzbMsAtUu6k2Gh9bBtWXA==
"@next/swc-linux-x64-gnu@16.0.6":
version "16.0.6"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.0.6.tgz#8ffa6574b3c64edae3c01255231c708f2abe21d9"
integrity sha512-PZJushBgfvKhJBy01yXMdgL+l5XKr7uSn5jhOQXQXiH3iPT2M9iG64yHpPNGIKitKrHJInwmhPVGogZBAJOCPw==
"@next/swc-linux-x64-musl@16.0.6":
version "16.0.6"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.0.6.tgz#4d2d912563b8423b304b5ebac091f52aa5b4ba49"
integrity sha512-LqY76IojrH9yS5fyATjLzlOIOgwyzBuNRqXwVxcGfZ58DWNQSyfnLGlfF6shAEqjwlDNLh4Z+P0rnOI87Y9jEw==
"@next/swc-win32-arm64-msvc@16.0.6":
version "16.0.6"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.0.6.tgz#d8f70405b5f0a3fb5037719deb0ae05085c335cd"
integrity sha512-eIfSNNqAkj0tqKRf0u7BVjqylJCuabSrxnpSENY3YKApqwDMeAqYPmnOwmVe6DDl3Lvkbe7cJAyP6i9hQ5PmmQ==
"@next/swc-win32-x64-msvc@16.0.6":
version "16.0.6"
resolved "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.0.6.tgz"
@@ -384,7 +589,7 @@
"@nodelib/fs.stat" "2.0.5"
run-parallel "^1.1.9"
"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
version "2.0.5"
resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
@@ -478,7 +683,7 @@
"@radix-ui/react-use-previous" "1.1.1"
"@radix-ui/react-use-size" "1.1.1"
"@radix-ui/react-collapsible@^1.1.12", "@radix-ui/react-collapsible@1.1.12":
"@radix-ui/react-collapsible@1.1.12", "@radix-ui/react-collapsible@^1.1.12":
version "1.1.12"
resolved "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz"
integrity sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==
@@ -502,7 +707,7 @@
"@radix-ui/react-primitive" "2.1.3"
"@radix-ui/react-slot" "1.2.3"
"@radix-ui/react-compose-refs@^1.1.1", "@radix-ui/react-compose-refs@1.1.2":
"@radix-ui/react-compose-refs@1.1.2", "@radix-ui/react-compose-refs@^1.1.1":
version "1.1.2"
resolved "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz"
integrity sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==
@@ -529,7 +734,7 @@
resolved "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz"
integrity sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==
"@radix-ui/react-dialog@^1.1.1", "@radix-ui/react-dialog@^1.1.15", "@radix-ui/react-dialog@^1.1.6", "@radix-ui/react-dialog@1.1.15":
"@radix-ui/react-dialog@1.1.15", "@radix-ui/react-dialog@^1.1.1", "@radix-ui/react-dialog@^1.1.15", "@radix-ui/react-dialog@^1.1.6":
version "1.1.15"
resolved "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz"
integrity sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==
@@ -607,7 +812,7 @@
"@radix-ui/react-primitive" "2.1.3"
"@radix-ui/react-use-controllable-state" "1.2.2"
"@radix-ui/react-id@^1.1.0", "@radix-ui/react-id@1.1.1":
"@radix-ui/react-id@1.1.1", "@radix-ui/react-id@^1.1.0":
version "1.1.1"
resolved "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz"
integrity sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==
@@ -734,7 +939,7 @@
"@radix-ui/react-compose-refs" "1.1.2"
"@radix-ui/react-use-layout-effect" "1.1.1"
"@radix-ui/react-primitive@^2.0.2", "@radix-ui/react-primitive@2.1.3":
"@radix-ui/react-primitive@2.1.3", "@radix-ui/react-primitive@^2.0.2":
version "2.1.3"
resolved "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz"
integrity sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==
@@ -853,13 +1058,6 @@
"@radix-ui/react-use-previous" "1.1.1"
"@radix-ui/react-use-size" "1.1.1"
"@radix-ui/react-slot@^1.2.4", "@radix-ui/react-slot@1.2.4":
version "1.2.4"
resolved "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz"
integrity sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==
dependencies:
"@radix-ui/react-compose-refs" "1.1.2"
"@radix-ui/react-slot@1.2.3":
version "1.2.3"
resolved "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz"
@@ -867,6 +1065,13 @@
dependencies:
"@radix-ui/react-compose-refs" "1.1.2"
"@radix-ui/react-slot@1.2.4", "@radix-ui/react-slot@^1.2.4":
version "1.2.4"
resolved "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz"
integrity sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==
dependencies:
"@radix-ui/react-compose-refs" "1.1.2"
"@radix-ui/react-switch@^1.2.6":
version "1.2.6"
resolved "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz"
@@ -907,7 +1112,7 @@
"@radix-ui/react-toggle" "1.1.10"
"@radix-ui/react-use-controllable-state" "1.2.2"
"@radix-ui/react-toggle@^1.1.10", "@radix-ui/react-toggle@1.1.10":
"@radix-ui/react-toggle@1.1.10", "@radix-ui/react-toggle@^1.1.10":
version "1.1.10"
resolved "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz"
integrity sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==
@@ -1034,6 +1239,68 @@
source-map-js "^1.2.1"
tailwindcss "4.1.17"
"@tailwindcss/oxide-android-arm64@4.1.17":
version "4.1.17"
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.17.tgz#17f0dc901f88a979c5bff618181bce596dff596d"
integrity sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==
"@tailwindcss/oxide-darwin-arm64@4.1.17":
version "4.1.17"
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.17.tgz#63e12e62b83f6949dbb10b5a7f6e441606835efc"
integrity sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==
"@tailwindcss/oxide-darwin-x64@4.1.17":
version "4.1.17"
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.17.tgz#6dad270d2777508f55e2b73eca0eaef625bc45a7"
integrity sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==
"@tailwindcss/oxide-freebsd-x64@4.1.17":
version "4.1.17"
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.17.tgz#e7628b4602ac7d73c11a9922ecb83c24337eff55"
integrity sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==
"@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17":
version "4.1.17"
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.17.tgz#4d96a6fe4c7ed20e7a013101ee46f46caca2233e"
integrity sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==
"@tailwindcss/oxide-linux-arm64-gnu@4.1.17":
version "4.1.17"
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.17.tgz#adc3c01cd73610870bfc21db5713571e08fb2210"
integrity sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==
"@tailwindcss/oxide-linux-arm64-musl@4.1.17":
version "4.1.17"
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.17.tgz#39ceda30407af56a1ee125b2c5ce856c6d29250f"
integrity sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==
"@tailwindcss/oxide-linux-x64-gnu@4.1.17":
version "4.1.17"
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.17.tgz#a3d4bd876c04d09856af0c394f5095fbc8a2b14c"
integrity sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==
"@tailwindcss/oxide-linux-x64-musl@4.1.17":
version "4.1.17"
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.17.tgz#bdc20aa4fb2d28cc928f2cfffff7a9cd03a51d5b"
integrity sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==
"@tailwindcss/oxide-wasm32-wasi@4.1.17":
version "4.1.17"
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.17.tgz#7c0804748935928751838f86ff4f879c38f8a6d7"
integrity sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==
dependencies:
"@emnapi/core" "^1.6.0"
"@emnapi/runtime" "^1.6.0"
"@emnapi/wasi-threads" "^1.1.0"
"@napi-rs/wasm-runtime" "^1.0.7"
"@tybys/wasm-util" "^0.10.1"
tslib "^2.4.0"
"@tailwindcss/oxide-win32-arm64-msvc@4.1.17":
version "4.1.17"
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.17.tgz#7222fc2ceee9d45ebe5aebf38707ee9833a20475"
integrity sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==
"@tailwindcss/oxide-win32-x64-msvc@4.1.17":
version "4.1.17"
resolved "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.17.tgz"
@@ -1068,9 +1335,9 @@
postcss "^8.4.41"
tailwindcss "4.1.17"
"@tybys/wasm-util@^0.10.0":
"@tybys/wasm-util@^0.10.0", "@tybys/wasm-util@^0.10.1":
version "0.10.1"
resolved "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz"
resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz#ecddd3205cf1e2d5274649ff0eedd2991ed7f414"
integrity sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==
dependencies:
tslib "^2.4.0"
@@ -1148,12 +1415,12 @@
dependencies:
undici-types "~6.21.0"
"@types/react-dom@*", "@types/react-dom@^19":
"@types/react-dom@^19":
version "19.2.3"
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz"
integrity sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==
"@types/react@*", "@types/react@^19", "@types/react@^19.2.0":
"@types/react@^19":
version "19.2.7"
resolved "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz"
integrity sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==
@@ -1175,7 +1442,7 @@
natural-compare "^1.4.0"
ts-api-utils "^2.1.0"
"@typescript-eslint/parser@^8.48.0", "@typescript-eslint/parser@8.48.0":
"@typescript-eslint/parser@8.48.0":
version "8.48.0"
resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.48.0.tgz"
integrity sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==
@@ -1203,7 +1470,7 @@
"@typescript-eslint/types" "8.48.0"
"@typescript-eslint/visitor-keys" "8.48.0"
"@typescript-eslint/tsconfig-utils@^8.48.0", "@typescript-eslint/tsconfig-utils@8.48.0":
"@typescript-eslint/tsconfig-utils@8.48.0", "@typescript-eslint/tsconfig-utils@^8.48.0":
version "8.48.0"
resolved "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.0.tgz"
integrity sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==
@@ -1219,7 +1486,7 @@
debug "^4.3.4"
ts-api-utils "^2.1.0"
"@typescript-eslint/types@^8.48.0", "@typescript-eslint/types@8.48.0":
"@typescript-eslint/types@8.48.0", "@typescript-eslint/types@^8.48.0":
version "8.48.0"
resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.0.tgz"
integrity sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==
@@ -1257,6 +1524,98 @@
"@typescript-eslint/types" "8.48.0"
eslint-visitor-keys "^4.2.1"
"@unrs/resolver-binding-android-arm-eabi@1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz#9f5b04503088e6a354295e8ea8fe3cb99e43af81"
integrity sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==
"@unrs/resolver-binding-android-arm64@1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz#7414885431bd7178b989aedc4d25cccb3865bc9f"
integrity sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==
"@unrs/resolver-binding-darwin-arm64@1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz#b4a8556f42171fb9c9f7bac8235045e82aa0cbdf"
integrity sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==
"@unrs/resolver-binding-darwin-x64@1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz#fd4d81257b13f4d1a083890a6a17c00de571f0dc"
integrity sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==
"@unrs/resolver-binding-freebsd-x64@1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz#d2513084d0f37c407757e22f32bd924a78cfd99b"
integrity sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==
"@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz#844d2605d057488d77fab09705f2866b86164e0a"
integrity sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==
"@unrs/resolver-binding-linux-arm-musleabihf@1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz#204892995cefb6bd1d017d52d097193bc61ddad3"
integrity sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==
"@unrs/resolver-binding-linux-arm64-gnu@1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz#023eb0c3aac46066a10be7a3f362e7b34f3bdf9d"
integrity sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==
"@unrs/resolver-binding-linux-arm64-musl@1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz#9e6f9abb06424e3140a60ac996139786f5d99be0"
integrity sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==
"@unrs/resolver-binding-linux-ppc64-gnu@1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz#b111417f17c9d1b02efbec8e08398f0c5527bb44"
integrity sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==
"@unrs/resolver-binding-linux-riscv64-gnu@1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz#92ffbf02748af3e99873945c9a8a5ead01d508a9"
integrity sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==
"@unrs/resolver-binding-linux-riscv64-musl@1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz#0bec6f1258fc390e6b305e9ff44256cb207de165"
integrity sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==
"@unrs/resolver-binding-linux-s390x-gnu@1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz#577843a084c5952f5906770633ccfb89dac9bc94"
integrity sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==
"@unrs/resolver-binding-linux-x64-gnu@1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz#36fb318eebdd690f6da32ac5e0499a76fa881935"
integrity sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==
"@unrs/resolver-binding-linux-x64-musl@1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz#bfb9af75f783f98f6a22c4244214efe4df1853d6"
integrity sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==
"@unrs/resolver-binding-wasm32-wasi@1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz#752c359dd875684b27429500d88226d7cc72f71d"
integrity sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==
dependencies:
"@napi-rs/wasm-runtime" "^0.2.11"
"@unrs/resolver-binding-win32-arm64-msvc@1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz#ce5735e600e4c2fbb409cd051b3b7da4a399af35"
integrity sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==
"@unrs/resolver-binding-win32-ia32-msvc@1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz#72fc57bc7c64ec5c3de0d64ee0d1810317bc60a6"
integrity sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==
"@unrs/resolver-binding-win32-x64-msvc@1.11.1":
version "1.11.1"
resolved "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz"
@@ -1267,7 +1626,7 @@ acorn-jsx@^5.3.2:
resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.15.0:
acorn@^8.15.0:
version "8.15.0"
resolved "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz"
integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==
@@ -1456,7 +1815,7 @@ braces@^3.0.3:
dependencies:
fill-range "^7.1.1"
browserslist@^4.24.0, "browserslist@>= 4.21.0":
browserslist@^4.24.0:
version "4.28.0"
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz"
integrity sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==
@@ -1574,7 +1933,7 @@ csstype@^3.0.2, csstype@^3.2.2:
resolved "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz"
integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==
d3-array@^3.1.6, "d3-array@2 - 3", "d3-array@2.10.0 - 3":
"d3-array@2 - 3", "d3-array@2.10.0 - 3", d3-array@^3.1.6:
version "3.2.4"
resolved "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz"
integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==
@@ -1596,7 +1955,7 @@ d3-ease@^3.0.1:
resolved "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz"
integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==
d3-interpolate@^3.0.1, "d3-interpolate@1.2.0 - 3":
"d3-interpolate@1.2.0 - 3", d3-interpolate@^3.0.1:
version "3.0.1"
resolved "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz"
integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==
@@ -1633,7 +1992,7 @@ d3-shape@^3.1.0:
dependencies:
d3-time "1 - 3"
d3-time@^3.0.0, "d3-time@1 - 3", "d3-time@2.1.1 - 3":
"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@^3.0.0:
version "3.1.0"
resolved "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz"
integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==
@@ -1978,7 +2337,7 @@ eslint-module-utils@^2.12.1:
dependencies:
debug "^3.2.7"
eslint-plugin-import@*, eslint-plugin-import@^2.32.0:
eslint-plugin-import@^2.32.0:
version "2.32.0"
resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz"
integrity sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==
@@ -2077,7 +2436,7 @@ eslint-visitor-keys@^4.2.1:
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz"
integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==
eslint@*, "eslint@^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9", "eslint@^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9", "eslint@^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7", "eslint@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^8.57.0 || ^9.0.0", eslint@^9, eslint@>=9.0.0:
eslint@^9:
version "9.39.1"
resolved "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz"
integrity sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==
@@ -2340,16 +2699,16 @@ glob-parent@^6.0.2:
dependencies:
is-glob "^4.0.3"
globals@^14.0.0:
version "14.0.0"
resolved "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz"
integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==
globals@16.4.0:
version "16.4.0"
resolved "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz"
integrity sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==
globals@^14.0.0:
version "14.0.0"
resolved "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz"
integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==
globalthis@^1.0.4:
version "1.0.4"
resolved "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz"
@@ -2682,7 +3041,7 @@ iterator.prototype@^1.1.4:
has-symbols "^1.1.0"
set-function-name "^2.0.2"
jiti@*, jiti@^2.6.1:
jiti@^2.6.1:
version "2.6.1"
resolved "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz"
integrity sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==
@@ -2768,6 +3127,56 @@ levn@^0.4.1:
prelude-ls "^1.2.1"
type-check "~0.4.0"
lightningcss-android-arm64@1.30.2:
version "1.30.2"
resolved "https://registry.yarnpkg.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz#6966b7024d39c94994008b548b71ab360eb3a307"
integrity sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==
lightningcss-darwin-arm64@1.30.2:
version "1.30.2"
resolved "https://registry.yarnpkg.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz#a5fa946d27c029e48c7ff929e6e724a7de46eb2c"
integrity sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==
lightningcss-darwin-x64@1.30.2:
version "1.30.2"
resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz#5ce87e9cd7c4f2dcc1b713f5e8ee185c88d9b7cd"
integrity sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==
lightningcss-freebsd-x64@1.30.2:
version "1.30.2"
resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz#6ae1d5e773c97961df5cff57b851807ef33692a5"
integrity sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==
lightningcss-linux-arm-gnueabihf@1.30.2:
version "1.30.2"
resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz#62c489610c0424151a6121fa99d77731536cdaeb"
integrity sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==
lightningcss-linux-arm64-gnu@1.30.2:
version "1.30.2"
resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz#2a3661b56fe95a0cafae90be026fe0590d089298"
integrity sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==
lightningcss-linux-arm64-musl@1.30.2:
version "1.30.2"
resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz#d7ddd6b26959245e026bc1ad9eb6aa983aa90e6b"
integrity sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==
lightningcss-linux-x64-gnu@1.30.2:
version "1.30.2"
resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz#5a89814c8e63213a5965c3d166dff83c36152b1a"
integrity sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==
lightningcss-linux-x64-musl@1.30.2:
version "1.30.2"
resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz#808c2e91ce0bf5d0af0e867c6152e5378c049728"
integrity sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==
lightningcss-win32-arm64-msvc@1.30.2:
version "1.30.2"
resolved "https://registry.yarnpkg.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz#ab4a8a8a2e6a82a4531e8bbb6bf0ff161ee6625a"
integrity sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==
lightningcss-win32-x64-msvc@1.30.2:
version "1.30.2"
resolved "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz"
@@ -3076,7 +3485,7 @@ picomatch@^2.3.1:
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
"picomatch@^3 || ^4", picomatch@^4.0.3:
picomatch@^4.0.3:
version "4.0.3"
resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz"
integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==
@@ -3086,15 +3495,6 @@ possible-typed-array-names@^1.0.0:
resolved "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz"
integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==
postcss@^8.4.41:
version "8.5.6"
resolved "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz"
integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==
dependencies:
nanoid "^3.3.11"
picocolors "^1.1.1"
source-map-js "^1.2.1"
postcss@8.4.31:
version "8.4.31"
resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz"
@@ -3104,6 +3504,15 @@ postcss@8.4.31:
picocolors "^1.0.0"
source-map-js "^1.0.2"
postcss@^8.4.41:
version "8.5.6"
resolved "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz"
integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==
dependencies:
nanoid "^3.3.11"
picocolors "^1.1.1"
source-map-js "^1.2.1"
prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz"
@@ -3137,14 +3546,14 @@ react-day-picker@^9.11.3:
date-fns "^4.1.0"
date-fns-jalali "^4.1.0-0"
"react-dom@^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom@^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom@^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^18 || ^19 || ^19.0.0-rc", "react-dom@^18.0.0 || ^19.0.0", "react-dom@^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom@^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", react-dom@>=16.6.0, react-dom@>=16.8.0, react-dom@19.2.0:
react-dom@19.2.0:
version "19.2.0"
resolved "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz"
integrity sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==
dependencies:
scheduler "^0.27.0"
react-hook-form@^7.55.0, react-hook-form@^7.67.0:
react-hook-form@^7.67.0:
version "7.67.0"
resolved "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.67.0.tgz"
integrity sha512-E55EOwKJHHIT/I6J9DmQbCWToAYSw9nN5R57MZw9rMtjh+YQreMDxRLfdjfxQbiJ3/qbg3Z02wGzBX4M+5fMtQ==
@@ -3210,7 +3619,7 @@ react-transition-group@^4.4.5:
loose-envify "^1.4.0"
prop-types "^15.6.2"
"react@^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react@^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.8.0 || ^17 || ^18 || ^19", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^18 || ^19 || ^19.0.0-rc", "react@^18.0.0 || ^19.0.0", "react@^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", react@^19.2.0, "react@>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0", react@>=16, react@>=16.6.0, react@>=16.8.0, react@19.2.0:
react@19.2.0:
version "19.2.0"
resolved "https://registry.npmjs.org/react/-/react-19.2.0.tgz"
integrity sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==
@@ -3340,17 +3749,7 @@ semver@^6.3.1:
resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.6.0:
version "7.7.3"
resolved "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz"
integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==
semver@^7.7.1:
version "7.7.3"
resolved "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz"
integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==
semver@^7.7.3:
semver@^7.6.0, semver@^7.7.1, semver@^7.7.3:
version "7.7.3"
resolved "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz"
integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==
@@ -3597,7 +3996,7 @@ tailwind-merge@^3.4.0:
resolved "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz"
integrity sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==
tailwindcss@^4, tailwindcss@4.1.17:
tailwindcss@4.1.17, tailwindcss@^4:
version "4.1.17"
resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz"
integrity sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==
@@ -3714,7 +4113,7 @@ typescript-eslint@^8.46.0:
"@typescript-eslint/typescript-estree" "8.48.0"
"@typescript-eslint/utils" "8.48.0"
typescript@^5, typescript@>=3.3.1, typescript@>=4.8.4, "typescript@>=4.8.4 <6.0.0":
typescript@^5:
version "5.9.3"
resolved "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz"
integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==