feat(landing): enhance landing page with privacy and terms of service

- Added new pages for Privacy Policy and Terms of Service, including markdown content rendering.
- Integrated `react-markdown` for displaying legal documents.
- Updated layout to include a footer with links to the new pages.
- Refactored layout components to improve structure and maintainability.
- Removed unused footer navigation items to streamline the design.

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei
2025-11-16 20:41:02 +08:00
parent d1dee58219
commit 2e6ef268e5
13 changed files with 697 additions and 115 deletions

View File

@@ -37,6 +37,7 @@
"react-dom": "19.2.0",
"react-error-boundary": "6.0.0",
"react-intersection-observer": "10.0.0",
"react-markdown": "^9.0.1",
"sonner": "2.0.7",
"tailwind-merge": "3.4.0",
"usehooks-ts": "3.1.1",

View File

@@ -4,6 +4,9 @@ import type { Viewport } from 'next'
import { HydrationEndDetector } from '~/components/common/HydrationEndDetector'
import { ScrollTop } from '~/components/common/ScrollTop'
import { NocturneBackground } from '~/components/landing/NocturneBackground'
import { Footer } from '~/components/layout'
import { PageHeader } from '~/components/layout/PageHeader'
import { Root } from '~/components/layout/root/Root'
import { sansFont, serifFont } from '~/lib/fonts'
@@ -100,7 +103,12 @@ export default async function RootLayout({
>
<Providers>
<div data-theme>
<Root>{children}</Root>
<Root>
<NocturneBackground />
<PageHeader />
{children}
<Footer />
</Root>
</div>
</Providers>

View File

@@ -1,6 +1,5 @@
'use client'
import { NocturneBackground } from '~/components/landing/NocturneBackground'
import {
ArtistNote,
ClosingCTA,
@@ -9,22 +8,17 @@ import {
NocturneHero,
PillarsSection,
} from '~/components/landing/NocturneSections'
import { Footer } from '~/components/layout/Footer'
import { PageHeader } from '~/components/layout/PageHeader'
export default function Home() {
return (
<div className="relative min-h-screen overflow-hidden bg-[#020202] text-white">
<NocturneBackground />
<main className="relative z-10 mx-auto flex w-full max-w-6xl flex-col gap-20 px-4 py-16 sm:px-6 lg:px-0">
<PageHeader />
<NocturneHero />
<PillarsSection />
<JourneySection />
<GalleryPreview />
<ArtistNote />
<ClosingCTA />
<Footer />
</main>
</div>
)

View File

@@ -0,0 +1,30 @@
import { readFile } from 'node:fs/promises'
import { resolve } from 'node:path'
import { MarkdownContent } from '~/components/common/MarkdownContent'
import { NormalContainer } from '~/components/layout/container/Normal'
export const metadata = {
title: 'Privacy Policy',
description: 'Afilmory Privacy Policy',
}
export default async function PrivacyPage() {
// Resolve path - try both monorepo root and app root
const cwd = process.cwd()
const filePath =
cwd.endsWith('apps/landing') || cwd.endsWith('apps/landing/')
? resolve(cwd, 'src/legal/privacy.md')
: resolve(cwd, 'apps/landing/src/legal/privacy.md')
const content = await readFile(filePath, 'utf-8')
return (
<div className="relative min-h-screen text-white">
<NormalContainer>
<article className="py-16">
<MarkdownContent content={content} />
</article>
</NormalContainer>
</div>
)
}

View File

@@ -0,0 +1,30 @@
import { readFile } from 'node:fs/promises'
import { resolve } from 'node:path'
import { MarkdownContent } from '~/components/common/MarkdownContent'
import { NormalContainer } from '~/components/layout/container/Normal'
export const metadata = {
title: 'Terms of Service',
description: 'Afilmory Terms of Service',
}
export default async function TermsPage() {
// Resolve path - try both monorepo root and app root
const cwd = process.cwd()
const filePath =
cwd.endsWith('apps/landing') || cwd.endsWith('apps/landing/')
? resolve(cwd, 'src/legal/tos.md')
: resolve(cwd, 'apps/landing/src/legal/tos.md')
const content = await readFile(filePath, 'utf-8')
return (
<div className="relative min-h-screen text-white">
<NormalContainer>
<article className="py-16">
<MarkdownContent content={content} />
</article>
</NormalContainer>
</div>
)
}

View File

@@ -0,0 +1,66 @@
'use client'
import type { FC } from 'react'
import ReactMarkdown from 'react-markdown'
interface MarkdownContentProps {
content: string
}
export const MarkdownContent: FC<MarkdownContentProps> = ({ content }) => {
return (
<div className="prose prose-invert prose-headings:text-white prose-p:text-white/80 prose-a:text-white prose-strong:text-white prose-ul:text-white/80 prose-ol:text-white/80 prose-li:text-white/80 prose-hr:border-white/20 max-w-none">
<ReactMarkdown
components={{
h1: ({ children }) => (
<h1 className="mt-8 mb-6 text-3xl font-bold text-white first:mt-0">
{children}
</h1>
),
h2: ({ children }) => (
<h2 className="mt-8 mb-4 text-2xl font-semibold text-white">
{children}
</h2>
),
h3: ({ children }) => (
<h3 className="mt-6 mb-3 text-xl font-semibold text-white">
{children}
</h3>
),
p: ({ children }) => (
<p className="mb-4 leading-7 text-white/80">{children}</p>
),
ul: ({ children }) => (
<ul className="mb-4 ml-6 list-disc space-y-2 text-white/80">
{children}
</ul>
),
ol: ({ children }) => (
<ol className="mb-4 ml-6 list-decimal space-y-2 text-white/80">
{children}
</ol>
),
li: ({ children }) => (
<li className="leading-7 text-white/80">{children}</li>
),
strong: ({ children }) => (
<strong className="font-semibold text-white">{children}</strong>
),
a: ({ href, children }) => (
<a
href={href}
className="text-white underline underline-offset-2 hover:text-white/80"
target={href?.startsWith('http') ? '_blank' : undefined}
rel={href?.startsWith('http') ? 'noopener noreferrer' : undefined}
>
{children}
</a>
),
hr: () => <hr className="my-8 border-white/20" />,
}}
>
{content}
</ReactMarkdown>
</div>
)
}

View File

@@ -2,43 +2,6 @@
import Link from 'next/link'
const footerNav = [
{
title: '策展主题',
links: [
{ label: '城市夜行', href: '#' },
{ label: '肖像诗篇', href: '#' },
{ label: '记忆胶片', href: '#' },
],
},
{
title: '体验',
links: [
{ label: '预约沉浸导览', href: '#' },
{ label: '私享展陈', href: '#' },
{ label: '影像叙事工作坊', href: '#' },
],
},
{
title: '合作',
links: [
{ label: '品牌共创', href: '#' },
{ label: '艺术项目', href: '#' },
{ label: '驻地策展', href: '#' },
],
},
]
const ContactRow = () => (
<div className="flex flex-wrap gap-4 text-sm text-white/70">
<span>hello@afilmory.studio</span>
<span className="text-white/40">/</span>
<span>WeChat · AFILMORY</span>
<span className="text-white/40">/</span>
<span>IG · @afilmory.gallery</span>
</div>
)
export const Footer = () => {
return (
<footer className="relative mt-24 overflow-hidden rounded-t-[48px] border border-white/10 bg-linear-to-b from-black/80 via-black/60 to-black px-6 py-14 text-white sm:px-10">
@@ -60,42 +23,17 @@ export const Footer = () => {
Afilmory
</p>
<ContactRow />
</div>
<div className="grid gap-8 sm:grid-cols-3">
{footerNav.map((column) => (
<div key={column.title} className="space-y-4">
<p className="text-xs tracking-[0.4em] text-white/45 uppercase">
{column.title}
</p>
<ul className="space-y-3 text-sm text-white/70">
{column.links.map((link) => (
<li key={link.label}>
<Link
href={link.href}
className="transition hover:text-white"
>
{link.label}
</Link>
</li>
))}
</ul>
</div>
))}
</div>
</div>
<div className="border-t border-white/10 pt-6 text-sm text-white/60">
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<p>© 2025 Afilmory Studio · </p>
<p>© 2025 Afilmory.art</p>
<div className="flex gap-6 text-white/70">
<Link href="#" className="transition hover:text-white">
<Link href="/privacy" className="transition hover:text-white">
Privacy Policy
</Link>
<Link href="#" className="transition hover:text-white">
</Link>
<Link href="#" className="transition hover:text-white">
<Link href="/terms" className="transition hover:text-white">
Terms of Service
</Link>
</div>
</div>

View File

@@ -4,4 +4,4 @@
*/
export { Footer } from './Footer'
export { Header } from './Header'
export { PageHeader } from './PageHeader'

View File

@@ -0,0 +1,134 @@
# Afilmory Privacy Policy
_Last Updated: 2025-11-16_
This Privacy Policy describes how Afilmory (“the Service”, “we”, “us”) collects, uses, stores, and protects your information.
By using the Service, you agree to the practices described here.
---
## 1. Information We Collect
### 1.1 Account Information
- Email address
- Username
- Encrypted password
- Third-party login identifiers (if used)
### 1.2 Usage Data
We collect system and operational logs including:
- IP address
- Device and browser information
- API request logs
- Error logs
- CDN access logs
- Upload/download timestamps
### 1.3 Image Data
- Images you upload
- Associated metadata (format, dimensions, EXIF if included)
We do **not** use your images for advertising, AI training, or profiling.
---
## 2. How We Use Collected Data
We use your information to:
- Provide core image hosting and processing functionality
- Authenticate your account
- Deliver content through CDN services
- Monitor performance and detect abuse
- Maintain security and prevent fraud
- Send essential communications (billing, service updates)
- Comply with legal obligations
We **do not sell** user data.
---
## 3. Data Sharing
We only share data when necessary with:
- Cloud infrastructure providers
- CDN and delivery networks
- Payment processors
- Error monitoring and analytics tools (if used)
- Legal authorities as required
We never provide third parties access to your private images except when required by law.
---
## 4. Data Security
We implement industry-standard security:
- HTTPS/TLS for all transfers
- Encrypted password storage
- Access controls and firewall protections
- Limited access to personal information
- Continuous monitoring for suspicious activity
However, no system is fully secure. You use the Service at your own risk.
---
## 5. Cookies & Local Storage
Afilmory uses cookies/local storage for:
- Authentication
- Session management
- Basic analytics
- Security (CSRF protection)
We do not use tracking cookies or ad cookies.
---
## 6. Data Retention
- Your content is retained until you delete it
- Logs may be retained 30180 days
- Backups persist temporarily by lifecycle rules
You may request complete deletion at any time.
---
## 7. Your Rights
You may:
- Access your personal data
- Edit or update account information
- Export or download uploaded content
- Request deletion of data
- Close your account at any time
For EU/EEA/UK users, GDPR protections apply (see ToS Addendum).
---
## 8. Children's Privacy
Afilmory is not intended for individuals under 13.
We do not knowingly collect data from minors.
---
## 9. Changes to This Policy
We may update this Privacy Policy from time to time.
Significant changes will be announced through the Service.
---
By using Afilmory, you acknowledge that you have read and agreed to this Privacy Policy.

View File

@@ -0,0 +1,265 @@
# Afilmory Terms of Service
_Last Updated: 2025-11-16_
Welcome to **Afilmory** (“the Service”, “we”, “us”). Afilmory is a personally operated SaaS platform for image upload, hosting, processing, and management.
By accessing or using the Service, you agree to the following Terms of Service.
This document consolidates all applicable policies, including:
- Acceptable Use Policy (AUP)
- Content Moderation Guidelines
- Pricing & Billing Terms
- DMCA Takedown Policy
- GDPR Addendum
into a single unified Terms of Service.
---
## 1. Description of the Service
Afilmory provides:
- Image upload, storage, and management
- Image processing (compression, resizing, optimization, format conversion)
- Static asset hosting and CDN delivery
- Developer APIs for programmatic access
- A user dashboard for managing digital assets
The Service may evolve over time. New features will also be governed by these Terms.
---
## 2. Account Registration and Eligibility
To use the Service, you must:
- Provide accurate, complete registration information
- Maintain the security of your account and password
- Be responsible for all activity occurring under your account
If you believe your account has been compromised, notify us immediately.
---
## 3. Ownership and License of User Content
### 3.1 Your Rights
You retain full ownership of all images, metadata, and materials you upload (“User Content”).
### 3.2 License to Afilmory
You grant us a **revocable, non-exclusive, worldwide license** to:
- Store and deliver your content
- Process images as required to operate the Service
- Generate cached or derivative assets (e.g., thumbnails, optimized versions)
- Serve content through CDN infrastructure
- Maintain backups for reliability
We will never sell your content or use it for advertising or training.
---
## 4. Acceptable Use Policy (AUP)
You may not use Afilmory for:
### 4.1 Illegal Activities
- Uploading illegal content
- Child sexual abuse material (CSAM) or sexualized depictions of minors
- Human trafficking or exploitation
- Non-consensual intimate imagery
### 4.2 Copyright Violations
- Uploading copyrighted material without authorization
- Removing or falsifying metadata
### 4.3 Harmful or Abusive Content
- Graphic violence, gore, or torture
- Hate speech and extremist propaganda
- Threats, harassment, bullying
### 4.4 Security Risks
- Malware, trojans, or harmful executable payloads
- Phishing images, QR-code scams, or malicious steganography
- Attempts to hack, bypass authentication, or disrupt the Service
### 4.5 Service Abuse
- Using Afilmory as a general-purpose file hosting service
- Hosting spam images or bulk SEO content
- Excessive automated scraping, DDoS-like traffic, or platform misuse
- Circumventing rate limits, bandwidth limits, or billing
### 4.6 API Restrictions
- Sharing API keys publicly
- Abusing API rate limits
- Automated actions intended to overload the system
Accounts violating the AUP may be suspended or terminated without notice.
---
## 5. Content Moderation
Afilmory may remove, restrict, or block content that:
- Violates this ToS or AUP
- Is reported under DMCA claims
- Poses security or legal risks
- Violates payment network policies (e.g., Stripe rules)
We may use automated tools and human review.
We are not obligated to provide prior notice for removals in urgent or legal situations.
---
## 6. Pricing, Billing & Refund Policy
### 6.1 Subscription Model
Afilmory offers usage-based subscription tiers that may depend on:
- Storage
- CDN bandwidth
- API usage
- Processing workload
Pricing may change; major changes will be announced in advance.
### 6.2 Automatic Renewal
- Plans renew automatically each billing cycle
- You are responsible for cancelling before the next cycle
- Failed payments may lead to suspension
### 6.3 **Strict No Refund Policy**
Afilmory does **not** offer refunds for:
- Partial billing periods
- Unused subscription time
- Failure to cancel
- Inactivity
- Dissatisfaction with features
- Bandwidth or storage overages
**All payments are final and non-refundable**, except for clear system billing errors.
### 6.4 Overage Fees
You agree to pay all charges associated with usage beyond your subscription limits.
---
## 7. DMCA Takedown Policy
Afilmory complies with the Digital Millennium Copyright Act (DMCA).
If you believe content hosted on Afilmory infringes your copyright, send a notice including:
1. Your name and contact information
2. Identification of the copyrighted work
3. The exact URL(s) of infringing content
4. A statement of good-faith belief
5. A statement under penalty of perjury
6. Your physical or electronic signature
Send notices to:
**support@afilmory.art**
We may remove or disable access to allegedly infringing content and notify the account owner.
Knowingly submitting false claims may result in account termination.
---
## 8. GDPR Addendum (For EU/EEA/UK Users)
Afilmory processes personal data as required to provide the Service.
### Rights of EU/EEA/UK users include:
- Access your data
- Correct inaccurate data
- Request deletion (Right to Erasure)
- Download your data (Portability)
- Restrict or object to processing
Requests can be sent to:
**support@afilmory.art**
We store data in regions that provide GDPR-equivalent protection (e.g., via SCCs).
---
## 9. Service Modifications & Termination
Afilmory may change or discontinue features at any time.
We may suspend or terminate your account if:
- You violate this ToS or AUP
- You fail to pay required fees
- Your usage creates security or operational risks
- We are required by law to do so
You may stop using the Service at any time.
---
## 10. Disclaimer of Warranties
The Service is provided **“as is”** and **“as available”** without warranty.
We do not guarantee:
- Uptime or availability
- Error-free operation
- Data integrity or preservation
- Protection against all attacks
- Compatibility with third-party systems
You use the Service at your own risk.
---
## 11. Limitation of Liability
To the maximum extent permitted by law:
- Our total liability is limited to amounts paid during the previous **12 months**
- We are not liable for indirect, incidental, punitive, or consequential damages
- We are not liable for data loss
---
## 12. Data Retention & Deletion
- You may delete your content at any time
- If your account is terminated, we may retain data for up to 30 days
- Backups may persist temporarily but are not publicly accessible
- You may request immediate deletion
---
## 13. Governing Law
These Terms are governed by the laws applicable in your local jurisdiction unless otherwise required.
---
## 14. Contact
For questions, DMCA notices, GDPR requests, or support:
**support@afilmory.art**
---
By using Afilmory, you acknowledge that you have read and agreed to this Terms of Service.

View File

@@ -17,7 +17,7 @@ export function DeleteFromStorageOption({ defaultChecked = false, disabled, onCh
onChange?.(Boolean(value))
}}
/>
<div className="space-y-1 text-sm leading-relaxed">
<div className="text-sm leading-relaxed">
<p className="font-medium"></p>
<p className="text-xs text-text-tertiary"></p>
</div>

View File

@@ -1,3 +1,4 @@
import { Prompt } from '@afilmory/ui'
import type { ReactNode } from 'react'
import { createContext, use, useEffect, useRef } from 'react'
import { toast } from 'sonner'
@@ -10,6 +11,7 @@ import { getRequestErrorMessage } from '~/lib/errors'
import { getPhotoStorageUrl } from '../../api'
import { useDeletePhotoAssetsMutation, usePhotoAssetListQuery, useUploadPhotoAssetsMutation } from '../../hooks'
import type { PhotoAssetListItem } from '../../types'
import { DeleteFromStorageOption } from './DeleteFromStorageOption'
import type { DeleteAssetOptions } from './types'
import type { PhotoUploadRequestOptions } from './upload.types'
@@ -51,6 +53,35 @@ function createPhotoLibraryStore(params: CreatePhotoLibraryStoreParams) {
const { requestDeleteAssets, requestUploadAssets, requestStorageUrl, refetchAssets } = params
return createStore<PhotoLibraryStoreState>((set, get) => {
const getAssetLabel = (asset: PhotoAssetListItem) =>
asset.manifest?.data?.title ?? asset.manifest?.data?.id ?? asset.photoId
const presentDeletePrompt = (
label: string,
onConfirm: (options: DeleteAssetOptions) => Promise<void> | void,
): Promise<void> => {
let deleteFromStorage = false
return new Promise((resolve) => {
Prompt.prompt({
title: '确认删除该资源?',
description: `删除后将无法恢复,是否继续删除「${label}」?如需同时删除远程存储文件,可勾选下方选项。`,
variant: 'danger',
onConfirmText: '删除',
onCancelText: '取消',
content: (
<DeleteFromStorageOption
onChange={(checked) => {
deleteFromStorage = checked
}}
/>
),
onConfirm: () => Promise.resolve(onConfirm({ deleteFromStorage })).finally(resolve),
onCancel: resolve,
})
})
}
const performDelete = async (ids: string[], options?: DeleteAssetOptions) => {
if (ids.length === 0) return
set({ isDeleting: true })
@@ -125,8 +156,27 @@ function createPhotoLibraryStore(params: CreatePhotoLibraryStoreParams) {
}
return { selectedIds: state.assets.map((asset) => asset.id) }
}),
deleteAsset: (asset, options) => performDelete([asset.id], options),
deleteSelected: () => performDelete(get().selectedIds),
deleteAsset: (asset, options) => {
if (options) {
return performDelete([asset.id], options)
}
const assetLabel = getAssetLabel(asset)
return presentDeletePrompt(assetLabel, (promptOptions) => performDelete([asset.id], promptOptions))
},
deleteSelected: () => {
const ids = get().selectedIds
if (ids.length === 0) {
return Promise.resolve()
}
const assets = get().assets ?? []
const selectedAssets = assets.filter((asset) => ids.includes(asset.id))
const targetLabel =
selectedAssets.length === 1 ? getAssetLabel(selectedAssets[0]) : `选中的 ${ids.length} 个资源`
return presentDeletePrompt(targetLabel, (promptOptions) => performDelete(ids, promptOptions))
},
uploadAssets: (files, options) => performUpload(Array.from(files), options),
openAsset: performOpenAsset,
refetchAssets,

140
pnpm-lock.yaml generated
View File

@@ -345,7 +345,7 @@ importers:
version: 12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
next:
specifier: 16.0.1
version: 16.0.1(@babel/core@7.28.5)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
version: 16.0.1(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
next-themes:
specifier: 0.4.6
version: 0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
@@ -364,6 +364,9 @@ importers:
react-intersection-observer:
specifier: 10.0.0
version: 10.0.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
react-markdown:
specifier: ^9.0.1
version: 9.1.0(@types/react@19.2.3)(react@19.2.0)
sonner:
specifier: 2.0.7
version: 2.0.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
@@ -575,7 +578,7 @@ importers:
version: 0.31.6
next:
specifier: 16.0.1
version: 16.0.1(@babel/core@7.28.5)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
version: 16.0.1(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
postcss:
specifier: 8.5.6
version: 8.5.6
@@ -623,7 +626,7 @@ importers:
version: 2.2.9(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@lobehub/fluent-emoji':
specifier: 2.0.0
version: 2.0.0(@babel/core@7.28.5)(@types/react@19.2.3)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
version: 2.0.0(@babel/core@7.28.4)(@types/react@19.2.3)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@maplibre/maplibre-gl-geocoder':
specifier: ^1.9.1
version: 1.9.1(maplibre-gl@5.12.0)
@@ -686,7 +689,7 @@ importers:
version: 10.2.0
jotai:
specifier: 2.15.1
version: 2.15.1(@babel/core@7.28.5)(@babel/template@7.27.2)(@types/react@19.2.3)(react@19.2.0)
version: 2.15.1(@babel/core@7.28.4)(@babel/template@7.27.2)(@types/react@19.2.3)(react@19.2.0)
maplibre-gl:
specifier: ^5.12.0
version: 5.12.0
@@ -734,7 +737,7 @@ importers:
version: 7.9.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
react-scan:
specifier: 0.4.3
version: 0.4.3(@types/react@19.2.3)(next@16.0.1(@babel/core@7.28.5)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react-router-dom@6.30.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-router@7.9.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)(rollup@2.79.2)
version: 0.4.3(@types/react@19.2.3)(next@16.0.1(@babel/core@7.28.4)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react-router-dom@6.30.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-router@7.9.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)(rollup@2.79.2)
react-use-measure:
specifier: 2.1.7
version: 2.1.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
@@ -10462,6 +10465,12 @@ packages:
'@types/react': '>=18'
react: '>=18'
react-markdown@9.1.0:
resolution: {integrity: sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw==}
peerDependencies:
'@types/react': '>=18'
react: '>=18'
react-merge-refs@3.0.2:
resolution: {integrity: sha512-MSZAfwFfdbEvwkKWP5EI5chuLYnNUxNS7vyS0i1Jp+wtd8J4Ga2ddzhaE68aMol2Z4vCnRM/oGOo1a3V75UPlw==}
peerDependencies:
@@ -12887,9 +12896,9 @@ snapshots:
regexpu-core: 6.4.0
semver: 6.3.1
'@babel/helper-define-polyfill-provider@0.6.4(@babel/core@7.28.5)':
'@babel/helper-define-polyfill-provider@0.6.4(@babel/core@7.28.4)':
dependencies:
'@babel/core': 7.28.5
'@babel/core': 7.28.4
'@babel/helper-compilation-targets': 7.27.2
'@babel/helper-plugin-utils': 7.27.1
debug: 4.4.3(supports-color@5.5.0)
@@ -13354,14 +13363,14 @@ snapshots:
'@babel/core': 7.28.5
'@babel/helper-plugin-utils': 7.27.1
'@babel/plugin-transform-runtime@7.27.4(@babel/core@7.28.5)':
'@babel/plugin-transform-runtime@7.27.4(@babel/core@7.28.4)':
dependencies:
'@babel/core': 7.28.5
'@babel/core': 7.28.4
'@babel/helper-module-imports': 7.27.1
'@babel/helper-plugin-utils': 7.27.1
babel-plugin-polyfill-corejs2: 0.4.13(@babel/core@7.28.5)
babel-plugin-polyfill-corejs3: 0.11.1(@babel/core@7.28.5)
babel-plugin-polyfill-regenerator: 0.6.4(@babel/core@7.28.5)
babel-plugin-polyfill-corejs2: 0.4.13(@babel/core@7.28.4)
babel-plugin-polyfill-corejs3: 0.11.1(@babel/core@7.28.4)
babel-plugin-polyfill-regenerator: 0.6.4(@babel/core@7.28.4)
semver: 6.3.1
transitivePeerDependencies:
- supports-color
@@ -14599,10 +14608,10 @@ snapshots:
'@lobehub/emojilib@1.0.0': {}
'@lobehub/fluent-emoji@2.0.0(@babel/core@7.28.5)(@types/react@19.2.3)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
'@lobehub/fluent-emoji@2.0.0(@babel/core@7.28.4)(@types/react@19.2.3)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
dependencies:
'@lobehub/emojilib': 1.0.0
'@lobehub/ui': 2.7.3(@babel/core@7.28.5)(@types/react@19.2.3)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@lobehub/ui': 2.7.3(@babel/core@7.28.4)(@types/react@19.2.3)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
antd: 5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
antd-style: 3.7.1(@types/react@19.2.3)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
emoji-regex: 10.4.0
@@ -14619,9 +14628,9 @@ snapshots:
- framer-motion
- supports-color
'@lobehub/icons@2.7.0(@babel/core@7.28.5)(@types/react@19.2.3)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
'@lobehub/icons@2.7.0(@babel/core@7.28.4)(@types/react@19.2.3)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
dependencies:
'@lobehub/ui': 2.7.3(@babel/core@7.28.5)(@types/react@19.2.3)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@lobehub/ui': 2.7.3(@babel/core@7.28.4)(@types/react@19.2.3)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
antd: 5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
antd-style: 3.7.1(@types/react@19.2.3)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
lucide-react: 0.469.0(react@19.2.0)
@@ -14636,7 +14645,7 @@ snapshots:
- framer-motion
- supports-color
'@lobehub/ui@2.7.3(@babel/core@7.28.5)(@types/react@19.2.3)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
'@lobehub/ui@2.7.3(@babel/core@7.28.4)(@types/react@19.2.3)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
dependencies:
'@ant-design/cssinjs': 1.23.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@dnd-kit/core': 6.3.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
@@ -14647,8 +14656,8 @@ snapshots:
'@emoji-mart/react': 1.1.1(emoji-mart@5.6.0)(react@19.2.0)
'@floating-ui/react': 0.27.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@giscus/react': 3.1.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@lobehub/fluent-emoji': 2.0.0(@babel/core@7.28.5)(@types/react@19.2.3)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@lobehub/icons': 2.7.0(@babel/core@7.28.5)(@types/react@19.2.3)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@lobehub/fluent-emoji': 2.0.0(@babel/core@7.28.4)(@types/react@19.2.3)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@lobehub/icons': 2.7.0(@babel/core@7.28.4)(@types/react@19.2.3)(acorn@8.15.0)(antd@5.26.2(luxon@3.7.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@mdx-js/mdx': 3.1.0(acorn@8.15.0)
'@mdx-js/react': 3.1.1(@types/react@19.2.3)(react@19.2.0)
'@radix-ui/react-slot': 1.2.4(@types/react@19.2.3)(react@19.2.0)
@@ -14678,7 +14687,7 @@ snapshots:
rc-menu: 9.16.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
re-resizable: 6.11.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
react: 19.2.0
react-avatar-editor: 13.0.2(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
react-avatar-editor: 13.0.2(@babel/core@7.28.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
react-dom: 19.2.0(react@19.2.0)
react-error-boundary: 5.0.0(react@19.2.0)
react-hotkeys-hook: 5.1.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
@@ -18149,11 +18158,11 @@ snapshots:
cosmiconfig: 7.1.0
resolve: 1.22.11
babel-plugin-polyfill-corejs2@0.4.13(@babel/core@7.28.5):
babel-plugin-polyfill-corejs2@0.4.13(@babel/core@7.28.4):
dependencies:
'@babel/compat-data': 7.28.0
'@babel/core': 7.28.5
'@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.28.5)
'@babel/core': 7.28.4
'@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.28.4)
semver: 6.3.1
transitivePeerDependencies:
- supports-color
@@ -18167,10 +18176,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
babel-plugin-polyfill-corejs3@0.11.1(@babel/core@7.28.5):
babel-plugin-polyfill-corejs3@0.11.1(@babel/core@7.28.4):
dependencies:
'@babel/core': 7.28.5
'@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.28.5)
'@babel/core': 7.28.4
'@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.28.4)
core-js-compat: 3.46.0
transitivePeerDependencies:
- supports-color
@@ -18183,10 +18192,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
babel-plugin-polyfill-regenerator@0.6.4(@babel/core@7.28.5):
babel-plugin-polyfill-regenerator@0.6.4(@babel/core@7.28.4):
dependencies:
'@babel/core': 7.28.5
'@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.28.5)
'@babel/core': 7.28.4
'@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.28.4)
transitivePeerDependencies:
- supports-color
@@ -18228,7 +18237,7 @@ snapshots:
nanostores: 1.0.1
zod: 4.1.12
optionalDependencies:
next: 16.0.1(@babel/core@7.28.5)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
next: 16.0.1(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
react: 19.2.0
react-dom: 19.2.0(react@19.2.0)
@@ -20770,6 +20779,13 @@ snapshots:
jose@6.1.0: {}
jotai@2.15.1(@babel/core@7.28.4)(@babel/template@7.27.2)(@types/react@19.2.3)(react@19.2.0):
optionalDependencies:
'@babel/core': 7.28.4
'@babel/template': 7.27.2
'@types/react': 19.2.3
react: 19.2.0
jotai@2.15.1(@babel/core@7.28.5)(@babel/template@7.27.2)(@types/react@19.2.3)(react@19.2.0):
optionalDependencies:
'@babel/core': 7.28.5
@@ -21823,7 +21839,32 @@ snapshots:
react: 19.2.0
react-dom: 19.2.0(react@19.2.0)
next@16.0.1(@babel/core@7.28.5)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
next@16.0.1(@babel/core@7.28.4)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
dependencies:
'@next/env': 16.0.1
'@swc/helpers': 0.5.15
caniuse-lite: 1.0.30001752
postcss: 8.4.31
react: 19.2.0
react-dom: 19.2.0(react@19.2.0)
styled-jsx: 5.1.6(@babel/core@7.28.4)(react@19.2.0)
optionalDependencies:
'@next/swc-darwin-arm64': 16.0.1
'@next/swc-darwin-x64': 16.0.1
'@next/swc-linux-arm64-gnu': 16.0.1
'@next/swc-linux-arm64-musl': 16.0.1
'@next/swc-linux-x64-gnu': 16.0.1
'@next/swc-linux-x64-musl': 16.0.1
'@next/swc-win32-arm64-msvc': 16.0.1
'@next/swc-win32-x64-msvc': 16.0.1
babel-plugin-react-compiler: 19.1.0-rc.3
sharp: 0.34.5
transitivePeerDependencies:
- '@babel/core'
- babel-plugin-macros
optional: true
next@16.0.1(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
dependencies:
'@next/env': 16.0.1
'@swc/helpers': 0.5.15
@@ -21841,7 +21882,6 @@ snapshots:
'@next/swc-linux-x64-musl': 16.0.1
'@next/swc-win32-arm64-msvc': 16.0.1
'@next/swc-win32-x64-msvc': 16.0.1
babel-plugin-react-compiler: 19.1.0-rc.3
sharp: 0.34.5
transitivePeerDependencies:
- '@babel/core'
@@ -22763,9 +22803,9 @@ snapshots:
react: 19.2.0
react-dom: 19.2.0(react@19.2.0)
react-avatar-editor@13.0.2(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
react-avatar-editor@13.0.2(@babel/core@7.28.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
dependencies:
'@babel/plugin-transform-runtime': 7.27.4(@babel/core@7.28.5)
'@babel/plugin-transform-runtime': 7.27.4(@babel/core@7.28.4)
'@babel/runtime': 7.28.4
prop-types: 15.8.1
react: 19.2.0
@@ -22889,6 +22929,24 @@ snapshots:
transitivePeerDependencies:
- supports-color
react-markdown@9.1.0(@types/react@19.2.3)(react@19.2.0):
dependencies:
'@types/hast': 3.0.4
'@types/mdast': 4.0.4
'@types/react': 19.2.3
devlop: 1.1.0
hast-util-to-jsx-runtime: 2.3.6
html-url-attributes: 3.0.1
mdast-util-to-hast: 13.2.0
react: 19.2.0
remark-parse: 11.0.0
remark-rehype: 11.1.2
unified: 11.0.5
unist-util-visit: 5.0.0
vfile: 6.0.3
transitivePeerDependencies:
- supports-color
react-merge-refs@3.0.2(react@19.2.0):
optionalDependencies:
react: 19.2.0
@@ -22946,7 +23004,7 @@ snapshots:
optionalDependencies:
react-dom: 19.2.0(react@19.2.0)
react-scan@0.4.3(@types/react@19.2.3)(next@16.0.1(@babel/core@7.28.5)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react-router-dom@6.30.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-router@7.9.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)(rollup@2.79.2):
react-scan@0.4.3(@types/react@19.2.3)(next@16.0.1(@babel/core@7.28.4)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react-router-dom@6.30.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-router@7.9.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)(rollup@2.79.2):
dependencies:
'@babel/core': 7.28.4
'@babel/generator': 7.28.3
@@ -22968,7 +23026,7 @@ snapshots:
react-dom: 19.2.0(react@19.2.0)
tsx: 4.20.6
optionalDependencies:
next: 16.0.1(@babel/core@7.28.5)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
next: 16.0.1(@babel/core@7.28.4)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
react-router: 7.9.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
react-router-dom: 6.30.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
unplugin: 2.1.0
@@ -22999,7 +23057,7 @@ snapshots:
react-dom: 19.2.0(react@19.2.0)
tsx: 4.20.6
optionalDependencies:
next: 16.0.1(@babel/core@7.28.5)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
next: 16.0.1(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
react-router: 7.9.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
react-router-dom: 6.30.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
unplugin: 2.1.0
@@ -23868,6 +23926,14 @@ snapshots:
dependencies:
inline-style-parser: 0.2.4
styled-jsx@5.1.6(@babel/core@7.28.4)(react@19.2.0):
dependencies:
client-only: 0.0.1
react: 19.2.0
optionalDependencies:
'@babel/core': 7.28.4
optional: true
styled-jsx@5.1.6(@babel/core@7.28.5)(react@19.2.0):
dependencies:
client-only: 0.0.1