Instantly share code, notes, and snippets.
Created
February 27, 2026 21:00
-
Star
1
(1)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save EncodeTheCode/7367065d583203169a7b1698b10ddfe2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>PS Classic Menu — Final</title> | |
| <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script> | |
| <style> | |
| html,body{ | |
| margin:0; | |
| height:100%; | |
| background:radial-gradient(circle at center,#1a1a1a 0%,#000 100%); | |
| overflow:hidden; | |
| font-family:Arial, sans-serif; | |
| } | |
| .stage{ | |
| position:relative; | |
| width:100%; | |
| height:100%; | |
| display:flex; | |
| align-items:center; | |
| justify-content:center; | |
| } | |
| .carousel{ | |
| position:relative; | |
| width:900px; | |
| height:500px; | |
| } | |
| .game{ | |
| position:absolute; | |
| left:50%; | |
| top:50%; | |
| transform:translate(-50%,-50%); | |
| border-radius:0.35em; | |
| border:1px solid rgba(255,255,255,0.15); | |
| overflow:hidden; | |
| transition: transform .45s cubic-bezier(.25,.8,.25,1), | |
| width .45s cubic-bezier(.25,.8,.25,1), | |
| height .45s cubic-bezier(.25,.8,.25,1), | |
| box-shadow .45s cubic-bezier(.25,.8,.25,1), | |
| opacity .3s ease; | |
| box-shadow:0 8px 20px rgba(0,0,0,.7); | |
| } | |
| .game img{ | |
| width:100%; | |
| height:100%; | |
| object-fit:cover; | |
| display:block; | |
| border-radius:inherit; | |
| } | |
| .game.front{ | |
| border:1px solid #fff !important; | |
| box-shadow: | |
| 0 0 20px rgba(255,255,255,.7), | |
| 0 0 60px rgba(255,255,255,.2); | |
| } | |
| .title{ | |
| position:absolute; | |
| bottom:-70px; | |
| left:50%; | |
| transform:translateX(-50%); | |
| width:500px; | |
| text-align:center; | |
| font-size:20px; | |
| letter-spacing:2px; | |
| text-transform:uppercase; | |
| color:#ccc; | |
| opacity:.9; | |
| pointer-events:none; | |
| text-shadow:0 0 8px rgba(255,255,255,.2); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="stage"> | |
| <div class="carousel" id="carousel"></div> | |
| </div> | |
| <script> | |
| /* ========================= */ | |
| /* PERSPECTIVE CONTROL */ | |
| /* ========================= */ | |
| let perspectiveTilt = 120; // ← adjust this value to tilt perspective | |
| let spacing = 0.75; | |
| const TRANSITION_DURATION = 1000; | |
| let selected = 0; | |
| let inSubMenu = false; | |
| /* ========================= */ | |
| /* MENUS */ | |
| /* ========================= */ | |
| const mainMenu = [ | |
| {title:'Crash Bandicoot', color:'#b2362f', image:'images/crash.jpg'}, | |
| {title:'Spyro the Dragon', color:'#2f6fb2'}, | |
| {title:'Tekken 3', color:'#2fb280'}, | |
| {title:'Final Fantasy VII', color:'#b28b2f'}, | |
| {title:'Metal Gear Solid', color:'#8a2fb2'}, | |
| {title:'Options', color:'#4c6fb2'} | |
| ]; | |
| const optionsMenu = [ | |
| {title:'Display Settings', color:'#444'}, | |
| {title:'Sound Settings', color:'#666'}, | |
| {title:'Controller Setup', color:'#888'}, | |
| {title:'Back', color:'#222'} | |
| ]; | |
| let activeMenu = mainMenu; | |
| const carousel = document.getElementById("carousel"); | |
| /* ========================= */ | |
| /* IMAGE FALLBACK */ | |
| /* ========================= */ | |
| function makeSVG(title,color){ | |
| return 'data:image/svg+xml;utf8,'+ | |
| encodeURIComponent(` | |
| <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-size="28" | |
| fill="white" | |
| text-anchor="middle" | |
| dominant-baseline="middle" | |
| font-family="Arial">${title}</text> | |
| </svg> | |
| `); | |
| } | |
| /* ========================= */ | |
| /* CREATE NODES */ | |
| /* ========================= */ | |
| function createNodes(){ | |
| carousel.innerHTML = ""; | |
| activeMenu.forEach(item=>{ | |
| const div=document.createElement("div"); | |
| div.className="game"; | |
| const img=document.createElement("img"); | |
| if(item.image){ | |
| img.src=item.image; | |
| img.onerror=function(){ | |
| this.src=makeSVG(item.title,item.color||"#444"); | |
| }; | |
| } else { | |
| img.src=makeSVG(item.title,item.color||"#444"); | |
| } | |
| div.appendChild(img); | |
| carousel.appendChild(div); | |
| }); | |
| const title=document.createElement("div"); | |
| title.className="title"; | |
| title.innerText=activeMenu[selected].title; | |
| carousel.appendChild(title); | |
| } | |
| /* ========================= */ | |
| /* POSITIONING LOGIC */ | |
| /* ========================= */ | |
| function updatePositions(){ | |
| const nodes=document.querySelectorAll(".game"); | |
| const total=nodes.length; | |
| const rect=carousel.getBoundingClientRect(); | |
| const rX=rect.width*0.45*spacing; | |
| const rY=rect.height*0.35*spacing; | |
| nodes.forEach((node,i)=>{ | |
| let offset=i-selected; | |
| if(offset>total/2) offset-=total; | |
| if(offset<-total/2) offset+=total; | |
| const angle=(offset/total)*Math.PI*2; | |
| const x=Math.sin(angle)*rX; | |
| const y=(Math.cos(angle)*rY) - perspectiveTilt; | |
| let abs=Math.abs(offset); | |
| let size; | |
| if(abs===0) size=110; | |
| else if(abs===1) size=80; | |
| else if(abs===2) size=55; | |
| else size=30; | |
| node.style.width=size+"px"; | |
| node.style.height=size+"px"; | |
| node.style.transform= | |
| `translate(-50%,-50%) translate(${x}px,${y}px)`; | |
| node.style.zIndex=100-abs; | |
| node.style.opacity = (abs>3) ? 0.25 : 1; | |
| node.classList.toggle("front",abs===0); | |
| }); | |
| const title=document.querySelector(".title"); | |
| if(title) title.innerText = activeMenu[selected].title; | |
| } | |
| /* ========================= */ | |
| /* BEZIER SPIRAL TRANSITION */ | |
| /* ========================= */ | |
| function spiralTransition(callback){ | |
| const nodes=document.querySelectorAll(".game"); | |
| const rect=carousel.getBoundingClientRect(); | |
| const centerX=rect.width/2; | |
| const centerY=rect.height/2; | |
| nodes.forEach(node=>{ | |
| const randAngle=Math.random()*Math.PI*2; | |
| const randDist=200+Math.random()*300; | |
| const targetX=centerX + Math.cos(randAngle)*randDist; | |
| const targetY=centerY + Math.sin(randAngle)*randDist; | |
| $(node).animate({ | |
| left:targetX, | |
| top:targetY, | |
| opacity:0 | |
| },{ | |
| duration:TRANSITION_DURATION/2, | |
| easing:"swing", | |
| complete:function(){ | |
| if(callback) callback(); | |
| } | |
| }); | |
| }); | |
| } | |
| /* ========================= */ | |
| /* MENU SWITCHING */ | |
| /* ========================= */ | |
| function enterSubMenu(){ | |
| if(activeMenu[selected].title === "Options"){ | |
| spiralTransition(()=>{ | |
| activeMenu = optionsMenu; | |
| selected = 0; | |
| inSubMenu = true; | |
| createNodes(); | |
| updatePositions(); | |
| }); | |
| } | |
| } | |
| function returnToMain(){ | |
| spiralTransition(()=>{ | |
| activeMenu = mainMenu; | |
| selected = 0; | |
| inSubMenu = false; | |
| createNodes(); | |
| updatePositions(); | |
| }); | |
| } | |
| /* ========================= */ | |
| /* NAVIGATION */ | |
| /* ========================= */ | |
| function moveLeft(){ | |
| selected = (selected - 1 + activeMenu.length) % activeMenu.length; | |
| updatePositions(); | |
| } | |
| function moveRight(){ | |
| selected = (selected + 1) % activeMenu.length; | |
| updatePositions(); | |
| } | |
| document.addEventListener("keydown", e => { | |
| switch(e.key){ | |
| case "a": | |
| case "A": | |
| case "ArrowLeft": | |
| e.preventDefault(); | |
| moveLeft(); | |
| break; | |
| case "d": | |
| case "D": | |
| case "ArrowRight": | |
| e.preventDefault(); | |
| moveRight(); | |
| break; | |
| case "Enter": | |
| e.preventDefault(); | |
| if(inSubMenu && activeMenu[selected].title === "Back"){ | |
| returnToMain(); | |
| } else { | |
| enterSubMenu(); | |
| } | |
| break; | |
| case "Escape": | |
| case "Backspace": | |
| if(inSubMenu){ | |
| returnToMain(); | |
| } | |
| break; | |
| } | |
| }); | |
| /* ========================= */ | |
| /* INIT */ | |
| /* ========================= */ | |
| createNodes(); | |
| updatePositions(); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment