Barre de navigation style Apple (glassmorphism) : capsule translucide + bouton rond, flou d’arrière-plan et sélecteur bleu qui glisse avec une animation fluide.
Accessible (ARIA + clavier), responsive, sans dépendances. Ajustez l’intensité via --blur / --sat; le fallback s’active automatiquement si backdrop-filter n’est pas disponible.
<!-- ================= Apple Frosted Tabs + Animated Selector ================= -->
<div class="store-dock">
<div class="tabs glass" role="tablist" aria-label="Store navigation">
<!-- Sélecteur bleu animé (passe derrière le tab actif) -->
<span class="tab-thumb" aria-hidden="true"></span>
<button class="tab is-active" role="tab" aria-selected="true" data-tab="today">
<svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 3h12v3H6zM5 7h14v12H5zM8 10h8M8 14h6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
<span>Today</span>
</button>
<button class="tab" role="tab" aria-selected="false" data-tab="games">
<svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 13c0-3.9 3.1-7 7-7s7 3.1 7 7M7 13h2M15 13h2M14 10l3-2" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
<span>Games</span>
</button>
<button class="tab" role="tab" aria-selected="false" data-tab="apps">
<svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 4h7v7H4zM13 4h7v7h-7zM4 13h7v7H4zM13 13h7v7h-7z" fill="currentColor"/></svg>
<span>Apps</span>
</button>
<button class="tab" role="tab" aria-selected="false" data-tab="arcade">
<svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 16h14M7 16v-2a5 5 0 0 1 10 0v2M8 11h2M14 11h2" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
<span>Arcade</span>
</button>
</div>
<button class="glass circle" aria-label="Search">
<svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M10.5 18a7.5 7.5 0 1 1 5.3-12.8L22 11.4l-2.1 2.1-5.6-5.6A7.5 7.5 0 0 1 10.5 18z" fill="currentColor"/></svg>
</button>
</div>
<style>
/* --------- Démo : fond chaud pour bien voir le frosted --------- */
.store-dock{
--pad: clamp(10px, 2.5vw, 18px);
padding: var(--pad);
display:flex; gap: clamp(10px,2vw,16px); align-items:center;
background:
linear-gradient(180deg,#a35f20 0%, #cc8a3a 30%, #b36a27 60%, #8c4f1a 100%);
border-radius: 18px;
}
/* =============== Capsule “frosted” =============== */
.glass{
--blur: 24px;
--sat: 180%;
--bg1: rgba(255,255,255,.22);
--bg2: rgba(255,255,255,.12);
--stroke: rgba(255,255,255,.65);
--inner: rgba(0,0,0,.18);
--shadow: rgba(0,0,0,.22);
position: relative;
border: 1px solid var(--stroke);
background: linear-gradient(180deg, var(--bg1), var(--bg2));
-webkit-backdrop-filter: blur(var(--blur)) saturate(var(--sat));
backdrop-filter: blur(var(--blur)) saturate(var(--sat));
box-shadow:
inset 0 1px 0 rgba(255,255,255,.7),
inset 0 -1px 0 var(--inner),
0 14px 26px var(--shadow);
border-radius: 9999px;
}
@supports not (backdrop-filter: blur(1px)){
.glass{ background: linear-gradient(180deg, rgba(250,250,250,.8), rgba(240,240,240,.6)); }
}
/* =============== Tabs capsule =============== */
.tabs{
display:grid;
grid-auto-columns: 1fr;
grid-auto-flow: column;
align-items:center;
position: relative;
min-height: 64px;
padding: 8px;
}
.tab{
position: relative;
z-index: 2; /* au-dessus du thumb */
display:flex; align-items:center; justify-content:center;
gap:10px; padding: 12px 18px; border:0; background: none; border-radius:18px;
font: 600 16px/1 -apple-system, system-ui, "SF Pro Text", Segoe UI, Roboto, Helvetica, Arial;
color:#1a1a1a; cursor:pointer; user-select:none;
white-space:nowrap;
}
.tab .ico{ width:22px; height:22px; color:#1a1a1a; opacity:.9; }
.tab.is-active{ color:#007aff; }
.tab.is-active .ico{ color:#007aff; }
/* Sélecteur animé (bleu + halo) */
.tab-thumb{
--x: 8px; /* left animée via JS */
--w: 140px; /* width animée via JS */
position:absolute; z-index:1; top:8px; left: var(--x); width: var(--w); height: calc(100% - 16px);
border-radius: 18px;
border: 1px solid rgba(255,255,255,.75);
background:
radial-gradient(120% 160% at 14% 30%, rgba(0,122,255,.55), rgba(0,122,255,.18) 35%, rgba(0,122,255,0) 65%),
radial-gradient(90% 140% at 35% 65%, rgba(255,255,255,.35), rgba(255,255,255,0) 55%),
linear-gradient(180deg, rgba(255,255,255,.40), rgba(255,255,255,.16));
box-shadow:
inset 0 1px 0 rgba(255,255,255,.75),
inset 0 -1px 0 rgba(0,0,0,.18),
0 8px 18px rgba(0,122,255,.35);
pointer-events:none;
/* animation “spring-like” (via transition custom) */
transition:
left 420ms cubic-bezier(.2,.8,.16,1),
width 420ms cubic-bezier(.2,.8,.16,1),
transform 420ms cubic-bezier(.2,.8,.16,1);
}
/* =============== Bouton cercle (recherche) =============== */
.circle{
width: 64px; height: 64px; display:grid; place-items:center;
border-radius: 9999px;
}
.circle .ico{ width:26px; height:26px; color:#1a1a1a; opacity:.9; }
/* Accessibilité */
@media (prefers-reduced-motion: reduce){
.tab-thumb{ transition: none !important; }
}
</style>
<script>
/* ===== Animated selector ===== */
(function(){
const tabs = document.querySelector('.tabs');
const thumb = tabs.querySelector('.tab-thumb');
const btns = [...tabs.querySelectorAll('.tab')];
function moveThumb(to){
const rWrap = tabs.getBoundingClientRect();
const r = to.getBoundingClientRect();
const x = r.left - rWrap.left; // position relative
const w = r.width;
thumb.style.setProperty('--x', (x) + 'px');
thumb.style.setProperty('--w', (w) + 'px');
btns.forEach(b=>{
const active = b === to;
b.classList.toggle('is-active', active);
b.setAttribute('aria-selected', active ? 'true' : 'false');
b.tabIndex = active ? 0 : -1;
});
}
// init position on load
moveThumb(btns.find(b => b.classList.contains('is-active')) || btns[0]);
// click / keyboard
btns.forEach(b=>{
b.addEventListener('click', ()=> moveThumb(b));
b.addEventListener('keydown', e=>{
if(e.key === 'ArrowRight' || e.key === 'ArrowLeft'){
e.preventDefault();
const i = btns.indexOf(document.activeElement);
const next = e.key === 'ArrowRight'
? btns[(i+1) % btns.length]
: btns[(i-1+btns.length) % btns.length];
next.focus(); moveThumb(next);
}
});
});
// responsive: recalc on resize
addEventListener('resize', ()=> {
const current = btns.find(b=> b.classList.contains('is-active')) || btns[0];
moveThumb(current);
}, {passive:true});
})();
</script>