feat(v6): nav simplifiée à 3 items + Configurateur de rocher interactif

Nav refondue selon demande cand0o :
- Accueil (#top), Mission (ex-Ambition, renommé), Configurateur (nouveau)
- Retrait des liens vers Concept/Pourquoi/Configurations/Architecture/
  CasUsage/Equipe/Roadmap (sections toujours présentes en scroll).

Nouveau composant Configurateur.astro :
- Grille de capteurs activables par catégorie (Acoustique, Physico-chimie,
  Optique, Météo, Courants), sans mention de modèle.
- Options énergie (Solaire / Solaire+éolien) et ancrage (Standard /
  Renforcé), transmission sans fil affichée comme invariant.
- Récap temps réel dans une aside sticky avec compteur de grandeurs,
  liste par catégorie, et CTA 'Connecter mon rocher au réseau'.
- Le CTA pré-remplit le textarea du form de contact avec la config
  choisie et scrolle vers #contact.
- Hint 'Réseau mesher' : chaque rocher déployé rejoint un maillage
  qui consolide la couverture de zone.

Hero reçoit id='top' pour l'ancre Accueil.
This commit is contained in:
2026-04-21 08:36:15 +00:00
parent ba23d151bf
commit 21019217c2
5 changed files with 292 additions and 16 deletions

View File

@@ -1,7 +1,7 @@
--- ---
--- ---
<section id="ambition" class="section relative overflow-hidden"> <section id="mission" class="section relative overflow-hidden">
<!-- Halo background --> <!-- Halo background -->
<div <div
aria-hidden="true" aria-hidden="true"
@@ -11,7 +11,7 @@
<div class="container-narrow"> <div class="container-narrow">
<div class="section-title-block"> <div class="section-title-block">
<span class="eyebrow">Ambition</span> <span class="eyebrow">Mission</span>
<h2 class="h-section mt-4"> <h2 class="h-section mt-4">
D'une station côtière à un grand réseau de mesure. D'une station côtière à un grand réseau de mesure.
</h2> </h2>

View File

@@ -0,0 +1,280 @@
---
const sensors = [
{ id: 'hydro', cat: 'Acoustique', label: 'Hydrophone', desc: "Écoute continue du milieu sous-marin." },
{ id: 'temp', cat: 'Physico-chimie', label: 'Température', desc: 'Sonde étanche intégrée.' },
{ id: 'cond', cat: 'Physico-chimie', label: 'Conductivité / salinité', desc: '' },
{ id: 'o2', cat: 'Physico-chimie', label: 'Oxygène dissous', desc: '' },
{ id: 'ph', cat: 'Physico-chimie', label: 'pH', desc: '' },
{ id: 'turb', cat: 'Physico-chimie', label: 'Turbidité', desc: '' },
{ id: 'chloro', cat: 'Physico-chimie', label: 'Chlorophylle-a', desc: '' },
{ id: 'no3', cat: 'Physico-chimie', label: 'Nitrates', desc: '' },
{ id: 'cam', cat: 'Optique', label: 'Caméra sous-marine', desc: '' },
{ id: 'led', cat: 'Optique', label: 'Éclairage LED', desc: '' },
{ id: 'weather', cat: 'Météo', label: 'Station météo', desc: 'Vent, pression, air.' },
{ id: 'current', cat: 'Courants', label: 'Courantométrie', desc: '' },
];
const defaults = ['hydro', 'temp'];
const cats = Array.from(new Set(sensors.map((s) => s.cat)));
---
<section id="configurateur" class="section">
<div class="container-narrow">
<div class="section-title-block">
<span class="eyebrow">Configurateur</span>
<h2 class="h-section mt-4">Composez votre rocher. Rejoignez le réseau.</h2>
<p class="lead mt-6">
Chaque site a ses enjeux. Cochez les grandeurs que vous voulez mesurer, ajustez
l'énergie et l'ancrage, et envoyez-nous votre configuration — nous revenons vers
vous avec une proposition adaptée. Chaque rocher déployé rejoint automatiquement
le réseau <strong class="text-foam-50">mesher</strong> NowYouSea, qui couvre le
littoral au fur et à mesure.
</p>
</div>
<div class="mt-10 grid gap-8 lg:grid-cols-[1.15fr_1fr]">
<!-- Left : selection -->
<div class="space-y-8">
<!-- Sensors -->
<div>
<h3 class="font-display text-lg font-semibold text-foam-50">Grandeurs à mesurer</h3>
<p class="mt-1 text-xs text-foam-100/60">Cliquez pour activer / désactiver.</p>
<div class="mt-5 space-y-5">
{cats.map((cat) => (
<div>
<div class="text-[11px] font-medium uppercase tracking-[0.18em] text-lagoon-400/80">{cat}</div>
<div class="mt-2 flex flex-wrap gap-2">
{sensors.filter((s) => s.cat === cat).map((s) => (
<button
type="button"
class="nys-sensor group relative flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.03] px-3.5 py-1.5 text-sm text-foam-100/80 transition hover:border-tide-400/40 hover:text-foam-50"
data-sensor-id={s.id}
data-sensor-label={s.label}
data-sensor-cat={s.cat}
data-default={defaults.includes(s.id) ? 'true' : 'false'}
title={s.desc}
>
<svg viewBox="0 0 24 24" class="nys-sensor-check h-3.5 w-3.5 text-tide-300 opacity-0 transition-opacity" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M20 6L9 17l-5-5"/>
</svg>
<span class="nys-sensor-label">{s.label}</span>
</button>
))}
</div>
</div>
))}
</div>
</div>
<!-- Options -->
<div class="grid gap-5 sm:grid-cols-2">
<div>
<h3 class="font-display text-lg font-semibold text-foam-50">Énergie</h3>
<div class="mt-3 flex flex-wrap gap-2">
<button type="button" class="nys-opt rounded-full border border-white/10 bg-white/[0.03] px-3.5 py-1.5 text-sm text-foam-100/80 transition hover:border-tide-400/40 hover:text-foam-50" data-opt-group="energy" data-opt-value="Solaire" data-default="true">Solaire</button>
<button type="button" class="nys-opt rounded-full border border-white/10 bg-white/[0.03] px-3.5 py-1.5 text-sm text-foam-100/80 transition hover:border-tide-400/40 hover:text-foam-50" data-opt-group="energy" data-opt-value="Solaire + éolien">Solaire + éolien</button>
</div>
</div>
<div>
<h3 class="font-display text-lg font-semibold text-foam-50">Ancrage</h3>
<div class="mt-3 flex flex-wrap gap-2">
<button type="button" class="nys-opt rounded-full border border-white/10 bg-white/[0.03] px-3.5 py-1.5 text-sm text-foam-100/80 transition hover:border-tide-400/40 hover:text-foam-50" data-opt-group="anchor" data-opt-value="Standard" data-default="true">Standard</button>
<button type="button" class="nys-opt rounded-full border border-white/10 bg-white/[0.03] px-3.5 py-1.5 text-sm text-foam-100/80 transition hover:border-tide-400/40 hover:text-foam-50" data-opt-group="anchor" data-opt-value="Renforcé">Renforcé (courants forts)</button>
</div>
</div>
</div>
</div>
<!-- Right : summary + CTA -->
<aside class="lg:sticky lg:top-20">
<div class="card relative overflow-hidden">
<div class="pointer-events-none absolute inset-0 -z-10 opacity-60" style="background: radial-gradient(ellipse at 60% 20%, rgba(56,189,248,0.12), transparent 55%);"></div>
<div class="flex items-center gap-3">
<span class="relative inline-flex h-10 w-10 items-center justify-center rounded-lg bg-gradient-to-br from-tide-500/30 to-lagoon-500/20 ring-1 ring-tide-400/30">
<svg viewBox="0 0 24 24" class="h-5 w-5 text-tide-300" fill="currentColor" aria-hidden="true">
<path d="M4 17c0-4 3-6 5-6 1 0 2-3 5-3s4 2 4 4c1 0 2 1 2 3s-2 4-5 4H6c-1 0-2-1-2-2z"/>
</svg>
</span>
<div>
<div class="text-[10px] font-medium uppercase tracking-[0.2em] text-tide-400">Votre rocher</div>
<div class="font-display text-lg font-semibold text-foam-50">
<span id="nys-sensor-count">0</span>
<span class="text-foam-100/60 text-sm font-normal"> · grandeur<span id="nys-sensor-plural"></span></span>
</div>
</div>
</div>
<ul id="nys-sensor-list" class="mt-5 space-y-2 text-sm text-foam-100/85 min-h-[140px]"></ul>
<div class="mt-5 grid gap-3 text-xs">
<div class="flex items-center justify-between rounded-lg bg-abyss-900/50 px-3 py-2 ring-1 ring-white/5">
<span class="text-foam-100/55 uppercase tracking-wider text-[10px]">Énergie</span>
<span id="nys-energy" class="text-foam-50">—</span>
</div>
<div class="flex items-center justify-between rounded-lg bg-abyss-900/50 px-3 py-2 ring-1 ring-white/5">
<span class="text-foam-100/55 uppercase tracking-wider text-[10px]">Ancrage</span>
<span id="nys-anchor" class="text-foam-50">—</span>
</div>
<div class="flex items-center justify-between rounded-lg bg-abyss-900/50 px-3 py-2 ring-1 ring-white/5">
<span class="text-foam-100/55 uppercase tracking-wider text-[10px]">Transmission</span>
<span class="text-foam-50">Sans fil</span>
</div>
</div>
<button
type="button"
id="nys-join-btn"
class="btn btn-primary mt-6 w-full justify-center"
>
Connecter mon rocher au réseau
<svg viewBox="0 0 24 24" class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M5 12h14M13 5l7 7-7 7"/>
</svg>
</button>
<p class="mt-3 text-[11px] text-foam-100/50">
Nous vous revenons sous 48 h avec une proposition chiffrée adaptée à votre site.
</p>
</div>
<!-- Mesh hint -->
<div class="mt-6 rounded-xl border border-white/5 bg-white/[0.02] px-5 py-4 text-xs leading-relaxed text-foam-100/70">
<div class="flex items-center gap-2">
<span class="relative inline-flex h-2 w-2">
<span class="absolute inline-flex h-full w-full animate-ping rounded-full bg-tide-400 opacity-60"></span>
<span class="relative inline-flex h-2 w-2 rounded-full bg-tide-400"></span>
</span>
<span class="font-medium uppercase tracking-widest text-[10px] text-tide-400">Réseau mesher</span>
</div>
<p class="mt-2">
Chaque rocher partage son signal avec ses voisins et consolide la couverture
de la zone — plus il y a de rochers, plus la carte devient précise.
</p>
</div>
</aside>
</div>
</div>
</section>
<script is:inline>
(() => {
const state = {
sensors: new Map(), // id -> { label, cat }
energy: 'Solaire',
anchor: 'Standard',
};
const activeCls = ['bg-tide-500/15', 'border-tide-400/40', 'text-foam-50'];
function setBtnActive(btn, active) {
btn.dataset.active = active ? 'true' : 'false';
activeCls.forEach((c) => btn.classList.toggle(c, active));
const check = btn.querySelector('.nys-sensor-check');
if (check) check.classList.toggle('opacity-100', active);
}
function render() {
const countEl = document.getElementById('nys-sensor-count');
const pluralEl = document.getElementById('nys-sensor-plural');
const listEl = document.getElementById('nys-sensor-list');
const energyEl = document.getElementById('nys-energy');
const anchorEl = document.getElementById('nys-anchor');
const n = state.sensors.size;
countEl.textContent = String(n);
pluralEl.textContent = n > 1 ? 's mesurées' : (n === 1 ? ' mesurée' : ' à choisir');
if (n === 0) {
listEl.innerHTML = '<li class="text-foam-100/50 italic">Aucune grandeur sélectionnée — choisissez au moins l\'hydrophone pour l\'acoustique.</li>';
} else {
const byCat = {};
state.sensors.forEach((v, id) => {
(byCat[v.cat] = byCat[v.cat] || []).push(v.label);
});
listEl.innerHTML = Object.entries(byCat).map(([cat, labels]) => `
<li>
<div class="text-[10px] uppercase tracking-widest text-foam-100/45">${cat}</div>
<div class="mt-0.5">${labels.join(' · ')}</div>
</li>
`).join('');
}
energyEl.textContent = state.energy;
anchorEl.textContent = state.anchor;
}
// Wire sensors
document.querySelectorAll('.nys-sensor').forEach((btn) => {
const id = btn.dataset.sensorId;
const label = btn.dataset.sensorLabel;
const cat = btn.dataset.sensorCat;
const isDefault = btn.dataset.default === 'true';
if (isDefault) {
state.sensors.set(id, { label, cat });
setBtnActive(btn, true);
}
btn.addEventListener('click', () => {
if (state.sensors.has(id)) {
state.sensors.delete(id);
setBtnActive(btn, false);
} else {
state.sensors.set(id, { label, cat });
setBtnActive(btn, true);
}
render();
});
});
// Wire options (energy / anchor)
document.querySelectorAll('.nys-opt').forEach((btn) => {
const group = btn.dataset.optGroup;
const value = btn.dataset.optValue;
const isDefault = btn.dataset.default === 'true';
if (isDefault) {
state[group] = value;
setBtnActive(btn, true);
}
btn.addEventListener('click', () => {
// single-select group
document.querySelectorAll(`.nys-opt[data-opt-group="${group}"]`).forEach((b) => setBtnActive(b, false));
setBtnActive(btn, true);
state[group] = value;
render();
});
});
// CTA : pre-fill contact form + scroll
const joinBtn = document.getElementById('nys-join-btn');
if (joinBtn) {
joinBtn.addEventListener('click', () => {
const sensorList = Array.from(state.sensors.values()).map((s) => s.label).join(', ');
const summary = `Configuration rocher NowYouSea :\n`
+ `— Grandeurs : ${sensorList || '(aucune)'}\n`
+ `— Énergie : ${state.energy}\n`
+ `— Ancrage : ${state.anchor}\n`
+ `— Transmission : sans fil`;
const ta = document.querySelector('#contact textarea')
|| document.querySelector('textarea[name="message"]')
|| document.querySelector('textarea');
if (ta) {
const existing = ta.value && ta.value.trim().length > 0 ? ta.value.trim() + '\n\n' : '';
ta.value = existing + summary;
ta.dispatchEvent(new Event('input', { bubbles: true }));
}
const contact = document.getElementById('contact');
if (contact) contact.scrollIntoView({ behavior: 'smooth', block: 'start' });
});
}
render();
})();
</script>

View File

@@ -1,7 +1,7 @@
--- ---
--- ---
<section class="relative overflow-hidden pt-16 md:pt-24 pb-24 md:pb-32"> <section id="top" class="relative overflow-hidden pt-16 md:pt-24 pb-24 md:pb-32">
<!-- Ambient caustic glow --> <!-- Ambient caustic glow -->
<div class="pointer-events-none absolute inset-0 caustic-bg"></div> <div class="pointer-events-none absolute inset-0 caustic-bg"></div>

View File

@@ -1,15 +1,9 @@
--- ---
const base = import.meta.env.BASE_URL; const base = import.meta.env.BASE_URL;
const items = [ const items = [
{ href: '#concept', label: 'Concept' }, { href: '#top', label: 'Accueil' },
{ href: '#ambition', label: 'Ambition' }, { href: '#mission', label: 'Mission' },
{ href: '#pourquoi', label: 'Pourquoi' }, { href: '#configurateur', label: 'Configurateur' },
{ href: '#configurations', label: 'Configurations' },
{ href: '#architecture', label: 'Architecture' },
{ href: '#cas-dusage', label: "Cas d'usage" },
{ href: '#equipe', label: 'Équipe' },
{ href: '#roadmap', label: 'Feuille de route' },
{ href: '#contact', label: 'Contact' },
]; ];
--- ---
@@ -26,8 +20,8 @@ const items = [
<span class="font-display text-base font-semibold tracking-tight whitespace-nowrap">NowYouSea</span> <span class="font-display text-base font-semibold tracking-tight whitespace-nowrap">NowYouSea</span>
</a> </a>
<nav aria-label="Navigation principale" class="hidden flex-1 justify-center xl:flex"> <nav aria-label="Navigation principale" class="hidden flex-1 justify-center md:flex">
<ul class="flex items-center gap-5 text-sm text-foam-100/75"> <ul class="flex items-center gap-8 text-sm text-foam-100/75">
{items.map((item) => ( {items.map((item) => (
<li> <li>
<a href={item.href} class="whitespace-nowrap hover:text-tide-400">{item.label}</a> <a href={item.href} class="whitespace-nowrap hover:text-tide-400">{item.label}</a>
@@ -36,7 +30,7 @@ const items = [
</ul> </ul>
</nav> </nav>
<div class="ml-auto shrink-0 xl:ml-0"> <div class="ml-auto shrink-0 md:ml-0">
<a href="#contact" class="btn btn-primary hidden whitespace-nowrap md:inline-flex"> <a href="#contact" class="btn btn-primary hidden whitespace-nowrap md:inline-flex">
Demander une démo Demander une démo
<svg viewBox="0 0 24 24" class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> <svg viewBox="0 0 24 24" class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">

View File

@@ -4,6 +4,7 @@ import Nav from '../components/Nav.astro';
import Hero from '../components/Hero.astro'; import Hero from '../components/Hero.astro';
import Concept from '../components/Concept.astro'; import Concept from '../components/Concept.astro';
import Ambition from '../components/Ambition.astro'; import Ambition from '../components/Ambition.astro';
import Configurateur from '../components/Configurateur.astro';
import Pourquoi from '../components/Pourquoi.astro'; import Pourquoi from '../components/Pourquoi.astro';
import Configurations from '../components/Configurations.astro'; import Configurations from '../components/Configurations.astro';
import Architecture from '../components/Architecture.astro'; import Architecture from '../components/Architecture.astro';
@@ -18,8 +19,9 @@ import Footer from '../components/Footer.astro';
<Nav /> <Nav />
<main id="main"> <main id="main">
<Hero /> <Hero />
<Concept />
<Ambition /> <Ambition />
<Configurateur />
<Concept />
<Pourquoi /> <Pourquoi />
<Configurations /> <Configurations />
<Architecture /> <Architecture />