diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..4e54088 --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +ADMIN_USERNAME=admin +ADMIN_PASSWORD=123456 +ADMIN_SESSION_SECRET=change-me-in-production +PORT=3000 diff --git a/.vscode/sftp.json b/.vscode/sftp.json new file mode 100644 index 0000000..d608a6e --- /dev/null +++ b/.vscode/sftp.json @@ -0,0 +1,11 @@ +{ + "name": "My Server", + "host": "10.0.0.158", + "protocol": "sftp", + "port": 22, + "username": "root", + "remotePath": "/root/links", + "uploadOnSave": false, + "useTempFile": false, + "openSsh": false +} diff --git a/README.md b/README.md index 7edad85..d4003ff 100644 --- a/README.md +++ b/README.md @@ -1,89 +1,93 @@ -# Social Links Website +# Social Links (Next.js) -Une application web Node.js élégante pour afficher et partager vos liens de réseaux sociaux personnels. - -![Social Links Website](https://github.com/user-attachments/assets/61f94cae-166c-4dc0-ad17-2412e184d4ad) +Une page de liens personnelle moderne construite avec Next.js (App Router). ## Fonctionnalités -- 🎨 Interface moderne et responsive -- 🔗 Affichage des liens vers vos réseaux sociaux -- 📱 Compatible mobile et desktop -- 🚀 Serveur Express.js léger -- 🌟 Design avec gradient et animations -- 📊 API REST pour récupérer les liens (extensibilité future) +- 🎨 UI moderne et responsive +- 🔎 Recherche instantanée dans les liens +- 🏷️ Filtres par catégories +- 🌗 Mode clair/sombre (persisté en local) +- 🔐 Espace admin protégé (analytics privées) +- ➕ Ajout et suppression de liens depuis le menu admin ## Installation -1. Clonez le repository : -```bash -git clone https://github.com/arthur-pbty/links.git -cd links -``` - -2. Installez les dépendances : ```bash npm install ``` -3. Personnalisez vos liens sociaux dans `app.js` (voir section Configuration) +## Démarrage -4. Démarrez l'application : ```bash -npm start +npm run dev ``` -5. Ouvrez votre navigateur sur `http://localhost:3000` +Puis ouvre http://localhost:3000 -## Configuration +## Scripts -Pour personnaliser vos liens sociaux, modifiez le tableau `socialLinks` dans le fichier `app.js` : +- `npm run dev` : développement +- `npm run build` : build de production +- `npm start` : démarrage en production -```javascript -const socialLinks = [ - { - name: 'GitHub', - url: 'https://github.com/votre-username', - icon: '🐙', - description: 'Mon profil GitHub' - }, - // Ajoutez vos autres liens ici... -]; +## Variables critiques (.env) + +Copie `.env.example` vers `.env` et ajuste les valeurs : + +```bash +cp .env.example .env ``` -### Structure d'un lien +Variables requises : -- `name` : Nom du réseau social -- `url` : URL vers votre profil -- `icon` : Emoji ou icône à afficher -- `description` : Description courte du lien +- `ADMIN_USERNAME` +- `ADMIN_PASSWORD` +- `ADMIN_SESSION_SECRET` +- `PORT` -## Scripts disponibles +## Configuration des données -- `npm start` : Démarre le serveur en mode production -- `npm run dev` : Démarre le serveur en mode développement +Modifie le fichier `config.js` : -## API +- `profile` : nom, bio, avatar +- `socialLinks` : liste de liens affichés -L'application expose également une API REST : +Chaque lien peut contenir : -- `GET /` : Page principale avec l'interface web -- `GET /api/links` : Récupère la liste des liens en JSON +- `name` +- `url` +- `icon` (fichier dans `public/`) +- `description` +- `category` -## Technologies utilisées +## Admin -- Node.js -- Express.js -- HTML5/CSS3 -- Design responsive +- URL admin : http://localhost:3000/admin +- Identifiant : admin +- Mot de passe : 123456 -## Déploiement +Dans le menu admin, vous pouvez : -Pour déployer sur des plateformes comme Heroku, Vercel, ou Railway : +- consulter les analytics (clics par lien) +- ajouter un lien +- supprimer un lien -1. Assurez-vous que la variable d'environnement `PORT` est configurée -2. Le serveur écoutera automatiquement sur `process.env.PORT || 3000` +## Docker Compose + +Le `docker-compose.yml` lance maintenant : + +1. `npm install` +2. `npm run build` +3. `npm start` + +Test prod Docker : + +```bash +docker compose up --build -d +docker compose ps +``` ## Licence -MIT - Voir le fichier [LICENSE](LICENSE) pour plus de détails. \ No newline at end of file +MIT - Voir [LICENSE](LICENSE) \ No newline at end of file diff --git a/app.js b/app.js deleted file mode 100644 index b0621a8..0000000 --- a/app.js +++ /dev/null @@ -1,70 +0,0 @@ -const express = require('express'); -const path = require('path'); -const config = require('./config'); - -const app = express(); -const PORT = config.server.port; - -// Configuration depuis le fichier config.js -const { profile, socialLinks } = config; - -// Middleware pour servir les fichiers statiques -app.use(express.static(path.join(__dirname, 'public'))); - -// Route principale -app.get('/', (req, res) => { - const html = ` - - - - - - Mes Liens - Arthur - - - -
-
-
- Avatar -
-

${profile.name}

-

${profile.bio}

-
- - - - -
- - - `; - - res.send(html); -}); - -// Route API pour obtenir les liens (optionnel, pour future extensibilité) -app.get('/api/links', (req, res) => { - res.json({ links: socialLinks }); -}); - -// Démarrage du serveur -app.listen(PORT, () => { - console.log(`🚀 Serveur démarré sur http://localhost:${PORT}`); - console.log(`📱 Site de liens sociaux prêt !`); -}); \ No newline at end of file diff --git a/app/admin/page.js b/app/admin/page.js new file mode 100644 index 0000000..a6cf4c5 --- /dev/null +++ b/app/admin/page.js @@ -0,0 +1,10 @@ +import AdminPanel from '../../components/AdminPanel'; + +export const metadata = { + title: 'Admin - Mes Liens', + description: 'Gestion des liens et analytics privées.' +}; + +export default function AdminPage() { + return ; +} diff --git a/app/api/admin/login/route.js b/app/api/admin/login/route.js new file mode 100644 index 0000000..785da77 --- /dev/null +++ b/app/api/admin/login/route.js @@ -0,0 +1,29 @@ +import { NextResponse } from 'next/server'; +import { + ADMIN_COOKIE, + createAdminSessionToken, + isValidAdminCredentials +} from '../../../../lib/auth'; + +export async function POST(request) { + const body = await request.json().catch(() => ({})); + const username = body.username || ''; + const password = body.password || ''; + + if (!isValidAdminCredentials(username, password)) { + return NextResponse.json({ error: 'Identifiants invalides.' }, { status: 401 }); + } + + const response = NextResponse.json({ ok: true }); + response.cookies.set({ + name: ADMIN_COOKIE, + value: createAdminSessionToken(), + httpOnly: true, + sameSite: 'strict', + secure: process.env.NODE_ENV === 'production', + path: '/', + maxAge: 60 * 60 * 12 + }); + + return response; +} diff --git a/app/api/admin/logout/route.js b/app/api/admin/logout/route.js new file mode 100644 index 0000000..4409be8 --- /dev/null +++ b/app/api/admin/logout/route.js @@ -0,0 +1,15 @@ +import { NextResponse } from 'next/server'; +import { ADMIN_COOKIE } from '../../../../lib/auth'; + +export async function POST() { + const response = NextResponse.json({ ok: true }); + response.cookies.set({ + name: ADMIN_COOKIE, + value: '', + path: '/', + httpOnly: true, + sameSite: 'strict', + maxAge: 0 + }); + return response; +} diff --git a/app/api/admin/session/route.js b/app/api/admin/session/route.js new file mode 100644 index 0000000..95ca8e9 --- /dev/null +++ b/app/api/admin/session/route.js @@ -0,0 +1,6 @@ +import { NextResponse } from 'next/server'; +import { isAdminRequest } from '../../../../lib/auth'; + +export async function GET(request) { + return NextResponse.json({ authenticated: isAdminRequest(request) }); +} diff --git a/app/api/analytics/click/route.js b/app/api/analytics/click/route.js new file mode 100644 index 0000000..29eba8a --- /dev/null +++ b/app/api/analytics/click/route.js @@ -0,0 +1,14 @@ +import { NextResponse } from 'next/server'; +import { incrementClick } from '../../../../lib/storage'; + +export async function POST(request) { + const body = await request.json().catch(() => ({})); + const linkId = body.linkId; + + if (!linkId) { + return NextResponse.json({ error: 'linkId requis.' }, { status: 400 }); + } + + await incrementClick(linkId); + return NextResponse.json({ ok: true }); +} diff --git a/app/api/analytics/route.js b/app/api/analytics/route.js new file mode 100644 index 0000000..06be789 --- /dev/null +++ b/app/api/analytics/route.js @@ -0,0 +1,12 @@ +import { NextResponse } from 'next/server'; +import { getAnalyticsForLinks } from '../../../lib/storage'; +import { isAdminRequest } from '../../../lib/auth'; + +export async function GET(request) { + if (!isAdminRequest(request)) { + return NextResponse.json({ error: 'Non autorisé.' }, { status: 401 }); + } + + const analytics = await getAnalyticsForLinks(); + return NextResponse.json({ analytics }); +} diff --git a/app/api/links/[id]/route.js b/app/api/links/[id]/route.js new file mode 100644 index 0000000..b17556b --- /dev/null +++ b/app/api/links/[id]/route.js @@ -0,0 +1,17 @@ +import { NextResponse } from 'next/server'; +import { removeLink } from '../../../../lib/storage'; +import { isAdminRequest } from '../../../../lib/auth'; + +export async function DELETE(request, { params }) { + if (!isAdminRequest(request)) { + return NextResponse.json({ error: 'Non autorisé.' }, { status: 401 }); + } + + const { id } = await params; + const deleted = await removeLink(id); + if (!deleted) { + return NextResponse.json({ error: 'Lien introuvable.' }, { status: 404 }); + } + + return NextResponse.json({ ok: true }); +} diff --git a/app/api/links/route.js b/app/api/links/route.js new file mode 100644 index 0000000..e1aa217 --- /dev/null +++ b/app/api/links/route.js @@ -0,0 +1,25 @@ +import { NextResponse } from 'next/server'; +import { addLink, getLinks } from '../../../lib/storage'; +import { isAdminRequest } from '../../../lib/auth'; + +export async function GET() { + const links = await getLinks(); + return NextResponse.json({ links }); +} + +export async function POST(request) { + if (!isAdminRequest(request)) { + return NextResponse.json({ error: 'Non autorisé.' }, { status: 401 }); + } + + const payload = await request.json().catch(() => ({})); + const required = ['name', 'url']; + const missing = required.some((field) => !payload[field]); + + if (missing) { + return NextResponse.json({ error: 'Nom et URL sont obligatoires.' }, { status: 400 }); + } + + const created = await addLink(payload); + return NextResponse.json({ link: created }, { status: 201 }); +} diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..ed3b72b --- /dev/null +++ b/app/globals.css @@ -0,0 +1,282 @@ +:root { + --bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + --surface: rgba(255, 255, 255, 0.95); + --text: #1f2937; + --muted: #6b7280; + --card: #ffffff; + --accent: #4f46e5; + --border: rgba(255, 255, 255, 0.2); + --chip: #eef2ff; +} + +[data-theme='dark'] { + --bg: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); + --surface: rgba(15, 23, 42, 0.92); + --text: #e5e7eb; + --muted: #94a3b8; + --card: rgba(30, 41, 59, 0.9); + --accent: #818cf8; + --border: rgba(148, 163, 184, 0.2); + --chip: rgba(79, 70, 229, 0.2); +} + +* { + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; + min-height: 100%; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; + background: var(--bg); + color: var(--text); +} + +.page { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 24px; +} + +.container { + width: 100%; + max-width: 520px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 24px; + padding: 28px; + box-shadow: 0 16px 48px rgba(0, 0, 0, 0.2); + backdrop-filter: blur(14px); +} + +.header { + display: flex; + justify-content: flex-end; + margin-bottom: 10px; +} + +.theme-toggle { + border: 1px solid var(--border); + background: var(--card); + color: var(--text); + border-radius: 999px; + padding: 8px 12px; + cursor: pointer; +} + +.profile { + text-align: center; + margin-bottom: 22px; +} + +.avatar { + width: 84px; + height: 84px; + border-radius: 50%; + object-fit: cover; + margin-bottom: 10px; + border: 2px solid var(--border); +} + +.profile h1 { + margin: 0; + font-size: 1.9rem; +} + +.bio { + margin-top: 8px; + color: var(--muted); +} + +.controls { + display: grid; + gap: 10px; + margin-bottom: 18px; +} + +.search { + width: 100%; + padding: 12px 14px; + border-radius: 12px; + border: 1px solid var(--border); + background: var(--card); + color: var(--text); +} + +.categories { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.chip { + border: 1px solid transparent; + background: var(--chip); + color: var(--text); + border-radius: 999px; + padding: 8px 12px; + cursor: pointer; +} + +.chip.active { + background: var(--accent); + color: #fff; +} + +.links { + display: flex; + flex-direction: column; + gap: 12px; +} + +.link-card { + display: flex; + align-items: center; + gap: 12px; + text-decoration: none; + color: var(--text); + padding: 14px; + border-radius: 14px; + border: 1px solid var(--border); + background: var(--card); + transition: transform 0.2s ease, border-color 0.2s ease; +} + +.link-card:hover { + transform: translateY(-2px); + border-color: var(--accent); +} + +.icon { + width: 34px; + height: 34px; + object-fit: contain; +} + +.link-info { + flex: 1; +} + +.link-title { + margin: 0; + font-size: 1.02rem; +} + +.link-description { + margin: 3px 0 0; + color: var(--muted); + font-size: 0.9rem; +} + +.link-meta { + text-align: right; + color: var(--muted); + font-size: 0.8rem; +} + +.empty { + text-align: center; + color: var(--muted); + padding: 16px; +} + +footer { + margin-top: 18px; + border-top: 1px solid var(--border); + padding-top: 14px; + text-align: center; + color: var(--muted); + font-size: 0.85rem; +} + +.admin-link { + color: var(--accent); + text-decoration: none; +} + +.admin-link:hover { + text-decoration: underline; +} + +.admin-container { + max-width: 760px; +} + +.admin-head { + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; +} + +.admin-title { + margin: 0; + font-size: 1.8rem; +} + +.admin-subtitle { + margin: 0 0 10px; + font-size: 1.2rem; +} + +.admin-section { + margin-top: 20px; +} + +.admin-form { + display: grid; + gap: 10px; +} + +.admin-list { + display: grid; + gap: 10px; +} + +.admin-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 14px; + padding: 12px; + border: 1px solid var(--border); + border-radius: 12px; + background: var(--card); +} + +.admin-count { + color: var(--muted); + font-weight: 600; +} + +.danger-btn { + border: 1px solid var(--accent); + background: transparent; + color: var(--accent); + border-radius: 8px; + padding: 8px 10px; + cursor: pointer; +} + +.admin-error { + margin-top: 14px; + color: var(--accent); +} + +@media (max-width: 600px) { + .container { + padding: 20px; + border-radius: 18px; + } + + .profile h1 { + font-size: 1.65rem; + } +} diff --git a/app/layout.js b/app/layout.js new file mode 100644 index 0000000..9210cd8 --- /dev/null +++ b/app/layout.js @@ -0,0 +1,14 @@ +import './globals.css'; + +export const metadata = { + title: 'Mes Liens - Arthur', + description: 'Tous mes liens sociaux et projets au même endroit.' +}; + +export default function RootLayout({ children }) { + return ( + + {children} + + ); +} diff --git a/app/page.js b/app/page.js new file mode 100644 index 0000000..1308ecf --- /dev/null +++ b/app/page.js @@ -0,0 +1,6 @@ +import LinksPage from '../components/LinksPage'; +import { profile } from '../config'; + +export default function Page() { + return ; +} diff --git a/components/AdminPanel.js b/components/AdminPanel.js new file mode 100644 index 0000000..15ee103 --- /dev/null +++ b/components/AdminPanel.js @@ -0,0 +1,267 @@ +'use client'; + +import { useEffect, useMemo, useState } from 'react'; + +const initialForm = { + name: '', + url: '', + icon: '/github.png', + description: '', + category: 'Autres' +}; + +export default function AdminPanel() { + const [isAuthenticated, setIsAuthenticated] = useState(false); + const [username, setUsername] = useState('admin'); + const [password, setPassword] = useState(''); + const [links, setLinks] = useState([]); + const [analytics, setAnalytics] = useState([]); + const [form, setForm] = useState(initialForm); + const [error, setError] = useState(''); + const [loading, setLoading] = useState(true); + + async function loadAdminData() { + const [linksResponse, analyticsResponse] = await Promise.all([ + fetch('/api/links'), + fetch('/api/analytics') + ]); + + if (!linksResponse.ok || !analyticsResponse.ok) { + setError('Impossible de charger les données admin.'); + return; + } + + const linksData = await linksResponse.json(); + const analyticsData = await analyticsResponse.json(); + + setLinks(linksData.links || []); + setAnalytics(analyticsData.analytics || []); + setError(''); + } + + useEffect(() => { + async function checkSession() { + const response = await fetch('/api/admin/session'); + const data = await response.json(); + + if (data.authenticated) { + setIsAuthenticated(true); + await loadAdminData(); + } + + setLoading(false); + } + + checkSession(); + }, []); + + async function handleLogin(event) { + event.preventDefault(); + setError(''); + + const response = await fetch('/api/admin/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username, password }) + }); + + if (!response.ok) { + setError('Identifiant ou mot de passe invalide.'); + return; + } + + setIsAuthenticated(true); + setPassword(''); + await loadAdminData(); + } + + async function handleLogout() { + await fetch('/api/admin/logout', { method: 'POST' }); + setIsAuthenticated(false); + setLinks([]); + setAnalytics([]); + } + + async function handleAddLink(event) { + event.preventDefault(); + setError(''); + + const response = await fetch('/api/links', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(form) + }); + + if (!response.ok) { + setError("Impossible d'ajouter le lien."); + return; + } + + setForm(initialForm); + await loadAdminData(); + } + + async function handleDeleteLink(linkId) { + setError(''); + const response = await fetch(`/api/links/${linkId}`, { method: 'DELETE' }); + + if (!response.ok) { + setError('Impossible de supprimer ce lien.'); + return; + } + + setError(''); + await loadAdminData(); + } + + const totalClicks = useMemo( + () => analytics.reduce((sum, item) => sum + (item.clicks || 0), 0), + [analytics] + ); + + if (loading) { + return ( +
+
+

Chargement...

+
+
+ ); + } + + if (!isAuthenticated) { + return ( +
+
+

Admin

+

Accès protégé aux analytics et à la gestion des liens.

+ +
+ setUsername(event.target.value)} + placeholder="Identifiant" + required + /> + setPassword(event.target.value)} + placeholder="Mot de passe" + required + /> + +
+ + {error ?

{error}

: null} +
+
+ ); + } + + return ( +
+
+
+

Menu Admin

+ +
+ +
+

Analytics

+

Total clics: {totalClicks}

+
+ {analytics.map((item) => ( +
+
+ {item.name} +
{item.url}
+
+
{item.clicks} clics
+
+ ))} +
+
+ +
+

Ajouter un lien

+
+ setForm({ ...form, name: event.target.value })} + placeholder="Nom" + required + /> + setForm({ ...form, url: event.target.value })} + placeholder="URL" + required + /> + setForm({ ...form, icon: event.target.value })} + placeholder="Icône (ex: /github.png)" + /> + setForm({ ...form, description: event.target.value })} + placeholder="Description" + /> + setForm({ ...form, category: event.target.value })} + placeholder="Catégorie" + /> + +
+
+ +
+

Supprimer un lien

+
+ {links.map((link) => ( +
+
+ {link.name} +
{link.url}
+
+ +
+ ))} +
+
+ + {error ?

{error}

: null} +
+
+ ); +} diff --git a/components/LinksPage.js b/components/LinksPage.js new file mode 100644 index 0000000..4da3807 --- /dev/null +++ b/components/LinksPage.js @@ -0,0 +1,144 @@ +'use client'; + +import { useEffect, useMemo, useState } from 'react'; + +function normalize(value) { + return (value || '').toLowerCase().trim(); +} + +function getDomain(url) { + try { + return new URL(url).hostname.replace('www.', ''); + } catch { + return ''; + } +} + +export default function LinksPage({ profile, socialLinks }) { + const [query, setQuery] = useState(''); + const [activeCategory, setActiveCategory] = useState('Tous'); + const [theme, setTheme] = useState('light'); + const [links, setLinks] = useState(socialLinks || []); + + useEffect(() => { + const storedTheme = localStorage.getItem('theme') || 'light'; + setTheme(storedTheme); + document.documentElement.setAttribute('data-theme', storedTheme); + + async function loadLinks() { + const response = await fetch('/api/links'); + if (!response.ok) { + return; + } + const data = await response.json(); + setLinks(data.links || []); + } + + loadLinks(); + }, []); + + const categories = useMemo(() => { + const set = new Set((links || []).map((link) => link.category || 'Autres')); + return ['Tous', ...Array.from(set)]; + }, [links]); + + const filteredLinks = useMemo(() => { + return (links || []).filter((link) => { + const matchesCategory = + activeCategory === 'Tous' || (link.category || 'Autres') === activeCategory; + const target = normalize(`${link.name} ${link.description} ${link.url}`); + const matchesSearch = target.includes(normalize(query)); + return matchesCategory && matchesSearch; + }); + }, [links, activeCategory, query]); + + function toggleTheme() { + const nextTheme = theme === 'light' ? 'dark' : 'light'; + setTheme(nextTheme); + localStorage.setItem('theme', nextTheme); + document.documentElement.setAttribute('data-theme', nextTheme); + } + + async function trackClick(linkId) { + await fetch('/api/analytics/click', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ linkId }) + }); + } + + return ( +
+
+
+ +
+ +
+ Avatar +

{profile.name}

+

{profile.bio}

+
+ +
+ setQuery(event.target.value)} + placeholder="Rechercher un lien..." + aria-label="Rechercher un lien" + /> + +
+ {categories.map((category) => ( + + ))} +
+
+ +
+ {filteredLinks.length === 0 ? ( +

Aucun lien ne correspond à ta recherche.

+ ) : ( + filteredLinks.map((link) => ( + trackClick(link.id)} + > + {link.name} +
+

{link.name}

+

{link.description}

+
+
+
{getDomain(link.url)}
+
+
+ )) + )} +
+ + +
+
+ ); +} diff --git a/config.js b/config.js index ced46a9..a8c410e 100644 --- a/config.js +++ b/config.js @@ -1,62 +1,59 @@ -// Configuration pour vos liens de réseaux sociaux -// Modifiez ce fichier pour personnaliser vos liens +// Configuration des données affichées dans l'application Next.js -module.exports = { - // Informations du profil - profile: { - name: 'Arthur', - bio: 'Développeur passionné | Sportif de haut niveau', - avatar: '/avatar.png' // Chemin relatif vers l'image dans public/ +export const profile = { + name: 'Arthur', + bio: 'Développeur passionné | Sportif de haut niveau', + avatar: '/avatar.png' +}; + +export const socialLinks = [ + { + name: 'GitHub', + url: 'https://github.com/arthur-pbty', + icon: '/github.png', + description: 'Mon profil GitHub', + category: 'Développement' }, - - // Liste des liens sociaux - socialLinks: [ - { - name: 'GitHub', - url: 'https://github.com/arthur-pbty', - icon: '/github.png', // Chemin relatif vers l'image dans public/ - description: 'Mon profil GitHub' - }, - { - name: 'Instagram', - url: 'https://www.instagram.com/arthur.pbty/', - icon: '/instagram.png', - description: 'Mes photos et stories' - }, - { - name: 'FFVoile', - url: 'https://www.ffvoile.fr/ffv/sportif/cif/cif_detail.aspx?NoLicence=1443697B', - icon: '/ffvoile.png', - description: 'Mon profil FFVoile' - }, - { - name: 'YouTube', - url: 'https://www.youtube.com/@arthur-pbty', - icon: '/youtube.png', - description: 'Ma chaîne YouTube' - }, - { - name: 'Discord', - url: 'https://discord.gg/MZSseRvCk8', - icon: '/discord.png', - description: 'Rejoignez mon serveur Discord' - }, - { - name: 'TikTok', - url: 'https://www.tiktok.com/@arthur.pbty', - icon: '/tiktok.png', - description: 'Mes vidéos TikTok' - }, - { - name: 'Twitch', - url: 'https://www.twitch.tv/arthur_pbty', - icon: '/twitch.png', - description: 'Mes streams en direct' - } - ], - - // Configuration du serveur - server: { - port: process.env.PORT || 3000 + { + name: 'Instagram', + url: 'https://www.instagram.com/arthur.pbty/', + icon: '/instagram.png', + description: 'Mes photos et stories', + category: 'Réseaux' + }, + { + name: 'FFVoile', + url: 'https://www.ffvoile.fr/ffv/sportif/cif/cif_detail.aspx?NoLicence=1443697B', + icon: '/ffvoile.png', + description: 'Mon profil FFVoile', + category: 'Sport' + }, + { + name: 'YouTube', + url: 'https://www.youtube.com/@arthur-pbty', + icon: '/youtube.png', + description: 'Ma chaîne YouTube', + category: 'Création' + }, + { + name: 'Discord', + url: 'https://discord.gg/MZSseRvCk8', + icon: '/discord.png', + description: 'Rejoignez mon serveur Discord', + category: 'Communauté' + }, + { + name: 'TikTok', + url: 'https://www.tiktok.com/@arthur.pbty', + icon: '/tiktok.png', + description: 'Mes vidéos TikTok', + category: 'Création' + }, + { + name: 'Twitch', + url: 'https://www.twitch.tv/arthur_pbty', + icon: '/twitch.png', + description: 'Mes streams en direct', + category: 'Streaming' } -}; \ No newline at end of file +]; \ No newline at end of file diff --git a/data/analytics.json b/data/analytics.json new file mode 100644 index 0000000..a2140c3 --- /dev/null +++ b/data/analytics.json @@ -0,0 +1,3 @@ +{ + "github-1": 1 +} \ No newline at end of file diff --git a/data/links.json b/data/links.json new file mode 100644 index 0000000..85e229f --- /dev/null +++ b/data/links.json @@ -0,0 +1,58 @@ +[ + { + "id": "github-1", + "name": "GitHub", + "url": "https://github.com/arthur-pbty", + "icon": "/github.png", + "description": "Mon profil GitHub", + "category": "Développement" + }, + { + "id": "instagram-2", + "name": "Instagram", + "url": "https://www.instagram.com/arthur.pbty/", + "icon": "/instagram.png", + "description": "Mes photos et stories", + "category": "Réseaux" + }, + { + "id": "ffvoile-3", + "name": "FFVoile", + "url": "https://www.ffvoile.fr/ffv/sportif/cif/cif_detail.aspx?NoLicence=1443697B", + "icon": "/ffvoile.png", + "description": "Mon profil FFVoile", + "category": "Sport" + }, + { + "id": "youtube-4", + "name": "YouTube", + "url": "https://www.youtube.com/@arthur-pbty", + "icon": "/youtube.png", + "description": "Ma chaîne YouTube", + "category": "Création" + }, + { + "id": "discord-5", + "name": "Discord", + "url": "https://discord.gg/MZSseRvCk8", + "icon": "/discord.png", + "description": "Rejoignez mon serveur Discord", + "category": "Communauté" + }, + { + "id": "tiktok-6", + "name": "TikTok", + "url": "https://www.tiktok.com/@arthur.pbty", + "icon": "/tiktok.png", + "description": "Mes vidéos TikTok", + "category": "Création" + }, + { + "id": "twitch-7", + "name": "Twitch", + "url": "https://www.twitch.tv/arthur_pbty", + "icon": "/twitch.png", + "description": "Mes streams en direct", + "category": "Streaming" + } +] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 442d264..581464b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,6 @@ -version: '3.8' - services: app: - image: node:18 + image: node:20 working_dir: /usr/src/app volumes: - ./:/usr/src/app @@ -10,6 +8,8 @@ services: - "3000:3000" restart: unless-stopped environment: - - NODE_ENV=production - - PORT=3000 - command: sh -c "npm install && npm start" \ No newline at end of file + - PORT=${PORT:-3000} + - ADMIN_USERNAME=${ADMIN_USERNAME} + - ADMIN_PASSWORD=${ADMIN_PASSWORD} + - ADMIN_SESSION_SECRET=${ADMIN_SESSION_SECRET} + command: sh -c "npm install && npm run build && npm start" \ No newline at end of file diff --git a/lib/auth.js b/lib/auth.js new file mode 100644 index 0000000..e40f69d --- /dev/null +++ b/lib/auth.js @@ -0,0 +1,35 @@ +import { createHash } from 'node:crypto'; + +function getRequiredEnv(name) { + const value = process.env[name]; + if (!value) { + throw new Error(`Missing required environment variable: ${name}`); + } + return value; +} + +export const ADMIN_USERNAME = getRequiredEnv('ADMIN_USERNAME'); +export const ADMIN_PASSWORD = getRequiredEnv('ADMIN_PASSWORD'); +export const ADMIN_SESSION_SECRET = getRequiredEnv('ADMIN_SESSION_SECRET'); +export const ADMIN_COOKIE = 'admin_session'; + +function hash(value) { + return createHash('sha256').update(value).digest('hex'); +} + +function expectedToken() { + return hash(`${ADMIN_USERNAME}:${ADMIN_PASSWORD}:${ADMIN_SESSION_SECRET}`); +} + +export function isValidAdminCredentials(username, password) { + return username === ADMIN_USERNAME && password === ADMIN_PASSWORD; +} + +export function createAdminSessionToken() { + return expectedToken(); +} + +export function isAdminRequest(request) { + const token = request.cookies.get(ADMIN_COOKIE)?.value; + return token === expectedToken(); +} diff --git a/lib/storage.js b/lib/storage.js new file mode 100644 index 0000000..0bab1fb --- /dev/null +++ b/lib/storage.js @@ -0,0 +1,121 @@ +import { mkdir, readFile, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { randomUUID } from 'node:crypto'; +import { socialLinks } from '../config'; + +const DATA_DIR = path.join(process.cwd(), 'data'); +const LINKS_FILE = path.join(DATA_DIR, 'links.json'); +const ANALYTICS_FILE = path.join(DATA_DIR, 'analytics.json'); + +function slugify(text) { + return (text || '') + .toLowerCase() + .trim() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, '') + .slice(0, 50); +} + +function withId(link, index) { + return { + id: link.id || `${slugify(link.name)}-${index + 1}`, + name: link.name, + url: link.url, + icon: link.icon, + description: link.description || '', + category: link.category || 'Autres' + }; +} + +async function fileExists(filePath) { + try { + await readFile(filePath, 'utf-8'); + return true; + } catch { + return false; + } +} + +async function readJson(filePath, fallback) { + try { + const content = await readFile(filePath, 'utf-8'); + return JSON.parse(content); + } catch { + return fallback; + } +} + +async function writeJson(filePath, value) { + await writeFile(filePath, JSON.stringify(value, null, 2), 'utf-8'); +} + +export async function ensureDataFiles() { + await mkdir(DATA_DIR, { recursive: true }); + + if (!(await fileExists(LINKS_FILE))) { + const initialLinks = socialLinks.map(withId); + await writeJson(LINKS_FILE, initialLinks); + } + + if (!(await fileExists(ANALYTICS_FILE))) { + await writeJson(ANALYTICS_FILE, {}); + } +} + +export async function getLinks() { + await ensureDataFiles(); + return readJson(LINKS_FILE, []); +} + +export async function addLink(payload) { + await ensureDataFiles(); + const links = await getLinks(); + + const newLink = { + id: randomUUID(), + name: payload.name, + url: payload.url, + icon: payload.icon || '/github.png', + description: payload.description || '', + category: payload.category || 'Autres' + }; + + const updatedLinks = [...links, newLink]; + await writeJson(LINKS_FILE, updatedLinks); + return newLink; +} + +export async function removeLink(linkId) { + await ensureDataFiles(); + const links = await getLinks(); + const nextLinks = links.filter((link) => link.id !== linkId); + + if (nextLinks.length === links.length) { + return false; + } + + await writeJson(LINKS_FILE, nextLinks); + return true; +} + +export async function incrementClick(linkId) { + await ensureDataFiles(); + const analytics = await readJson(ANALYTICS_FILE, {}); + analytics[linkId] = (analytics[linkId] || 0) + 1; + await writeJson(ANALYTICS_FILE, analytics); +} + +export async function getAnalyticsForLinks() { + await ensureDataFiles(); + const [links, analytics] = await Promise.all([ + readJson(LINKS_FILE, []), + readJson(ANALYTICS_FILE, {}) + ]); + + return links.map((link) => ({ + id: link.id, + name: link.name, + url: link.url, + clicks: analytics[link.id] || 0 + })); +} diff --git a/package-lock.json b/package-lock.json index f96e4ea..573f302 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,825 +9,905 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "express": "^4.18.2" + "next": "^16.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" } }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/etag": { + "node_modules/@emnapi/runtime": { "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", "license": "MIT", - "engines": { - "node": ">= 0.6" + "optional": true, + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, + "optional": true, "engines": { - "node": ">= 0.10.0" + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" + "url": "https://opencollective.com/libvips" }, - "engines": { - "node": ">= 0.8" + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" + "url": "https://opencollective.com/libvips" }, - "engines": { - "node": ">= 0.4" + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" } }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" } }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, "dependencies": { - "function-bind": "^1.1.2" + "@emnapi/runtime": "^1.7.0" }, "engines": { - "node": ">= 0.4" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@next/env": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz", + "integrity": "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==", + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.6.tgz", + "integrity": "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==", + "cpu": [ + "arm64" + ], "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.6.tgz", + "integrity": "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.6.tgz", + "integrity": "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.6.tgz", + "integrity": "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.6.tgz", + "integrity": "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.6.tgz", + "integrity": "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.6.tgz", + "integrity": "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.6.tgz", + "integrity": "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "tslib": "^2.8.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" }, "engines": { - "node": ">= 0.8" + "node": ">=6.0.0" } }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "node_modules/caniuse-lite": { + "version": "1.0.30001772", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001772.tgz", + "integrity": "sha512-mIwLZICj+ntVTw4BT2zfp+yu/AqV6GMKfJVJMx3MwPxs+uk/uj2GLl2dH8LQbjiLDX66amCga5nKFyDgRR43kg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/next": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz", + "integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "@next/env": "16.1.6", + "@swc/helpers": "0.5.15", + "baseline-browser-mapping": "^2.8.3", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.1.6", + "@next/swc-darwin-x64": "16.1.6", + "@next/swc-linux-arm64-gnu": "16.1.6", + "@next/swc-linux-arm64-musl": "16.1.6", + "@next/swc-linux-x64-gnu": "16.1.6", + "@next/swc-linux-x64-musl": "16.1.6", + "@next/swc-win32-arm64-msvc": "16.1.6", + "@next/swc-win32-x64-msvc": "16.1.6", + "sharp": "^0.34.4" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", - "engines": { - "node": ">= 0.10" + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" } }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "optional": true, "bin": { - "mime": "cli.js" + "semver": "bin/semver.js" }, "engines": { - "node": ">=4" + "node": ">=10" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, "dependencies": { - "mime-db": "1.52.0" + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" }, "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" + "url": "https://opencollective.com/libvips" }, - "engines": { - "node": ">= 0.8" + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" } }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { + "node_modules/source-map-js": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", "engines": { - "node": ">= 0.6" + "node": ">=0.10.0" } }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "client-only": "0.0.1" }, "engines": { - "node": ">= 0.8" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" + "babel-plugin-macros": { + "optional": true } - ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" } }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" } } } diff --git a/package.json b/package.json index 383ecc6..495abfd 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,19 @@ { "name": "links", "version": "1.0.0", - "description": "A web app to distribute personal social media links", - "main": "app.js", + "description": "Une page de liens moderne avec Next.js", + "main": "next.config.js", "scripts": { - "start": "node app.js", - "dev": "node app.js" + "dev": "node ./node_modules/next/dist/bin/next dev", + "build": "node ./node_modules/next/dist/bin/next build", + "start": "node ./node_modules/next/dist/bin/next start" }, "keywords": ["links", "social", "profile"], "author": "arthur-pbty", "license": "MIT", "dependencies": { - "express": "^4.18.2" + "next": "^16.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" } } \ No newline at end of file diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..0c5ee36 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/style.css b/public/style.css deleted file mode 100644 index b435891..0000000 --- a/public/style.css +++ /dev/null @@ -1,149 +0,0 @@ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - min-height: 100vh; - display: flex; - align-items: center; - justify-content: center; - padding: 20px; -} - -.container { - background: rgba(255, 255, 255, 0.95); - border-radius: 20px; - padding: 40px; - max-width: 400px; - width: 100%; - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); - backdrop-filter: blur(10px); -} - -.profile { - text-align: center; - margin-bottom: 30px; -} - -.avatar { - font-size: 4rem; - margin-bottom: 15px; -} - -.profile h1 { - font-size: 2rem; - color: #333; - margin-bottom: 8px; - font-weight: 600; -} - -.bio { - color: #666; - font-size: 1rem; - line-height: 1.4; -} - -.links { - display: flex; - flex-direction: column; - gap: 15px; - margin-bottom: 30px; -} - -.link-card { - display: flex; - align-items: center; - padding: 20px; - background: #fff; - border-radius: 15px; - text-decoration: none; - color: #333; - border: 2px solid transparent; - transition: all 0.3s ease; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); -} - -.link-card:hover { - transform: translateY(-2px); - box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1); - border-color: #667eea; -} - -.icon { - font-size: 1.5rem; - margin-right: 15px; - min-width: 30px; -} - -.link-info { - flex: 1; -} - -.link-info h3 { - font-size: 1.1rem; - font-weight: 600; - margin-bottom: 4px; - color: #333; -} - -.link-info p { - font-size: 0.9rem; - color: #666; - line-height: 1.3; -} - -.arrow { - font-size: 1.2rem; - color: #667eea; - transition: transform 0.3s ease; -} - -.link-card:hover .arrow { - transform: translateX(5px); -} - -footer { - text-align: center; - color: #888; - font-size: 0.85rem; - padding-top: 20px; - border-top: 1px solid #eee; -} - -/* Responsive design */ -@media (max-width: 480px) { - .container { - padding: 30px 20px; - margin: 10px; - } - - .avatar { - font-size: 3rem; - } - - .profile h1 { - font-size: 1.75rem; - } - - .link-card { - padding: 16px; - } - - .icon { - font-size: 1.3rem; - margin-right: 12px; - min-width: 25px; - } - - .link-info h3 { - font-size: 1rem; - } - - .link-info p { - font-size: 0.85rem; - } -} \ No newline at end of file