<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Développement d'applications et sites sur mesure pour PME | nynju</title>
<meta name="description" content="nynju conçoit vos applications métier, sites web et automatisations sur mesure, avec l'IA, en délais courts. Studio français basé dans les Pyrénées-Orientales. Devis gratuit.">
<link rel="canonical" href="https://nynju.com/">
<meta name="robots" content="index, follow">
<meta name="theme-color" content="#F5F5F3">
<meta property="og:type" content="website">
<meta property="og:locale" content="fr_FR">
<meta property="og:site_name" content="nynju">
<meta property="og:title" content="nynju · Applications et sites sur mesure pour PME">
<meta property="og:description" content="Avancez dans la galerie nynju et découvrez les applications et les sites que nous avons conçus : Rock n' School, Class8, Scan2Be, C4TALOG, Sonatem et plus.">
<meta property="og:url" content="https://nynju.com/">
<meta property="og:image" content="https://media.base44.com/images/public/user_697a1585431555598b515a4c/4849a69ec_image.png">
<meta name="twitter:card" content="summary_large_image">
<link rel="icon" type="image/webp" href="https://res.cloudinary.com/piz3gvjx/image/upload/v1792837461/fav_nynju.webp">
<link rel="apple-touch-icon" href="https://res.cloudinary.com/piz3gvjx/image/upload/c_pad,w_180,h_180,b_rgb:F5F5F3,f_png/v1792837461/fav_nynju.webp">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500&family=Manrope:wght@200;300&display=swap" rel="stylesheet">
<script>document.documentElement.className+=' gx';</script>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "ProfessionalService",
"@id": "https://nynju.com/#org",
"name": "nynju",
"url": "https://nynju.com/",
"description": "Studio de conception et de développement d'applications, de sites web et d'automatisations sur mesure pour les PME.",
"email": "support@nynju.com",
"founder": { "@type": "Person", "name": "Thibaud Labbé" },
"address": { "@type": "PostalAddress", "streetAddress": "6, rue des Flamants Roses", "postalCode": "66200", "addressLocality": "Alenya", "addressCountry": "FR" },
"areaServed": "FR",
"knowsAbout": ["Développement d'applications métier", "Création de sites web", "Automatisation", "Intelligence artificielle"],
"parentOrganization": { "@type": "Organization", "name": "Theas Group", "url": "https://theas.group/" }
},
{
"@type": "ItemList",
"name": "Réalisations nynju",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "url": "https://rocknschool.com/", "name": "Rock n' School" },
{ "@type": "ListItem", "position": 2, "url": "https://class8.fr/", "name": "Class8" },
{ "@type": "ListItem", "position": 3, "url": "https://app.theas.group/", "name": "Scan2Be" },
{ "@type": "ListItem", "position": 4, "url": "https://catalogue.nynju.com/", "name": "C4TALOG" },
{ "@type": "ListItem", "position": 5, "url": "https://sonatem.fr/", "name": "Sonatem" },
{ "@type": "ListItem", "position": 6, "url": "https://musique-libre.fr/", "name": "Musique libre" },
{ "@type": "ListItem", "position": 7, "url": "https://worldzic.com/", "name": "Worldzic" },
{ "@type": "ListItem", "position": 8, "url": "https://webradio.sonatem.fr/", "name": "Moodcast" },
{ "@type": "ListItem", "position": 9, "url": "https://sonaweb.sonatem.fr/", "name": "Sonaweb" }
]
}
]
}
</script>
<style>
:root {
--bg: #F5F5F3;
--ink: #111111;
--ink2: #161616;
--line: #D8D8D5;
--display: "Manrope", "Inter", "Helvetica Neue", Helvetica, Arial, sans-serif;
--text: "Inter", "Helvetica Neue", Helvetica, Arial, sans-serif;
color-scheme: light;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { -webkit-text-size-adjust: 100%; }
body { background: var(--bg); color: var(--ink); font: 400 16px/1.6 var(--text); -webkit-font-smoothing: antialiased; }
a { color: inherit; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
ol { list-style: none; }
::selection { background: var(--ink); color: var(--bg); }
:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; }
/* ---------- En-tête ---------- */
.top {
position: fixed; inset: 0 0 auto 0; z-index: 40;
display: flex; align-items: center; justify-content: space-between;
padding: 18px 24px;
padding-top: max(18px, env(safe-area-inset-top));
}
.logo { font: 500 15px/1 var(--text); letter-spacing: -.01em; text-decoration: none; }
.logo i { font-style: normal; opacity: .45; }
.top-link {
font-size: .62rem; text-transform: uppercase; letter-spacing: .28em; text-decoration: none;
border-bottom: 1px solid rgba(17,17,17,.25); padding: 10px 0 4px; transition: border-color .5s;
}
.top-link:hover { border-color: var(--ink); }
/* ---------- Typo partagée ---------- */
.eyebrow { font-size: .6rem; font-weight: 500; text-transform: uppercase; letter-spacing: .32em; color: rgba(17,17,17,.72); }
.label { font-size: .56rem; font-weight: 500; text-transform: uppercase; letter-spacing: .3em; color: rgba(17,17,17,.62); }
.step-title {
font: 300 1.42rem/1.06 var(--text); letter-spacing: -.035em; color: var(--ink);
}
.body { max-width: 46ch; font-size: .84rem; line-height: 1.7; color: var(--ink2); }
.rule { width: 56px; height: 1px; background: var(--line); }
.pair { display: flex; flex-direction: column; gap: 6px; }
.pair:first-of-type { padding-top: 4px; }
.cta {
display: inline-flex; align-items: center; gap: 12px; margin-top: 4px; min-height: 44px;
font-size: .6rem; text-transform: uppercase; letter-spacing: .26em; color: var(--ink); text-decoration: none;
}
.cta span { border-bottom: 1px solid rgba(17,17,17,.25); padding-bottom: 4px; transition: border-color .5s; }
.cta:hover span { border-color: var(--ink); }
.cta svg, .btn svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.6; transition: transform .5s; }
.cta:hover svg { transform: translateX(4px); }
a.cta:hover svg { transform: translate(2px, -2px); }
a.cta span {
position: relative; border: 0; font-weight: 600;
background: linear-gradient(90deg, #dc2626 0%, #ea580c 100%);
-webkit-background-clip: text; background-clip: text; color: transparent;
}
a.cta span::after {
content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
background: linear-gradient(90deg, #dc2626, #ea580c); opacity: .45; transition: opacity .5s;
}
a.cta:hover span::after { opacity: 1; }
a.cta svg { stroke: #ea580c; }
.stats { display: flex; flex-wrap: wrap; gap: 14px 26px; padding-top: 4px; }
.stats div { display: flex; flex-direction: column; gap: 2px; max-width: 12rem; }
.stats dt { font: 200 1.5rem/1 var(--display); letter-spacing: -.02em; color: var(--ink); }
.stats dd { font-size: .64rem; line-height: 1.45; color: rgba(17,17,17,.75); }
/* ---------- Galerie : version statique (sans JS) ---------- */
.journey { position: relative; }
.backdrop, #corridor, .scrim, .ghost, .rail, .hint { display: none; }
.hero { position: relative; }
.hero-in { position: relative; min-height: 70vh; padding: 96px 24px 48px; display: flex; flex-direction: column; justify-content: space-between; gap: 24px; }
.hero-words { display: flex; flex-direction: column; justify-content: space-between; flex: 1; }
.hero-words span { font: 200 16vw/.84 var(--display); letter-spacing: -.05em; color: var(--ink); }
.hero-words span + span { align-self: flex-end; text-align: right; }
.hero-kicker, .hero-sign { font-size: .62rem; font-weight: 500; text-transform: uppercase; letter-spacing: .34em; color: rgba(17,17,17,.75); max-width: 22ch; }
.hero-sign { color: rgba(17,17,17,.6); }
.step { position: relative; display: flex; align-items: center; }
.inner {
position: relative; margin: 0 auto; width: 100%; max-width: 1320px;
display: flex; flex-direction: column; align-items: center; gap: 20px;
padding: 56px 24px 96px;
}
.text { display: flex; flex-direction: column; gap: 16px; align-items: flex-start; width: 100%; }
.c-center .inner { justify-content: center; }
.c-center .text { align-items: center; text-align: center; }
.c-center .stats { justify-content: center; }
.visual { position: relative; width: 100%; }
.frame {
position: relative; overflow: hidden; background: #fff;
border: 1px solid rgba(216,216,213,.7);
aspect-ratio: 16 / 10; max-height: 20vh; width: 100%;
transform: scale(var(--media-scale, 1)); transform-origin: center;
}
.plate {
position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
padding: 16px; text-align: center;
background:
linear-gradient(180deg, rgba(255,255,255,0) 0%, rgba(245,245,243,.7) 100%),
repeating-linear-gradient(90deg, transparent 0 22px, rgba(216,216,213,.35) 22px 23px);
}
.plate-name { font: 200 clamp(1.4rem, 4vw, 3.4rem)/1 var(--display); letter-spacing: -.04em; color: var(--ink); }
.plate-url { font-size: .56rem; text-transform: uppercase; letter-spacing: .3em; color: rgba(17,17,17,.6); }
.frame img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: 50% 12%; opacity: 0; transition: opacity .8s; }
.frame img.on { opacity: 1; }
/* ---------- Galerie : mode immersif (JS actif) ---------- */
html.gx .journey { height: calc((var(--steps) + 1) * 110vh); }
html.gx .stage { position: sticky; top: 0; height: 100vh; height: 100svh; overflow: hidden; }
html.gx .backdrop {
display: block; position: absolute; inset: 0;
background:
radial-gradient(40% 46% at 50% 46%, #ffffff 0%, rgba(255,255,255,.55) 45%, rgba(245,245,243,0) 100%),
linear-gradient(90deg, rgba(216,216,213,.55) 0 7%, transparent 7% 93%, rgba(216,216,213,.55) 93%),
var(--bg);
}
html.gx #corridor { display: block; position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; transition: opacity 1.4s ease-out; }
html.gx #corridor.on { opacity: 1; }
html.gx .hero { position: absolute; inset: 0; z-index: 2; pointer-events: none; will-change: transform, opacity; }
html.gx .hero-in { position: absolute; inset: 0; min-height: 0; padding: 96px 24px 112px; animation: gx-reveal 1.7s cubic-bezier(.23,1,.32,1) both; }
html.gx .hint { display: flex; position: absolute; left: 0; right: 0; top: 50%; transform: translateY(-50%); flex-direction: column; align-items: center; gap: 16px; }
.mouse { position: relative; width: 24px; height: 38px; border: 1.5px solid rgba(17,17,17,.55); border-radius: 12px; }
.wheel { position: absolute; left: 50%; top: 7px; width: 3px; height: 8px; margin-left: -1.5px; border-radius: 2px; background: var(--ink); animation: gx-wheel 1.9s cubic-bezier(.65,0,.35,1) infinite; }
@keyframes gx-wheel { 0% { transform: translateY(0); opacity: 0; } 15% { opacity: 1; } 70% { transform: translateY(13px); opacity: 0; } 100% { transform: translateY(13px); opacity: 0; } }
.hint-text { display: flex; align-items: center; gap: 12px; font-size: .6rem; text-transform: uppercase; letter-spacing: .42em; color: rgba(17,17,17,.8); animation: gx-pulse 3.4s ease-in-out infinite; }
.hint-text i { width: 24px; height: 1px; background: rgba(36,36,36,.3); }
html.gx .hero-in { padding-bottom: 190px; }
html.gx .hero-kicker { position: absolute; left: 24px; bottom: 112px; }
html.gx .hero-sign { position: absolute; right: 24px; bottom: 112px; text-align: right; }
html.gx .step { position: absolute; inset: 0; z-index: 3; opacity: 0; visibility: hidden; pointer-events: none; will-change: transform, opacity; }
html.gx .step.live { pointer-events: auto; }
html.gx .scrim { display: block; position: absolute; inset: 0; pointer-events: none; }
html.gx .inner { padding: 56px 24px 96px; }
.c-right .scrim, .c-plateL .scrim { background-image: radial-gradient(74% 82% at 74% 50%, rgba(245,245,243,.97) 0%, rgba(245,245,243,.6) 54%, rgba(245,245,243,0) 100%); }
.c-left .scrim, .c-plateR .scrim { background-image: radial-gradient(74% 82% at 26% 50%, rgba(245,245,243,.97) 0%, rgba(245,245,243,.6) 54%, rgba(245,245,243,0) 100%); }
.c-center .scrim { background-image: radial-gradient(82% 86% at 50% 50%, rgba(245,245,243,.97) 0%, rgba(245,245,243,.66) 58%, rgba(245,245,243,0) 100%); }
.c-asym .scrim { background-image: radial-gradient(78% 84% at 58% 48%, rgba(245,245,243,.97) 0%, rgba(245,245,243,.58) 56%, rgba(245,245,243,0) 100%); }
html.gx .rail { display: block; position: absolute; left: 0; right: 0; bottom: 0; z-index: 5; padding-bottom: env(safe-area-inset-bottom); }
.rail::before { content: ""; position: absolute; left: 0; right: 0; top: -56px; height: 56px; background: linear-gradient(180deg, rgba(245,245,243,0), rgba(245,245,243,.7)); pointer-events: none; }
.rail-row { position: relative; display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; padding: 0 24px 10px; }
.dots { display: flex; align-items: center; gap: 2px; flex-wrap: wrap; }
.dot { display: flex; align-items: center; height: 24px; padding: 0 2px; }
.dot span { display: block; height: 1px; width: 8px; background: var(--line); transition: width .5s, background-color .5s; }
.dot:hover span { background: rgba(36,36,36,.5); }
.dot[aria-current="true"] span { width: 20px; background: var(--ink); }
.rail-meta { display: flex; align-items: baseline; gap: 16px; }
.rail-cat { display: none; font-size: .58rem; text-transform: uppercase; letter-spacing: .3em; color: rgba(17,17,17,.62); }
.rail-num { font: 300 .85rem/1 var(--display); letter-spacing: .16em; font-variant-numeric: tabular-nums; white-space: nowrap; }
.rail-num b { font-weight: 300; }
.rail-num span { color: rgba(36,36,36,.35); }
.rail-track { position: relative; height: 1px; background: var(--line); }
.rail-fill { height: 1px; background: var(--ink); transform: scaleX(0); transform-origin: left; }
/* ---------- Sections classiques ---------- */
.sec { position: relative; z-index: 1; background: var(--bg); padding: 88px 0; border-top: 1px solid var(--line); }
.wrap { max-width: 1320px; margin: 0 auto; padding: 0 24px; }
.sec-title { font: 300 clamp(1.7rem, 4.2vw, 3.1rem)/1.04 var(--text); letter-spacing: -.035em; margin: 14px 0 12px; }
.sec-title.big { font: 200 clamp(2.4rem, 8vw, 6.5rem)/.95 var(--display); letter-spacing: -.045em; }
.sec-sub { max-width: 52ch; font-size: .9rem; line-height: 1.75; color: var(--ink2); margin-bottom: 40px; }
.grid4, .grid3 { display: grid; grid-template-columns: 1fr; border-top: 1px solid var(--line); }
.grid4 li, .grid3 div { padding: 24px 0; border-bottom: 1px solid var(--line); }
.grid4 .n { font: 200 2.2rem/1 var(--display); letter-spacing: -.03em; color: rgba(17,17,17,.3); }
.grid4 h3, .grid3 h3 { font: 400 1.05rem/1.3 var(--text); letter-spacing: -.01em; margin: 10px 0 6px; }
.grid3 h3 { margin-top: 0; }
.grid4 p, .grid3 p { font-size: .88rem; line-height: 1.7; color: var(--ink2); max-width: 40ch; }
.btn {
display: inline-flex; align-items: center; gap: 14px; min-height: 52px; padding: 0 26px;
background: var(--ink); color: var(--bg); text-decoration: none;
font-size: .64rem; text-transform: uppercase; letter-spacing: .26em; transition: background-color .4s;
}
.btn:hover { background: #2b2b2b; }
.btn:hover svg { transform: translateX(4px); }
.foot { border-top: 1px solid var(--line); padding: 44px 0 calc(40px + env(safe-area-inset-bottom)); font-size: .8rem; line-height: 1.75; color: rgba(17,17,17,.85); }
.foot-cols { display: grid; grid-template-columns: 1fr; gap: 28px; }
.foot-h { font-size: .58rem; font-weight: 500; text-transform: uppercase; letter-spacing: .3em; color: var(--ink); margin-bottom: 10px; }
.foot-end { margin-top: 28px; font-size: .7rem; color: rgba(36,36,36,.5); }
/* ---------- Tablette et ordinateur ---------- */
@media (min-width: 768px) {
.top { padding: 26px 56px; }
.step-title { font-size: 3.1rem; line-height: 1.02; }
.body { font-size: .98rem; line-height: 1.8; }
.text { gap: 20px; }
.inner, html.gx .inner { gap: 48px; padding: 96px 64px; }
.stats dt { font-size: 2rem; }
html.gx .hero-in { padding: 112px 56px 150px; }
.hero-words span { font-size: 10.5vw; }
html.gx .hero-kicker { left: 56px; bottom: 96px; }
html.gx .hero-sign { right: 56px; bottom: 96px; }
html.gx .ghost {
display: block; position: absolute; pointer-events: none; user-select: none;
font: 200 20vw/1 var(--display); letter-spacing: -.04em; color: var(--ink); opacity: .07;
}
.c-right .ghost { left: 4vw; top: 50%; transform: translateY(-50%); }
.c-left .ghost { right: 5vw; top: 50%; transform: translateY(-50%); }
.c-center .ghost { left: 50%; top: 5vh; transform: translateX(-50%); }
.c-asym .ghost { left: 3vw; bottom: 8vh; }
.c-plateR .ghost { left: 3vw; top: 50%; transform: translateY(-50%); }
.c-plateL .ghost { right: 3vw; top: 50%; transform: translateY(-50%); }
.text { position: relative; }
.text .ghost-near { left: auto; right: calc(100% + 1.5vw); top: 50%; bottom: auto; transform: translateY(-50%); font-size: 16vw; }
.text .ghost-near.ghost-r { right: auto; left: calc(100% + 1.5vw); }
.c-right .inner, .c-asym .inner, .c-plateR .inner { flex-direction: row; justify-content: flex-end; }
.c-left .inner, .c-plateL .inner { flex-direction: row; justify-content: flex-start; }
.c-right .text { align-items: flex-end; text-align: right; max-width: 36rem; }
.c-right .stats { justify-content: flex-end; }
.c-left .text { max-width: 28rem; }
.c-center .text { max-width: 44rem; }
.c-asym .text { max-width: 27rem; padding-right: 40px; }
.c-plateR .text, .c-plateL .text { max-width: 30rem; }
.c-plateR .visual { order: 2; flex: none; width: 50%; }
.c-plateL .visual { order: -1; flex: none; width: 50%; }
.frame { max-height: 56vh; }
.rail-row { padding: 0 56px 12px; }
.dots { gap: 4px; }
.dot span { width: 12px; }
.dot[aria-current="true"] span { width: 24px; }
.rail-cat { display: block; }
.sec { padding: 128px 0; }
.wrap { padding: 0 56px; }
.grid4 { grid-template-columns: repeat(4, 1fr); }
.grid3 { grid-template-columns: repeat(3, 1fr); }
.grid4 li, .grid3 div { padding: 28px 28px 28px 0; border-bottom: 0; }
.grid4 li + li, .grid3 div + div { padding-left: 28px; border-left: 1px solid var(--line); }
.foot-cols { grid-template-columns: 2fr 1fr; }
}
@media (min-width: 1024px) {
.c-plateR .visual, .c-plateL .visual { width: 52%; }
}
/* ---------- Animations ---------- */
@keyframes gx-reveal { from { opacity: 0; transform: translate3d(0, 2.5vh, 0); } to { opacity: 1; transform: none; } }
@keyframes gx-pulse { 0%, 100% { opacity: .35; transform: translateY(0); } 50% { opacity: 1; transform: translateY(3px); } }
@media (prefers-reduced-motion: reduce) {
html.gx .hero-in, .hint-text, .wheel { animation: none; }
#corridor { transition: none; }
}
</style>
</head>
<body>
<header class="top">
<a class="logo" href="/" aria-label="nynju, accueil">nynju<i>•</i></a>
<!-- Emplacement prévu : lecteur de musique d'ambiance -->
<a class="top-link" href="#contact">Devis gratuit</a>
</header>
<main>
<div class="journey" id="journey" style="--steps:15">
<div class="stage" id="stage">
<div class="backdrop" aria-hidden="true"></div>
<canvas id="corridor" aria-hidden="true"></canvas>
<div class="hero" id="hero"><div class="hero-in">
<p class="hero-words" aria-hidden="true"><span>NYNJU</span><span>GALLERY</span></p>
<div class="hint" aria-hidden="true"><span class="mouse"><span class="wheel"></span></span><span class="hint-text"><i></i>Défiler pour avancer<i></i></span></div>
<p class="hero-kicker">Studio numérique · applications & sites sur mesure</p>
<p class="hero-sign">Conçu et développé par nynju</p>
</div></div>
<section class="step c-right" id="studio" data-speed="1" data-cat="nynju" data-num="01"><div class="scrim" aria-hidden="true"></div>
<div class="inner"><article class="text">
<span class="ghost ghost-near" aria-hidden="true">01</span>
<p class="eyebrow">01 · nynju</p>
<h1 class="step-title">Votre application ou votre site sur mesure, livré en quelques semaines.</h1>
<div class="rule" aria-hidden="true"></div>
<p class="body">nynju conçoit des outils numériques taillés pour votre métier : application interne, site web, portail client, automatisation. Nous utilisons l'IA pour développer plus vite et moins cher, sans sacrifier la qualité.</p>
<dl class="stats"><div><dt>9</dt><dd>applications et sites conçus et en service</dd></div><div><dt>25 ans</dt><dd>d'expérience du terrain B2B</dd></div><div><dt>1</dt><dd>interlocuteur, celui qui développe</dd></div></dl>
<button type="button" class="cta" data-go="next"><span>Continuer la visite</span><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></button>
</article></div>
</section>
<section class="step c-left" id="applications-metier" data-speed="1.15" data-cat="Ce que nous construisons" data-num="02"><div class="scrim" aria-hidden="true"></div>
<div class="inner"><article class="text">
<span class="ghost ghost-near ghost-r" aria-hidden="true">02</span>
<p class="eyebrow">02 · Ce que nous construisons</p>
<h2 class="step-title">Applications métier</h2>
<div class="rule" aria-hidden="true"></div>
<p class="body">Gestion, suivi, tableaux de bord et outils internes pensés pour vos équipes. Des outils qui collent à votre façon de travailler, plutôt qu'un logiciel générique que vous devez contourner.</p>
</article></div>
</section>
<section class="step c-asym" id="sites-web" data-speed="1.15" data-cat="Ce que nous construisons" data-num="03"><div class="scrim" aria-hidden="true"></div>
<div class="inner"><article class="text">
<span class="ghost ghost-near" aria-hidden="true">03</span>
<p class="eyebrow">03 · Ce que nous construisons</p>
<h2 class="step-title">Sites web sur mesure</h2>
<div class="rule" aria-hidden="true"></div>
<p class="body">Rapides, optimisés pour Google et faciles à faire évoluer.</p>
</article></div>
</section>
<section class="step c-center" id="automatisation" data-speed="1.15" data-cat="Ce que nous construisons" data-num="04"><div class="scrim" aria-hidden="true"></div><span class="ghost" aria-hidden="true">04</span>
<div class="inner"><article class="text">
<p class="eyebrow">04 · Ce que nous construisons</p>
<h2 class="step-title">Automatisation</h2>
<div class="rule" aria-hidden="true"></div>
<p class="body">Relier vos outils et supprimer les saisies répétitives.</p>
</article></div>
</section>
<section class="step c-right" id="intelligence-artificielle" data-speed="1.15" data-cat="Ce que nous construisons" data-num="05"><div class="scrim" aria-hidden="true"></div>
<div class="inner"><article class="text">
<span class="ghost ghost-near" aria-hidden="true">05</span>
<p class="eyebrow">05 · Ce que nous construisons</p>
<h2 class="step-title">Intégration de l'IA</h2>
<div class="rule" aria-hidden="true"></div>
<p class="body">Extraction de données, rédaction assistée et assistants internes.</p>
</article></div>
</section>
<section class="step c-center" id="realisations" data-speed="1" data-cat="Portfolio" data-num="06"><div class="scrim" aria-hidden="true"></div><span class="ghost" aria-hidden="true">06</span>
<div class="inner"><article class="text">
<p class="eyebrow">06 · Portfolio</p>
<h2 class="step-title">Nos réalisations</h2>
<div class="rule" aria-hidden="true"></div>
<p class="body">Chaque projet ci-dessous a été conçu et développé par nynju, puis exploité par son propriétaire.</p>
<button type="button" class="cta" data-go="next"><span>Voir les projets</span><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></button>
</article></div>
</section>
<section class="step c-plateR" id="rock-n-school" data-speed="1" data-cat="Site web · Client" data-num="07"><div class="scrim" aria-hidden="true"></div><span class="ghost" aria-hidden="true">07</span>
<div class="inner"><article class="text">
<p class="eyebrow">07 · Site web · Client</p>
<h3 class="step-title">Rock n' School</h3>
<div class="rule" aria-hidden="true"></div>
<div class="pair"><span class="label">Le besoin</span><p class="body">Un collège et lycée privé artistique de Perpignan voulait présenter son concept, ses programmes et faciliter les inscriptions.</p></div>
<div class="pair"><span class="label">Ce que nous avons construit</span><p class="body">Un site sur mesure avec les programmes, la FAQ, le blog et la prise de contact. L'école utilise aussi Class8 pour sa gestion.</p></div>
<a class="cta" href="https://rocknschool.com/" target="_blank" rel="noopener"><span>Voir le site</span><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 17 17 7M8 7h9v9"/></svg></a>
</article><div class="visual"><div class="frame"><div class="plate" aria-hidden="true"><span class="plate-name">Rock n' School</span><span class="plate-url">rocknschool.com</span></div><img data-src="https://res.cloudinary.com/piz3gvjx/image/upload/v1792836263/01_rocknschool.com-71k-webp.webp" alt="Aperçu du projet Rock n&#x27; School" width="1600" height="1000" decoding="async"><noscript><img src="https://res.cloudinary.com/piz3gvjx/image/upload/v1792836263/01_rocknschool.com-71k-webp.webp" alt="Aperçu du projet Rock n&#x27; School" width="1600" height="1000" loading="lazy"></noscript></div></div></div>
</section>
<section class="step c-plateL" id="class8" data-speed="1" data-cat="Application SaaS · Theas Group" data-num="08"><div class="scrim" aria-hidden="true"></div><span class="ghost" aria-hidden="true">08</span>
<div class="inner"><article class="text">
<p class="eyebrow">08 · Application SaaS · Theas Group</p>
<h3 class="step-title">Class8</h3>
<div class="rule" aria-hidden="true"></div>
<div class="pair"><span class="label">Le besoin</span><p class="body">Gérer une école privée sans logiciel lourd ni formation.</p></div>
<div class="pair"><span class="label">Ce que nous avons construit</span><p class="body">Appel, absences avec alerte aux familles, notes, bulletins PDF et droits par rôle.</p></div>
<a class="cta" href="https://class8.fr/" target="_blank" rel="noopener"><span>Voir Class8</span><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 17 17 7M8 7h9v9"/></svg></a>
</article><div class="visual"><div class="frame"><div class="plate" aria-hidden="true"><span class="plate-name">Class8</span><span class="plate-url">class8.fr</span></div><img data-src="https://res.cloudinary.com/piz3gvjx/image/upload/v1792837132/02_class8.fr-35k_k-webp.webp" alt="Aperçu du projet Class8" width="1600" height="1000" decoding="async"><noscript><img src="https://res.cloudinary.com/piz3gvjx/image/upload/v1792837132/02_class8.fr-35k_k-webp.webp" alt="Aperçu du projet Class8" width="1600" height="1000" loading="lazy"></noscript></div></div></div>
</section>
<section class="step c-plateR" id="scan2be" data-speed="1" data-cat="Application mobile · Theas Group" data-num="09"><div class="scrim" aria-hidden="true"></div><span class="ghost" aria-hidden="true">09</span>
<div class="inner"><article class="text">
<p class="eyebrow">09 · Application mobile · Theas Group</p>
<h3 class="step-title">Scan2Be</h3>
<div class="rule" aria-hidden="true"></div>
<div class="pair"><span class="label">Le besoin</span><p class="body">Ne plus perdre de prospects entre le salon et la relance.</p></div>
<div class="pair"><span class="label">Ce que nous avons construit</span><p class="body">Scan de cartes de visite par IA, notes vocales, e-mail de relance prêt à envoyer et plan équipe.</p></div>
<a class="cta" href="https://app.theas.group/" target="_blank" rel="noopener"><span>Voir Scan2Be</span><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 17 17 7M8 7h9v9"/></svg></a>
</article><div class="visual"><div class="frame"><div class="plate" aria-hidden="true"><span class="plate-name">Scan2Be</span><span class="plate-url">app.theas.group</span></div><img data-src="https://res.cloudinary.com/piz3gvjx/image/upload/v1792837756/03.Scan2Be-theas.group-38k-webp.webp" alt="Aperçu du projet Scan2Be" width="1600" height="1000" decoding="async"><noscript><img src="https://res.cloudinary.com/piz3gvjx/image/upload/v1792837756/03.Scan2Be-theas.group-38k-webp.webp" alt="Aperçu du projet Scan2Be" width="1600" height="1000" loading="lazy"></noscript></div></div></div>
</section>
<section class="step c-plateL" id="c4talog" data-speed="1" data-cat="Application SaaS · Theas Group" data-num="10"><div class="scrim" aria-hidden="true"></div><span class="ghost" aria-hidden="true">10</span>
<div class="inner"><article class="text">
<p class="eyebrow">10 · Application SaaS · Theas Group</p>
<h3 class="step-title">C4TALOG</h3>
<div class="rule" aria-hidden="true"></div>
<div class="pair"><span class="label">Le besoin</span><p class="body">Montrer tout un assortiment B2B sans maquetter un catalogue à la main.</p></div>
<div class="pair"><span class="label">Ce que nous avons construit</span><p class="body">Un catalogue feuilletable généré depuis un fichier CSV, relié à la boutique en ligne.</p></div>
<a class="cta" href="https://catalogue.nynju.com/" target="_blank" rel="noopener"><span>Voir C4TALOG</span><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 17 17 7M8 7h9v9"/></svg></a>
</article><div class="visual"><div class="frame"><div class="plate" aria-hidden="true"><span class="plate-name">C4TALOG</span><span class="plate-url">catalogue.nynju.com</span></div><img data-src="https://res.cloudinary.com/piz3gvjx/image/upload/v1792837997/04.catalogue.nynju.com-47k-webp.webp" alt="Aperçu du projet C4TALOG" width="1600" height="1000" decoding="async"><noscript><img src="https://res.cloudinary.com/piz3gvjx/image/upload/v1792837997/04.catalogue.nynju.com-47k-webp.webp" alt="Aperçu du projet C4TALOG" width="1600" height="1000" loading="lazy"></noscript></div></div></div>
</section>
<section class="step c-plateR" id="sonatem" data-speed="1" data-cat="Site vitrine · Sonatem" data-num="11"><div class="scrim" aria-hidden="true"></div><span class="ghost" aria-hidden="true">11</span>
<div class="inner"><article class="text">
<p class="eyebrow">11 · Site vitrine · Sonatem</p>
<h3 class="step-title">Sonatem</h3>
<div class="rule" aria-hidden="true"></div>
<div class="pair"><span class="label">Le besoin</span><p class="body">Présenter une offre de musique d'ambiance sans SACEM aux professionnels.</p></div>
<div class="pair"><span class="label">Ce que nous avons construit</span><p class="body">Le site de la marque, son contenu SEO et ses signaux pour les moteurs IA.</p></div>
<a class="cta" href="https://sonatem.fr/" target="_blank" rel="noopener"><span>Voir le site</span><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 17 17 7M8 7h9v9"/></svg></a>
</article><div class="visual"><div class="frame"><div class="plate" aria-hidden="true"><span class="plate-name">Sonatem</span><span class="plate-url">sonatem.fr</span></div><img data-src="https://res.cloudinary.com/piz3gvjx/image/upload/v1792838147/05.sonatem.fr-80k-webp.webp" alt="Aperçu du projet Sonatem" width="1600" height="1000" decoding="async"><noscript><img src="https://res.cloudinary.com/piz3gvjx/image/upload/v1792838147/05.sonatem.fr-80k-webp.webp" alt="Aperçu du projet Sonatem" width="1600" height="1000" loading="lazy"></noscript></div></div></div>
</section>
<section class="step c-plateL" id="musique-libre" data-speed="1" data-cat="E-commerce · Sonatem" data-num="12"><div class="scrim" aria-hidden="true"></div><span class="ghost" aria-hidden="true">12</span>
<div class="inner"><article class="text">
<p class="eyebrow">12 · E-commerce · Sonatem</p>
<h3 class="step-title">Musique libre</h3>
<div class="rule" aria-hidden="true"></div>
<div class="pair"><span class="label">Le besoin</span><p class="body">Vendre des albums d'ambiance aux commerces français.</p></div>
<div class="pair"><span class="label">Ce que nous avons construit</span><p class="body">Une boutique avec fiches albums optimisées, commandes et blog.</p></div>
<a class="cta" href="https://musique-libre.fr/" target="_blank" rel="noopener"><span>Voir le site</span><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 17 17 7M8 7h9v9"/></svg></a>
</article><div class="visual"><div class="frame"><div class="plate" aria-hidden="true"><span class="plate-name">Musique libre</span><span class="plate-url">musique-libre.fr</span></div><img data-src="https://res.cloudinary.com/piz3gvjx/image/upload/v1792838275/06.musique-libre.fr-39k-webp.webp" alt="Aperçu du projet Musique libre" width="1600" height="1000" decoding="async"><noscript><img src="https://res.cloudinary.com/piz3gvjx/image/upload/v1792838275/06.musique-libre.fr-39k-webp.webp" alt="Aperçu du projet Musique libre" width="1600" height="1000" loading="lazy"></noscript></div></div></div>
</section>
<section class="step c-plateR" id="worldzic" data-speed="1" data-cat="E-commerce international · Sonatem" data-num="13"><div class="scrim" aria-hidden="true"></div><span class="ghost" aria-hidden="true">13</span>
<div class="inner"><article class="text">
<p class="eyebrow">13 · E-commerce international · Sonatem</p>
<h3 class="step-title">Worldzic</h3>
<div class="rule" aria-hidden="true"></div>
<div class="pair"><span class="label">Le besoin</span><p class="body">Ouvrir le catalogue musical à l'étranger.</p></div>
<div class="pair"><span class="label">Ce que nous avons construit</span><p class="body">Une boutique en 6 langues avec téléchargement MP3.</p></div>
<a class="cta" href="https://worldzic.com/" target="_blank" rel="noopener"><span>Voir le site</span><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 17 17 7M8 7h9v9"/></svg></a>
</article><div class="visual"><div class="frame"><div class="plate" aria-hidden="true"><span class="plate-name">Worldzic</span><span class="plate-url">worldzic.com</span></div><img data-src="https://res.cloudinary.com/piz3gvjx/image/upload/v1792835471/01.worldzic.com-108k-webp.webp" alt="Aperçu du projet Worldzic" width="1600" height="1000" decoding="async"><noscript><img src="https://res.cloudinary.com/piz3gvjx/image/upload/v1792835471/01.worldzic.com-108k-webp.webp" alt="Aperçu du projet Worldzic" width="1600" height="1000" loading="lazy"></noscript></div></div></div>
</section>
<section class="step c-plateL" id="moodcast" data-speed="1" data-cat="Webradio · Sonatem" data-num="14"><div class="scrim" aria-hidden="true"></div><span class="ghost" aria-hidden="true">14</span>
<div class="inner"><article class="text">
<p class="eyebrow">14 · Webradio · Sonatem</p>
<h3 class="step-title">Moodcast</h3>
<div class="rule" aria-hidden="true"></div>
<div class="pair"><span class="label">Le besoin</span><p class="body">Faire écouter l'ambiance sonore en continu.</p></div>
<div class="pair"><span class="label">Ce que nous avons construit</span><p class="body">Une webradio en ligne, accessible depuis n'importe quel navigateur.</p></div>
<a class="cta" href="https://webradio.sonatem.fr/" target="_blank" rel="noopener"><span>Écouter</span><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 17 17 7M8 7h9v9"/></svg></a>
</article><div class="visual"><div class="frame"><div class="plate" aria-hidden="true"><span class="plate-name">Moodcast</span><span class="plate-url">webradio.sonatem.fr</span></div><img data-src="https://res.cloudinary.com/piz3gvjx/image/upload/v1792838429/08.Moodcast-Webradio.sonatem.fr-38k-webp.webp" alt="Aperçu du projet Moodcast" width="1600" height="1000" decoding="async"><noscript><img src="https://res.cloudinary.com/piz3gvjx/image/upload/v1792838429/08.Moodcast-Webradio.sonatem.fr-38k-webp.webp" alt="Aperçu du projet Moodcast" width="1600" height="1000" loading="lazy"></noscript></div></div></div>
</section>
<section class="step c-plateR" id="sonaweb" data-speed="1" data-cat="Lecteur privé · Sonatem" data-num="15"><div class="scrim" aria-hidden="true"></div><span class="ghost" aria-hidden="true">15</span>
<div class="inner"><article class="text">
<p class="eyebrow">15 · Lecteur privé · Sonatem</p>
<h3 class="step-title">Sonaweb</h3>
<div class="rule" aria-hidden="true"></div>
<div class="pair"><span class="label">Le besoin</span><p class="body">Diffuser la même ambiance dans tous les points de vente d'un réseau.</p></div>
<div class="pair"><span class="label">Ce que nous avons construit</span><p class="body">Un lecteur musical privé réservé aux franchisés.</p></div>
<a class="cta" href="https://sonaweb.sonatem.fr/" target="_blank" rel="noopener"><span>Voir Sonaweb</span><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 17 17 7M8 7h9v9"/></svg></a>
</article><div class="visual"><div class="frame"><div class="plate" aria-hidden="true"><span class="plate-name">Sonaweb</span><span class="plate-url">sonaweb.sonatem.fr</span></div><img data-src="https://res.cloudinary.com/piz3gvjx/image/upload/v1792838567/09.sonaweb.sonatem.fr-36k-webp.webp" alt="Aperçu du projet Sonaweb" width="1600" height="1000" decoding="async"><noscript><img src="https://res.cloudinary.com/piz3gvjx/image/upload/v1792838567/09.sonaweb.sonatem.fr-36k-webp.webp" alt="Aperçu du projet Sonaweb" width="1600" height="1000" loading="lazy"></noscript></div></div></div>
</section>
<div class="rail" aria-label="Progression de la visite">
<div class="rail-row">
<div class="dots">
<button type="button" class="dot" data-i="0" aria-label="Aller à l'étape 01 — Votre application ou votre site sur mesure, livré en quelques semaines."><span></span></button>
<button type="button" class="dot" data-i="1" aria-label="Aller à l'étape 02 — Applications métier"><span></span></button>
<button type="button" class="dot" data-i="2" aria-label="Aller à l'étape 03 — Sites web sur mesure"><span></span></button>
<button type="button" class="dot" data-i="3" aria-label="Aller à l'étape 04 — Automatisation"><span></span></button>
<button type="button" class="dot" data-i="4" aria-label="Aller à l'étape 05 — Intégration de l'IA"><span></span></button>
<button type="button" class="dot" data-i="5" aria-label="Aller à l'étape 06 — Nos réalisations"><span></span></button>
<button type="button" class="dot" data-i="6" aria-label="Aller à l'étape 07 — Rock n' School"><span></span></button>
<button type="button" class="dot" data-i="7" aria-label="Aller à l'étape 08 — Class8"><span></span></button>
<button type="button" class="dot" data-i="8" aria-label="Aller à l'étape 09 — Scan2Be"><span></span></button>
<button type="button" class="dot" data-i="9" aria-label="Aller à l'étape 10 — C4TALOG"><span></span></button>
<button type="button" class="dot" data-i="10" aria-label="Aller à l'étape 11 — Sonatem"><span></span></button>
<button type="button" class="dot" data-i="11" aria-label="Aller à l'étape 12 — Musique libre"><span></span></button>
<button type="button" class="dot" data-i="12" aria-label="Aller à l'étape 13 — Worldzic"><span></span></button>
<button type="button" class="dot" data-i="13" aria-label="Aller à l'étape 14 — Moodcast"><span></span></button>
<button type="button" class="dot" data-i="14" aria-label="Aller à l'étape 15 — Sonaweb"><span></span></button>
</div>
<div class="rail-meta"><span class="rail-cat" id="rail-cat">nynju</span><span class="rail-num"><b id="rail-num">01</b> <span>/ 15</span></span></div>
</div>
<div class="rail-track"><div class="rail-fill" id="rail-fill"></div></div>
</div>
</div>
</div>
<section class="sec" id="methode">
<div class="wrap">
<p class="eyebrow">Méthode</p>
<h2 class="sec-title">Comment nous travaillons</h2>
<p class="sec-sub">Vous voyez votre outil prendre forme à chaque étape, sans surprise sur le prix ni sur les délais.</p>
<ol class="grid4">
<li><span class="n">01</span><h3>Échange</h3><p>On part de votre besoin réel, pas d'une liste de fonctions.</p></li>
<li><span class="n">02</span><h3>Maquette</h3><p>Vous validez les écrans avant qu'une ligne de code soit écrite.</p></li>
<li><span class="n">03</span><h3>Développement</h3><p>Livraison par étapes : vous testez au fur et à mesure.</p></li>
<li><span class="n">04</span><h3>Mise en ligne et suivi</h3><p>Hébergement, corrections et évolutions.</p></li>
</ol>
</div>
</section>
<section class="sec" id="pourquoi">
<div class="wrap">
<p class="eyebrow">Studio</p>
<h2 class="sec-title">Pourquoi nynju</h2>
<p class="sec-sub">Un studio à taille humaine, qui exploite lui-même plusieurs applications au quotidien.</p>
<div class="grid3">
<div><h3>Un seul interlocuteur</h3><p>Vous parlez directement à celui qui conçoit et développe votre outil.</p></div>
<div><h3>Prix et délais annoncés</h3><p>Un devis clair avant de démarrer, sans engagement.</p></div>
<div><h3>La culture du terrain</h3><p>25 ans dans le B2B : on comprend votre métier avant de coder.</p></div>
</div>
</div>
</section>
<section class="sec contact" id="contact">
<div class="wrap">
<p class="eyebrow">Contact</p>
<h2 class="sec-title big">Parlez-nous de votre projet</h2>
<p class="sec-sub">Décrivez votre besoin en quelques lignes : nous vous répondons avec une première estimation.</p>
<a class="btn" href="mailto:support@nynju.com?subject=Demande%20de%20devis"><span>Demander un devis gratuit</span><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg></a>
</div>
</section>
</main>
<footer class="foot" id="mentions-legales">
<div class="wrap foot-cols">
<div>
<h2 class="foot-h">Mentions légales</h2>
<p>nynju est une marque de Thibaud Labbé, entrepreneur individuel.<br>
6, rue des Flamants Roses, 66200 Alenya, France · <a href="mailto:support@nynju.com">support@nynju.com</a><br>
Directeur de la publication : Thibaud Labbé.<br>
Hébergeur : Cloudflare, Inc., 101 Townsend St, San Francisco, CA 94107, États-Unis.</p>
</div>
<div>
<h2 class="foot-h">Groupe</h2>
<p>Studio de conception des applications <a href="https://theas.group/" target="_blank" rel="noopener">Theas Group</a>.</p>
</div>
</div>
<div class="wrap foot-end">© 2026 nynju</div>
</footer>
<script>
(function () {
"use strict";
var root = document.documentElement;
if (root.className.indexOf("gx") < 0) return;
var THREE_URL = "https://cdn.jsdelivr.net/npm/three@0.171.0/build/three.module.min.js";
var HERO_SPAN = 0.15;
var journey = document.getElementById("journey");
var hero = document.getElementById("hero");
var canvas = document.getElementById("corridor");
var fill = document.getElementById("rail-fill");
var railNum = document.getElementById("rail-num");
var railCat = document.getElementById("rail-cat");
var steps = Array.prototype.slice.call(document.querySelectorAll(".step"));
var dots = Array.prototype.slice.call(document.querySelectorAll(".dot"));
var N = steps.length;
var SPAN = (1 - HERO_SPAN) / N;
var reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
function clamp01(v) { return v < 0 ? 0 : v > 1 ? 1 : v; }
function easeOutQuint(t) { return 1 - Math.pow(1 - t, 5); }
function smoothstep(t) { return t * t * (3 - 2 * t); }
function easeInOutCubic(t) { return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; }
var jTop = 0, jMax = 1;
function measure() {
jTop = journey.getBoundingClientRect().top + window.scrollY;
jMax = Math.max(1, journey.offsetHeight - window.innerHeight);
}
measure();
window.addEventListener("resize", measure, { passive: true });
window.addEventListener("load", measure);
function journeyTop() { return jTop; }
function maxScroll() { return jMax; }
function readProgress() { return clamp01((window.scrollY - journeyTop()) / maxScroll()); }
/* Position d'un panneau pour une progression donnée : il glisse depuis la droite,
se pose, puis se dissout vers la gauche (même courbe que la galerie Base44). */
function panelState(i, speed, progress) {
var start = HERO_SPAN + i * SPAN;
var local = clamp01((progress - start) / (SPAN * 0.96));
var inFraction = Math.min(0.34, Math.max(0.12, 0.26 / (speed || 1)));
var holdEnd = Math.min(inFraction + 0.5, 0.82);
var tIn = easeOutQuint(clamp01(local / inFraction));
var tOut = smoothstep(clamp01((local - holdEnd) / (1 - holdEnd)));
return {
opacity: tIn * (1 - tOut),
x: reduced ? 0 : (1 - tIn) * 64 - tOut * 38,
y: reduced ? 0 : (1 - tIn) * 4,
scale: reduced ? 1 : 1 + (1 - tIn) * 0.07 + tOut * 0.04
};
}
function activeIndex(progress) {
var raw = Math.floor((progress - HERO_SPAN) / SPAN);
return Math.max(0, Math.min(N - 1, raw));
}
function scrollToProgress(p, duration) {
var to = journeyTop() + clamp01(p) * maxScroll();
if (reduced) { window.scrollTo(0, to); return; }
var from = window.scrollY, t0 = performance.now(), d = duration || 1100;
function step(now) {
var t = Math.min(1, (now - t0) / d);
window.scrollTo(0, from + (to - from) * easeInOutCubic(t));
if (t < 1) requestAnimationFrame(step);
}
requestAnimationFrame(step);
}
function goTo(i) { scrollToProgress(HERO_SPAN + Math.max(0, Math.min(N - 1, i)) * SPAN + SPAN * 0.4); }
var speeds = steps.map(function (s) { return parseFloat(s.getAttribute("data-speed")) || 1; });
var frames = steps.map(function (s) { return s.querySelector(".frame"); });
var shown = steps.map(function () { return false; });
steps.forEach(function (s, i) {
var btn = s.querySelector("[data-go]");
if (!btn) return;
btn.addEventListener("click", function () {
if (btn.getAttribute("data-go") === "restart") scrollToProgress(0);
else goTo(i + 1);
});
});
dots.forEach(function (d, i) { d.addEventListener("click", function () { goTo(i); }); });
/* Images (Cloudinary) : chargées seulement quand leur panneau apparaît. */
function loadImage(i) {
var img = frames[i] && frames[i].querySelector("img[data-src]");
if (!img) return;
img.onload = function () { img.classList.add("on"); };
img.src = img.getAttribute("data-src");
img.removeAttribute("data-src");
}
var scene = null;
var current = readProgress();
var lastApplied = -1;
var active = -1;
function apply(p) {
if (scene) scene.setProgress(p);
if (Math.abs(p - lastApplied) < 0.00005) return;
lastApplied = p;
var exit = easeOutQuint(clamp01(p / HERO_SPAN));
hero.style.opacity = String(1 - exit);
hero.style.transform = reduced ? "none" : "translate3d(0," + (-exit * 7) + "vh,0)";
hero.style.visibility = exit > 0.99 ? "hidden" : "visible";
for (var i = 0; i < N; i++) {
var st = panelState(i, speeds[i], p);
var el = steps[i];
el.style.opacity = String(st.opacity);
el.style.transform = "translate3d(" + st.x + "%," + st.y + "vh,0)";
el.style.visibility = st.opacity > 0.05 ? "visible" : "hidden";
if (st.opacity > 0.85) el.classList.add("live"); else el.classList.remove("live");
el.style.setProperty("--media-scale", String(st.scale));
if (st.opacity > 0.02 && !shown[i]) { shown[i] = true; loadImage(i); }
}
fill.style.transform = "scaleX(" + p + ")";
var a = activeIndex(p);
if (a !== active) {
if (active >= 0) dots[active].removeAttribute("aria-current");
active = a;
dots[a].setAttribute("aria-current", "true");
railNum.textContent = steps[a].getAttribute("data-num");
railCat.textContent = steps[a].getAttribute("data-cat");
}
}
var visible = true;
function tick() {
if (visible) {
var target = readProgress();
current = reduced ? target : current + (target - current) * 0.12;
if (Math.abs(target - current) < 0.0004) current = target;
apply(current);
}
requestAnimationFrame(tick);
}
apply(current);
requestAnimationFrame(tick);
if ("IntersectionObserver" in window) {
new IntersectionObserver(function (entries) {
visible = entries[0].isIntersecting;
if (scene) scene.setRunning(visible && !document.hidden);
}).observe(journey);
}
document.addEventListener("visibilitychange", function () {
if (scene) scene.setRunning(visible && !document.hidden);
});
/* ---------- Couloir 3D (chargé après l'affichage du texte) ---------- */
function webglOK() {
try {
var c = document.createElement("canvas");
return !!(window.WebGLRenderingContext && (c.getContext("webgl2") || c.getContext("webgl")));
} catch (e) { return false; }
}
function makeCanvas(w, h) { var c = document.createElement("canvas"); c.width = w; c.height = h || w; return c; }
function finish(T, c, srgb, repeat) {
var t = new T.CanvasTexture(c);
if (repeat !== false) t.wrapS = t.wrapT = T.RepeatWrapping;
if (srgb !== false) t.colorSpace = T.SRGBColorSpace;
t.anisotropy = 4;
return t;
}
function marbleTexture(T) {
var size = 768, c = makeCanvas(size), x = c.getContext("2d");
var g = x.createLinearGradient(0, 0, size, size);
g.addColorStop(0, "#fbfbf9"); g.addColorStop(0.5, "#f5f5f3"); g.addColorStop(1, "#ffffff");
x.fillStyle = g; x.fillRect(0, 0, size, size);
for (var i = 0; i < 24; i++) {
var px = Math.random() * size, py = Math.random() * size;
x.beginPath(); x.moveTo(px, py);
for (var s = 0; s < 7; s++) {
var nx = px + (Math.random() - 0.5) * size * 0.55, ny = py + (Math.random() - 0.5) * size * 0.55;
x.quadraticCurveTo(px + (Math.random() - 0.5) * 130, py + (Math.random() - 0.5) * 130, nx, ny);
px = nx; py = ny;
}
x.strokeStyle = "rgba(152,154,154," + (0.04 + Math.random() * 0.06).toFixed(3) + ")";
x.lineWidth = 0.6 + Math.random() * 2.2; x.stroke();
}
for (var k = 0; k < 8000; k++) {
var sh = 200 + Math.floor(Math.random() * 55);
x.fillStyle = "rgba(" + sh + "," + sh + "," + sh + ",0.05)";
x.fillRect(Math.random() * size, Math.random() * size, 1, 1);
}
return finish(T, c);
}
function flutingTexture(T) {
var w = 512, h = 64, flutes = 20, c = makeCanvas(w, h), x = c.getContext("2d"), band = w / flutes;
x.fillStyle = "#808080"; x.fillRect(0, 0, w, h);
for (var i = 0; i < flutes; i++) {
var g = x.createLinearGradient(i * band, 0, (i + 1) * band, 0);
g.addColorStop(0, "#3f3f3f"); g.addColorStop(0.5, "#d9d9d9"); g.addColorStop(1, "#3f3f3f");
x.fillStyle = g; x.fillRect(i * band, 0, band, h);
}
return finish(T, c, false);
}
function floorTexture(T) {
var size = 1024, tiles = 4, c = makeCanvas(size), x = c.getContext("2d"), st = size / tiles;
x.fillStyle = "#eff0ec"; x.fillRect(0, 0, size, size);
for (var r = 0; r < tiles; r++) for (var q = 0; q < tiles; q++) {
var sh = 238 + Math.floor(Math.random() * 14);
x.fillStyle = "rgb(" + sh + "," + sh + "," + (sh - 3) + ")";
x.fillRect(q * st + 2, r * st + 2, st - 4, st - 4);
}
for (var i = 0; i < 14; i++) {
var px = Math.random() * size, py = Math.random() * size;
x.beginPath(); x.moveTo(px, py);
x.quadraticCurveTo(px + size * 0.4, py + (Math.random() - 0.5) * 90, px + size * 0.75, py + (Math.random() - 0.5) * 200);
x.strokeStyle = "rgba(196,198,196," + (0.05 + Math.random() * 0.07).toFixed(3) + ")";
x.lineWidth = 0.8 + Math.random() * 2; x.stroke();
}
x.strokeStyle = "rgba(178,178,174,0.85)"; x.lineWidth = 2.4;
for (var j = 0; j <= tiles; j++) {
var p = j * st;
x.beginPath(); x.moveTo(p, 0); x.lineTo(p, size); x.stroke();
x.beginPath(); x.moveTo(0, p); x.lineTo(size, p); x.stroke();
}
return finish(T, c);
}
function glowTexture(T) {
var size = 512, c = makeCanvas(size), x = c.getContext("2d");
var g = x.createRadialGradient(size / 2, size / 2, 0, size / 2, size / 2, size / 2);
g.addColorStop(0, "rgba(255,255,255,1)"); g.addColorStop(0.3, "rgba(255,255,255,0.78)");
g.addColorStop(0.65, "rgba(255,255,255,0.22)"); g.addColorStop(1, "rgba(255,255,255,0)");
x.fillStyle = g; x.fillRect(0, 0, size, size);
return finish(T, c, true, false);
}
function createCorridor(T, cv) {
var SPACING = 2.75, COLUMN_X = 3.2, EYE = 1.62, TRAVEL = 78, FLOOR_TILE_SPAN = 5.48, MARBLE = 0xf5f5f3;
var narrow = window.innerWidth < 768;
var renderer = new T.WebGLRenderer({ canvas: cv, antialias: !narrow, powerPreference: "high-performance" });
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, narrow ? 1.5 : 1.75));
var scene3 = new T.Scene();
scene3.background = new T.Color(MARBLE);
scene3.fog = new T.Fog(MARBLE, 10, 44);
var camera = new T.PerspectiveCamera(43, 1, 0.1, 260);
camera.position.set(0, EYE, 0);
scene3.add(new T.HemisphereLight(0xffffff, 0xdededa, 1.35));
var key = new T.DirectionalLight(0xffffff, 1.15); key.position.set(-6, 16, 8); scene3.add(key);
var rim = new T.DirectionalLight(0xffffff, 0.5); rim.position.set(6, 9, -14); scene3.add(rim);
var marble = marbleTexture(T);
var shaftMat = new T.MeshStandardMaterial({ map: marble, bumpMap: flutingTexture(T), bumpScale: 0.03, color: 0xffffff, roughness: 0.72 });
var stoneMat = new T.MeshStandardMaterial({ map: marble, color: 0xfbfbf9, roughness: 0.8 });
var vaultMat = new T.MeshStandardMaterial({ map: marble, color: 0xf3f3f0, roughness: 0.88, side: T.DoubleSide });
var shaftGeo = new T.CylinderGeometry(0.42, 0.46, 6, narrow ? 16 : 22);
var baseGeo = new T.BoxGeometry(1.28, 0.22, 1.28);
var plinthGeo = new T.BoxGeometry(1.04, 0.18, 1.04);
var neckGeo = new T.CylinderGeometry(0.5, 0.4, 0.44, 18);
var abacusGeo = new T.BoxGeometry(0.94, 0.16, 0.94);
var capGeo = new T.BoxGeometry(1.18, 0.16, 1.18);
var beamGeo = new T.BoxGeometry(1.4, 0.52, SPACING + 0.02);
var vaultGeo = new T.CylinderGeometry(3.98, 3.98, SPACING + 0.02, 30, 1, true, -Math.PI / 2, Math.PI);
var ribGeo = new T.TorusGeometry(3.98, 0.17, 6, 34, Math.PI);
function mesh(g, m, y) { var o = new T.Mesh(g, m); o.position.y = y; return o; }
function column() {
var c = new T.Group();
c.add(mesh(baseGeo, stoneMat, 0.11), mesh(plinthGeo, stoneMat, 0.31), mesh(shaftGeo, shaftMat, 3.4),
mesh(neckGeo, stoneMat, 6.62), mesh(abacusGeo, stoneMat, 6.92), mesh(capGeo, stoneMat, 7.06));
return c;
}
var corridor = new T.Group();
var bays = narrow ? 16 : 26;
for (var i = 0; i < bays; i++) {
var bay = new T.Group();
var l = column(); l.position.x = -COLUMN_X;
var r = column(); r.position.x = COLUMN_X;
var bl = new T.Mesh(beamGeo, stoneMat); bl.position.set(-COLUMN_X, 7.42, 0);
var br = new T.Mesh(beamGeo, stoneMat); br.position.set(COLUMN_X, 7.42, 0);
var vault = new T.Mesh(vaultGeo, vaultMat); vault.rotation.x = -Math.PI / 2; vault.position.y = 7.68;
var rib = new T.Mesh(ribGeo, stoneMat); rib.position.set(0, 7.68, -SPACING / 2);
bay.add(l, r, bl, br, vault, rib);
bay.position.z = SPACING - i * SPACING;
corridor.add(bay);
}
scene3.add(corridor);
var floorTex = floorTexture(T);
floorTex.repeat.set(1.75, 31);
var floor = new T.Mesh(new T.PlaneGeometry(9.6, 170), new T.MeshStandardMaterial({ map: floorTex, color: 0xffffff, roughness: 0.4 }));
floor.rotation.x = -Math.PI / 2; floor.position.set(0, 0, -60);
scene3.add(floor);
var glow = new T.Mesh(new T.PlaneGeometry(16, 13),
new T.MeshBasicMaterial({ map: glowTexture(T), transparent: true, depthWrite: false, fog: false, opacity: 0.95 }));
glow.position.set(0, EYE + 1.4, -43);
scene3.add(glow);
var progress = 0, pointer = { x: 0, y: 0 }, clock = new T.Clock(), elapsed = 0, running = true;
function resize() {
var w = cv.clientWidth || window.innerWidth, h = cv.clientHeight || window.innerHeight;
renderer.setSize(w, h, false);
camera.aspect = w / h;
camera.fov = camera.aspect < 1 ? 58 : 43;
camera.updateProjectionMatrix();
}
function frame() {
requestAnimationFrame(frame);
if (!running) return;
elapsed += Math.min(clock.getDelta(), 0.05);
var travel = progress * TRAVEL;
corridor.position.z = travel % SPACING;
floorTex.offset.y = travel / FLOOR_TILE_SPAN;
camera.position.x += (pointer.x * 0.3 - camera.position.x) * 0.06;
camera.position.y = EYE + Math.sin(elapsed * 0.5) * 0.02 - pointer.y * 0.05;
camera.rotation.y = -pointer.x * 0.02;
camera.rotation.x = pointer.y * 0.01;
renderer.render(scene3, camera);
}
resize();
frame();
return {
setProgress: function (v) { progress = clamp01(v); },
setPointer: function (x, y) { pointer = { x: x, y: y }; },
setRunning: function (v) { if (v && !running) clock.getDelta(); running = v; },
resize: resize
};
}
function loadCorridor() {
if (!webglOK()) return;
import(THREE_URL).then(function (T) {
scene = createCorridor(T, canvas);
scene.setProgress(current);
scene.setRunning(visible && !document.hidden);
canvas.className = "on";
window.addEventListener("resize", function () { scene.resize(); }, { passive: true });
window.addEventListener("pointermove", function (e) {
if (e.pointerType !== "mouse") return;
scene.setPointer((e.clientX / window.innerWidth) * 2 - 1, (e.clientY / window.innerHeight) * 2 - 1);
}, { passive: true });
}).catch(function () { /* le décor CSS reste affiché */ });
}
function whenIdle(fn) {
if ("requestIdleCallback" in window) window.requestIdleCallback(fn, { timeout: 1500 });
else setTimeout(fn, 600);
}
if (document.readyState === "complete") whenIdle(loadCorridor);
else window.addEventListener("load", function () { whenIdle(loadCorridor); });
})();
</script>
<script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a5006182dcd29ee7',t:'MTc5MjkyMjQ5Ng=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body>
</html>