Compare commits

...

10 Commits

Author SHA1 Message Date
ordinarthur
6782e4d40b feat: add Dockerfiles for K3s deployment
Some checks failed
Build & Deploy to K3s / build-and-deploy (push) Failing after 3m34s
Dockerfile.ssr for Astro SSR, Dockerfile.api for Fastify API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 17:54:26 +02:00
ordinarthur
9a9519ce29 update site 2026-04-11 17:42:39 +02:00
ordinarthur
1b53e04b5d feat: switch to SSR for live Sanity updates
Migrate from SSG to SSR with @astrojs/node adapter so Sanity CMS
changes are reflected immediately without rebuild. Separate ports
for Astro SSR (4321) and Fastify API (3000) in production.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 11:41:34 +02:00
ordinarthur
7c77ac6c19 stylé de ouf 2 2026-04-04 11:15:58 +02:00
ordinarthur
f0042e49ec stylé de ouf 2026-04-04 11:15:54 +02:00
ordinarthur
4e2adf1afb fix: Safari mobile compatibility 2026-03-30 18:22:37 +02:00
ordinarthur
209d222eb0 remove: all scan/tech overlay effects 2026-03-30 18:19:20 +02:00
ordinarthur
15d7f856cb fix: smooth zoom reveal on product panel image 2026-03-30 18:16:37 +02:00
ordinarthur
0e07c4acd6 fix: remove scan effect on product panel image 2026-03-30 18:14:10 +02:00
ordinarthur
5257e53487 correct 2026-03-30 18:12:47 +02:00
31 changed files with 1984 additions and 872 deletions

View File

@ -1 +1 @@
[["Map",1,2],"meta::meta",["Map",3,4,5,6],"astro-version","5.18.1","astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"static\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":true,\"inlineStylesheets\":\"auto\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":4321,\"streaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"route\":\"/_image\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"security\":{\"checkOrigin\":true,\"allowedDomains\":[],\"actionBodySizeLimit\":1048576},\"env\":{\"schema\":{},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\":false,\"csp\":false,\"staticImportMetaEnv\":false,\"chromeDevtoolsWorkspace\":false,\"failOnPrerenderConflict\":false,\"svgo\":false},\"legacy\":{\"collections\":false}}"] [["Map",1,2],"meta::meta",["Map",3,4,5,6],"astro-version","5.18.1","astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"server\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":false,\"inlineStylesheets\":\"auto\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":4321,\"streaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"route\":\"/_image\",\"entrypoint\":\"astro/assets/endpoint/dev\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"security\":{\"checkOrigin\":true,\"allowedDomains\":[],\"actionBodySizeLimit\":1048576},\"env\":{\"schema\":{},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\":false,\"csp\":false,\"staticImportMetaEnv\":false,\"chromeDevtoolsWorkspace\":false,\"failOnPrerenderConflict\":false,\"svgo\":false},\"legacy\":{\"collections\":false},\"session\":{\"driver\":\"fs-lite\",\"options\":{\"base\":\"/Users/arthurbarre/dev/freelance/rebours/node_modules/.astro/sessions\"}}}"]

1
.astro/types.d.ts vendored
View File

@ -1 +1,2 @@
/// <reference types="astro/client" /> /// <reference types="astro/client" />
/// <reference path="content.d.ts" />

View File

@ -3,8 +3,8 @@
"configurations": [ "configurations": [
{ {
"name": "rebours-dev", "name": "rebours-dev",
"runtimeExecutable": "pnpm", "runtimeExecutable": "/Users/arthurbarre/.nvm/versions/node/v25.6.1/bin/node",
"runtimeArgs": ["dev"], "runtimeArgs": ["/Users/arthurbarre/.nvm/versions/node/v25.6.1/bin/pnpm", "dev"],
"port": 4321 "port": 4321
} }
] ]

18
.dockerignore Normal file
View File

@ -0,0 +1,18 @@
node_modules
dist
.git
.gitignore
.env
.env.*
*.md
.vscode
.idea
.DS_Store
.astro
sanity
k8s
.gitea
migrate-images.mjs
seed-sanity.mjs
seed-sanity-homepage.mjs
clean-duplicates.mjs

View File

@ -0,0 +1,99 @@
name: Build & Deploy to K3s
on:
push:
branches: [main]
env:
REGISTRY: git.arthurbarre.fr
SSR_IMAGE: git.arthurbarre.fr/ordinarthur/rebours-ssr
API_IMAGE: git.arthurbarre.fr/ordinarthur/rebours-api
REGISTRY_USER: ordinarthur
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to Gitea Container Registry
run: |
echo "${{ secrets.REGISTRY_PASSWORD }}" | \
docker login ${{ env.REGISTRY }} -u ${{ env.REGISTRY_USER }} --password-stdin
- name: Build SSR image
run: |
docker build \
-f Dockerfile.ssr \
-t ${{ env.SSR_IMAGE }}:${{ github.sha }} \
-t ${{ env.SSR_IMAGE }}:latest \
.
- name: Build API image
run: |
docker build \
-f Dockerfile.api \
-t ${{ env.API_IMAGE }}:${{ github.sha }} \
-t ${{ env.API_IMAGE }}:latest \
.
- name: Push SSR image
run: |
docker push ${{ env.SSR_IMAGE }}:${{ github.sha }}
docker push ${{ env.SSR_IMAGE }}:latest
- name: Push API image
run: |
docker push ${{ env.API_IMAGE }}:${{ github.sha }}
docker push ${{ env.API_IMAGE }}:latest
- name: Install kubectl
run: |
curl -LO "https://dl.k8s.io/release/$(curl -Ls https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
mv kubectl /usr/local/bin/kubectl
- name: Configure kubeconfig
run: |
mkdir -p ~/.kube
echo "${{ secrets.KUBECONFIG }}" | base64 -d > ~/.kube/config
- name: Apply namespace and shared resources
run: |
kubectl apply -f k8s/namespace.yml
kubectl apply -f k8s/configmap.yml
kubectl apply -f k8s/service.yml
- name: Create image pull secret
run: |
kubectl -n rebours create secret docker-registry gitea-registry-secret \
--docker-server=${{ env.REGISTRY }} \
--docker-username=${{ env.REGISTRY_USER }} \
--docker-password="${{ secrets.REGISTRY_PASSWORD }}" \
--dry-run=client -o yaml | kubectl apply -f -
- name: Create app secrets
run: |
kubectl -n rebours create secret generic rebours-secrets \
--from-literal=STRIPE_SECRET_KEY="${{ secrets.STRIPE_SECRET_KEY }}" \
--from-literal=STRIPE_WEBHOOK_SECRET="${{ secrets.STRIPE_WEBHOOK_SECRET }}" \
--from-literal=SANITY_API_TOKEN="${{ secrets.SANITY_API_TOKEN }}" \
--dry-run=client -o yaml | kubectl apply -f -
- name: Deploy workloads
run: |
kubectl apply -f k8s/deployment.yml
kubectl -n rebours set image deployment/rebours-ssr \
rebours-ssr=${{ env.SSR_IMAGE }}:${{ github.sha }}
kubectl -n rebours set image deployment/rebours-api \
rebours-api=${{ env.API_IMAGE }}:${{ github.sha }}
kubectl -n rebours rollout status deployment/rebours-api --timeout=120s
kubectl -n rebours rollout status deployment/rebours-ssr --timeout=180s
kubectl -n rebours rollout status deployment/rebours-proxy --timeout=60s
- name: Cleanup old images
run: |
docker image prune -f

View File

@ -37,7 +37,7 @@ rebours/
| Couche | Techno | | Couche | Techno |
|--------|--------| |--------|--------|
| Front (SSG) | Astro + HTML/CSS/JS vanilla + GSAP | | Front (SSR) | Astro + HTML/CSS/JS vanilla + GSAP |
| CMS | Sanity (headless, hébergé) | | CMS | Sanity (headless, hébergé) |
| API | Fastify (Node.js) | | API | Fastify (Node.js) |
| Paiement | Stripe Checkout (price_data inline) | | Paiement | Stripe Checkout (price_data inline) |
@ -65,20 +65,21 @@ STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_... STRIPE_WEBHOOK_SECRET=whsec_...
DOMAIN=http://localhost:4321 DOMAIN=http://localhost:4321
PORT=8888 FASTIFY_PORT=3000 # Port Fastify API (prod)
ASTRO_PORT=4321 # Port Astro SSR (prod)
``` ```
### Lancer le projet ### Lancer le projet
```bash ```bash
npm install pnpm install
npm run dev pnpm dev
``` ```
Cela lance en parallèle (via `concurrently`) : Cela lance en parallèle (via `concurrently`) :
- `astro dev` sur http://localhost:4321 - `astro dev` sur http://localhost:4321
- `node --watch server.mjs` (mode dev, PORT=8888) - `node --watch server.mjs` (mode dev)
Le proxy Vite dans `astro.config.mjs` redirige `/api/*` vers `http://127.0.0.1:8888`. Le proxy Vite dans `astro.config.mjs` redirige `/api/*` vers le serveur Fastify.
### Sanity Studio ### Sanity Studio
```bash ```bash
@ -90,8 +91,8 @@ Accessible sur http://localhost:3333
### Build ### Build
```bash ```bash
npm run build pnpm build
# Génère ./dist/ (fichiers statiques Astro) # Génère ./dist/ (serveur Astro SSR + assets client)
``` ```
--- ---
@ -116,8 +117,7 @@ Champs principaux :
1. Ouvrir Sanity Studio 1. Ouvrir Sanity Studio
2. Créer un nouveau document "Produit" 2. Créer un nouveau document "Produit"
3. Remplir les champs, uploader l'image 3. Remplir les champs, uploader l'image
4. Publier 4. Publier → visible immédiatement sur le site (SSR, pas de rebuild nécessaire)
5. Rebuild le site : `npm run build` + déployer
### Images ### Images
Les images sont servies via le CDN Sanity avec transformations automatiques. Les images sont servies via le CDN Sanity avec transformations automatiques.
@ -154,12 +154,19 @@ Quand un client clique "Commander" :
### Serveur : ordinarthur@10.10.0.13 ### Serveur : ordinarthur@10.10.0.13
### Architecture prod ### Architecture prod (SSR)
``` ```
Internet -> Nginx (port 80) -> /var/www/html/rebours/dist/ (statiques) Internet -> Nginx (port 80) -> / -> proxy -> Astro SSR :4321
-> /api/* -> proxy -> Fastify :3000 -> /_astro/* -> fichiers statiques (dist/client/)
-> /api/* -> proxy -> Fastify :3000
``` ```
### Services systemd
| Service | Port | Rôle |
|---------|------|------|
| `rebours-ssr` | 4321 | Astro SSR (pages dynamiques) |
| `rebours` | 3000 | Fastify API (Stripe, checkout) |
### Variables d'environnement en prod ### Variables d'environnement en prod
```env ```env
SANITY_PROJECT_ID=... SANITY_PROJECT_ID=...
@ -167,14 +174,15 @@ SANITY_DATASET=production
STRIPE_SECRET_KEY=sk_live_... STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_... STRIPE_WEBHOOK_SECRET=whsec_...
DOMAIN=https://rebours.studio DOMAIN=https://rebours.studio
PORT=3000 FASTIFY_PORT=3000
ASTRO_PORT=4321
``` ```
### Déploiement ### Déploiement
```bash ```bash
npm run build pnpm build
scp -r dist/* ordinarthur@10.10.0.13:/tmp/rebours-dist/ scp -r dist/* ordinarthur@10.10.0.13:/tmp/rebours-dist/
ssh ordinarthur@10.10.0.13 "sudo cp -r /tmp/rebours-dist/* /var/www/html/rebours/dist/" ssh ordinarthur@10.10.0.13 "sudo rm -rf /var/www/html/rebours/dist && sudo mkdir -p /var/www/html/rebours/dist && sudo cp -r /tmp/rebours-dist/* /var/www/html/rebours/dist/ && sudo chown -R ordinarthur:ordinarthur /var/www/html/rebours/dist && sudo systemctl restart rebours-ssr"
``` ```
Si server.mjs a changé : Si server.mjs a changé :
@ -189,8 +197,8 @@ ssh ordinarthur@10.10.0.13 "sudo cp /tmp/server.mjs /var/www/html/rebours/server
| URL | Comportement | | URL | Comportement |
|-----|-------------| |-----|-------------|
| `/` | Page principale Astro (SSG) | | `/` | Page principale Astro (SSR, données Sanity live) |
| `/collection/{slug}` | Page produit (SSG), auto-open panel via `window.__OPEN_PANEL__` | | `/collection/{slug}` | Page produit (SSR), auto-open panel via `window.__OPEN_PANEL__` |
| `/success?session_id=...` | Page de confirmation Stripe | | `/success?session_id=...` | Page de confirmation Stripe |
| `/robots.txt` | Généré au build | | `/robots.txt` | Généré au build |
| `/sitemap.xml` | Généré au build depuis Sanity | | `/sitemap.xml` | Généré au build depuis Sanity |

15
Dockerfile.api Normal file
View File

@ -0,0 +1,15 @@
# Fastify API — no build step, runs server.mjs directly
FROM node:22-alpine
WORKDIR /app
RUN corepack enable && corepack prepare pnpm@latest --activate
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile --prod
COPY server.mjs ./
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "server.mjs"]

25
Dockerfile.ssr Normal file
View File

@ -0,0 +1,25 @@
# --- Stage 1: Build Astro SSR ---
FROM node:22-alpine AS build
WORKDIR /app
RUN corepack enable && corepack prepare pnpm@latest --activate
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
# --- Stage 2: Runtime ---
FROM node:22-alpine AS runtime
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/package.json ./
ENV HOST=0.0.0.0
ENV PORT=4321
EXPOSE 4321
CMD ["node", "dist/server/entry.mjs"]

View File

@ -1,8 +1,10 @@
// @ts-check // @ts-check
import { defineConfig } from 'astro/config'; import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
export default defineConfig({ export default defineConfig({
output: 'static', output: 'server',
adapter: node({ mode: 'standalone' }),
outDir: './dist', outDir: './dist',
server: { port: 4321 }, server: { port: 4321 },
vite: { vite: {

44
k8s/configmap.yml Normal file
View File

@ -0,0 +1,44 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: rebours-config
namespace: rebours
data:
NODE_ENV: "production"
SANITY_PROJECT_ID: "y821x5qu"
SANITY_DATASET: "production"
DOMAIN: "https://rebours.studio"
FASTIFY_PORT: "3000"
ASTRO_PORT: "4321"
proxy.conf: |
server {
listen 80;
server_name _;
client_max_body_size 10M;
# API → Fastify
location /api/ {
proxy_pass http://rebours-api:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
# Static assets from Astro client build (cached)
location /_astro/ {
proxy_pass http://rebours-ssr:4321;
proxy_set_header Host $host;
add_header Cache-Control "public, max-age=31536000, immutable";
}
# SSR → Astro
location / {
proxy_pass http://rebours-ssr:4321;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
}

144
k8s/deployment.yml Normal file
View File

@ -0,0 +1,144 @@
# --- Astro SSR ---
apiVersion: apps/v1
kind: Deployment
metadata:
name: rebours-ssr
namespace: rebours
labels:
app: rebours-ssr
spec:
replicas: 1
selector:
matchLabels:
app: rebours-ssr
template:
metadata:
labels:
app: rebours-ssr
spec:
imagePullSecrets:
- name: gitea-registry-secret
containers:
- name: rebours-ssr
image: git.arthurbarre.fr/ordinarthur/rebours-ssr:latest
ports:
- containerPort: 4321
envFrom:
- configMapRef:
name: rebours-config
- secretRef:
name: rebours-secrets
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
readinessProbe:
httpGet:
path: /
port: 4321
initialDelaySeconds: 10
periodSeconds: 10
livenessProbe:
httpGet:
path: /
port: 4321
initialDelaySeconds: 15
periodSeconds: 30
---
# --- Fastify API ---
apiVersion: apps/v1
kind: Deployment
metadata:
name: rebours-api
namespace: rebours
labels:
app: rebours-api
spec:
replicas: 1
selector:
matchLabels:
app: rebours-api
template:
metadata:
labels:
app: rebours-api
spec:
imagePullSecrets:
- name: gitea-registry-secret
containers:
- name: rebours-api
image: git.arthurbarre.fr/ordinarthur/rebours-api:latest
ports:
- containerPort: 3000
envFrom:
- configMapRef:
name: rebours-config
- secretRef:
name: rebours-secrets
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "300m"
readinessProbe:
httpGet:
path: /api/health
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /api/health
port: 3000
initialDelaySeconds: 10
periodSeconds: 30
---
# --- Nginx Proxy ---
apiVersion: apps/v1
kind: Deployment
metadata:
name: rebours-proxy
namespace: rebours
labels:
app: rebours-proxy
spec:
replicas: 1
selector:
matchLabels:
app: rebours-proxy
template:
metadata:
labels:
app: rebours-proxy
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
volumeMounts:
- name: proxy-config
mountPath: /etc/nginx/conf.d/default.conf
subPath: proxy.conf
resources:
requests:
memory: "32Mi"
cpu: "25m"
limits:
memory: "64Mi"
cpu: "100m"
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 10
volumes:
- name: proxy-config
configMap:
name: rebours-config

4
k8s/namespace.yml Normal file
View File

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: rebours

43
k8s/service.yml Normal file
View File

@ -0,0 +1,43 @@
# --- SSR (ClusterIP — internal) ---
apiVersion: v1
kind: Service
metadata:
name: rebours-ssr
namespace: rebours
spec:
selector:
app: rebours-ssr
ports:
- name: http
port: 4321
targetPort: 4321
---
# --- API (ClusterIP — internal) ---
apiVersion: v1
kind: Service
metadata:
name: rebours-api
namespace: rebours
spec:
selector:
app: rebours-api
ports:
- name: http
port: 3000
targetPort: 3000
---
# --- Proxy (NodePort — external access) ---
apiVersion: v1
kind: Service
metadata:
name: rebours-proxy
namespace: rebours
spec:
type: NodePort
selector:
app: rebours-proxy
ports:
- name: http
port: 80
targetPort: 80
nodePort: 30083

30
migrate-images.mjs Normal file
View File

@ -0,0 +1,30 @@
/**
* Migration: product.image (single) product.images (array)
* Run: node migrate-images.mjs
*/
import 'dotenv/config'
import { createClient } from '@sanity/client'
const sanity = createClient({
projectId: process.env.SANITY_PROJECT_ID,
dataset: process.env.SANITY_DATASET || 'production',
apiVersion: '2024-01-01',
useCdn: false,
token: process.env.SANITY_API_TOKEN,
})
const products = await sanity.fetch('*[_type == "product" && defined(image)]{ _id, image }')
console.log(`Found ${products.length} products to migrate`)
for (const p of products) {
console.log(`Migrating ${p._id}...`)
await sanity
.patch(p._id)
.set({ images: [p.image] })
.unset(['image'])
.commit()
console.log(` ✓ done`)
}
console.log('Migration complete!')

View File

@ -2,10 +2,7 @@ server {
listen 80; listen 80;
server_name rebours.studio; server_name rebours.studio;
root /var/www/html/rebours/dist; # ── API proxy Fastify ────────────────<EFBFBD><EFBFBD><EFBFBD>─────────────────────────────────
index index.html;
# ── API proxy Fastify ──────────────────────────────────────────────────
location /api/ { location /api/ {
proxy_pass http://127.0.0.1:3000; proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host; proxy_set_header Host $host;
@ -14,28 +11,23 @@ server {
proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-Proto https;
} }
# ── Cache : Astro hashed immutable ───────────────────────────────────── # ── Static assets from Astro client build ──────────────────<EFBFBD><EFBFBD><EFBFBD>─────────────
location /_astro/ { location /_astro/ {
root /var/www/html/rebours/dist/client;
add_header Cache-Control "public, max-age=31536000, immutable"; add_header Cache-Control "public, max-age=31536000, immutable";
} }
# ── Cache : CSS/JS sans hash revalidation ───────────────────────────── location ~* \.(css|js|jpg|jpeg|png|gif|webp|svg|woff2|woff|ttf|ico|mp3)$ {
location ~* \.(css|js)$ { root /var/www/html/rebours/dist/client;
add_header Cache-Control "no-cache";
}
# ── Cache : assets 7 jours ────────────────────────────────────────────
location ~* \.(jpg|jpeg|png|gif|webp|svg|woff2|woff|ttf|ico|mp3)$ {
add_header Cache-Control "public, max-age=604800"; add_header Cache-Control "public, max-age=604800";
} }
# ── HTML : jamais caché ────────────────────────────────────────────────── # ── SSR Astro Node server ─────────────────────<EFBFBD><EFBFBD><EFBFBD>────────────────────────
location ~* \.html$ {
add_header Cache-Control "no-store";
}
# ── SPA fallback ─────────────────────────────────────────────────────────
location / { location / {
try_files $uri $uri/ $uri.html /index.html; proxy_pass http://127.0.0.1:4321;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
} }
} }

View File

@ -7,9 +7,11 @@
"build": "astro build", "build": "astro build",
"preview": "astro preview", "preview": "astro preview",
"server": "NODE_ENV=production node server.mjs", "server": "NODE_ENV=production node server.mjs",
"start": "NODE_ENV=production node dist/server/entry.mjs",
"astro": "astro" "astro": "astro"
}, },
"dependencies": { "dependencies": {
"@astrojs/node": "^9.5.5",
"@fastify/cors": "^10.0.2", "@fastify/cors": "^10.0.2",
"@sanity/client": "^7", "@sanity/client": "^7",
"@sanity/image-url": "^1", "@sanity/image-url": "^1",

133
pnpm-lock.yaml generated
View File

@ -8,6 +8,9 @@ importers:
.: .:
dependencies: dependencies:
'@astrojs/node':
specifier: ^9.5.5
version: 9.5.5(astro@5.18.1(@types/node@25.4.0)(jiti@2.6.1)(rollup@4.59.0)(typescript@5.9.3))
'@fastify/cors': '@fastify/cors':
specifier: ^10.0.2 specifier: ^10.0.2
version: 10.1.0 version: 10.1.0
@ -47,6 +50,11 @@ packages:
'@astrojs/markdown-remark@6.3.11': '@astrojs/markdown-remark@6.3.11':
resolution: {integrity: sha512-hcaxX/5aC6lQgHeGh1i+aauvSwIT6cfyFjKWvExYSxUhZZBBdvCliOtu06gbQyhbe0pGJNoNmqNlQZ5zYUuIyQ==} resolution: {integrity: sha512-hcaxX/5aC6lQgHeGh1i+aauvSwIT6cfyFjKWvExYSxUhZZBBdvCliOtu06gbQyhbe0pGJNoNmqNlQZ5zYUuIyQ==}
'@astrojs/node@9.5.5':
resolution: {integrity: sha512-rtU2BGU5u3SfGURpANfMxVzCIoR86MkaN05ncza9rbtuMKJ/XnRJt/BbyVknDbOJ71hoci0SIsJwKcJR8vvi/A==}
peerDependencies:
astro: ^5.17.3
'@astrojs/prism@3.3.0': '@astrojs/prism@3.3.0':
resolution: {integrity: sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==} resolution: {integrity: sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==}
engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0}
@ -989,6 +997,10 @@ packages:
defu@6.1.4: defu@6.1.4:
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
depd@2.0.0:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'}
dequal@2.0.3: dequal@2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -1038,12 +1050,19 @@ packages:
resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==} resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==}
engines: {node: '>=4'} engines: {node: '>=4'}
ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
emoji-regex@10.6.0: emoji-regex@10.6.0:
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
emoji-regex@8.0.0: emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
encodeurl@2.0.0:
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
engines: {node: '>= 0.8'}
entities@4.5.0: entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'} engines: {node: '>=0.12'}
@ -1069,6 +1088,9 @@ packages:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'} engines: {node: '>=6'}
escape-html@1.0.3:
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
escape-string-regexp@5.0.0: escape-string-regexp@5.0.0:
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -1079,6 +1101,10 @@ packages:
estree-walker@3.0.3: estree-walker@3.0.3:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
etag@1.8.1:
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
engines: {node: '>= 0.6'}
event-source-polyfill@1.0.31: event-source-polyfill@1.0.31:
resolution: {integrity: sha512-4IJSItgS/41IxN5UVAVuAyczwZF7ZIEsM1XAoUzIHA6A+xzusEZUutdXz2Nr+MQPLxfTiCvqE79/C8HT8fKFvA==} resolution: {integrity: sha512-4IJSItgS/41IxN5UVAVuAyczwZF7ZIEsM1XAoUzIHA6A+xzusEZUutdXz2Nr+MQPLxfTiCvqE79/C8HT8fKFvA==}
@ -1149,6 +1175,10 @@ packages:
resolution: {integrity: sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==} resolution: {integrity: sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==}
engines: {node: '>=20'} engines: {node: '>=20'}
fresh@2.0.0:
resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
engines: {node: '>= 0.8'}
fsevents@2.3.3: fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@ -1218,6 +1248,10 @@ packages:
http-cache-semantics@4.2.0: http-cache-semantics@4.2.0:
resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==}
http-errors@2.0.1:
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
engines: {node: '>= 0.8'}
import-meta-resolve@4.2.0: import-meta-resolve@4.2.0:
resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==}
@ -1423,6 +1457,14 @@ packages:
micromark@4.0.2: micromark@4.0.2:
resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==}
mime-db@1.54.0:
resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
engines: {node: '>= 0.6'}
mime-types@3.0.2:
resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==}
engines: {node: '>=18'}
mimic-response@3.1.0: mimic-response@3.1.0:
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -1475,6 +1517,10 @@ packages:
resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
on-finished@2.4.1:
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
engines: {node: '>= 0.8'}
oniguruma-parser@0.12.1: oniguruma-parser@0.12.1:
resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==}
@ -1553,6 +1599,10 @@ packages:
radix3@1.1.2: radix3@1.1.2:
resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==}
range-parser@1.2.1:
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
engines: {node: '>= 0.6'}
readable-stream@3.6.2: readable-stream@3.6.2:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@ -1663,9 +1713,19 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
hasBin: true hasBin: true
send@1.2.1:
resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==}
engines: {node: '>= 18'}
server-destroy@1.0.1:
resolution: {integrity: sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==}
set-cookie-parser@2.7.2: set-cookie-parser@2.7.2:
resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
sharp@0.34.5: sharp@0.34.5:
resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
@ -1698,6 +1758,10 @@ packages:
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
engines: {node: '>= 10.x'} engines: {node: '>= 10.x'}
statuses@2.0.2:
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
engines: {node: '>= 0.8'}
string-width@4.2.3: string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -1764,6 +1828,10 @@ packages:
resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==} resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==}
engines: {node: '>=12'} engines: {node: '>=12'}
toidentifier@1.0.1:
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
engines: {node: '>=0.6'}
tree-kill@1.2.2: tree-kill@1.2.2:
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
hasBin: true hasBin: true
@ -2061,6 +2129,15 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@astrojs/node@9.5.5(astro@5.18.1(@types/node@25.4.0)(jiti@2.6.1)(rollup@4.59.0)(typescript@5.9.3))':
dependencies:
'@astrojs/internal-helpers': 0.7.6
astro: 5.18.1(@types/node@25.4.0)(jiti@2.6.1)(rollup@4.59.0)(typescript@5.9.3)
send: 1.2.1
server-destroy: 1.0.1
transitivePeerDependencies:
- supports-color
'@astrojs/prism@3.3.0': '@astrojs/prism@3.3.0':
dependencies: dependencies:
prismjs: 1.30.0 prismjs: 1.30.0
@ -2826,6 +2903,8 @@ snapshots:
defu@6.1.4: {} defu@6.1.4: {}
depd@2.0.0: {}
dequal@2.0.3: {} dequal@2.0.3: {}
destr@2.0.5: {} destr@2.0.5: {}
@ -2869,10 +2948,14 @@ snapshots:
dset@3.1.4: {} dset@3.1.4: {}
ee-first@1.1.1: {}
emoji-regex@10.6.0: {} emoji-regex@10.6.0: {}
emoji-regex@8.0.0: {} emoji-regex@8.0.0: {}
encodeurl@2.0.0: {}
entities@4.5.0: {} entities@4.5.0: {}
entities@6.0.1: {} entities@6.0.1: {}
@ -2939,6 +3022,8 @@ snapshots:
escalade@3.2.0: {} escalade@3.2.0: {}
escape-html@1.0.3: {}
escape-string-regexp@5.0.0: {} escape-string-regexp@5.0.0: {}
estree-walker@2.0.2: {} estree-walker@2.0.2: {}
@ -2947,6 +3032,8 @@ snapshots:
dependencies: dependencies:
'@types/estree': 1.0.8 '@types/estree': 1.0.8
etag@1.8.1: {}
event-source-polyfill@1.0.31: {} event-source-polyfill@1.0.31: {}
eventemitter3@5.0.4: {} eventemitter3@5.0.4: {}
@ -3020,6 +3107,8 @@ snapshots:
dependencies: dependencies:
tiny-inflate: 1.0.3 tiny-inflate: 1.0.3
fresh@2.0.0: {}
fsevents@2.3.3: fsevents@2.3.3:
optional: true optional: true
@ -3149,6 +3238,14 @@ snapshots:
http-cache-semantics@4.2.0: {} http-cache-semantics@4.2.0: {}
http-errors@2.0.1:
dependencies:
depd: 2.0.0
inherits: 2.0.4
setprototypeof: 1.2.0
statuses: 2.0.2
toidentifier: 1.0.1
import-meta-resolve@4.2.0: {} import-meta-resolve@4.2.0: {}
inherits@2.0.4: {} inherits@2.0.4: {}
@ -3525,6 +3622,12 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
mime-db@1.54.0: {}
mime-types@3.0.2:
dependencies:
mime-db: 1.54.0
mimic-response@3.1.0: {} mimic-response@3.1.0: {}
mnemonist@0.40.0: mnemonist@0.40.0:
@ -3565,6 +3668,10 @@ snapshots:
on-exit-leak-free@2.1.2: {} on-exit-leak-free@2.1.2: {}
on-finished@2.4.1:
dependencies:
ee-first: 1.1.1
oniguruma-parser@0.12.1: {} oniguruma-parser@0.12.1: {}
oniguruma-to-es@4.3.4: oniguruma-to-es@4.3.4:
@ -3650,6 +3757,8 @@ snapshots:
radix3@1.1.2: {} radix3@1.1.2: {}
range-parser@1.2.1: {}
readable-stream@3.6.2: readable-stream@3.6.2:
dependencies: dependencies:
inherits: 2.0.4 inherits: 2.0.4
@ -3820,8 +3929,28 @@ snapshots:
semver@7.7.4: {} semver@7.7.4: {}
send@1.2.1:
dependencies:
debug: 4.4.3
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
fresh: 2.0.0
http-errors: 2.0.1
mime-types: 3.0.2
ms: 2.1.3
on-finished: 2.4.1
range-parser: 1.2.1
statuses: 2.0.2
transitivePeerDependencies:
- supports-color
server-destroy@1.0.1: {}
set-cookie-parser@2.7.2: {} set-cookie-parser@2.7.2: {}
setprototypeof@1.2.0: {}
sharp@0.34.5: sharp@0.34.5:
dependencies: dependencies:
'@img/colour': 1.1.0 '@img/colour': 1.1.0
@ -3881,6 +4010,8 @@ snapshots:
split2@4.2.0: {} split2@4.2.0: {}
statuses@2.0.2: {}
string-width@4.2.3: string-width@4.2.3:
dependencies: dependencies:
emoji-regex: 8.0.0 emoji-regex: 8.0.0
@ -3951,6 +4082,8 @@ snapshots:
toad-cache@3.7.0: {} toad-cache@3.7.0: {}
toidentifier@1.0.1: {}
tree-kill@1.2.2: {} tree-kill@1.2.2: {}
trim-lines@3.0.1: {} trim-lines@3.0.1: {}

File diff suppressed because it is too large Load Diff

View File

@ -9,10 +9,10 @@
"deploy": "sanity deploy" "deploy": "sanity deploy"
}, },
"dependencies": { "dependencies": {
"@sanity/vision": "^3", "@sanity/vision": "^3.99.0",
"react": "^18", "react": "^19.2.4",
"react-dom": "^18", "react-dom": "^19.2.4",
"sanity": "^3", "sanity": "^3.99.0",
"styled-components": "^6" "styled-components": "^6"
}, },
"devDependencies": { "devDependencies": {

449
sanity/pnpm-lock.yaml generated
View File

@ -9,20 +9,20 @@ importers:
.: .:
dependencies: dependencies:
'@sanity/vision': '@sanity/vision':
specifier: ^3 specifier: ^3.99.0
version: 3.99.0(@babel/runtime@7.29.2)(@codemirror/lint@6.9.5)(@codemirror/theme-one-dark@6.1.3)(@emotion/is-prop-valid@1.4.0)(codemirror@6.0.2)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)(styled-components@6.3.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) version: 3.99.0(@babel/runtime@7.29.2)(@codemirror/lint@6.9.5)(@codemirror/theme-one-dark@6.1.3)(@emotion/is-prop-valid@1.4.0)(codemirror@6.0.2)(react-dom@19.2.4(react@19.2.4))(react-is@18.3.1)(react@19.2.4)(styled-components@6.3.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4))
react: react:
specifier: ^18 specifier: ^19.2.4
version: 18.3.1 version: 19.2.4
react-dom: react-dom:
specifier: ^18 specifier: ^19.2.4
version: 18.3.1(react@18.3.1) version: 19.2.4(react@19.2.4)
sanity: sanity:
specifier: ^3 specifier: ^3.99.0
version: 3.99.0(@emotion/is-prop-valid@1.4.0)(@types/node@25.5.0)(@types/react@19.2.14)(immer@10.2.0)(jiti@2.6.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(styled-components@6.3.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.9.3)(yaml@2.8.3) version: 3.99.0(@emotion/is-prop-valid@1.4.0)(@types/node@25.5.0)(@types/react@19.2.14)(immer@10.2.0)(jiti@2.6.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(styled-components@6.3.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(yaml@2.8.3)
styled-components: styled-components:
specifier: ^6 specifier: ^6
version: 6.3.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 6.3.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
devDependencies: devDependencies:
'@sanity/eslint-config-studio': '@sanity/eslint-config-studio':
specifier: ^4 specifier: ^4
@ -705,6 +705,9 @@ packages:
'@codemirror/view@6.40.0': '@codemirror/view@6.40.0':
resolution: {integrity: sha512-WA0zdU7xfF10+5I3HhUUq3kqOx3KjqmtQ9lqZjfK7jtYk4G72YW9rezcSywpaUMCWOMlq+6E0pO1IWg1TNIhtg==} resolution: {integrity: sha512-WA0zdU7xfF10+5I3HhUUq3kqOx3KjqmtQ9lqZjfK7jtYk4G72YW9rezcSywpaUMCWOMlq+6E0pO1IWg1TNIhtg==}
'@codemirror/view@6.41.0':
resolution: {integrity: sha512-6H/qadXsVuDY219Yljhohglve8xf4B8xJkVOEWfA5uiYKiTFppjqsvsfR5iPA0RbvRBoOyTZpbLIxe9+0UR8xA==}
'@csstools/color-helpers@5.1.0': '@csstools/color-helpers@5.1.0':
resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==}
engines: {node: '>=18'} engines: {node: '>=18'}
@ -4259,10 +4262,10 @@ packages:
peerDependencies: peerDependencies:
react: ^17.0.0 || ^18.0.0 || ^19.0.0 || ^0.0.0-experimental react: ^17.0.0 || ^18.0.0 || ^19.0.0 || ^0.0.0-experimental
react-dom@18.3.1: react-dom@19.2.4:
resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==}
peerDependencies: peerDependencies:
react: ^18.3.1 react: ^19.2.4
react-fast-compare@3.2.2: react-fast-compare@3.2.2:
resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==}
@ -4310,8 +4313,8 @@ packages:
react: ^18.3 || >=19.0.0-0 react: ^18.3 || >=19.0.0-0
rxjs: ^7 rxjs: ^7
react@18.3.1: react@19.2.4:
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
read-pkg-up@7.0.1: read-pkg-up@7.0.1:
@ -4513,8 +4516,8 @@ packages:
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
engines: {node: '>=v12.22.7'} engines: {node: '>=v12.22.7'}
scheduler@0.23.2: scheduler@0.27.0:
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
scroll-into-view-if-needed@3.1.0: scroll-into-view-if-needed@3.1.0:
resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==}
@ -6286,7 +6289,7 @@ snapshots:
dependencies: dependencies:
'@codemirror/language': 6.12.3 '@codemirror/language': 6.12.3
'@codemirror/state': 6.6.0 '@codemirror/state': 6.6.0
'@codemirror/view': 6.40.0 '@codemirror/view': 6.41.0
'@lezer/highlight': 1.2.3 '@lezer/highlight': 1.2.3
'@codemirror/view@6.40.0': '@codemirror/view@6.40.0':
@ -6296,6 +6299,13 @@ snapshots:
style-mod: 4.1.3 style-mod: 4.1.3
w3c-keyname: 2.2.8 w3c-keyname: 2.2.8
'@codemirror/view@6.41.0':
dependencies:
'@codemirror/state': 6.6.0
crelt: 1.0.6
style-mod: 4.1.3
w3c-keyname: 2.2.8
'@csstools/color-helpers@5.1.0': {} '@csstools/color-helpers@5.1.0': {}
'@csstools/color-helpers@6.0.2': {} '@csstools/color-helpers@6.0.2': {}
@ -6344,36 +6354,36 @@ snapshots:
'@date-fns/utc@2.1.1': {} '@date-fns/utc@2.1.1': {}
'@dnd-kit/accessibility@3.1.1(react@18.3.1)': '@dnd-kit/accessibility@3.1.1(react@19.2.4)':
dependencies: dependencies:
react: 18.3.1 react: 19.2.4
tslib: 2.8.1 tslib: 2.8.1
'@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': '@dnd-kit/core@6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies: dependencies:
'@dnd-kit/accessibility': 3.1.1(react@18.3.1) '@dnd-kit/accessibility': 3.1.1(react@19.2.4)
'@dnd-kit/utilities': 3.2.2(react@18.3.1) '@dnd-kit/utilities': 3.2.2(react@19.2.4)
react: 18.3.1 react: 19.2.4
react-dom: 18.3.1(react@18.3.1) react-dom: 19.2.4(react@19.2.4)
tslib: 2.8.1 tslib: 2.8.1
'@dnd-kit/modifiers@6.0.1(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': '@dnd-kit/modifiers@6.0.1(@dnd-kit/core@6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)':
dependencies: dependencies:
'@dnd-kit/core': 6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@dnd-kit/core': 6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@dnd-kit/utilities': 3.2.2(react@18.3.1) '@dnd-kit/utilities': 3.2.2(react@19.2.4)
react: 18.3.1 react: 19.2.4
tslib: 2.8.1 tslib: 2.8.1
'@dnd-kit/sortable@7.0.2(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': '@dnd-kit/sortable@7.0.2(@dnd-kit/core@6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)':
dependencies: dependencies:
'@dnd-kit/core': 6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@dnd-kit/core': 6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@dnd-kit/utilities': 3.2.2(react@18.3.1) '@dnd-kit/utilities': 3.2.2(react@19.2.4)
react: 18.3.1 react: 19.2.4
tslib: 2.8.1 tslib: 2.8.1
'@dnd-kit/utilities@3.2.2(react@18.3.1)': '@dnd-kit/utilities@3.2.2(react@19.2.4)':
dependencies: dependencies:
react: 18.3.1 react: 19.2.4
tslib: 2.8.1 tslib: 2.8.1
'@emotion/is-prop-valid@1.4.0': '@emotion/is-prop-valid@1.4.0':
@ -6496,11 +6506,11 @@ snapshots:
'@floating-ui/core': 1.7.5 '@floating-ui/core': 1.7.5
'@floating-ui/utils': 0.2.11 '@floating-ui/utils': 0.2.11
'@floating-ui/react-dom@2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': '@floating-ui/react-dom@2.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies: dependencies:
'@floating-ui/dom': 1.7.6 '@floating-ui/dom': 1.7.6
react: 18.3.1 react: 19.2.4
react-dom: 18.3.1(react@18.3.1) react-dom: 19.2.4(react@19.2.4)
'@floating-ui/utils@0.2.11': {} '@floating-ui/utils@0.2.11': {}
@ -6697,22 +6707,22 @@ snapshots:
dependencies: dependencies:
mux-embed: 5.17.10 mux-embed: 5.17.10
'@mux/mux-player-react@3.11.7(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': '@mux/mux-player-react@3.11.7(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies: dependencies:
'@mux/mux-player': 3.11.7(react@18.3.1) '@mux/mux-player': 3.11.7(react@19.2.4)
'@mux/playback-core': 0.33.3 '@mux/playback-core': 0.33.3
prop-types: 15.8.1 prop-types: 15.8.1
react: 18.3.1 react: 19.2.4
react-dom: 18.3.1(react@18.3.1) react-dom: 19.2.4(react@19.2.4)
optionalDependencies: optionalDependencies:
'@types/react': 19.2.14 '@types/react': 19.2.14
'@mux/mux-player@3.11.7(react@18.3.1)': '@mux/mux-player@3.11.7(react@19.2.4)':
dependencies: dependencies:
'@mux/mux-video': 0.30.5 '@mux/mux-video': 0.30.5
'@mux/playback-core': 0.33.3 '@mux/playback-core': 0.33.3
media-chrome: 4.18.3(react@18.3.1) media-chrome: 4.18.3(react@19.2.4)
player.style: 0.3.1(react@18.3.1) player.style: 0.3.1(react@19.2.4)
transitivePeerDependencies: transitivePeerDependencies:
- react - react
@ -6832,7 +6842,7 @@ snapshots:
get-random-values-esm: 1.0.2 get-random-values-esm: 1.0.2
lodash: 4.17.23 lodash: 4.17.23
'@portabletext/editor@1.58.1(@sanity/schema@3.99.0(@types/react@19.2.14)(debug@4.4.3))(@sanity/types@3.99.0(@types/react@19.2.14)(debug@4.4.3))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rxjs@7.8.2)': '@portabletext/editor@1.58.1(@sanity/schema@3.99.0(@types/react@19.2.14)(debug@4.4.3))(@sanity/types@3.99.0(@types/react@19.2.14)(debug@4.4.3))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)':
dependencies: dependencies:
'@portabletext/block-tools': 1.1.38(@sanity/types@3.99.0(@types/react@19.2.14)(debug@4.4.3))(@types/react@19.2.14) '@portabletext/block-tools': 1.1.38(@sanity/types@3.99.0(@types/react@19.2.14)(debug@4.4.3))(@types/react@19.2.14)
'@portabletext/keyboard-shortcuts': 1.1.0 '@portabletext/keyboard-shortcuts': 1.1.0
@ -6840,19 +6850,19 @@ snapshots:
'@portabletext/to-html': 2.0.17 '@portabletext/to-html': 2.0.17
'@sanity/schema': 3.99.0(@types/react@19.2.14)(debug@4.4.3) '@sanity/schema': 3.99.0(@types/react@19.2.14)(debug@4.4.3)
'@sanity/types': 3.99.0(@types/react@19.2.14)(debug@4.4.3) '@sanity/types': 3.99.0(@types/react@19.2.14)(debug@4.4.3)
'@xstate/react': 6.1.0(@types/react@19.2.14)(react@18.3.1)(xstate@5.30.0) '@xstate/react': 6.1.0(@types/react@19.2.14)(react@19.2.4)(xstate@5.30.0)
debug: 4.4.3(supports-color@8.1.1) debug: 4.4.3(supports-color@8.1.1)
get-random-values-esm: 1.0.2 get-random-values-esm: 1.0.2
immer: 10.2.0 immer: 10.2.0
lodash: 4.17.23 lodash: 4.17.23
lodash.startcase: 4.4.0 lodash.startcase: 4.4.0
react: 18.3.1 react: 19.2.4
react-compiler-runtime: 19.1.0-rc.2(react@18.3.1) react-compiler-runtime: 19.1.0-rc.2(react@19.2.4)
rxjs: 7.8.2 rxjs: 7.8.2
slate: 0.117.2 slate: 0.117.2
slate-dom: 0.116.0(slate@0.117.2) slate-dom: 0.116.0(slate@0.117.2)
slate-react: 0.117.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate-dom@0.116.0(slate@0.117.2))(slate@0.117.2) slate-react: 0.117.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(slate-dom@0.116.0(slate@0.117.2))(slate@0.117.2)
use-effect-event: 1.0.2(react@18.3.1) use-effect-event: 1.0.2(react@19.2.4)
xstate: 5.30.0 xstate: 5.30.0
transitivePeerDependencies: transitivePeerDependencies:
- '@types/react' - '@types/react'
@ -6866,11 +6876,11 @@ snapshots:
'@sanity/diff-match-patch': 3.2.0 '@sanity/diff-match-patch': 3.2.0
lodash: 4.17.23 lodash: 4.17.23
'@portabletext/react@3.2.4(react@18.3.1)': '@portabletext/react@3.2.4(react@19.2.4)':
dependencies: dependencies:
'@portabletext/toolkit': 2.0.18 '@portabletext/toolkit': 2.0.18
'@portabletext/types': 2.0.15 '@portabletext/types': 2.0.15
react: 18.3.1 react: 19.2.4
'@portabletext/to-html@2.0.17': '@portabletext/to-html@2.0.17':
dependencies: dependencies:
@ -6883,16 +6893,16 @@ snapshots:
'@portabletext/types@2.0.15': {} '@portabletext/types@2.0.15': {}
'@rexxars/react-json-inspector@9.0.1(react@18.3.1)': '@rexxars/react-json-inspector@9.0.1(react@19.2.4)':
dependencies: dependencies:
debounce: 1.2.1 debounce: 1.2.1
md5-o-matic: 0.1.1 md5-o-matic: 0.1.1
react: 18.3.1 react: 19.2.4
'@rexxars/react-split-pane@1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': '@rexxars/react-split-pane@1.0.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies: dependencies:
react: 18.3.1 react: 19.2.4
react-dom: 18.3.1(react@18.3.1) react-dom: 19.2.4(react@19.2.4)
'@rolldown/pluginutils@1.0.0-beta.27': {} '@rolldown/pluginutils@1.0.0-beta.27': {}
@ -6980,13 +6990,13 @@ snapshots:
nanoid: 3.3.11 nanoid: 3.3.11
rxjs: 7.8.2 rxjs: 7.8.2
'@sanity/cli@3.99.0(@types/node@25.5.0)(@types/react@19.2.14)(react@18.3.1)(typescript@5.9.3)(yaml@2.8.3)': '@sanity/cli@3.99.0(@types/node@25.5.0)(@types/react@19.2.14)(react@19.2.4)(typescript@5.9.3)(yaml@2.8.3)':
dependencies: dependencies:
'@babel/traverse': 7.29.0 '@babel/traverse': 7.29.0
'@sanity/client': 7.20.0(debug@4.4.3) '@sanity/client': 7.20.0(debug@4.4.3)
'@sanity/codegen': 3.99.0 '@sanity/codegen': 3.99.0
'@sanity/runtime-cli': 9.2.0(@types/node@25.5.0)(debug@4.4.3)(typescript@5.9.3)(yaml@2.8.3) '@sanity/runtime-cli': 9.2.0(@types/node@25.5.0)(debug@4.4.3)(typescript@5.9.3)(yaml@2.8.3)
'@sanity/telemetry': 0.8.1(react@18.3.1) '@sanity/telemetry': 0.8.1(react@19.2.4)
'@sanity/template-validator': 2.4.6 '@sanity/template-validator': 2.4.6
'@sanity/util': 3.99.0(@types/react@19.2.14)(debug@4.4.3) '@sanity/util': 3.99.0(@types/react@19.2.14)(debug@4.4.3)
chalk: 4.1.2 chalk: 4.1.2
@ -7129,9 +7139,9 @@ snapshots:
'@sanity/generate-help-url@3.0.1': {} '@sanity/generate-help-url@3.0.1': {}
'@sanity/icons@3.7.4(react@18.3.1)': '@sanity/icons@3.7.4(react@19.2.4)':
dependencies: dependencies:
react: 18.3.1 react: 19.2.4
'@sanity/id-utils@1.0.0': '@sanity/id-utils@1.0.0':
dependencies: dependencies:
@ -7168,24 +7178,24 @@ snapshots:
- '@types/react' - '@types/react'
- supports-color - supports-color
'@sanity/insert-menu@1.1.13(@emotion/is-prop-valid@1.4.0)(@sanity/types@3.99.0(@types/react@19.2.14)(debug@4.4.3))(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)(styled-components@6.3.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1))': '@sanity/insert-menu@1.1.13(@emotion/is-prop-valid@1.4.0)(@sanity/types@3.99.0(@types/react@19.2.14)(debug@4.4.3))(react-dom@19.2.4(react@19.2.4))(react-is@18.3.1)(react@19.2.4)(styled-components@6.3.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4))':
dependencies: dependencies:
'@sanity/icons': 3.7.4(react@18.3.1) '@sanity/icons': 3.7.4(react@19.2.4)
'@sanity/types': 3.99.0(@types/react@19.2.14)(debug@4.4.3) '@sanity/types': 3.99.0(@types/react@19.2.14)(debug@4.4.3)
'@sanity/ui': 2.16.22(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)(styled-components@6.3.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) '@sanity/ui': 2.16.22(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.4(react@19.2.4))(react-is@18.3.1)(react@19.2.4)(styled-components@6.3.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4))
lodash: 4.17.23 lodash: 4.17.23
react: 18.3.1 react: 19.2.4
react-compiler-runtime: 19.1.0-rc.2(react@18.3.1) react-compiler-runtime: 19.1.0-rc.2(react@19.2.4)
react-dom: 18.3.1(react@18.3.1) react-dom: 19.2.4(react@19.2.4)
react-is: 18.3.1 react-is: 18.3.1
transitivePeerDependencies: transitivePeerDependencies:
- '@emotion/is-prop-valid' - '@emotion/is-prop-valid'
- styled-components - styled-components
'@sanity/logos@2.2.2(react@18.3.1)': '@sanity/logos@2.2.2(react@19.2.4)':
dependencies: dependencies:
'@sanity/color': 3.0.6 '@sanity/color': 3.0.6
react: 18.3.1 react: 19.2.4
'@sanity/media-library-types@1.2.0': {} '@sanity/media-library-types@1.2.0': {}
@ -7240,13 +7250,13 @@ snapshots:
- '@sanity/client' - '@sanity/client'
- '@sanity/types' - '@sanity/types'
'@sanity/preview-url-secret@2.1.16(@sanity/client@7.20.0(debug@4.4.3))(@sanity/icons@3.7.4(react@18.3.1))(sanity@3.99.0(@emotion/is-prop-valid@1.4.0)(@types/node@25.5.0)(@types/react@19.2.14)(immer@10.2.0)(jiti@2.6.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(styled-components@6.3.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.9.3)(yaml@2.8.3))': '@sanity/preview-url-secret@2.1.16(@sanity/client@7.20.0(debug@4.4.3))(@sanity/icons@3.7.4(react@19.2.4))(sanity@3.99.0(@emotion/is-prop-valid@1.4.0)(@types/node@25.5.0)(@types/react@19.2.14)(immer@10.2.0)(jiti@2.6.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(styled-components@6.3.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(yaml@2.8.3))':
dependencies: dependencies:
'@sanity/client': 7.20.0(debug@4.4.3) '@sanity/client': 7.20.0(debug@4.4.3)
'@sanity/uuid': 3.0.2 '@sanity/uuid': 3.0.2
optionalDependencies: optionalDependencies:
'@sanity/icons': 3.7.4(react@18.3.1) '@sanity/icons': 3.7.4(react@19.2.4)
sanity: 3.99.0(@emotion/is-prop-valid@1.4.0)(@types/node@25.5.0)(@types/react@19.2.14)(immer@10.2.0)(jiti@2.6.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(styled-components@6.3.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.9.3)(yaml@2.8.3) sanity: 3.99.0(@emotion/is-prop-valid@1.4.0)(@types/node@25.5.0)(@types/react@19.2.14)(immer@10.2.0)(jiti@2.6.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(styled-components@6.3.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(yaml@2.8.3)
'@sanity/runtime-cli@9.2.0(@types/node@25.5.0)(debug@4.4.3)(typescript@5.9.3)(yaml@2.8.3)': '@sanity/runtime-cli@9.2.0(@types/node@25.5.0)(debug@4.4.3)(typescript@5.9.3)(yaml@2.8.3)':
dependencies: dependencies:
@ -7307,7 +7317,7 @@ snapshots:
- debug - debug
- supports-color - supports-color
'@sanity/sdk@0.0.0-alpha.25(@types/react@19.2.14)(debug@4.4.3)(immer@10.2.0)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1))': '@sanity/sdk@0.0.0-alpha.25(@types/react@19.2.14)(debug@4.4.3)(immer@10.2.0)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4))':
dependencies: dependencies:
'@sanity/client': 6.29.1(debug@4.4.3) '@sanity/client': 6.29.1(debug@4.4.3)
'@sanity/comlink': 3.1.1 '@sanity/comlink': 3.1.1
@ -7318,7 +7328,7 @@ snapshots:
lodash-es: 4.17.23 lodash-es: 4.17.23
reselect: 5.1.1 reselect: 5.1.1
rxjs: 7.8.2 rxjs: 7.8.2
zustand: 5.0.12(@types/react@19.2.14)(immer@10.2.0)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1)) zustand: 5.0.12(@types/react@19.2.14)(immer@10.2.0)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4))
transitivePeerDependencies: transitivePeerDependencies:
- '@types/react' - '@types/react'
- debug - debug
@ -7326,10 +7336,10 @@ snapshots:
- react - react
- use-sync-external-store - use-sync-external-store
'@sanity/telemetry@0.8.1(react@18.3.1)': '@sanity/telemetry@0.8.1(react@19.2.4)':
dependencies: dependencies:
lodash: 4.17.23 lodash: 4.17.23
react: 18.3.1 react: 19.2.4
rxjs: 7.8.2 rxjs: 7.8.2
typeid-js: 0.3.0 typeid-js: 0.3.0
@ -7354,21 +7364,21 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- debug - debug
'@sanity/ui@2.16.22(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)(styled-components@6.3.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1))': '@sanity/ui@2.16.22(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.4(react@19.2.4))(react-is@18.3.1)(react@19.2.4)(styled-components@6.3.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4))':
dependencies: dependencies:
'@floating-ui/react-dom': 2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@floating-ui/react-dom': 2.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@juggle/resize-observer': 3.4.0 '@juggle/resize-observer': 3.4.0
'@sanity/color': 3.0.6 '@sanity/color': 3.0.6
'@sanity/icons': 3.7.4(react@18.3.1) '@sanity/icons': 3.7.4(react@19.2.4)
csstype: 3.2.3 csstype: 3.2.3
motion: 12.38.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) motion: 12.38.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
react: 18.3.1 react: 19.2.4
react-compiler-runtime: 1.0.0(react@18.3.1) react-compiler-runtime: 1.0.0(react@19.2.4)
react-dom: 18.3.1(react@18.3.1) react-dom: 19.2.4(react@19.2.4)
react-is: 18.3.1 react-is: 18.3.1
react-refractor: 2.2.0(react@18.3.1) react-refractor: 2.2.0(react@19.2.4)
styled-components: 6.3.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1) styled-components: 6.3.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
use-effect-event: 2.0.3(react@18.3.1) use-effect-event: 2.0.3(react@19.2.4)
transitivePeerDependencies: transitivePeerDependencies:
- '@emotion/is-prop-valid' - '@emotion/is-prop-valid'
@ -7401,7 +7411,7 @@ snapshots:
'@types/uuid': 8.3.4 '@types/uuid': 8.3.4
uuid: 8.3.2 uuid: 8.3.2
'@sanity/vision@3.99.0(@babel/runtime@7.29.2)(@codemirror/lint@6.9.5)(@codemirror/theme-one-dark@6.1.3)(@emotion/is-prop-valid@1.4.0)(codemirror@6.0.2)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)(styled-components@6.3.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1))': '@sanity/vision@3.99.0(@babel/runtime@7.29.2)(@codemirror/lint@6.9.5)(@codemirror/theme-one-dark@6.1.3)(@emotion/is-prop-valid@1.4.0)(codemirror@6.0.2)(react-dom@19.2.4(react@19.2.4))(react-is@18.3.1)(react@19.2.4)(styled-components@6.3.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4))':
dependencies: dependencies:
'@codemirror/autocomplete': 6.20.1 '@codemirror/autocomplete': 6.20.1
'@codemirror/commands': 6.10.3 '@codemirror/commands': 6.10.3
@ -7412,25 +7422,25 @@ snapshots:
'@codemirror/view': 6.40.0 '@codemirror/view': 6.40.0
'@juggle/resize-observer': 3.4.0 '@juggle/resize-observer': 3.4.0
'@lezer/highlight': 1.2.3 '@lezer/highlight': 1.2.3
'@rexxars/react-json-inspector': 9.0.1(react@18.3.1) '@rexxars/react-json-inspector': 9.0.1(react@19.2.4)
'@rexxars/react-split-pane': 1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@rexxars/react-split-pane': 1.0.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@sanity/color': 3.0.6 '@sanity/color': 3.0.6
'@sanity/icons': 3.7.4(react@18.3.1) '@sanity/icons': 3.7.4(react@19.2.4)
'@sanity/ui': 2.16.22(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)(styled-components@6.3.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) '@sanity/ui': 2.16.22(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.4(react@19.2.4))(react-is@18.3.1)(react@19.2.4)(styled-components@6.3.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4))
'@sanity/uuid': 3.0.2 '@sanity/uuid': 3.0.2
'@uiw/react-codemirror': 4.25.9(@babel/runtime@7.29.2)(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.5)(@codemirror/search@6.6.0)(@codemirror/state@6.6.0)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.40.0)(codemirror@6.0.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@uiw/react-codemirror': 4.25.9(@babel/runtime@7.29.2)(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.5)(@codemirror/search@6.6.0)(@codemirror/state@6.6.0)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.40.0)(codemirror@6.0.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
is-hotkey-esm: 1.0.0 is-hotkey-esm: 1.0.0
json-2-csv: 5.5.10 json-2-csv: 5.5.10
json5: 2.2.3 json5: 2.2.3
lodash: 4.17.23 lodash: 4.17.23
quick-lru: 5.1.1 quick-lru: 5.1.1
react: 18.3.1 react: 19.2.4
react-compiler-runtime: 19.1.0-rc.2(react@18.3.1) react-compiler-runtime: 19.1.0-rc.2(react@19.2.4)
react-fast-compare: 3.2.2 react-fast-compare: 3.2.2
react-rx: 4.2.2(react@18.3.1)(rxjs@7.8.2) react-rx: 4.2.2(react@19.2.4)(rxjs@7.8.2)
rxjs: 7.8.2 rxjs: 7.8.2
styled-components: 6.3.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1) styled-components: 6.3.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
use-effect-event: 2.0.3(react@18.3.1) use-effect-event: 2.0.3(react@19.2.4)
transitivePeerDependencies: transitivePeerDependencies:
- '@babel/runtime' - '@babel/runtime'
- '@codemirror/lint' - '@codemirror/lint'
@ -7474,24 +7484,24 @@ snapshots:
'@sentry/core@8.55.1': {} '@sentry/core@8.55.1': {}
'@sentry/react@8.55.1(react@18.3.1)': '@sentry/react@8.55.1(react@19.2.4)':
dependencies: dependencies:
'@sentry/browser': 8.55.1 '@sentry/browser': 8.55.1
'@sentry/core': 8.55.1 '@sentry/core': 8.55.1
hoist-non-react-statics: 3.3.2 hoist-non-react-statics: 3.3.2
react: 18.3.1 react: 19.2.4
'@tanstack/react-table@8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': '@tanstack/react-table@8.21.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies: dependencies:
'@tanstack/table-core': 8.21.3 '@tanstack/table-core': 8.21.3
react: 18.3.1 react: 19.2.4
react-dom: 18.3.1(react@18.3.1) react-dom: 19.2.4(react@19.2.4)
'@tanstack/react-virtual@3.13.23(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': '@tanstack/react-virtual@3.13.23(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies: dependencies:
'@tanstack/virtual-core': 3.13.23 '@tanstack/virtual-core': 3.13.23
react: 18.3.1 react: 19.2.4
react-dom: 18.3.1(react@18.3.1) react-dom: 19.2.4(react@19.2.4)
'@tanstack/table-core@8.21.3': {} '@tanstack/table-core@8.21.3': {}
@ -7666,7 +7676,7 @@ snapshots:
'@codemirror/state': 6.6.0 '@codemirror/state': 6.6.0
'@codemirror/view': 6.40.0 '@codemirror/view': 6.40.0
'@uiw/react-codemirror@4.25.9(@babel/runtime@7.29.2)(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.5)(@codemirror/search@6.6.0)(@codemirror/state@6.6.0)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.40.0)(codemirror@6.0.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': '@uiw/react-codemirror@4.25.9(@babel/runtime@7.29.2)(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.5)(@codemirror/search@6.6.0)(@codemirror/state@6.6.0)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.40.0)(codemirror@6.0.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies: dependencies:
'@babel/runtime': 7.29.2 '@babel/runtime': 7.29.2
'@codemirror/commands': 6.10.3 '@codemirror/commands': 6.10.3
@ -7675,8 +7685,8 @@ snapshots:
'@codemirror/view': 6.40.0 '@codemirror/view': 6.40.0
'@uiw/codemirror-extensions-basic-setup': 4.25.9(@codemirror/autocomplete@6.20.1)(@codemirror/commands@6.10.3)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.5)(@codemirror/search@6.6.0)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0) '@uiw/codemirror-extensions-basic-setup': 4.25.9(@codemirror/autocomplete@6.20.1)(@codemirror/commands@6.10.3)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.5)(@codemirror/search@6.6.0)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)
codemirror: 6.0.2 codemirror: 6.0.2
react: 18.3.1 react: 19.2.4
react-dom: 18.3.1(react@18.3.1) react-dom: 19.2.4(react@19.2.4)
transitivePeerDependencies: transitivePeerDependencies:
- '@codemirror/autocomplete' - '@codemirror/autocomplete'
- '@codemirror/language' - '@codemirror/language'
@ -7699,11 +7709,11 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@xstate/react@6.1.0(@types/react@19.2.14)(react@18.3.1)(xstate@5.30.0)': '@xstate/react@6.1.0(@types/react@19.2.14)(react@19.2.4)(xstate@5.30.0)':
dependencies: dependencies:
react: 18.3.1 react: 19.2.4
use-isomorphic-layout-effect: 1.2.1(@types/react@19.2.14)(react@18.3.1) use-isomorphic-layout-effect: 1.2.1(@types/react@19.2.14)(react@19.2.4)
use-sync-external-store: 1.6.0(react@18.3.1) use-sync-external-store: 1.6.0(react@19.2.4)
optionalDependencies: optionalDependencies:
xstate: 5.30.0 xstate: 5.30.0
transitivePeerDependencies: transitivePeerDependencies:
@ -8063,9 +8073,9 @@ snapshots:
dependencies: dependencies:
custom-media-element: 1.4.6 custom-media-element: 1.4.6
ce-la-react@0.3.2(react@18.3.1): ce-la-react@0.3.2(react@19.2.4):
dependencies: dependencies:
react: 18.3.1 react: 19.2.4
chalk@2.4.2: chalk@2.4.2:
dependencies: dependencies:
@ -8144,7 +8154,7 @@ snapshots:
'@codemirror/lint': 6.9.5 '@codemirror/lint': 6.9.5
'@codemirror/search': 6.6.0 '@codemirror/search': 6.6.0
'@codemirror/state': 6.6.0 '@codemirror/state': 6.6.0
'@codemirror/view': 6.40.0 '@codemirror/view': 6.41.0
color-convert@1.9.3: color-convert@1.9.3:
dependencies: dependencies:
@ -8927,15 +8937,15 @@ snapshots:
hasown: 2.0.2 hasown: 2.0.2
mime-types: 2.1.35 mime-types: 2.1.35
framer-motion@12.38.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): framer-motion@12.38.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
dependencies: dependencies:
motion-dom: 12.38.0 motion-dom: 12.38.0
motion-utils: 12.36.0 motion-utils: 12.36.0
tslib: 2.8.1 tslib: 2.8.1
optionalDependencies: optionalDependencies:
'@emotion/is-prop-valid': 1.4.0 '@emotion/is-prop-valid': 1.4.0
react: 18.3.1 react: 19.2.4
react-dom: 18.3.1(react@18.3.1) react-dom: 19.2.4(react@19.2.4)
from2@2.3.0: from2@2.3.0:
dependencies: dependencies:
@ -9716,22 +9726,22 @@ snapshots:
mdn-data@2.27.1: {} mdn-data@2.27.1: {}
media-chrome@4.11.1(react@18.3.1): media-chrome@4.11.1(react@19.2.4):
dependencies: dependencies:
'@vercel/edge': 1.2.2 '@vercel/edge': 1.2.2
ce-la-react: 0.3.2(react@18.3.1) ce-la-react: 0.3.2(react@19.2.4)
transitivePeerDependencies: transitivePeerDependencies:
- react - react
media-chrome@4.16.1(react@18.3.1): media-chrome@4.16.1(react@19.2.4):
dependencies: dependencies:
ce-la-react: 0.3.2(react@18.3.1) ce-la-react: 0.3.2(react@19.2.4)
transitivePeerDependencies: transitivePeerDependencies:
- react - react
media-chrome@4.18.3(react@18.3.1): media-chrome@4.18.3(react@19.2.4):
dependencies: dependencies:
ce-la-react: 0.3.2(react@18.3.1) ce-la-react: 0.3.2(react@19.2.4)
transitivePeerDependencies: transitivePeerDependencies:
- react - react
@ -9842,14 +9852,14 @@ snapshots:
motion-utils@12.36.0: {} motion-utils@12.36.0: {}
motion@12.38.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): motion@12.38.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
dependencies: dependencies:
framer-motion: 12.38.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) framer-motion: 12.38.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
tslib: 2.8.1 tslib: 2.8.1
optionalDependencies: optionalDependencies:
'@emotion/is-prop-valid': 1.4.0 '@emotion/is-prop-valid': 1.4.0
react: 18.3.1 react: 19.2.4
react-dom: 18.3.1(react@18.3.1) react-dom: 19.2.4(react@19.2.4)
ms@2.0.0: {} ms@2.0.0: {}
@ -10152,15 +10162,15 @@ snapshots:
dependencies: dependencies:
find-up: 5.0.0 find-up: 5.0.0
player.style@0.1.10(react@18.3.1): player.style@0.1.10(react@19.2.4):
dependencies: dependencies:
media-chrome: 4.11.1(react@18.3.1) media-chrome: 4.11.1(react@19.2.4)
transitivePeerDependencies: transitivePeerDependencies:
- react - react
player.style@0.3.1(react@18.3.1): player.style@0.3.1(react@19.2.4):
dependencies: dependencies:
media-chrome: 4.16.1(react@18.3.1) media-chrome: 4.16.1(react@19.2.4)
transitivePeerDependencies: transitivePeerDependencies:
- react - react
@ -10250,72 +10260,69 @@ snapshots:
dependencies: dependencies:
performance-now: 2.1.0 performance-now: 2.1.0
react-clientside-effect@1.2.8(react@18.3.1): react-clientside-effect@1.2.8(react@19.2.4):
dependencies: dependencies:
'@babel/runtime': 7.29.2 '@babel/runtime': 7.29.2
react: 18.3.1 react: 19.2.4
react-compiler-runtime@1.0.0(react@18.3.1): react-compiler-runtime@1.0.0(react@19.2.4):
dependencies: dependencies:
react: 18.3.1 react: 19.2.4
react-compiler-runtime@19.1.0-rc.2(react@18.3.1): react-compiler-runtime@19.1.0-rc.2(react@19.2.4):
dependencies: dependencies:
react: 18.3.1 react: 19.2.4
react-dom@18.3.1(react@18.3.1): react-dom@19.2.4(react@19.2.4):
dependencies: dependencies:
loose-envify: 1.4.0 react: 19.2.4
react: 18.3.1 scheduler: 0.27.0
scheduler: 0.23.2
react-fast-compare@3.2.2: {} react-fast-compare@3.2.2: {}
react-focus-lock@2.13.7(@types/react@19.2.14)(react@18.3.1): react-focus-lock@2.13.7(@types/react@19.2.14)(react@19.2.4):
dependencies: dependencies:
'@babel/runtime': 7.29.2 '@babel/runtime': 7.29.2
focus-lock: 1.3.6 focus-lock: 1.3.6
prop-types: 15.8.1 prop-types: 15.8.1
react: 18.3.1 react: 19.2.4
react-clientside-effect: 1.2.8(react@18.3.1) react-clientside-effect: 1.2.8(react@19.2.4)
use-callback-ref: 1.3.3(@types/react@19.2.14)(react@18.3.1) use-callback-ref: 1.3.3(@types/react@19.2.14)(react@19.2.4)
use-sidecar: 1.1.3(@types/react@19.2.14)(react@18.3.1) use-sidecar: 1.1.3(@types/react@19.2.14)(react@19.2.4)
optionalDependencies: optionalDependencies:
'@types/react': 19.2.14 '@types/react': 19.2.14
react-i18next@14.0.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): react-i18next@14.0.2(i18next@23.16.8)(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
dependencies: dependencies:
'@babel/runtime': 7.29.2 '@babel/runtime': 7.29.2
html-parse-stringify: 3.0.1 html-parse-stringify: 3.0.1
i18next: 23.16.8 i18next: 23.16.8
react: 18.3.1 react: 19.2.4
optionalDependencies: optionalDependencies:
react-dom: 18.3.1(react@18.3.1) react-dom: 19.2.4(react@19.2.4)
react-is@16.13.1: {} react-is@16.13.1: {}
react-is@18.3.1: {} react-is@18.3.1: {}
react-refractor@2.2.0(react@18.3.1): react-refractor@2.2.0(react@19.2.4):
dependencies: dependencies:
react: 18.3.1 react: 19.2.4
refractor: 3.6.0 refractor: 3.6.0
unist-util-filter: 2.0.3 unist-util-filter: 2.0.3
unist-util-visit-parents: 3.1.1 unist-util-visit-parents: 3.1.1
react-refresh@0.17.0: {} react-refresh@0.17.0: {}
react-rx@4.2.2(react@18.3.1)(rxjs@7.8.2): react-rx@4.2.2(react@19.2.4)(rxjs@7.8.2):
dependencies: dependencies:
observable-callback: 1.0.3(rxjs@7.8.2) observable-callback: 1.0.3(rxjs@7.8.2)
react: 18.3.1 react: 19.2.4
react-compiler-runtime: 1.0.0(react@18.3.1) react-compiler-runtime: 1.0.0(react@19.2.4)
rxjs: 7.8.2 rxjs: 7.8.2
use-effect-event: 2.0.3(react@18.3.1) use-effect-event: 2.0.3(react@19.2.4)
react@18.3.1: react@19.2.4: {}
dependencies:
loose-envify: 1.4.0
read-pkg-up@7.0.1: read-pkg-up@7.0.1:
dependencies: dependencies:
@ -10561,22 +10568,22 @@ snapshots:
safer-buffer@2.1.2: {} safer-buffer@2.1.2: {}
sanity@3.99.0(@emotion/is-prop-valid@1.4.0)(@types/node@25.5.0)(@types/react@19.2.14)(immer@10.2.0)(jiti@2.6.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(styled-components@6.3.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.9.3)(yaml@2.8.3): sanity@3.99.0(@emotion/is-prop-valid@1.4.0)(@types/node@25.5.0)(@types/react@19.2.14)(immer@10.2.0)(jiti@2.6.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(styled-components@6.3.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(yaml@2.8.3):
dependencies: dependencies:
'@dnd-kit/core': 6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@dnd-kit/core': 6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@dnd-kit/modifiers': 6.0.1(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) '@dnd-kit/modifiers': 6.0.1(@dnd-kit/core@6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)
'@dnd-kit/sortable': 7.0.2(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) '@dnd-kit/sortable': 7.0.2(@dnd-kit/core@6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)
'@dnd-kit/utilities': 3.2.2(react@18.3.1) '@dnd-kit/utilities': 3.2.2(react@19.2.4)
'@juggle/resize-observer': 3.4.0 '@juggle/resize-observer': 3.4.0
'@mux/mux-player-react': 3.11.7(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mux/mux-player-react': 3.11.7(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@portabletext/block-tools': 1.1.38(@sanity/types@3.99.0(@types/react@19.2.14)(debug@4.4.3))(@types/react@19.2.14) '@portabletext/block-tools': 1.1.38(@sanity/types@3.99.0(@types/react@19.2.14)(debug@4.4.3))(@types/react@19.2.14)
'@portabletext/editor': 1.58.1(@sanity/schema@3.99.0(@types/react@19.2.14)(debug@4.4.3))(@sanity/types@3.99.0(@types/react@19.2.14)(debug@4.4.3))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rxjs@7.8.2) '@portabletext/editor': 1.58.1(@sanity/schema@3.99.0(@types/react@19.2.14)(debug@4.4.3))(@sanity/types@3.99.0(@types/react@19.2.14)(debug@4.4.3))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)
'@portabletext/react': 3.2.4(react@18.3.1) '@portabletext/react': 3.2.4(react@19.2.4)
'@portabletext/toolkit': 2.0.18 '@portabletext/toolkit': 2.0.18
'@rexxars/react-json-inspector': 9.0.1(react@18.3.1) '@rexxars/react-json-inspector': 9.0.1(react@19.2.4)
'@sanity/asset-utils': 2.3.0 '@sanity/asset-utils': 2.3.0
'@sanity/bifur-client': 0.4.1 '@sanity/bifur-client': 0.4.1
'@sanity/cli': 3.99.0(@types/node@25.5.0)(@types/react@19.2.14)(react@18.3.1)(typescript@5.9.3)(yaml@2.8.3) '@sanity/cli': 3.99.0(@types/node@25.5.0)(@types/react@19.2.14)(react@19.2.4)(typescript@5.9.3)(yaml@2.8.3)
'@sanity/client': 7.20.0(debug@4.4.3) '@sanity/client': 7.20.0(debug@4.4.3)
'@sanity/color': 3.0.6 '@sanity/color': 3.0.6
'@sanity/comlink': 3.1.1 '@sanity/comlink': 3.1.1
@ -10585,28 +10592,28 @@ snapshots:
'@sanity/diff-patch': 5.0.0 '@sanity/diff-patch': 5.0.0
'@sanity/eventsource': 5.0.2 '@sanity/eventsource': 5.0.2
'@sanity/export': 3.45.3(@types/react@19.2.14) '@sanity/export': 3.45.3(@types/react@19.2.14)
'@sanity/icons': 3.7.4(react@18.3.1) '@sanity/icons': 3.7.4(react@19.2.4)
'@sanity/id-utils': 1.0.0 '@sanity/id-utils': 1.0.0
'@sanity/image-url': 1.2.0 '@sanity/image-url': 1.2.0
'@sanity/import': 3.38.3(@types/react@19.2.14) '@sanity/import': 3.38.3(@types/react@19.2.14)
'@sanity/insert-menu': 1.1.13(@emotion/is-prop-valid@1.4.0)(@sanity/types@3.99.0(@types/react@19.2.14)(debug@4.4.3))(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)(styled-components@6.3.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) '@sanity/insert-menu': 1.1.13(@emotion/is-prop-valid@1.4.0)(@sanity/types@3.99.0(@types/react@19.2.14)(debug@4.4.3))(react-dom@19.2.4(react@19.2.4))(react-is@18.3.1)(react@19.2.4)(styled-components@6.3.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4))
'@sanity/logos': 2.2.2(react@18.3.1) '@sanity/logos': 2.2.2(react@19.2.4)
'@sanity/media-library-types': 1.2.0 '@sanity/media-library-types': 1.2.0
'@sanity/message-protocol': 0.13.3 '@sanity/message-protocol': 0.13.3
'@sanity/migrate': 3.99.0(@types/react@19.2.14) '@sanity/migrate': 3.99.0(@types/react@19.2.14)
'@sanity/mutator': 3.99.0(@types/react@19.2.14) '@sanity/mutator': 3.99.0(@types/react@19.2.14)
'@sanity/presentation-comlink': 1.0.33(@sanity/client@7.20.0(debug@4.4.3))(@sanity/types@3.99.0(@types/react@19.2.14)(debug@4.4.3)) '@sanity/presentation-comlink': 1.0.33(@sanity/client@7.20.0(debug@4.4.3))(@sanity/types@3.99.0(@types/react@19.2.14)(debug@4.4.3))
'@sanity/preview-url-secret': 2.1.16(@sanity/client@7.20.0(debug@4.4.3))(@sanity/icons@3.7.4(react@18.3.1))(sanity@3.99.0(@emotion/is-prop-valid@1.4.0)(@types/node@25.5.0)(@types/react@19.2.14)(immer@10.2.0)(jiti@2.6.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(styled-components@6.3.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.9.3)(yaml@2.8.3)) '@sanity/preview-url-secret': 2.1.16(@sanity/client@7.20.0(debug@4.4.3))(@sanity/icons@3.7.4(react@19.2.4))(sanity@3.99.0(@emotion/is-prop-valid@1.4.0)(@types/node@25.5.0)(@types/react@19.2.14)(immer@10.2.0)(jiti@2.6.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(styled-components@6.3.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(yaml@2.8.3))
'@sanity/schema': 3.99.0(@types/react@19.2.14)(debug@4.4.3) '@sanity/schema': 3.99.0(@types/react@19.2.14)(debug@4.4.3)
'@sanity/sdk': 0.0.0-alpha.25(@types/react@19.2.14)(debug@4.4.3)(immer@10.2.0)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1)) '@sanity/sdk': 0.0.0-alpha.25(@types/react@19.2.14)(debug@4.4.3)(immer@10.2.0)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4))
'@sanity/telemetry': 0.8.1(react@18.3.1) '@sanity/telemetry': 0.8.1(react@19.2.4)
'@sanity/types': 3.99.0(@types/react@19.2.14)(debug@4.4.3) '@sanity/types': 3.99.0(@types/react@19.2.14)(debug@4.4.3)
'@sanity/ui': 2.16.22(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)(styled-components@6.3.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) '@sanity/ui': 2.16.22(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.4(react@19.2.4))(react-is@18.3.1)(react@19.2.4)(styled-components@6.3.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4))
'@sanity/util': 3.99.0(@types/react@19.2.14)(debug@4.4.3) '@sanity/util': 3.99.0(@types/react@19.2.14)(debug@4.4.3)
'@sanity/uuid': 3.0.2 '@sanity/uuid': 3.0.2
'@sentry/react': 8.55.1(react@18.3.1) '@sentry/react': 8.55.1(react@19.2.4)
'@tanstack/react-table': 8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tanstack/react-table': 8.21.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@tanstack/react-virtual': 3.13.23(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tanstack/react-virtual': 3.13.23(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@types/react-is': 19.2.0 '@types/react-is': 19.2.0
'@types/shallow-equals': 1.0.3 '@types/shallow-equals': 1.0.3
'@types/speakingurl': 13.0.6 '@types/speakingurl': 13.0.6
@ -10614,7 +10621,7 @@ snapshots:
'@types/use-sync-external-store': 1.5.0 '@types/use-sync-external-store': 1.5.0
'@types/which': 3.0.4 '@types/which': 3.0.4
'@vitejs/plugin-react': 4.7.0(vite@6.4.1(@types/node@25.5.0)(jiti@2.6.1)(yaml@2.8.3)) '@vitejs/plugin-react': 4.7.0(vite@6.4.1(@types/node@25.5.0)(jiti@2.6.1)(yaml@2.8.3))
'@xstate/react': 6.1.0(@types/react@19.2.14)(react@18.3.1)(xstate@5.30.0) '@xstate/react': 6.1.0(@types/react@19.2.14)(react@19.2.4)(xstate@5.30.0)
archiver: 7.0.1 archiver: 7.0.1
arrify: 2.0.1 arrify: 2.0.1
async-mutex: 0.4.1 async-mutex: 0.4.1
@ -10633,7 +10640,7 @@ snapshots:
exif-component: 1.0.1 exif-component: 1.0.1
fast-deep-equal: 3.1.3 fast-deep-equal: 3.1.3
form-data: 4.0.5 form-data: 4.0.5
framer-motion: 12.38.0(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) framer-motion: 12.38.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
get-it: 8.7.0(debug@4.4.3) get-it: 8.7.0(debug@4.4.3)
get-random-values-esm: 1.0.2 get-random-values-esm: 1.0.2
groq-js: 1.29.0 groq-js: 1.29.0
@ -10663,22 +10670,22 @@ snapshots:
path-to-regexp: 6.3.0 path-to-regexp: 6.3.0
peek-stream: 1.1.3 peek-stream: 1.1.3
pirates: 4.0.7 pirates: 4.0.7
player.style: 0.1.10(react@18.3.1) player.style: 0.1.10(react@19.2.4)
pluralize-esm: 9.0.5 pluralize-esm: 9.0.5
polished: 4.3.1 polished: 4.3.1
preferred-pm: 4.1.1 preferred-pm: 4.1.1
pretty-ms: 7.0.1 pretty-ms: 7.0.1
quick-lru: 5.1.1 quick-lru: 5.1.1
raf: 3.4.1 raf: 3.4.1
react: 18.3.1 react: 19.2.4
react-compiler-runtime: 19.1.0-rc.2(react@18.3.1) react-compiler-runtime: 19.1.0-rc.2(react@19.2.4)
react-dom: 18.3.1(react@18.3.1) react-dom: 19.2.4(react@19.2.4)
react-fast-compare: 3.2.2 react-fast-compare: 3.2.2
react-focus-lock: 2.13.7(@types/react@19.2.14)(react@18.3.1) react-focus-lock: 2.13.7(@types/react@19.2.14)(react@19.2.4)
react-i18next: 14.0.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-i18next: 14.0.2(i18next@23.16.8)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
react-is: 18.3.1 react-is: 18.3.1
react-refractor: 2.2.0(react@18.3.1) react-refractor: 2.2.0(react@19.2.4)
react-rx: 4.2.2(react@18.3.1)(rxjs@7.8.2) react-rx: 4.2.2(react@19.2.4)(rxjs@7.8.2)
read-pkg-up: 7.0.1 read-pkg-up: 7.0.1
refractor: 3.6.0 refractor: 3.6.0
resolve-from: 5.0.0 resolve-from: 5.0.0
@ -10692,15 +10699,15 @@ snapshots:
semver: 7.7.4 semver: 7.7.4
shallow-equals: 1.0.0 shallow-equals: 1.0.0
speakingurl: 14.0.1 speakingurl: 14.0.1
styled-components: 6.3.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1) styled-components: 6.3.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
tar-fs: 2.1.4 tar-fs: 2.1.4
tar-stream: 3.1.8 tar-stream: 3.1.8
tinyglobby: 0.2.15 tinyglobby: 0.2.15
urlpattern-polyfill: 10.1.0 urlpattern-polyfill: 10.1.0
use-device-pixel-ratio: 1.1.2(react@18.3.1) use-device-pixel-ratio: 1.1.2(react@19.2.4)
use-effect-event: 2.0.3(react@18.3.1) use-effect-event: 2.0.3(react@19.2.4)
use-hot-module-reload: 2.0.0(react@18.3.1) use-hot-module-reload: 2.0.0(react@19.2.4)
use-sync-external-store: 1.6.0(react@18.3.1) use-sync-external-store: 1.6.0(react@19.2.4)
uuid: 11.1.0 uuid: 11.1.0
vite: 6.4.1(@types/node@25.5.0)(jiti@2.6.1)(yaml@2.8.3) vite: 6.4.1(@types/node@25.5.0)(jiti@2.6.1)(yaml@2.8.3)
which: 5.0.0 which: 5.0.0
@ -10737,9 +10744,7 @@ snapshots:
dependencies: dependencies:
xmlchars: 2.2.0 xmlchars: 2.2.0
scheduler@0.23.2: scheduler@0.27.0: {}
dependencies:
loose-envify: 1.4.0
scroll-into-view-if-needed@3.1.0: scroll-into-view-if-needed@3.1.0:
dependencies: dependencies:
@ -10846,14 +10851,14 @@ snapshots:
slate: 0.117.2 slate: 0.117.2
tiny-invariant: 1.3.1 tiny-invariant: 1.3.1
slate-react@0.117.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(slate-dom@0.116.0(slate@0.117.2))(slate@0.117.2): slate-react@0.117.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(slate-dom@0.116.0(slate@0.117.2))(slate@0.117.2):
dependencies: dependencies:
'@juggle/resize-observer': 3.4.0 '@juggle/resize-observer': 3.4.0
direction: 1.0.4 direction: 1.0.4
is-hotkey: 0.2.0 is-hotkey: 0.2.0
lodash: 4.17.23 lodash: 4.17.23
react: 18.3.1 react: 19.2.4
react-dom: 18.3.1(react@18.3.1) react-dom: 19.2.4(react@19.2.4)
scroll-into-view-if-needed: 3.1.0 scroll-into-view-if-needed: 3.1.0
slate: 0.117.2 slate: 0.117.2
slate-dom: 0.116.0(slate@0.117.2) slate-dom: 0.116.0(slate@0.117.2)
@ -11020,7 +11025,7 @@ snapshots:
style-mod@4.1.3: {} style-mod@4.1.3: {}
styled-components@6.3.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1): styled-components@6.3.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
dependencies: dependencies:
'@emotion/is-prop-valid': 1.4.0 '@emotion/is-prop-valid': 1.4.0
'@emotion/unitless': 0.10.0 '@emotion/unitless': 0.10.0
@ -11028,12 +11033,12 @@ snapshots:
css-to-react-native: 3.2.0 css-to-react-native: 3.2.0
csstype: 3.2.3 csstype: 3.2.3
postcss: 8.4.49 postcss: 8.4.49
react: 18.3.1 react: 19.2.4
shallowequal: 1.1.0 shallowequal: 1.1.0
stylis: 4.3.6 stylis: 4.3.6
tslib: 2.8.1 tslib: 2.8.1
optionalDependencies: optionalDependencies:
react-dom: 18.3.1(react@18.3.1) react-dom: 19.2.4(react@19.2.4)
stylis@4.3.6: {} stylis@4.3.6: {}
@ -11326,46 +11331,46 @@ snapshots:
urlpattern-polyfill@10.1.0: {} urlpattern-polyfill@10.1.0: {}
use-callback-ref@1.3.3(@types/react@19.2.14)(react@18.3.1): use-callback-ref@1.3.3(@types/react@19.2.14)(react@19.2.4):
dependencies: dependencies:
react: 18.3.1 react: 19.2.4
tslib: 2.8.1 tslib: 2.8.1
optionalDependencies: optionalDependencies:
'@types/react': 19.2.14 '@types/react': 19.2.14
use-device-pixel-ratio@1.1.2(react@18.3.1): use-device-pixel-ratio@1.1.2(react@19.2.4):
dependencies: dependencies:
react: 18.3.1 react: 19.2.4
use-effect-event@1.0.2(react@18.3.1): use-effect-event@1.0.2(react@19.2.4):
dependencies: dependencies:
react: 18.3.1 react: 19.2.4
use-effect-event@2.0.3(react@18.3.1): use-effect-event@2.0.3(react@19.2.4):
dependencies: dependencies:
react: 18.3.1 react: 19.2.4
use-hot-module-reload@2.0.0(react@18.3.1): use-hot-module-reload@2.0.0(react@19.2.4):
dependencies: dependencies:
react: 18.3.1 react: 19.2.4
use-isomorphic-layout-effect@1.2.1(@types/react@19.2.14)(react@18.3.1): use-isomorphic-layout-effect@1.2.1(@types/react@19.2.14)(react@19.2.4):
dependencies: dependencies:
react: 18.3.1 react: 19.2.4
optionalDependencies: optionalDependencies:
'@types/react': 19.2.14 '@types/react': 19.2.14
use-sidecar@1.1.3(@types/react@19.2.14)(react@18.3.1): use-sidecar@1.1.3(@types/react@19.2.14)(react@19.2.4):
dependencies: dependencies:
detect-node-es: 1.1.0 detect-node-es: 1.1.0
react: 18.3.1 react: 19.2.4
tslib: 2.8.1 tslib: 2.8.1
optionalDependencies: optionalDependencies:
'@types/react': 19.2.14 '@types/react': 19.2.14
use-sync-external-store@1.6.0(react@18.3.1): use-sync-external-store@1.6.0(react@19.2.4):
dependencies: dependencies:
react: 18.3.1 react: 19.2.4
util-deprecate@1.0.2: {} util-deprecate@1.0.2: {}
@ -11593,9 +11598,9 @@ snapshots:
zod@3.25.76: {} zod@3.25.76: {}
zustand@5.0.12(@types/react@19.2.14)(immer@10.2.0)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1)): zustand@5.0.12(@types/react@19.2.14)(immer@10.2.0)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)):
optionalDependencies: optionalDependencies:
'@types/react': 19.2.14 '@types/react': 19.2.14
immer: 10.2.0 immer: 10.2.0
react: 18.3.1 react: 19.2.4
use-sync-external-store: 1.6.0(react@18.3.1) use-sync-external-store: 1.6.0(react@19.2.4)

View File

@ -9,6 +9,25 @@ export default defineConfig({
projectId: 'y821x5qu', projectId: 'y821x5qu',
dataset: 'production', dataset: 'production',
plugins: [structureTool()], plugins: [
structureTool({
structure: (S) =>
S.list()
.title('Contenu')
.items([
S.listItem()
.title('Page d\'accueil')
.id('homePage')
.child(
S.document()
.schemaType('homePage')
.documentId('homePage')
.title('Page d\'accueil')
),
S.divider(),
S.documentTypeListItem('product').title('Produits'),
]),
}),
],
schema: { types: schemaTypes }, schema: { types: schemaTypes },
}) })

167
sanity/schemas/homePage.ts Normal file
View File

@ -0,0 +1,167 @@
import { defineType, defineField } from 'sanity'
export default defineType({
name: 'homePage',
title: 'Page d\'accueil',
type: 'document',
groups: [
{ name: 'hero', title: 'Hero' },
{ name: 'collection', title: 'Collection' },
{ name: 'contact', title: 'Contact / WhatsApp' },
{ name: 'footer', title: 'Footer' },
{ name: 'seo', title: 'SEO' },
],
fields: [
// ── HERO ──────────────────────────────────────────────────
defineField({
name: 'heroLabel',
title: 'Label hero',
type: 'string',
initialValue: '// ARCHIVE_001 — 2026',
group: 'hero',
}),
defineField({
name: 'heroTitle',
title: 'Titre hero',
type: 'string',
initialValue: 'REBOURS STUDIO',
description: 'Utiliser | pour un retour à la ligne (ex: REBOURS|STUDIO)',
group: 'hero',
validation: (rule) => rule.required(),
}),
defineField({
name: 'heroSubtitle',
title: 'Sous-titre hero',
type: 'text',
rows: 2,
initialValue: 'Mobilier d\'art contemporain.\nSpace Age × Memphis.',
group: 'hero',
}),
defineField({
name: 'heroStatus',
title: 'Status hero',
type: 'text',
rows: 2,
initialValue: 'STATUS: [PROTOTYPE EN COURS]\nCOLLECTION_001 — BIENTÔT DISPONIBLE',
group: 'hero',
}),
defineField({
name: 'heroImage',
title: 'Image hero',
type: 'image',
options: { hotspot: true },
description: 'Image principale du hero. Si vide, utilise l\'image du premier produit.',
group: 'hero',
fields: [
defineField({
name: 'alt',
title: 'Texte alternatif',
type: 'string',
}),
],
}),
// ── COLLECTION ────────────────────────────────────────────
defineField({
name: 'collectionLabel',
title: 'Label collection',
type: 'string',
initialValue: '// COLLECTION_001',
group: 'collection',
}),
defineField({
name: 'collectionCta',
title: 'Texte d\'action collection',
type: 'string',
initialValue: 'CLIQUER POUR OUVRIR',
description: 'Affiché après le nombre d\'objets',
group: 'collection',
}),
// ── CONTACT / WHATSAPP ────────────────────────────────────
defineField({
name: 'contactLabel',
title: 'Label contact',
type: 'string',
initialValue: '// CONTACT',
group: 'contact',
}),
defineField({
name: 'contactTitle',
title: 'Titre contact',
type: 'string',
initialValue: 'UNE QUESTION ? PARLONS-EN',
description: 'Utiliser | pour un retour à la ligne',
group: 'contact',
}),
defineField({
name: 'contactDescription',
title: 'Description contact',
type: 'text',
rows: 2,
initialValue: 'Commandes sur mesure, questions techniques,\nou simplement dire bonjour.',
group: 'contact',
}),
defineField({
name: 'whatsappNumber',
title: 'Numéro WhatsApp',
type: 'string',
initialValue: '33651755191',
description: 'Format international sans + (ex: 33612345678)',
group: 'contact',
validation: (rule) => rule.required(),
}),
defineField({
name: 'whatsappButtonText',
title: 'Texte bouton WhatsApp',
type: 'string',
initialValue: 'CONTACTEZ-NOUS SUR WHATSAPP',
group: 'contact',
}),
defineField({
name: 'contactResponseTime',
title: 'Temps de réponse',
type: 'string',
initialValue: 'RÉPONSE SOUS 24H',
group: 'contact',
}),
// ── FOOTER ────────────────────────────────────────────────
defineField({
name: 'footerText',
title: 'Texte footer',
type: 'string',
initialValue: '© 2026 REBOURS STUDIO — PARIS',
group: 'footer',
}),
defineField({
name: 'instagramUrl',
title: 'Lien Instagram',
type: 'url',
initialValue: 'https://instagram.com/rebour.studio',
group: 'footer',
}),
// ── SEO ───────────────────────────────────────────────────
defineField({
name: 'seoTitle',
title: 'Titre SEO',
type: 'string',
initialValue: 'REBOURS — Mobilier d\'art contemporain | Collection 001',
group: 'seo',
}),
defineField({
name: 'seoDescription',
title: 'Description SEO',
type: 'text',
rows: 3,
initialValue: 'REBOURS Studio crée du mobilier d\'art contemporain inspiré du Space Age et du mouvement Memphis. Pièces uniques fabriquées à Paris. Collection 001 en cours.',
group: 'seo',
}),
],
preview: {
prepare() {
return { title: 'Page d\'accueil' }
},
},
})

View File

@ -1,3 +1,4 @@
import product from './product' import product from './product'
import homePage from './homePage'
export const schemaTypes = [product] export const schemaTypes = [product, homePage]

View File

@ -103,18 +103,24 @@ export default defineType({
rows: 4, rows: 4,
}), }),
defineField({ defineField({
name: 'image', name: 'images',
title: 'Image produit', title: 'Images produit',
type: 'image', type: 'array',
options: { hotspot: true }, description: 'La première image est utilisée comme image principale',
validation: (rule) => rule.required(), validation: (rule) => rule.required().min(1),
fields: [ of: [
defineField({ {
name: 'alt', type: 'image',
title: 'Texte alternatif', options: { hotspot: true },
type: 'string', fields: [
description: 'Description de l\'image pour le SEO et l\'accessibilité', defineField({
}), name: 'alt',
title: 'Texte alternatif',
type: 'string',
description: 'Description de l\'image pour le SEO et l\'accessibilité',
}),
],
},
], ],
}), }),
defineField({ defineField({
@ -174,7 +180,7 @@ export default defineType({
select: { select: {
title: 'productDisplayName', title: 'productDisplayName',
subtitle: 'type', subtitle: 'type',
media: 'image', media: 'images.0',
}, },
}, },
}) })

54
seed-sanity-homepage.mjs Normal file
View File

@ -0,0 +1,54 @@
import { createClient } from '@sanity/client'
import dotenv from 'dotenv'
dotenv.config()
const client = createClient({
projectId: process.env.SANITY_PROJECT_ID,
dataset: process.env.SANITY_DATASET || 'production',
apiVersion: '2024-01-01',
useCdn: false,
token: process.env.SANITY_API_TOKEN,
})
const homePage = {
_id: 'homePage',
_type: 'homePage',
// Hero
heroLabel: '// ARCHIVE_001 — 2026',
heroTitle: 'REBOURS|STUDIO',
heroSubtitle: 'Mobilier d\'art contemporain.\nSpace Age × Memphis.',
heroStatus: 'STATUS: [PROTOTYPE EN COURS]\nCOLLECTION_001 — BIENTÔT DISPONIBLE',
// Collection
collectionLabel: '// COLLECTION_001',
collectionCta: 'CLIQUER POUR OUVRIR',
// Contact / WhatsApp
contactLabel: '// CONTACT',
contactTitle: 'UNE QUESTION ?|PARLONS-EN',
contactDescription: 'Commandes sur mesure, questions techniques,\nou simplement dire bonjour.',
whatsappNumber: '33651755191',
whatsappButtonText: 'CONTACTEZ-NOUS SUR WHATSAPP',
contactResponseTime: 'RÉPONSE SOUS 24H',
// Footer
footerText: '© 2026 REBOURS STUDIO — PARIS',
instagramUrl: 'https://instagram.com/rebour.studio',
// SEO
seoTitle: 'REBOURS — Mobilier d\'art contemporain | Collection 001',
seoDescription: 'REBOURS Studio crée du mobilier d\'art contemporain inspiré du Space Age et du mouvement Memphis. Pièces uniques fabriquées à Paris. Collection 001 en cours.',
}
async function seed() {
console.log('Creating homePage document...')
const result = await client.createOrReplace(homePage)
console.log(`✓ homePage created: ${result._id}`)
}
seed().catch((err) => {
console.error('Error:', err.message)
process.exit(1)
})

View File

@ -71,7 +71,7 @@ app.post('/api/checkout', async (request, reply) => {
description, description,
price, price,
currency, currency,
"imageUrl": image.asset->url, "imageUrl": images[0].asset->url,
"slug": slug.current "slug": slug.current
}`, }`,
{ slug } { slug }
@ -131,7 +131,7 @@ app.get('/api/session/:id', async (request) => {
// ── Start ─────────────────────────────────────────────────────────────────── // ── Start ───────────────────────────────────────────────────────────────────
try { try {
await app.listen({ port: process.env.PORT ?? 3000, host: '0.0.0.0' }) await app.listen({ port: process.env.FASTIFY_PORT ?? process.env.PORT ?? 3000, host: '0.0.0.0' })
} catch (err) { } catch (err) {
app.log.error(err) app.log.error(err)
process.exit(1) process.exit(1)

View File

@ -12,6 +12,8 @@ const {
ogImage = 'https://rebours.studio/assets/lamp-violet.jpg', ogImage = 'https://rebours.studio/assets/lamp-violet.jpg',
canonical = 'https://rebours.studio/', canonical = 'https://rebours.studio/',
} = Astro.props; } = Astro.props;
const cssVersion = Date.now();
--- ---
<!DOCTYPE html> <!DOCTYPE html>
@ -56,7 +58,7 @@ const {
<slot name="head" /> <slot name="head" />
<link rel="stylesheet" href="/style.css"> <link rel="stylesheet" href={`/style.css?v=${cssVersion}`}>
</head> </head>
<body> <body>
<slot /> <slot />

View File

@ -5,7 +5,7 @@ export const sanity = createClient({
projectId: import.meta.env.SANITY_PROJECT_ID, projectId: import.meta.env.SANITY_PROJECT_ID,
dataset: import.meta.env.SANITY_DATASET || 'production', dataset: import.meta.env.SANITY_DATASET || 'production',
apiVersion: '2024-01-01', apiVersion: '2024-01-01',
useCdn: true, useCdn: false,
}) })
const builder = imageUrlBuilder(sanity) const builder = imageUrlBuilder(sanity)
@ -29,8 +29,8 @@ const PRODUCT_FIELDS = `
description, description,
specs, specs,
notes, notes,
image, images,
"imageAlt": image.alt, "imageAlt": images[0].alt,
seoTitle, seoTitle,
seoDescription, seoDescription,
price, price,
@ -51,3 +51,28 @@ export async function getProductBySlug(slug) {
{ slug } { slug }
) )
} }
export async function getHomePage() {
return sanity.fetch(
`*[_type == "homePage" && _id == "homePage"][0] {
heroLabel,
heroTitle,
heroSubtitle,
heroStatus,
heroImage,
"heroImageAlt": heroImage.alt,
collectionLabel,
collectionCta,
contactLabel,
contactTitle,
contactDescription,
whatsappNumber,
whatsappButtonText,
contactResponseTime,
footerText,
instagramUrl,
seoTitle,
seoDescription
}`
)
}

View File

@ -1,26 +1,21 @@
--- ---
import Base from '../../layouts/Base.astro'; import Base from '../../layouts/Base.astro';
import { getPublishedProducts, urlFor } from '../../lib/sanity.mjs'; import { getPublishedProducts, getProductBySlug, urlFor } from '../../lib/sanity.mjs';
export async function getStaticPaths() { const { slug } = Astro.params;
const products = await getPublishedProducts(); const product = await getProductBySlug(slug);
return products.map(p => ({ if (!product) {
params: { slug: p.slug }, return Astro.redirect('/');
props: {
slug: p.slug,
name: p.name,
title: p.seoTitle || `REBOURS — ${p.productDisplayName} | Collection 001`,
description: p.seoDescription || p.description?.substring(0, 155) || '',
ogImage: p.image ? urlFor(p.image).width(1200).url() : '',
productName: p.productDisplayName,
price: p.price ? String(p.price / 100) : null,
availability: p.availability || 'https://schema.org/PreOrder',
},
}));
} }
const { slug, title, description, ogImage, name, productName, price, availability } = Astro.props; const name = product.name;
const title = product.seoTitle || `REBOURS — ${product.productDisplayName} | Collection 001`;
const description = product.seoDescription || product.description?.substring(0, 155) || '';
const ogImage = product.images?.[0] ? urlFor(product.images[0]).width(1200).url() : '';
const productName = product.productDisplayName;
const price = product.price ? String(product.price / 100) : null;
const availability = product.availability || 'https://schema.org/PreOrder';
const allProducts = await getPublishedProducts(); const allProducts = await getPublishedProducts();
@ -81,7 +76,10 @@ const schemaBreadcrumb = {
</div> </div>
<div class="panel-inner"> <div class="panel-inner">
<div class="panel-img-col"> <div class="panel-img-col">
<img id="panel-img" src="" alt="Image produit REBOURS Studio"> <div class="panel-gallery" id="panel-gallery">
<img id="panel-img" src="" alt="Image produit REBOURS Studio">
</div>
<div class="panel-gallery-nav" id="panel-gallery-nav"></div>
</div> </div>
<div class="panel-info-col"> <div class="panel-info-col">
<p class="panel-index" id="panel-index"></p> <p class="panel-index" id="panel-index"></p>
@ -109,11 +107,11 @@ const schemaBreadcrumb = {
<p id="panel-desc" class="panel-desc"></p> <p id="panel-desc" class="panel-desc"></p>
<hr> <hr>
<details class="accordion"> <details class="accordion" open>
<summary>SPÉCIFICATIONS TECHNIQUES <span>↓</span></summary> <summary>SPÉCIFICATIONS TECHNIQUES <span>↓</span></summary>
<div class="accordion-body" id="panel-specs"></div> <div class="accordion-body" id="panel-specs"></div>
</details> </details>
<details class="accordion"> <details class="accordion" open>
<summary>NOTES DE CONCEPTION <span>↓</span></summary> <summary>NOTES DE CONCEPTION <span>↓</span></summary>
<div class="accordion-body" id="panel-notes"></div> <div class="accordion-body" id="panel-notes"></div>
</details> </details>
@ -173,9 +171,9 @@ const schemaBreadcrumb = {
<p class="hero-sub mono-sm">STATUS: [PROTOTYPE EN COURS]<br>COLLECTION_001 — BIENTÔT DISPONIBLE</p> <p class="hero-sub mono-sm">STATUS: [PROTOTYPE EN COURS]<br>COLLECTION_001 — BIENTÔT DISPONIBLE</p>
</div> </div>
<div class="hero-right"> <div class="hero-right">
{allProducts[0]?.image && ( {allProducts[0]?.images?.[0] && (
<img <img
src={urlFor(allProducts[0].image).width(1024).url()} src={urlFor(allProducts[0].images[0]).width(1024).url()}
alt="REBOURS — Mobilier d'art contemporain, Paris 2026" alt="REBOURS — Mobilier d'art contemporain, Paris 2026"
class="hero-img" class="hero-img"
width="1024" height="1024" width="1024" height="1024"
@ -194,8 +192,13 @@ const schemaBreadcrumb = {
<div class="product-grid"> <div class="product-grid">
{allProducts.map((p, i) => { {allProducts.map((p, i) => {
const imgUrl = p.image ? urlFor(p.image).width(800).url() : ''; const mainImg = p.images?.[0];
const imgUrl = mainImg ? urlFor(mainImg).width(800).url() : '';
const alt = p.imageAlt || `${p.productDisplayName} — mobilier d'art contemporain, REBOURS Studio Paris`; const alt = p.imageAlt || `${p.productDisplayName} — mobilier d'art contemporain, REBOURS Studio Paris`;
const allImgs = (p.images || []).map(img => ({
url: urlFor(img).width(1200).url(),
alt: img.alt || alt,
}));
return ( return (
<article class="product-card" <article class="product-card"
data-index={p.index} data-index={p.index}
@ -207,7 +210,8 @@ const schemaBreadcrumb = {
data-desc={p.description} data-desc={p.description}
data-specs={p.specs || ''} data-specs={p.specs || ''}
data-notes={p.notes || ''} data-notes={p.notes || ''}
data-img={p.image ? urlFor(p.image).width(1200).url() : ''} data-img={mainImg ? urlFor(mainImg).width(1200).url() : ''}
data-images={JSON.stringify(allImgs)}
data-price={p.price ? String(p.price) : ''} data-price={p.price ? String(p.price) : ''}
data-slug={p.slug} data-slug={p.slug}
data-img-alt={alt} data-img-alt={alt}

View File

@ -1,12 +1,38 @@
--- ---
import Base from '../layouts/Base.astro'; import Base from '../layouts/Base.astro';
import { getPublishedProducts, urlFor } from '../lib/sanity.mjs'; import { getPublishedProducts, getHomePage, urlFor } from '../lib/sanity.mjs';
const products = await getPublishedProducts(); const [products, home] = await Promise.all([getPublishedProducts(), getHomePage()]);
const firstImage = products[0]?.image const heroImg = home?.heroImage
? urlFor(products[0].image).width(1024).url() ? urlFor(home.heroImage).width(1024).url()
: '/assets/table-terrazzo.jpg'; : products[0]?.images?.[0]
? urlFor(products[0].images[0]).width(1024).url()
: '/assets/table-terrazzo.jpg';
const heroImgAlt = home?.heroImageAlt || 'REBOURS — Mobilier d\'art contemporain, Paris 2026';
const heroLabel = home?.heroLabel || '// ARCHIVE_001 — 2026';
const heroTitleParts = (home?.heroTitle || 'REBOURS|STUDIO').split('|');
const heroSubtitle = home?.heroSubtitle || 'Mobilier d\'art contemporain.\nSpace Age × Memphis.';
const heroStatus = home?.heroStatus || 'STATUS: [PROTOTYPE EN COURS]\nCOLLECTION_001 — BIENTÔT DISPONIBLE';
const collectionLabel = home?.collectionLabel || '// COLLECTION_001';
const collectionCta = home?.collectionCta || 'CLIQUER POUR OUVRIR';
const contactLabel = home?.contactLabel || '// CONTACT';
const contactTitleParts = (home?.contactTitle || 'UNE QUESTION ?|PARLONS-EN').split('|');
const contactDesc = home?.contactDescription || 'Commandes sur mesure, questions techniques,\nou simplement dire bonjour.';
const whatsappNumber = home?.whatsappNumber || '33651755191';
const whatsappBtnText = home?.whatsappButtonText || 'CONTACTEZ-NOUS SUR WHATSAPP';
const contactResponseTime = home?.contactResponseTime || 'RÉPONSE SOUS 24H';
const footerText = home?.footerText || '© 2026 REBOURS STUDIO — PARIS';
const instagramUrl = home?.instagramUrl || 'https://instagram.com/rebour.studio';
const seoTitle = home?.seoTitle || 'REBOURS — Mobilier d\'art contemporain | Collection 001';
const seoDesc = home?.seoDescription || 'REBOURS Studio crée du mobilier d\'art contemporain inspiré du Space Age et du mouvement Memphis. Pièces uniques fabriquées à Paris. Collection 001 en cours.';
const firstImage = heroImg;
const schemaOrg = { const schemaOrg = {
"@context": "https://schema.org", "@context": "https://schema.org",
@ -25,7 +51,7 @@ const schemaOrg = {
"@type": "Product", "@type": "Product",
"name": p.productDisplayName, "name": p.productDisplayName,
"description": p.seoDescription || p.description?.substring(0, 155), "description": p.seoDescription || p.description?.substring(0, 155),
"image": p.image ? urlFor(p.image).width(1024).url() : undefined, "image": p.images?.[0] ? urlFor(p.images[0]).width(1024).url() : undefined,
}, },
"price": String(p.price / 100), "price": String(p.price / 100),
"priceCurrency": p.currency || 'EUR', "priceCurrency": p.currency || 'EUR',
@ -36,8 +62,8 @@ const schemaOrg = {
--- ---
<Base <Base
title="REBOURS — Mobilier d'art contemporain | Collection 001" title={seoTitle}
description="REBOURS Studio crée du mobilier d'art contemporain inspiré du Space Age et du mouvement Memphis. Pièces uniques fabriquées à Paris. Collection 001 en cours." description={seoDesc}
canonical="https://rebours.studio/" canonical="https://rebours.studio/"
> >
<Fragment slot="head"> <Fragment slot="head">
@ -54,7 +80,10 @@ const schemaOrg = {
</div> </div>
<div class="panel-inner"> <div class="panel-inner">
<div class="panel-img-col"> <div class="panel-img-col">
<img id="panel-img" src="" alt="Image produit REBOURS Studio"> <div class="panel-gallery" id="panel-gallery">
<img id="panel-img" src="" alt="Image produit REBOURS Studio">
</div>
<div class="panel-gallery-nav" id="panel-gallery-nav"></div>
</div> </div>
<div class="panel-info-col"> <div class="panel-info-col">
<p class="panel-index" id="panel-index"></p> <p class="panel-index" id="panel-index"></p>
@ -82,11 +111,11 @@ const schemaOrg = {
<p id="panel-desc" class="panel-desc"></p> <p id="panel-desc" class="panel-desc"></p>
<hr> <hr>
<details class="accordion"> <details class="accordion" open>
<summary>SPÉCIFICATIONS TECHNIQUES <span>↓</span></summary> <summary>SPÉCIFICATIONS TECHNIQUES <span>↓</span></summary>
<div class="accordion-body" id="panel-specs"></div> <div class="accordion-body" id="panel-specs"></div>
</details> </details>
<details class="accordion"> <details class="accordion" open>
<summary>NOTES DE CONCEPTION <span>↓</span></summary> <summary>NOTES DE CONCEPTION <span>↓</span></summary>
<div class="accordion-body" id="panel-notes"></div> <div class="accordion-body" id="panel-notes"></div>
</details> </details>
@ -141,15 +170,15 @@ const schemaOrg = {
<!-- HERO --> <!-- HERO -->
<section class="hero" aria-label="Introduction"> <section class="hero" aria-label="Introduction">
<div class="hero-left"> <div class="hero-left">
<p class="label">// ARCHIVE_001 — 2026</p> <p class="label">{heroLabel}</p>
<h1>REBOURS<br>STUDIO</h1> <h1>{heroTitleParts.map((part, i) => <>{i > 0 && <br/>}{part}</>)}</h1>
<p class="hero-sub">Mobilier d'art contemporain.<br>Space Age × Memphis.</p> <p class="hero-sub" set:html={heroSubtitle.replace(/\n/g, '<br>')} />
<p class="hero-sub mono-sm">STATUS: [PROTOTYPE EN COURS]<br>COLLECTION_001 — BIENTÔT DISPONIBLE</p> <p class="hero-sub mono-sm" set:html={heroStatus.replace(/\n/g, '<br>')} />
</div> </div>
<div class="hero-right"> <div class="hero-right">
<img <img
src={firstImage} src={heroImg}
alt="REBOURS — Mobilier d'art contemporain, Paris 2026" alt={heroImgAlt}
class="hero-img" class="hero-img"
width="1024" height="1024" width="1024" height="1024"
fetchpriority="high"> fetchpriority="high">
@ -161,14 +190,19 @@ const schemaOrg = {
<!-- COLLECTION GRID --> <!-- COLLECTION GRID -->
<section class="collection" id="collection" aria-label="Collection 001"> <section class="collection" id="collection" aria-label="Collection 001">
<div class="collection-header"> <div class="collection-header">
<p class="label">// COLLECTION_001</p> <p class="label">{collectionLabel}</p>
<span class="label">{products.length} OBJETS — CLIQUER POUR OUVRIR</span> <span class="label">{products.length} OBJETS — {collectionCta}</span>
</div> </div>
<div class="product-grid"> <div class="product-grid">
{products.map((p, i) => { {products.map((p, i) => {
const imgUrl = p.image ? urlFor(p.image).width(800).url() : ''; const mainImg = p.images?.[0];
const imgUrl = mainImg ? urlFor(mainImg).width(800).url() : '';
const alt = p.imageAlt || `${p.productDisplayName} — mobilier d'art contemporain, REBOURS Studio Paris`; const alt = p.imageAlt || `${p.productDisplayName} — mobilier d'art contemporain, REBOURS Studio Paris`;
const allImgs = (p.images || []).map(img => ({
url: urlFor(img).width(1200).url(),
alt: img.alt || alt,
}));
return ( return (
<article class="product-card" <article class="product-card"
data-index={p.index} data-index={p.index}
@ -180,7 +214,8 @@ const schemaOrg = {
data-desc={p.description} data-desc={p.description}
data-specs={p.specs || ''} data-specs={p.specs || ''}
data-notes={p.notes || ''} data-notes={p.notes || ''}
data-img={p.image ? urlFor(p.image).width(1200).url() : ''} data-img={mainImg ? urlFor(mainImg).width(1200).url() : ''}
data-images={JSON.stringify(allImgs)}
data-price={p.price ? String(p.price) : ''} data-price={p.price ? String(p.price) : ''}
data-slug={p.slug} data-slug={p.slug}
data-img-alt={alt} data-img-alt={alt}
@ -202,30 +237,32 @@ const schemaOrg = {
</div> </div>
</section> </section>
<!-- NEWSLETTER --> <!-- CONTACT WHATSAPP -->
<section class="newsletter" id="contact" aria-label="Accès anticipé"> <section class="newsletter" id="contact" aria-label="Contact WhatsApp">
<div class="nl-left"> <div class="nl-left">
<p class="label">// ACCÈS_ANTICIPÉ</p> <p class="label">{contactLabel}</p>
<h2>REJOINDRE<br>L'EXPÉRIENCE</h2> <h2>{contactTitleParts.map((part, i) => <>{i > 0 && <br/>}{part}</>)}</h2>
</div> </div>
<div class="nl-right"> <div class="nl-right">
<form class="nl-form" onsubmit="event.preventDefault();" aria-label="Inscription newsletter"> <div class="nl-form" style="pointer-events: auto;">
<label for="nl-email">EMAIL :</label> <p class="mono-sm" style="line-height: 1.9; margin-bottom: 0.5rem;" set:html={contactDesc.replace(/\n/g, '<br>')} />
<div class="nl-row"> <a href={`https://wa.me/${whatsappNumber}`} target="_blank" rel="noopener" class="whatsapp-btn" aria-label="Nous contacter sur WhatsApp">
<input type="email" id="nl-email" name="email" placeholder="votre@email.com" autocomplete="email" required> <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" style="flex-shrink:0;">
<button type="submit">ENVOYER →</button> <path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z"/>
</div> </svg>
<p class="mono-sm"><span class="blink">■</span> CONNECTION_STATUS: PENDING</p> {whatsappBtnText}
</form> </a>
<p class="mono-sm"><span class="blink">■</span> {contactResponseTime}</p>
</div>
</div> </div>
</section> </section>
</main> </main>
<footer class="footer"> <footer class="footer">
<span>© 2026 REBOURS STUDIO — PARIS</span> <span>{footerText}</span>
<nav aria-label="Liens secondaires"> <nav aria-label="Liens secondaires">
<a href="https://instagram.com/rebour.studio" rel="noopener" target="_blank">INSTAGRAM</a> <a href={instagramUrl} rel="noopener" target="_blank">INSTAGRAM</a>
&nbsp;/&nbsp; &nbsp;/&nbsp;
<a href="#" class="contact-trigger">CONTACT</a> <a href="#" class="contact-trigger">CONTACT</a>
</nav> </nav>
@ -234,7 +271,7 @@ const schemaOrg = {
</div> </div>
<!-- CONTACT MODAL --> <!-- CONTACT MODAL -->
<div id="contact-modal" class="contact-modal" aria-hidden="true"> <div id="contact-modal" class="contact-modal" aria-hidden="true" data-whatsapp={whatsappNumber}>
<div class="contact-modal-backdrop"></div> <div class="contact-modal-backdrop"></div>
<div class="contact-modal-content"> <div class="contact-modal-content">
<div class="contact-modal-header"> <div class="contact-modal-header">
@ -263,7 +300,7 @@ const schemaOrg = {
<button type="submit" class="contact-submit"> <button type="submit" class="contact-submit">
ENVOYER LE MESSAGE → ENVOYER LE MESSAGE →
</button> </button>
<p class="mono-sm contact-note"><span class="blink">■</span> REDIRECTION VERS VOTRE CLIENT MAIL</p> <p class="mono-sm contact-note"><span class="blink">■</span> REDIRECTION VERS WHATSAPP</p>
</form> </form>
</div> </div>
</div> </div>

View File

@ -46,10 +46,10 @@ document.addEventListener('DOMContentLoaded', () => {
const x = e.clientX; const x = e.clientX;
const y = e.clientY; const y = e.clientY;
cursorH.style.left = (x - 20) + 'px'; cursorH.style.left = (x - 16) + 'px';
cursorH.style.top = y + 'px'; cursorH.style.top = y + 'px';
cursorV.style.left = x + 'px'; cursorV.style.left = x + 'px';
cursorV.style.top = (y - 20) + 'px'; cursorV.style.top = (y - 16) + 'px';
cursorCenter.style.left = x + 'px'; cursorCenter.style.left = x + 'px';
cursorCenter.style.top = y + 'px'; cursorCenter.style.top = y + 'px';
cursorCoords.style.left = (x + 16) + 'px'; cursorCoords.style.left = (x + 16) + 'px';
@ -85,6 +85,12 @@ document.addEventListener('DOMContentLoaded', () => {
attachCursorHover(document.querySelectorAll( attachCursorHover(document.querySelectorAll(
'a, button, input, .product-card, summary, .panel-close' 'a, button, input, .product-card, summary, .panel-close'
)); ));
// WhatsApp hover — green center dot
document.querySelectorAll('.whatsapp-btn').forEach(el => {
el.addEventListener('mouseenter', () => cursorCenter.classList.add('cad-whatsapp'));
el.addEventListener('mouseleave', () => cursorCenter.classList.remove('cad-whatsapp'));
});
} }
// ========================================================== // ==========================================================
@ -131,8 +137,70 @@ document.addEventListener('DOMContentLoaded', () => {
// 3. GSAP SCROLL ANIMATIONS — CAD REVEAL // 3. GSAP SCROLL ANIMATIONS — CAD REVEAL
// ========================================================== // ==========================================================
// ---- Hero parallax ---- // ---- Header fade in ----
const header = document.querySelector('.header');
if (header) {
gsap.fromTo(header,
{ opacity: 0, y: -10 },
{ opacity: 1, y: 0, duration: 0.5, ease: 'power2.out' }
);
}
// ---- Hero animations (scroll-triggered, replay in/out) ----
const heroLabel = document.querySelector('.hero-left .label');
const heroH1 = document.querySelector('.hero-left h1');
const heroSubs = document.querySelectorAll('.hero-sub');
const heroImg = document.querySelector('.hero-img'); const heroImg = document.querySelector('.hero-img');
const heroRight = document.querySelector('.hero-right');
const heroTl = gsap.timeline({
defaults: { ease: 'power3.out' },
scrollTrigger: {
trigger: '.hero',
start: 'top 95%',
end: 'bottom 5%',
toggleActions: 'play reverse play reverse',
},
});
if (heroLabel) {
heroTl.fromTo(heroLabel,
{ opacity: 0, x: -20 },
{ opacity: 1, x: 0, duration: 0.6 },
0.1
);
}
if (heroH1) {
heroTl.fromTo(heroH1,
{ opacity: 0, y: 40, clipPath: 'inset(0 0 100% 0)' },
{ opacity: 1, y: 0, clipPath: 'inset(0 0 0% 0)', duration: 1 },
0.2
);
}
heroSubs.forEach((sub, i) => {
heroTl.fromTo(sub,
{ opacity: 0, y: 20 },
{ opacity: 1, y: 0, duration: 0.6 },
0.5 + i * 0.15
);
});
if (heroImg && heroRight) {
heroTl.fromTo(heroRight,
{ clipPath: 'inset(0 0 0 100%)' },
{ clipPath: 'inset(0 0 0 0%)', duration: 1.2, ease: 'power4.inOut' },
0.3
);
heroTl.fromTo(heroImg,
{ scale: 1.15, opacity: 0 },
{ scale: 1, opacity: 0.92, duration: 1.4, ease: 'power2.out' },
0.4
);
}
// Hero parallax on scroll
if (heroImg) { if (heroImg) {
gsap.to(heroImg, { gsap.to(heroImg, {
yPercent: 15, yPercent: 15,
@ -153,280 +221,88 @@ document.addEventListener('DOMContentLoaded', () => {
line.className = 'cad-construction-line'; line.className = 'cad-construction-line';
collectionHeader.appendChild(line); collectionHeader.appendChild(line);
gsap.from(line, { gsap.fromTo(line,
scaleX: 0, { scaleX: 0 },
transformOrigin: 'left center', {
duration: 0.8, scaleX: 1,
ease: 'power2.out', transformOrigin: 'left center',
scrollTrigger: { duration: 0.8,
trigger: collectionHeader, ease: 'power2.out',
start: 'top 85%', scrollTrigger: {
}, trigger: collectionHeader,
}); start: 'top 95%',
end: 'bottom 5%',
toggleActions: 'play reverse play reverse',
},
}
);
} }
// ---- Product cards: staggered CAD reveal ---- // ---- Product cards: scale + fade reveal on scroll (replays) ----
const cards = document.querySelectorAll('.product-card'); const cards = document.querySelectorAll('.product-card');
cards.forEach((card, i) => { cards.forEach((card, i) => {
const imgWrap = card.querySelector('.card-img-wrap');
const img = card.querySelector('.card-img-wrap img'); const img = card.querySelector('.card-img-wrap img');
const meta = card.querySelector('.card-meta'); const meta = card.querySelector('.card-meta');
const imgWrap = card.querySelector('.card-img-wrap');
if (!img) return; if (!img) return;
// Create a CAD scan line per card
const scanLine = document.createElement('div');
scanLine.className = 'card-scanline';
imgWrap.appendChild(scanLine);
// Create corner marks per card
const cornerOverlay = document.createElement('div');
cornerOverlay.className = 'card-corners';
cornerOverlay.innerHTML =
'<span class="cc cc-tl"></span>' +
'<span class="cc cc-tr"></span>' +
'<span class="cc cc-br"></span>' +
'<span class="cc cc-bl"></span>';
imgWrap.appendChild(cornerOverlay);
// Create coordinate annotation per card
const coordTag = document.createElement('div');
coordTag.className = 'card-coord-tag';
coordTag.textContent = `[${String(i + 1).padStart(3, '0')}] — ${img.naturalWidth || '1024'} × ${img.naturalHeight || '1024'} px`;
imgWrap.appendChild(coordTag);
// GSAP timeline triggered on scroll
const tl = gsap.timeline({ const tl = gsap.timeline({
scrollTrigger: { scrollTrigger: {
trigger: card, trigger: card,
start: 'top 88%', start: 'top 95%',
toggleActions: 'play none none none', end: 'bottom 5%',
toggleActions: 'play reverse play reverse',
}, },
}); });
// 1. Image clip-path reveal (bottom → top scan) // Clip-path reveal + scale + fade
tl.fromTo(imgWrap,
{ clipPath: 'inset(8% 8% 8% 8%)' },
{ clipPath: 'inset(0% 0% 0% 0%)', duration: 0.8, ease: 'power3.out' },
0
);
tl.fromTo(img, tl.fromTo(img,
{ clipPath: 'inset(100% 0 0 0)', filter: 'brightness(1.8) grayscale(100%)' }, { opacity: 0, scale: 1.12 },
{ clipPath: 'inset(0% 0 0 0)', filter: 'brightness(1) grayscale(15%)', { opacity: 1, scale: 1, duration: 0.9, ease: 'power2.out' },
duration: 0.9, ease: 'power3.out' },
0 0
); );
// 2. Scan line sweeps up
tl.fromTo(scanLine,
{ top: '100%', opacity: 1 },
{ top: '-2%', opacity: 0, duration: 0.9, ease: 'power3.out' },
0
);
// 3. Corner brackets appear
tl.fromTo(cornerOverlay.querySelectorAll('.cc'),
{ opacity: 0, scale: 0.5 },
{ opacity: 1, scale: 1, duration: 0.4, stagger: 0.06, ease: 'back.out(2)' },
0.3
);
// 4. Coordinate tag types in
tl.fromTo(coordTag,
{ opacity: 0, x: -10 },
{ opacity: 1, x: 0, duration: 0.4, ease: 'power2.out' },
0.5
);
// 5. Meta bar slides in
if (meta) { if (meta) {
tl.fromTo(meta, tl.fromTo(meta,
{ opacity: 0, y: 8 }, { opacity: 0, y: 15 },
{ opacity: 1, y: 0, duration: 0.4, ease: 'power2.out' }, { opacity: 1, y: 0, duration: 0.5, ease: 'power2.out' },
0.4 0.25
); );
} }
}); });
// ---- Newsletter section: slide in ---- // ---- Newsletter section: slide in (replays) ----
const nlLeft = document.querySelector('.nl-left'); const nlLeft = document.querySelector('.nl-left');
const nlRight = document.querySelector('.nl-right'); const nlRight = document.querySelector('.nl-right');
if (nlLeft && nlRight) { if (nlLeft && nlRight) {
gsap.from(nlLeft, { gsap.fromTo(nlLeft,
opacity: 0, x: -30, duration: 0.7, ease: 'power2.out', { opacity: 0, x: -40 },
scrollTrigger: { trigger: '.newsletter', start: 'top 80%' }, {
}); opacity: 1, x: 0, duration: 0.7, ease: 'power2.out',
gsap.from(nlRight, { scrollTrigger: { trigger: '.newsletter', start: 'top 95%', end: 'bottom 5%', toggleActions: 'play reverse play reverse' },
opacity: 0, x: 30, duration: 0.7, ease: 'power2.out', delay: 0.15, }
scrollTrigger: { trigger: '.newsletter', start: 'top 80%' }, );
}); gsap.fromTo(nlRight,
{ opacity: 0, x: 40 },
{
opacity: 1, x: 0, duration: 0.7, ease: 'power2.out', delay: 0.15,
scrollTrigger: { trigger: '.newsletter', start: 'top 95%', end: 'bottom 5%', toggleActions: 'play reverse play reverse' },
}
);
} }
// ========================================================== // ==========================================================
// 4. TECHNICAL DRAWING OVERLAY (panel) // 4. (REMOVED) — no overlay effects on panel image
// ========================================================== // ==========================================================
const panelImgCol = document.querySelector('.panel-img-col');
let techOverlay = null;
let techTimeline = null;
function createTechOverlay(card) {
if (techOverlay) techOverlay.remove();
techOverlay = document.createElement('div');
techOverlay.className = 'tech-overlay';
// Corner brackets
['tl', 'tr', 'br', 'bl'].forEach(pos => {
const corner = document.createElement('div');
corner.className = `tech-corner tech-corner--${pos}`;
techOverlay.appendChild(corner);
});
// Center crosshair
const centerH = document.createElement('div');
centerH.className = 'tech-center-h';
const centerV = document.createElement('div');
centerV.className = 'tech-center-v';
techOverlay.appendChild(centerH);
techOverlay.appendChild(centerV);
// Horizontal dimension line
const dimH = document.createElement('div');
dimH.className = 'tech-dim tech-dim--h';
dimH.innerHTML =
'<span class="tech-dim-arrow">◂</span>' +
'<span class="tech-dim-line"></span>' +
'<span class="tech-dim-text">840</span>' +
'<span class="tech-dim-line"></span>' +
'<span class="tech-dim-arrow">▸</span>';
techOverlay.appendChild(dimH);
// Vertical dimension line
const dimV = document.createElement('div');
dimV.className = 'tech-dim tech-dim--v';
dimV.innerHTML =
'<span class="tech-dim-arrow">▴</span>' +
'<span class="tech-dim-line"></span>' +
'<span class="tech-dim-text">420</span>' +
'<span class="tech-dim-line"></span>' +
'<span class="tech-dim-arrow">▾</span>';
techOverlay.appendChild(dimV);
// Reference text
const ref = document.createElement('div');
ref.className = 'tech-ref';
const idx = card ? card.dataset.index : '001';
ref.textContent = `REF: ${idx} — SCALE 1:5 — UNIT: mm`;
techOverlay.appendChild(ref);
// Blueprint grid
const grid = document.createElement('div');
grid.className = 'tech-grid';
techOverlay.appendChild(grid);
// Scan line
const scanline = document.createElement('div');
scanline.className = 'tech-scanline';
techOverlay.appendChild(scanline);
if (panelImgCol) panelImgCol.appendChild(techOverlay);
}
function animateTechOverlay() {
if (!techOverlay) return;
// Kill previous timeline
if (techTimeline) techTimeline.kill();
const corners = techOverlay.querySelectorAll('.tech-corner');
const centerH = techOverlay.querySelector('.tech-center-h');
const centerV = techOverlay.querySelector('.tech-center-v');
const dimH = techOverlay.querySelector('.tech-dim--h');
const dimV = techOverlay.querySelector('.tech-dim--v');
const ref = techOverlay.querySelector('.tech-ref');
const grid = techOverlay.querySelector('.tech-grid');
const scanline = techOverlay.querySelector('.tech-scanline');
techTimeline = gsap.timeline();
// 1. Blueprint grid fades in
techTimeline.fromTo(grid,
{ opacity: 0 },
{ opacity: 1, duration: 0.6, ease: 'power1.in' },
0
);
// 2. Scan line sweeps
techTimeline.fromTo(scanline,
{ top: '0%', opacity: 1 },
{ top: '100%', opacity: 0, duration: 1.1, ease: 'power2.inOut' },
0
);
// 3. Corner brackets draw in (scale from corner)
corners.forEach((c, i) => {
const origins = ['top left', 'top right', 'bottom right', 'bottom left'];
techTimeline.fromTo(c,
{ opacity: 0, scale: 0, borderColor: 'rgba(232,168,0,0)' },
{ opacity: 1, scale: 1, borderColor: 'rgba(232,168,0,0.6)',
transformOrigin: origins[i],
duration: 0.4, ease: 'back.out(1.5)' },
0.15 + i * 0.08
);
});
// 4. Center crosshair extends
techTimeline.fromTo(centerH,
{ scaleX: 0, opacity: 0 },
{ scaleX: 1, opacity: 1, duration: 0.5, ease: 'power2.out' },
0.3
);
techTimeline.fromTo(centerV,
{ scaleY: 0, opacity: 0 },
{ scaleY: 1, opacity: 1, duration: 0.5, ease: 'power2.out' },
0.35
);
// 5. Dimension lines extend
techTimeline.fromTo(dimH,
{ opacity: 0, scaleX: 0 },
{ opacity: 1, scaleX: 1, transformOrigin: 'center center',
duration: 0.6, ease: 'power2.out' },
0.4
);
techTimeline.fromTo(dimV,
{ opacity: 0, scaleY: 0 },
{ opacity: 1, scaleY: 1, transformOrigin: 'center center',
duration: 0.6, ease: 'power2.out' },
0.45
);
// 6. Reference text types in
techTimeline.fromTo(ref,
{ opacity: 0, x: -15 },
{ opacity: 1, x: 0, duration: 0.4, ease: 'power2.out' },
0.6
);
// 7. Panel image scan reveal
const panelImg = document.getElementById('panel-img');
if (panelImg) {
techTimeline.fromTo(panelImg,
{ clipPath: 'inset(0 0 100% 0)', filter: 'brightness(1.8) grayscale(100%)' },
{ clipPath: 'inset(0 0 0% 0)', filter: 'brightness(1) contrast(1) grayscale(0%)',
duration: 1, ease: 'power3.out' },
0.05
);
}
}
function hideTechOverlay() {
if (techTimeline) techTimeline.kill();
if (techOverlay) {
gsap.to(techOverlay, { opacity: 0, duration: 0.3, onComplete: () => {
if (techOverlay) techOverlay.remove();
techOverlay = null;
}});
}
}
// ========================================================== // ==========================================================
// 5. PRODUCT PANEL // 5. PRODUCT PANEL
// ========================================================== // ==========================================================
@ -436,18 +312,23 @@ document.addEventListener('DOMContentLoaded', () => {
const panelCards = document.querySelectorAll('.product-card'); const panelCards = document.querySelectorAll('.product-card');
const fields = { const fields = {
img: document.getElementById('panel-img'), img: document.getElementById('panel-img'),
index: document.getElementById('panel-index'), gallery: document.getElementById('panel-gallery'),
name: document.getElementById('panel-name'), galleryNav: document.getElementById('panel-gallery-nav'),
type: document.getElementById('panel-type'), index: document.getElementById('panel-index'),
mat: document.getElementById('panel-mat'), name: document.getElementById('panel-name'),
year: document.getElementById('panel-year'), type: document.getElementById('panel-type'),
status: document.getElementById('panel-status'), mat: document.getElementById('panel-mat'),
desc: document.getElementById('panel-desc'), year: document.getElementById('panel-year'),
specs: document.getElementById('panel-specs'), status: document.getElementById('panel-status'),
notes: document.getElementById('panel-notes'), desc: document.getElementById('panel-desc'),
specs: document.getElementById('panel-specs'),
notes: document.getElementById('panel-notes'),
}; };
let currentGalleryIndex = 0;
let currentGalleryImages = [];
// ---- CHECKOUT LOGIC ---- // ---- CHECKOUT LOGIC ----
const checkoutSection = document.getElementById('checkout-section'); const checkoutSection = document.getElementById('checkout-section');
const checkoutToggleBtn = document.getElementById('checkout-toggle-btn'); const checkoutToggleBtn = document.getElementById('checkout-toggle-btn');
@ -508,9 +389,67 @@ document.addEventListener('DOMContentLoaded', () => {
.replace(/[^a-z0-9-]/g, ''); .replace(/[^a-z0-9-]/g, '');
} }
function showGalleryImage(index) {
if (!currentGalleryImages.length) return;
currentGalleryIndex = index;
fields.img.src = currentGalleryImages[index].url;
fields.img.alt = currentGalleryImages[index].alt;
// Update nav dots
fields.galleryNav.querySelectorAll('.gallery-dot').forEach((dot, i) => {
dot.classList.toggle('active', i === index);
});
}
function openPanel(card, pushState = true) { function openPanel(card, pushState = true) {
fields.img.src = card.dataset.img; // Gallery setup
fields.img.alt = card.dataset.imgAlt || card.dataset.name; try {
currentGalleryImages = JSON.parse(card.dataset.images || '[]');
} catch { currentGalleryImages = []; }
if (currentGalleryImages.length > 0) {
currentGalleryIndex = 0;
fields.img.src = currentGalleryImages[0].url;
fields.img.alt = currentGalleryImages[0].alt;
} else {
fields.img.src = card.dataset.img;
fields.img.alt = card.dataset.imgAlt || card.dataset.name;
}
// Build nav dots
fields.galleryNav.innerHTML = '';
if (currentGalleryImages.length > 1) {
fields.galleryNav.style.display = 'flex';
currentGalleryImages.forEach((_, i) => {
const dot = document.createElement('button');
dot.className = 'gallery-dot' + (i === 0 ? ' active' : '');
dot.setAttribute('aria-label', `Image ${i + 1}`);
dot.addEventListener('click', () => showGalleryImage(i));
fields.galleryNav.appendChild(dot);
});
// Arrow buttons
const prevBtn = document.createElement('button');
prevBtn.className = 'gallery-arrow gallery-prev';
prevBtn.textContent = '←';
prevBtn.setAttribute('aria-label', 'Image précédente');
prevBtn.addEventListener('click', () => {
showGalleryImage((currentGalleryIndex - 1 + currentGalleryImages.length) % currentGalleryImages.length);
});
const nextBtn = document.createElement('button');
nextBtn.className = 'gallery-arrow gallery-next';
nextBtn.textContent = '→';
nextBtn.setAttribute('aria-label', 'Image suivante');
nextBtn.addEventListener('click', () => {
showGalleryImage((currentGalleryIndex + 1) % currentGalleryImages.length);
});
fields.galleryNav.prepend(prevBtn);
fields.galleryNav.appendChild(nextBtn);
} else {
fields.galleryNav.style.display = 'none';
}
fields.index.textContent = card.dataset.index; fields.index.textContent = card.dataset.index;
fields.name.textContent = card.dataset.name; fields.name.textContent = card.dataset.name;
fields.type.textContent = card.dataset.type; fields.type.textContent = card.dataset.type;
@ -547,7 +486,7 @@ document.addEventListener('DOMContentLoaded', () => {
checkoutSubmitBtn.textContent = 'PROCÉDER AU PAIEMENT →'; checkoutSubmitBtn.textContent = 'PROCÉDER AU PAIEMENT →';
checkoutForm.reset(); checkoutForm.reset();
panel.querySelectorAll('details').forEach(d => d.removeAttribute('open')); panel.querySelectorAll('details').forEach(d => d.setAttribute('open', ''));
panel.classList.add('is-open'); panel.classList.add('is-open');
panel.setAttribute('aria-hidden', 'false'); panel.setAttribute('aria-hidden', 'false');
document.body.style.overflow = 'hidden'; document.body.style.overflow = 'hidden';
@ -556,10 +495,6 @@ document.addEventListener('DOMContentLoaded', () => {
'summary, .panel-close, .checkout-btn, .checkout-submit' 'summary, .panel-close, .checkout-btn, .checkout-submit'
)); ));
// Technical drawing overlay (delayed for panel slide-in)
createTechOverlay(card);
setTimeout(() => animateTechOverlay(), 350);
if (pushState) { if (pushState) {
const cardSlug = card.dataset.slug || toSlug(card.dataset.name); const cardSlug = card.dataset.slug || toSlug(card.dataset.name);
history.pushState({ slug: cardSlug }, '', `/collection/${cardSlug}`); history.pushState({ slug: cardSlug }, '', `/collection/${cardSlug}`);
@ -567,7 +502,6 @@ document.addEventListener('DOMContentLoaded', () => {
} }
function closePanel(pushState = true) { function closePanel(pushState = true) {
hideTechOverlay();
panel.classList.remove('is-open'); panel.classList.remove('is-open');
panel.setAttribute('aria-hidden', 'true'); panel.setAttribute('aria-hidden', 'true');
document.body.style.overflow = ''; document.body.style.overflow = '';
@ -727,7 +661,7 @@ document.addEventListener('DOMContentLoaded', () => {
} }
}); });
// Form → mailto // Form → WhatsApp
if (contactForm) { if (contactForm) {
contactForm.addEventListener('submit', (e) => { contactForm.addEventListener('submit', (e) => {
e.preventDefault(); e.preventDefault();
@ -736,9 +670,9 @@ document.addEventListener('DOMContentLoaded', () => {
const subject = document.getElementById('contact-subject').value.trim() || 'Contact depuis rebours.studio'; const subject = document.getElementById('contact-subject').value.trim() || 'Contact depuis rebours.studio';
const message = document.getElementById('contact-message').value.trim(); const message = document.getElementById('contact-message').value.trim();
const body = `${message}\n\n${name}\n${email}`; const waNumber = contactModal.dataset.whatsapp || '33651755191';
const mailto = `mailto:contact@rebours.fr?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`; const text = `*${subject}*\n\n${message}\n\n${name}\n${email}`;
window.location.href = mailto; window.open(`https://wa.me/${waNumber}?text=${encodeURIComponent(text)}`, '_blank');
}); });
} }