update site

This commit is contained in:
ordinarthur 2026-04-11 17:42:39 +02:00
parent 1b53e04b5d
commit 9a9519ce29
22 changed files with 1105 additions and 299 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 path="content.d.ts" />

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

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

@ -492,10 +492,18 @@ hr {
border-right: var(--border);
background: #1a1a1a;
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
}
.panel-gallery {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
position: relative;
min-height: 0;
}
#panel-img {
@ -506,6 +514,52 @@ hr {
opacity: 0.92;
}
/* Gallery navigation */
.panel-gallery-nav {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 12px 0;
background: #1a1a1a;
border-top: 1px solid rgba(232, 168, 0, 0.15);
}
.gallery-dot {
width: 8px;
height: 8px;
border-radius: 50%;
border: 1px solid var(--amber);
background: transparent;
cursor: pointer;
padding: 0;
transition: background 0.2s;
}
.gallery-dot.active {
background: var(--amber);
}
.gallery-dot:hover {
background: rgba(232, 168, 0, 0.5);
}
.gallery-arrow {
background: none;
border: 1px solid rgba(232, 168, 0, 0.3);
color: var(--amber);
font-family: 'Space Mono', monospace;
font-size: 0.75rem;
cursor: pointer;
padding: 2px 8px;
transition: border-color 0.2s, background 0.2s;
}
.gallery-arrow:hover {
border-color: var(--amber);
background: rgba(232, 168, 0, 0.1);
}
/* Colonne infos : scrollable */
.panel-info-col {
overflow-y: auto;
@ -981,18 +1035,17 @@ hr {
position: fixed;
inset: 0;
z-index: 1001;
display: flex;
display: none;
align-items: center;
justify-content: center;
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: opacity 0.3s ease, visibility 0.3s ease;
transition: opacity 0.3s ease;
}
.contact-modal.is-open {
display: flex;
opacity: 1;
visibility: visible;
pointer-events: auto;
}
@ -1092,6 +1145,7 @@ hr {
color: var(--clr-black);
pointer-events: auto;
resize: vertical;
width: 100%;
}
.contact-field input::placeholder,
@ -1136,7 +1190,7 @@ hr {
.hero-left {
border-right: none;
border-bottom: var(--border);
min-height: 70vw;
min-height: auto;
padding: 3rem var(--pad);
order: -1;
}

View File

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

449
sanity/pnpm-lock.yaml generated
View File

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

View File

@ -9,6 +9,25 @@ export default defineConfig({
projectId: 'y821x5qu',
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 },
})

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 homePage from './homePage'
export const schemaTypes = [product]
export const schemaTypes = [product, homePage]

View File

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

View File

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

View File

@ -29,8 +29,8 @@ const PRODUCT_FIELDS = `
description,
specs,
notes,
image,
"imageAlt": image.alt,
images,
"imageAlt": images[0].alt,
seoTitle,
seoDescription,
price,
@ -51,3 +51,28 @@ export async function getProductBySlug(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

@ -12,7 +12,7 @@ if (!product) {
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.image ? urlFor(product.image).width(1200).url() : '';
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';
@ -76,7 +76,10 @@ const schemaBreadcrumb = {
</div>
<div class="panel-inner">
<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 class="panel-info-col">
<p class="panel-index" id="panel-index"></p>
@ -168,9 +171,9 @@ const schemaBreadcrumb = {
<p class="hero-sub mono-sm">STATUS: [PROTOTYPE EN COURS]<br>COLLECTION_001 — BIENTÔT DISPONIBLE</p>
</div>
<div class="hero-right">
{allProducts[0]?.image && (
{allProducts[0]?.images?.[0] && (
<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"
class="hero-img"
width="1024" height="1024"
@ -189,8 +192,13 @@ const schemaBreadcrumb = {
<div class="product-grid">
{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 allImgs = (p.images || []).map(img => ({
url: urlFor(img).width(1200).url(),
alt: img.alt || alt,
}));
return (
<article class="product-card"
data-index={p.index}
@ -202,7 +210,8 @@ const schemaBreadcrumb = {
data-desc={p.description}
data-specs={p.specs || ''}
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-slug={p.slug}
data-img-alt={alt}

View File

@ -1,12 +1,38 @@
---
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
? urlFor(products[0].image).width(1024).url()
: '/assets/table-terrazzo.jpg';
const heroImg = home?.heroImage
? urlFor(home.heroImage).width(1024).url()
: 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 = {
"@context": "https://schema.org",
@ -25,7 +51,7 @@ const schemaOrg = {
"@type": "Product",
"name": p.productDisplayName,
"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),
"priceCurrency": p.currency || 'EUR',
@ -36,8 +62,8 @@ const schemaOrg = {
---
<Base
title="REBOURS — Mobilier d'art contemporain | Collection 001"
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."
title={seoTitle}
description={seoDesc}
canonical="https://rebours.studio/"
>
<Fragment slot="head">
@ -54,7 +80,10 @@ const schemaOrg = {
</div>
<div class="panel-inner">
<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 class="panel-info-col">
<p class="panel-index" id="panel-index"></p>
@ -141,15 +170,15 @@ const schemaOrg = {
<!-- HERO -->
<section class="hero" aria-label="Introduction">
<div class="hero-left">
<p class="label">// ARCHIVE_001 — 2026</p>
<h1>REBOURS<br>STUDIO</h1>
<p class="hero-sub">Mobilier d'art contemporain.<br>Space Age × Memphis.</p>
<p class="hero-sub mono-sm">STATUS: [PROTOTYPE EN COURS]<br>COLLECTION_001 — BIENTÔT DISPONIBLE</p>
<p class="label">{heroLabel}</p>
<h1>{heroTitleParts.map((part, i) => <>{i > 0 && <br/>}{part}</>)}</h1>
<p class="hero-sub" set:html={heroSubtitle.replace(/\n/g, '<br>')} />
<p class="hero-sub mono-sm" set:html={heroStatus.replace(/\n/g, '<br>')} />
</div>
<div class="hero-right">
<img
src={firstImage}
alt="REBOURS — Mobilier d'art contemporain, Paris 2026"
src={heroImg}
alt={heroImgAlt}
class="hero-img"
width="1024" height="1024"
fetchpriority="high">
@ -161,14 +190,19 @@ const schemaOrg = {
<!-- COLLECTION GRID -->
<section class="collection" id="collection" aria-label="Collection 001">
<div class="collection-header">
<p class="label">// COLLECTION_001</p>
<span class="label">{products.length} OBJETS — CLIQUER POUR OUVRIR</span>
<p class="label">{collectionLabel}</p>
<span class="label">{products.length} OBJETS — {collectionCta}</span>
</div>
<div class="product-grid">
{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 allImgs = (p.images || []).map(img => ({
url: urlFor(img).width(1200).url(),
alt: img.alt || alt,
}));
return (
<article class="product-card"
data-index={p.index}
@ -180,7 +214,8 @@ const schemaOrg = {
data-desc={p.description}
data-specs={p.specs || ''}
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-slug={p.slug}
data-img-alt={alt}
@ -205,19 +240,19 @@ const schemaOrg = {
<!-- CONTACT WHATSAPP -->
<section class="newsletter" id="contact" aria-label="Contact WhatsApp">
<div class="nl-left">
<p class="label">// CONTACT</p>
<h2>UNE QUESTION ?<br>PARLONS-EN</h2>
<p class="label">{contactLabel}</p>
<h2>{contactTitleParts.map((part, i) => <>{i > 0 && <br/>}{part}</>)}</h2>
</div>
<div class="nl-right">
<div class="nl-form" style="pointer-events: auto;">
<p class="mono-sm" style="line-height: 1.9; margin-bottom: 0.5rem;">Commandes sur mesure, questions techniques,<br>ou simplement dire bonjour.</p>
<a href="https://wa.me/33651755191" target="_blank" rel="noopener" class="whatsapp-btn" aria-label="Nous contacter sur WhatsApp">
<p class="mono-sm" style="line-height: 1.9; margin-bottom: 0.5rem;" set:html={contactDesc.replace(/\n/g, '<br>')} />
<a href={`https://wa.me/${whatsappNumber}`} target="_blank" rel="noopener" class="whatsapp-btn" aria-label="Nous contacter sur WhatsApp">
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" style="flex-shrink:0;">
<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"/>
</svg>
CONTACTEZ-NOUS SUR WHATSAPP
{whatsappBtnText}
</a>
<p class="mono-sm"><span class="blink">■</span> RÉPONSE SOUS 24H</p>
<p class="mono-sm"><span class="blink">■</span> {contactResponseTime}</p>
</div>
</div>
</section>
@ -225,9 +260,9 @@ const schemaOrg = {
</main>
<footer class="footer">
<span>© 2026 REBOURS STUDIO — PARIS</span>
<span>{footerText}</span>
<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;
<a href="#" class="contact-trigger">CONTACT</a>
</nav>
@ -236,7 +271,7 @@ const schemaOrg = {
</div>
<!-- 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-content">
<div class="contact-modal-header">

View File

@ -312,18 +312,23 @@ document.addEventListener('DOMContentLoaded', () => {
const panelCards = document.querySelectorAll('.product-card');
const fields = {
img: document.getElementById('panel-img'),
index: document.getElementById('panel-index'),
name: document.getElementById('panel-name'),
type: document.getElementById('panel-type'),
mat: document.getElementById('panel-mat'),
year: document.getElementById('panel-year'),
status: document.getElementById('panel-status'),
desc: document.getElementById('panel-desc'),
specs: document.getElementById('panel-specs'),
notes: document.getElementById('panel-notes'),
img: document.getElementById('panel-img'),
gallery: document.getElementById('panel-gallery'),
galleryNav: document.getElementById('panel-gallery-nav'),
index: document.getElementById('panel-index'),
name: document.getElementById('panel-name'),
type: document.getElementById('panel-type'),
mat: document.getElementById('panel-mat'),
year: document.getElementById('panel-year'),
status: document.getElementById('panel-status'),
desc: document.getElementById('panel-desc'),
specs: document.getElementById('panel-specs'),
notes: document.getElementById('panel-notes'),
};
let currentGalleryIndex = 0;
let currentGalleryImages = [];
// ---- CHECKOUT LOGIC ----
const checkoutSection = document.getElementById('checkout-section');
const checkoutToggleBtn = document.getElementById('checkout-toggle-btn');
@ -384,9 +389,67 @@ document.addEventListener('DOMContentLoaded', () => {
.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) {
fields.img.src = card.dataset.img;
fields.img.alt = card.dataset.imgAlt || card.dataset.name;
// Gallery setup
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.name.textContent = card.dataset.name;
fields.type.textContent = card.dataset.type;
@ -607,8 +670,9 @@ document.addEventListener('DOMContentLoaded', () => {
const subject = document.getElementById('contact-subject').value.trim() || 'Contact depuis rebours.studio';
const message = document.getElementById('contact-message').value.trim();
const waNumber = contactModal.dataset.whatsapp || '33651755191';
const text = `*${subject}*\n\n${message}\n\n${name}\n${email}`;
window.open(`https://wa.me/33651755191?text=${encodeURIComponent(text)}`, '_blank');
window.open(`https://wa.me/${waNumber}?text=${encodeURIComponent(text)}`, '_blank');
});
}