Skip to content

Instantly share code, notes, and snippets.

@EncodeTheCode
Created February 27, 2026 23:37
Show Gist options
  • Select an option

  • Save EncodeTheCode/5418b75023469560a32cb9e8eaf8145e to your computer and use it in GitHub Desktop.

Select an option

Save EncodeTheCode/5418b75023469560a32cb9e8eaf8145e to your computer and use it in GitHub Desktop.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>PS Classic Menu — Fixed Options Enter</title>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<style>
:root{ --selected-size:200px; }
html,body{height:100%;margin:0;background:radial-gradient(circle at center,#111 0%,#000 100%);color:#eee;font-family:system-ui, -apple-system, "Segoe UI", Roboto, Arial;}
.stage{position:relative;width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;}
.carousel{position:relative;width:900px;height:500px;pointer-events:none;will-change:transform;}
.game{
--size:100px;
position:absolute; left:50%; top:50%;
transform:translate(-50%,-50%);
width:var(--size); height:var(--size);
display:flex; align-items:center; justify-content:center;
pointer-events:auto; cursor:pointer;
border-radius:0.35em; overflow:hidden;
border:1px solid rgba(255,255,255,0.06);
background:linear-gradient(180deg,#151515,#0b0b0f);
box-shadow:0 8px 20px rgba(0,0,0,0.65);
transition:
transform 420ms cubic-bezier(.25,.8,.25,1),
width 420ms cubic-bezier(.25,.8,.25,1),
height 420ms cubic-bezier(.25,.8,.25,1),
opacity 260ms ease,
box-shadow 260ms ease,
border-color 200ms ease;
will-change: transform;
}
.game img{
width:100%; height:100%; object-fit:cover; display:block; border-radius:inherit; pointer-events:none; backface-visibility:hidden;
}
.game.front{
border:1px solid #fff;
box-shadow:0 14px 36px rgba(0,0,0,0.6), 0 0 18px rgba(255,255,255,0.06);
}
.game.single{ z-index:9999!important; }
.game.single img{ width:var(--selected-size); height:var(--selected-size); border-radius:0.35em; box-shadow:0 28px 68px rgba(0,0,0,0.75); }
.controls{position:absolute;top:16px;left:50%;transform:translateX(-50%);color:#ddd;font-size:13px;background:rgba(255,255,255,0.02);padding:8px 12px;border-radius:8px;border:1px solid rgba(255,255,255,0.03);display:flex;gap:12px;align-items:center;pointer-events:auto;}
.arrowBtn{background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.04);color:#ddd;padding:8px 10px;border-radius:8px;cursor:pointer;}
.hud{position:absolute;bottom:26px;width:100%;text-align:center;color:#ddd;font-size:14px;pointer-events:none;}
.game-title{position:absolute;bottom:86px;left:50%;transform:translateX(-50%);color:#bfbfbf;font-size:18px;letter-spacing:1.6px;text-transform:uppercase;pointer-events:none;text-align:center;width:min(900px,94vw);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;opacity:0.98;}
@media (max-width:900px){ .carousel{width:94vw;height:46vh;} .game-title{font-size:14px;bottom:70px;} }
.game:focus{outline:none;}
</style>
</head>
<body>
<div class="stage">
<div class="carousel" id="carousel" aria-hidden="false"></div>
<div class="controls" aria-hidden="false">
<div class="menuLabel" id="menuTitle">Main Menu</div>
<div style="display:flex;gap:10px;align-items:center;">
<button class="arrowBtn" id="btnLeft" aria-label="Move left">A / ←</button>
<button class="arrowBtn" id="btnRight" aria-label="Move right">D / →</button>
</div>
</div>
<div class="game-title" id="gameTitle" aria-live="polite" aria-atomic="true"></div>
<div class="hud" id="hintText">Press <strong>A</strong>/<strong>D</strong> or ←/→ to rotate, <strong>Enter</strong> to select, <strong>R</strong> to return</div>
</div>
<script>
let spacing=0.69,perspectiveTilt=0.32,baseOffset=227,spiralStrength=1.15,boomerangIntensity=0.9,stage1Duration=820,stage2Duration=520;
const mainGames=[{title:'Crash Bandicoot',image:'https://upload.wikimedia.org/wikipedia/en/4/44/Crash_Bandicoot_Cover.png'},{title:'Spyro the Dragon',color:'#2f6fb2',image:'https://upload.wikimedia.org/wikipedia/en/5/5b/Spyro_the_Dragon_cover.png'},{title:'Tekken 3',color:'#2fb280'},{title:'Final Fantasy VII',color:'#b28b2f'},{title:'Metal Gear Solid',color:'#8a2fb2'},{title:'Ridge Racer',color:'#b22f6f'},{title:'Tombi!',color:'#2fb29e'},{title:'Barbie: Race &amp; Ride',color:'#6fb22f'},{title:'Barbie Explorer',color:'#ff97fd'},{title:'Wipeout 2097',color:'#b24c2f'},{title:'Worms Armageddon',color:'#2fb2b2'},{title:'Tomb Raider 3',color:'#b24ca8'},{title:'NHL 2001',color:'#ea1daa'},{title:'Crash Bash',color:'#ab4ec4'},{title:'Crash Team Racing',color:'#f1edde'},{title:'Grand Theft Auto 2',color:'#ae7d15'},{title:'Army Men: Omega Soldier',color:'#7e1ed3'},{title:'Croc 2',color:'#ed8a2e'},{title:'Pac-Man World',color:'#aae4ea'},{title:"Hello Kitty's Cube Frenzy",color:'#842aa3'},{title:'Syphon Filter',color:'#bc784a'},{title:'Syphon Filter 2',color:'#bc784a'},{title:'Syphon Filter 3',color:'#bc784a'},{title:'Muppet Monster Adventure',color:'#d74a83'},{title:'Muppet RaceMania',color:'#d74a83'},{title:'Resident Evil',color:'#ff1c2b'},{title:'Alundra',color:'#ffe57e'},{title:'Alundra 2',color:'#ffe57e'},{title:'Ape Escape',color:'#59ffbe'},{title:'Options',color:'#4c6fb2'}];
const optionsGames=[{title:'Display Settings',color:'#3f7fb2'},{title:'Audio Settings',color:'#5fb27f'},{title:'Controls',color:'#b27f3f'},{title:'Network',color:'#b25f9a'},{title:'System Info',color:'#7f8fb2'},{title:'Back',color:'#4c6fb2'}];
function escapeForSVG(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');}
function makeSVGData(title,color){const safe=escapeForSVG(title);const svg=`<svg xmlns='http://www.w3.org/2000/svg' width='400' height='400'><defs><linearGradient id='g' x1='0' x2='1'><stop offset='0' stop-color='${color}'/><stop offset='1' stop-color='#111'/></linearGradient></defs><rect width='100%' height='100%' fill='url(#g)'/><text x='50%' y='50%' font-family='Verdana, Arial, sans-serif' font-size='34' fill='#fff' dominant-baseline='middle' text-anchor='middle'>${safe}</text></svg>`;return 'data:image/svg+xml;utf8,'+encodeURIComponent(svg);}
function cubicBezierPoint(p0,p1,p2,p3,t){const u=1-t;const x=u*u*u*p0.x+3*u*u*t*p1.x+3*u*t*t*p2.x+t*t*t*p3.x;const y=u*u*u*p0.y+3*u*u*t1*p1.y+3*u*t*t*p2.y+t*t*t*p3.y;return {x,y};}
function easeOutCubic(x){return 1-Math.pow(1-x,3);}
function signedAngle(raw){while(raw<=-Math.PI)raw+=2*Math.PI;while(raw>Math.PI)raw-=2*Math.PI;return raw;}
const $carousel=$('#carousel');let menuData=mainGames.slice(),nodes=[],N=0,selected=0,currentMenu='main',menuStack=[],animating=false,inSingleView=false,savedSelected=null;
function getLayoutParams(){const rect=$carousel[0].getBoundingClientRect();const baseRX=Math.min(rect.width,900)*0.42;const baseRY=Math.min(rect.height,500)*0.34;const rX=baseRX*spacing;const rY=baseRY*spacing;return {rX,rY,width:rect.width,height:rect.height};}
function angleStepFor(n){return 2*Math.PI/n;}
function sizeFromD(d){if(d<=0.5){const t=d/0.5;return 110-(110-80)*t;}else{const t=(d-0.5)/0.5;return 80-(80-55)*t;}}
function buildMenu(menuArray){
$carousel.empty();nodes=[];
menuArray.forEach((g,i)=>{
const el=document.createElement('div');el.className='game';el.dataset.index=i;el.setAttribute('role','button');el.setAttribute('tabindex','0');
const img=document.createElement('img');img.alt=g.title;img.src=makeSVGData(g.title.replace(/&amp;/g,'&'),g.color||'#444');el.appendChild(img);$carousel.append(el);nodes.push(el);
});
N=nodes.length;
nodes.forEach((node,idx)=>{
node.addEventListener('click',()=>{if(inSingleView){restoreFromSingleView().then(()=>{selected=idx;updatePositions();updateGameTitle();});return;}selected=idx;updatePositions();updateGameTitle();});
node.addEventListener('keydown',(ev)=>{if(ev.key==='Enter'||ev.key===' '){ev.preventDefault();if(!animating&&!inSingleView){selected=Number(node.dataset.index);handleEnterOnSelected();}}});
});
}
function circlePosFor(i,sel,count,params){const step=angleStepFor(count);const raw=signedAngle((i-sel)*step);const d=Math.abs(raw)/Math.PI;const size=sizeFromD(d);const x=Math.sin(raw)*params.rX;const y=Math.cos(raw)*params.rY*perspectiveTilt+baseOffset;const scale=size/110;return {x,y,scale,raw,d,size};}
function updatePositions(){
if(inSingleView||!nodes.length)return;
const params=getLayoutParams();let frontIndex=null;
for(let i=0;i<N;i++){
const node=nodes[i];const pos=circlePosFor(i,selected,N,params);
node.style.setProperty('--size',pos.size+'px');node.style.width=pos.size+'px';node.style.height=pos.size+'px';
node.style.transform=`translate(-50%,-50%) translate(${pos.x}px,${pos.y}px) scale(${pos.scale}) rotate(0deg)`;
node.style.zIndex=Math.round((1-pos.d)*1000);node.style.opacity=1;
if(Math.abs(pos.raw)<(angleStepFor(N)*0.5)){node.classList.add('front');node.setAttribute('aria-current','true');frontIndex=i;}else{node.classList.remove('front');node.removeAttribute('aria-current');}
}
if(frontIndex===null&&nodes[selected]){nodes[selected].classList.add('front');nodes[selected].setAttribute('aria-current','true');frontIndex=selected;}
try{if(frontIndex!==null&&nodes[frontIndex])nodes[frontIndex].focus();}catch(e){}
updateGameTitle();
}
function animateEntrance(durationOverride){if(!nodes.length)return;const params=getLayoutParams();const targets=[];for(let i=0;i<N;i++){const pos=circlePosFor(i,selected,N,params);targets.push(pos);const node=nodes[i];node.style.transform=`translate(-50%,-50%) translate(0px,0px) scale(0.12) rotate(0deg)`;node.style.opacity=0;node.classList.remove('single');}nodes.forEach(n=>$(n).stop(true,true));animating=true;const dur=durationOverride||900;$({t:0}).animate({t:1},{duration:dur,easing:'swing',step(now){const p=now;const e=easeOutCubic;for(let i=0;i<N;i++){const node=nodes[i],tar=targets[i];const spin=(1-p)*(3*2*Math.PI);const angle=tar.raw+spin;const x=Math.sin(angle)*tar.x*e(p);const y=Math.cos(angle)*tar.y*e(p);const scale=0.12+(tar.scale-0.12)*e(p);node.style.transform=`translate(-50%,-50%) translate(${x}px,${y}px) scale(${scale}) rotate(0deg)`;node.style.opacity=Math.min(1,p*1.05);node.style.zIndex=tar.size;}},complete(){animating=false;updatePositions();try{nodes[selected].focus();}catch(e){}}});}
/* ------------------- ENTER / OPTIONS HANDLING ------------------- */
function handleEnterOnSelected(){
const item=menuData[selected];if(!item)return;
const realTitle=String(item.title).replace(/&amp;/g,'&').trim().toLowerCase();
if(realTitle==='options'){openOptions();return;}
if(realTitle==='back'){returnToParent();return;}
enterSelect();
}
function openOptions(){if(animating)return;transitionToMenu(optionsGames,'Options',0);}
function returnToParent(){const prev=menuStack.pop();if(!prev){animating=false;return;}transitionToMenu(prev.data.slice(),prev.title||'Main Menu',prev.selectedIndex||0);}
function enterSelect(){if(animating||inSingleView)return $.Deferred().resolve().promise();animating=true;inSingleView=true;savedSelected=selected;nodes.forEach(n=>$(n).stop(true,true));const chosen=nodes[selected];const img=chosen.querySelector('img');const curSize=parseFloat(getComputedStyle(chosen).getPropertyValue('--size'))||100;nodes.forEach((node,idx)=>{if(idx===selected)return;$(node).animate({opacity:0},260).promise().done(()=>{node.style.pointerEvents='none';});});const dfd=$.Deferred();$({t:0}).animate({t:1},{duration:420,easing:'swing',step(now){const p=now;const curScale=(curSize/110);const targetScale=(parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--selected-size'))||200)/110;const scale=curScale+(targetScale-curScale)*p;chosen.style.transform=`translate(-50%,-50%) translate(0px,0px) scale(${scale})`;chosen.style.zIndex=9999;chosen.style.opacity=1;if(img){const sizePx=(curSize*(1-p))+((parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--selected-size'))||200)*p);img.style.width=sizePx+'px';img.style.height=sizePx+'px';}},complete(){chosen.classList.add('single');animating=false;dfd.resolve();}});return dfd.promise();}
function restoreFromSingleView(){if(!inSingleView&&!animating)return $.Deferred().resolve().promise();animating=true;nodes.forEach(n=>$(n).stop(true,true));const promises=nodes.map(node=>{const d=$.Deferred();node.style.pointerEvents='auto';$(node).animate({opacity:1},240,function(){const img=node.querySelector('img');if(img){img.style.width='';img.style.height='';}d.resolve();});return d.promise();});return $.when(...promises).then(()=>{nodes.forEach(n=>{n.classList.remove('single');n.style.zIndex='';});inSingleView=false;animating=false;if(savedSelected!==null){selected=savedSelected;savedSelected=null;}setTimeout(updatePositions,20);});}
/* ------------------- NAVIGATION ------------------- */
function moveLeft(){if(inSingleView){restoreFromSingleView().then(()=>{selected=(selected-1+N)%N;updatePositions();});return;}selected=(selected-1+N)%N;updatePositions();}
function moveRight(){if(inSingleView){restoreFromSingleView().then(()=>{selected=(selected+1)%N;updatePositions();});return;}selected=(selected+1)%N;updatePositions();}
$(window).on('keydown',(ev)=>{const tag=(document.activeElement&&document.activeElement.tagName)||"";if(tag==="INPUT"||tag==="TEXTAREA")return;const k=ev.key;if(k==='a'||k==='A'||k==='ArrowLeft'){ev.preventDefault();moveLeft();}else if(k==='d'||k==='D'||k==='ArrowRight'){ev.preventDefault();moveRight();}else if(k==='Enter'){ev.preventDefault();if(!animating&&!inSingleView)handleEnterOnSelected();}else if(k==='r'||k==='R'){ev.preventDefault();if(inSingleView&&!animating)restoreFromSingleView();else if(currentMenu==='options'&&!animating)returnToParent();}});
$('#btnLeft').on('click',moveLeft);$('#btnRight').on('click',moveRight);$(window).on('resize',()=>{setTimeout(updatePositions,60);});
/* ---------- INIT ---------- */
function initMain(){menuData=mainGames.slice();buildMenu(menuData);selected=0;$('#menuTitle').text('Main Menu');setTimeout(()=>{animateEntrance(19000);},50);}
$(function(){initMain();});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment