feat: SEO improvements, OG image endpoint, favicon, and metadata (#104)

- Add dynamic OG image generation endpoint (/og) adapted from monorepo
- Replace favicon with monorepo version, remove unused icon.svg
- Add seo.ts config, robots.ts, sitemap.ts for better search indexing
- Add per-page metadata (editor, viewer, settings, profile, community)
- Remove stale package-lock.json that caused ENOWORKSPACES errors with Next.js
- Add OG fonts (Geist Regular, Plus Jakarta Sans SemiBold)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Aymeric Rabot
2026-02-19 13:38:34 -05:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 867681e1da
commit 9a0f83a1ec
20 changed files with 354 additions and 7791 deletions
+2
View File
@@ -2,6 +2,7 @@
# Dependencies # Dependencies
node_modules node_modules
package-lock.json
.pnp .pnp
.pnp.js .pnp.js
@@ -42,3 +43,4 @@ yarn-error.log*
.DS_Store .DS_Store
*.pem *.pem
/.playwright-mcp /.playwright-mcp
og-test
@@ -0,0 +1,18 @@
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Editor Workspace',
description: 'Edit your Pascal projects in a full 3D workspace.',
robots: {
index: false,
follow: false,
},
}
export default function EditorProjectLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return children
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

+43 -2
View File
@@ -4,6 +4,7 @@ import { Analytics } from '@vercel/analytics/react'
import { SpeedInsights } from '@vercel/speed-insights/next' import { SpeedInsights } from '@vercel/speed-insights/next'
import { VercelToolbar } from '@vercel/toolbar/next' import { VercelToolbar } from '@vercel/toolbar/next'
import { UsernameGate } from '@/features/community/components/username-gate' import { UsernameGate } from '@/features/community/components/username-gate'
import { siteConfig } from './seo'
import './globals.css' import './globals.css'
const geistSans = localFont({ const geistSans = localFont({
@@ -16,8 +17,48 @@ const geistMono = localFont({
}) })
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'Pascal Editor', metadataBase: new URL(siteConfig.url),
description: 'Open-source 3D building editor', title: {
default: siteConfig.name,
template: '%s | Pascal Editor',
},
description: siteConfig.description,
applicationName: siteConfig.name,
keywords: [...siteConfig.keywords],
authors: [{ name: 'Pascal', url: 'https://pascal.app' }],
creator: 'Pascal',
publisher: 'Pascal',
alternates: {
canonical: '/',
},
icons: [{ rel: 'icon', url: '/favicon.ico' }],
openGraph: {
title: siteConfig.name,
description: siteConfig.description,
url: siteConfig.url,
siteName: siteConfig.name,
images: [{ url: siteConfig.ogImage, alt: 'Pascal Editor' }],
locale: 'en_US',
type: 'website',
},
twitter: {
card: 'summary_large_image',
title: siteConfig.name,
description: siteConfig.description,
creator: siteConfig.twitterHandle,
images: [siteConfig.ogImage],
},
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
'max-image-preview': 'large',
'max-snippet': -1,
'max-video-preview': -1,
},
},
} }
export default function RootLayout({ export default function RootLayout({
+163
View File
@@ -0,0 +1,163 @@
import { readFile } from 'node:fs/promises'
import { join } from 'node:path'
import { ImageResponse } from 'next/og'
import * as z from 'zod'
import { siteConfig } from '@/app/seo'
const ogImageSchema = z.object({
title: z.string().default(''),
description: z.string().default(''),
theme: z.enum(['light', 'dark']).default('dark'),
})
let sansFont: ArrayBuffer | null = null
let jakartaFont: ArrayBuffer | null = null
async function loadFonts() {
if (!(sansFont && jakartaFont)) {
const fontDir = join(process.cwd(), 'public', 'fonts')
const [sans, jakarta] = await Promise.all([
readFile(join(fontDir, 'geist-regular.ttf')),
readFile(join(fontDir, 'PlusJakartaSans-SemiBold.ttf')),
])
sansFont = sans.buffer.slice(sans.byteOffset, sans.byteOffset + sans.byteLength)
jakartaFont = jakarta.buffer.slice(jakarta.byteOffset, jakarta.byteOffset + jakarta.byteLength)
}
return { sans: sansFont, jakarta: jakartaFont }
}
function PascalLogo({ color = '#fff', size = 100 }: { color?: string; size?: number }) {
return (
<svg fill="none" height={size} style={{ display: 'flex' }} viewBox="0 0 100 100" width={size}>
<rect fill={color} height="40" width="20" y="60" />
<rect fill={color} height="40" width="20" x="40" y="30" />
<rect fill={color} height="40" width="20" x="80" />
</svg>
)
}
export async function GET(req: Request) {
try {
const url = new URL(req.url)
const values = ogImageSchema.parse(Object.fromEntries(url.searchParams))
const heading =
values.title.length > 140 ? `${values.title.substring(0, 140)}...` : values.title
const { theme } = values
const paint = theme === 'dark' ? '#fff' : '#000'
const logoColor = theme === 'dark' ? 'rgba(255, 255, 255, 0.9)' : 'rgba(0, 0, 0, 0.9)'
const fontSize = heading.length > 100 ? '70px' : '100px'
const showLargeLogo = !(values.title || values.description)
return new ImageResponse(
<div
style={{
color: paint,
background:
theme === 'dark'
? 'linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 50%, #0f0f0f 100%)'
: 'white',
}}
tw="flex relative flex-col w-full h-full items-start bg-cover"
>
{showLargeLogo ? (
<div tw="flex flex-col flex-1 py-10 px-12 h-full justify-center">
<div style={{ display: 'flex', marginBottom: 24 }}>
<PascalLogo color={logoColor} size={80} />
</div>
<div
style={{
fontFamily: 'jakarta-semibold',
fontWeight: 'bolder',
fontSize: '72px',
letterSpacing: '-0.02em',
color: paint,
display: 'flex',
}}
>
Pascal Editor
</div>
<div
style={{
fontFamily: 'sans-regular',
fontWeight: 'normal',
fontSize: '28px',
letterSpacing: '0.05em',
color: 'rgba(255, 255, 255, 0.6)',
marginTop: 16,
display: 'flex',
}}
>
{siteConfig.description}
</div>
</div>
) : (
<div tw="flex flex-col flex-1 py-10 px-12 h-full">
<div style={{ display: 'flex', marginBottom: 24 }}>
<PascalLogo color={logoColor} size={40} />
</div>
<div
style={{
fontFamily: 'jakarta-semibold',
fontWeight: 'bolder',
marginLeft: '-3px',
fontSize,
letterSpacing: '.05rem',
}}
tw="flex leading-[1.1] text-[80px] tracking-tighter font-sans mb-4"
>
{heading}
</div>
<div
style={{
fontFamily: 'sans-regular',
fontWeight: 'normal',
letterSpacing: '.1rem',
}}
tw="flex flex-1 text-[50px] tracking-tight font-sans"
>
{values.description}
</div>
</div>
)}
{!showLargeLogo && (
<div
style={{
fontFamily: 'sans-regular',
fontWeight: 'normal',
letterSpacing: '.1rem',
}}
tw="w-full pt-24 p-10 text-[30px] tracking-tight font-sans text-right w-full"
>
editor.pascal.app
</div>
)}
</div>,
{
width: 1200,
height: 630,
fonts: await loadFonts().then(({ sans, jakarta }) => [
{
name: 'sans-regular',
data: sans,
style: 'normal' as const,
weight: 400 as const,
},
{
name: 'jakarta-semibold',
data: jakarta,
style: 'normal' as const,
weight: 600 as const,
},
]),
},
)
} catch (error) {
console.log('error', error)
return new Response('Failed to generate image', {
status: 500,
})
}
}
+7
View File
@@ -1,5 +1,12 @@
import type { Metadata } from 'next'
import CommunityHub from '@/features/community/components/community-hub' import CommunityHub from '@/features/community/components/community-hub'
export const metadata: Metadata = {
title: 'Community Projects',
description:
'Create and share 3D home projects with Pascal Editor, the open-source building editor.',
}
export default function Home() { export default function Home() {
return <CommunityHub /> return <CommunityHub />
} }
+16
View File
@@ -0,0 +1,16 @@
import type { MetadataRoute } from 'next'
import { siteConfig } from './seo'
export default function robots(): MetadataRoute.Robots {
return {
rules: [
{
userAgent: '*',
allow: ['/', '/viewer/', '/u/'],
disallow: ['/api/', '/editor/', '/settings', '/_next/'],
},
],
sitemap: `${siteConfig.url}/sitemap.xml`,
host: siteConfig.url,
}
}
+22
View File
@@ -0,0 +1,22 @@
import { BASE_URL } from '@/lib/utils'
export const siteConfig = {
name: 'Pascal Editor',
description:
'Pascal Editor is an open-source 3D building editor for designing, editing, and sharing home projects.',
url: BASE_URL,
website: 'editor.pascal.app',
ogImage: '/og',
keywords: [
'Pascal Editor',
'open-source 3D editor',
'3D building editor',
'home design software',
'architecture editor',
'collaborative design',
],
twitterHandle: '@pascal_app',
links: {
github: 'https://github.com/pascalorg/editor',
},
} as const
+10
View File
@@ -1,10 +1,20 @@
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
import type { Metadata } from 'next'
import { redirect } from 'next/navigation' import { redirect } from 'next/navigation'
import { getSession } from '@/features/community/lib/auth/server' import { getSession } from '@/features/community/lib/auth/server'
import { getUserProfile, getConnectedAccounts } from '@/features/community/lib/auth/actions' import { getUserProfile, getConnectedAccounts } from '@/features/community/lib/auth/actions'
import { SettingsPage } from '@/features/community/components/settings-page' import { SettingsPage } from '@/features/community/components/settings-page'
export const metadata: Metadata = {
title: 'Account Settings',
description: 'Manage your Pascal Editor account profile and connected providers.',
robots: {
index: false,
follow: false,
},
}
export default async function Settings() { export default async function Settings() {
const session = await getSession() const session = await getSession()
if (!session?.user) { if (!session?.user) {
+22
View File
@@ -0,0 +1,22 @@
import type { MetadataRoute } from 'next'
import { siteConfig } from './seo'
export default function sitemap(): MetadataRoute.Sitemap {
const currentDate = new Date()
const baseUrl = siteConfig.url
return [
{
url: baseUrl,
lastModified: currentDate,
changeFrequency: 'daily',
priority: 1,
},
{
url: `${baseUrl}/viewer/demo_1`,
lastModified: currentDate,
changeFrequency: 'weekly',
priority: 0.7,
},
]
}
+17
View File
@@ -1,10 +1,27 @@
export const dynamic = 'force-dynamic' export const dynamic = 'force-dynamic'
import type { Metadata } from 'next'
import { notFound } from 'next/navigation' import { notFound } from 'next/navigation'
import { getPublicProfile } from '@/features/community/lib/auth/actions' import { getPublicProfile } from '@/features/community/lib/auth/actions'
import { getPublicProjectsByUserId } from '@/features/community/lib/projects/actions' import { getPublicProjectsByUserId } from '@/features/community/lib/projects/actions'
import { PublicProfilePage } from '@/features/community/components/public-profile-page' import { PublicProfilePage } from '@/features/community/components/public-profile-page'
export async function generateMetadata({
params,
}: {
params: Promise<{ username: string }>
}): Promise<Metadata> {
const { username } = await params
return {
title: `${username}'s Projects`,
description: `Public projects shared by @${username} on Pascal Editor.`,
alternates: {
canonical: `/u/${encodeURIComponent(username)}`,
},
}
}
export default async function ProfilePage({ export default async function ProfilePage({
params, params,
}: { }: {
+14
View File
@@ -0,0 +1,14 @@
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Project Viewer',
description: 'View and share 3D projects built with Pascal Editor.',
}
export default function ViewerLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return children
}
@@ -36,7 +36,7 @@ export function IconRail({
className, className,
)} )}
> >
{/* Pascal Logo - Link to Hub */} {/* Pascal logo - link to the home page */}
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Link <Link
@@ -52,7 +52,7 @@ export function IconRail({
/> />
</Link> </Link>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side="right">Back to Hub</TooltipContent> <TooltipContent side="right">Back to Pascal Editor</TooltipContent>
</Tooltip> </Tooltip>
{/* Divider */} {/* Divider */}
@@ -118,18 +118,19 @@ export default function CommunityHub() {
height={64} height={64}
className="h-5 w-5" className="h-5 w-5"
/> />
<h1 className="text-2xl font-bold">Hub</h1> <h1 className="text-2xl font-bold">Pascal Editor</h1>
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<a <a
href="https://github.com/pascalorg/editor" href="https://github.com/pascalorg/editor"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:text-foreground" className="flex items-center gap-2 rounded-lg border border-border px-3 py-2 text-muted-foreground transition-colors hover:border-foreground/20 hover:text-foreground"
> >
<svg className="h-5 w-5" viewBox="0 0 24 24" fill="currentColor"> <svg className="h-4 w-4" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" /> <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
</svg> </svg>
<span className="hidden sm:inline text-sm font-medium">Open Source</span>
</a> </a>
{!isAuthenticated ? ( {!isAuthenticated ? (
<button <button
@@ -40,7 +40,7 @@ export function PublicProfilePage({ profile, projects }: PublicProfilePageProps)
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
{/* Header — same layout as the Hub */} {/* Header — same layout as the community home */}
<header className="border-b border-border bg-background/95 backdrop-blur sticky top-0 z-10"> <header className="border-b border-border bg-background/95 backdrop-blur sticky top-0 z-10">
<div className="container mx-auto px-6 py-4"> <div className="container mx-auto px-6 py-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
@@ -52,17 +52,18 @@ export function PublicProfilePage({ profile, projects }: PublicProfilePageProps)
height={64} height={64}
className="h-5 w-5" className="h-5 w-5"
/> />
<span className="text-2xl font-bold">Hub</span> <span className="text-2xl font-bold">Pascal Editor</span>
</Link> </Link>
<a <a
href="https://github.com/pascalorg/editor" href="https://github.com/pascalorg/editor"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:text-foreground" className="flex items-center gap-2 rounded-lg border border-border px-3 py-2 text-muted-foreground transition-colors hover:border-foreground/20 hover:text-foreground"
> >
<svg className="h-5 w-5" viewBox="0 0 24 24" fill="currentColor"> <svg className="h-4 w-4" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" /> <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
</svg> </svg>
<span className="hidden sm:inline text-sm font-medium">Open Source</span>
</a> </a>
</div> </div>
</div> </div>
Binary file not shown.
+8 -8
View File
@@ -15,7 +15,7 @@
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^2.4.2", "@biomejs/biome": "^2.4.2",
"supabase": "2.75.3", "supabase": "2.75.3",
"turbo": "^2.8.9", "turbo": "^2.8.10",
"typescript": "5.9.2", "typescript": "5.9.2",
"ultracite": "^7.2.3", "ultracite": "^7.2.3",
}, },
@@ -1487,19 +1487,19 @@
"tunnel-rat": ["tunnel-rat@0.1.2", "", { "dependencies": { "zustand": "^4.3.2" } }, "sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ=="], "tunnel-rat": ["tunnel-rat@0.1.2", "", { "dependencies": { "zustand": "^4.3.2" } }, "sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ=="],
"turbo": ["turbo@2.8.9", "", { "optionalDependencies": { "turbo-darwin-64": "2.8.9", "turbo-darwin-arm64": "2.8.9", "turbo-linux-64": "2.8.9", "turbo-linux-arm64": "2.8.9", "turbo-windows-64": "2.8.9", "turbo-windows-arm64": "2.8.9" }, "bin": { "turbo": "bin/turbo" } }, "sha512-G+Mq8VVQAlpz/0HTsxiNNk/xywaHGl+dk1oiBREgOEVCCDjXInDlONWUn5srRnC9s5tdHTFD1bx1N19eR4hI+g=="], "turbo": ["turbo@2.8.10", "", { "optionalDependencies": { "turbo-darwin-64": "2.8.10", "turbo-darwin-arm64": "2.8.10", "turbo-linux-64": "2.8.10", "turbo-linux-arm64": "2.8.10", "turbo-windows-64": "2.8.10", "turbo-windows-arm64": "2.8.10" }, "bin": { "turbo": "bin/turbo" } }, "sha512-OxbzDES66+x7nnKGg2MwBA1ypVsZoDTLHpeaP4giyiHSixbsiTaMyeJqbEyvBdp5Cm28fc+8GG6RdQtic0ijwQ=="],
"turbo-darwin-64": ["turbo-darwin-64@2.8.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-KnCw1ZI9KTnEAhdI9avZrnZ/z4wsM++flMA1w8s8PKOqi5daGpFV36qoPafg4S8TmYMe52JPWEoFr0L+lQ5JIw=="], "turbo-darwin-64": ["turbo-darwin-64@2.8.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-A03fXh+B7S8mL3PbdhTd+0UsaGrhfyPkODvzBDpKRY7bbeac4MDFpJ7I+Slf2oSkCEeSvHKR7Z4U71uKRUfX7g=="],
"turbo-darwin-arm64": ["turbo-darwin-arm64@2.8.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-CbD5Y2NKJKBXTOZ7z7Cc7vGlFPZkYjApA7ri9lH4iFwKV1X7MoZswh9gyRLetXYWImVX1BqIvP8KftulJg/wIA=="], "turbo-darwin-arm64": ["turbo-darwin-arm64@2.8.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-sidzowgWL3s5xCHLeqwC9M3s9M0i16W1nuQF3Mc7fPHpZ+YPohvcbVFBB2uoRRHYZg6yBnwD4gyUHKTeXfwtXA=="],
"turbo-linux-64": ["turbo-linux-64@2.8.9", "", { "os": "linux", "cpu": "x64" }, "sha512-OXC9HdCtsHvyH+5KUoH8ds+p5WU13vdif0OPbsFzZca4cUXMwKA3HWwUuCgQetk0iAE4cscXpi/t8A263n3VTg=="], "turbo-linux-64": ["turbo-linux-64@2.8.10", "", { "os": "linux", "cpu": "x64" }, "sha512-YK9vcpL3TVtqonB021XwgaQhY9hJJbKKUhLv16osxV0HkcQASQWUqR56yMge7puh6nxU67rQlTq1b7ksR1T3KA=="],
"turbo-linux-arm64": ["turbo-linux-arm64@2.8.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-yI5n8jNXiFA6+CxnXG0gO7h5ZF1+19K8uO3/kXPQmyl37AdiA7ehKJQOvf9OPAnmkGDHcF2HSCPltabERNRmug=="], "turbo-linux-arm64": ["turbo-linux-arm64@2.8.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-3+j2tL0sG95iBJTm+6J8/45JsETQABPqtFyYjVjBbi6eVGdtNTiBmHNKrbvXRlQ3ZbUG75bKLaSSDHSEEN+btQ=="],
"turbo-windows-64": ["turbo-windows-64@2.8.9", "", { "os": "win32", "cpu": "x64" }, "sha512-/OztzeGftJAg258M/9vK2ZCkUKUzqrWXJIikiD2pm8TlqHcIYUmepDbyZSDfOiUjMy6NzrLFahpNLnY7b5vNgg=="], "turbo-windows-64": ["turbo-windows-64@2.8.10", "", { "os": "win32", "cpu": "x64" }, "sha512-hdeF5qmVY/NFgiucf8FW0CWJWtyT2QPm5mIsX0W1DXAVzqKVXGq+Zf+dg4EUngAFKjDzoBeN6ec2Fhajwfztkw=="],
"turbo-windows-arm64": ["turbo-windows-arm64@2.8.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-xZ2VTwVTjIqpFZKN4UBxDHCPM3oJ2J5cpRzCBSmRpJ/Pn33wpiYjs+9FB2E03svKaD04/lSSLlEUej0UYsugfg=="], "turbo-windows-arm64": ["turbo-windows-arm64@2.8.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-QGdr/Q8LWmj+ITMkSvfiz2glf0d7JG0oXVzGL3jxkGqiBI1zXFj20oqVY0qWi+112LO9SVrYdpHS0E/oGFrMbQ=="],
"tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="],
-7771
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -37,7 +37,7 @@
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^2.4.2", "@biomejs/biome": "^2.4.2",
"supabase": "2.75.3", "supabase": "2.75.3",
"turbo": "^2.8.9", "turbo": "^2.8.10",
"typescript": "5.9.2", "typescript": "5.9.2",
"ultracite": "^7.2.3" "ultracite": "^7.2.3"
}, },