Created
March 10, 2026 01:11
-
-
Save EncodeTheCode/9b88e87f11e9e58be1baa61cc8fa6e3a 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 lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width,initial-scale=1" /> | |
| <title>PS1 Demo One — Underwater Menu (HTML5 + jQuery)</title> | |
| <!-- jQuery (CDN) --> | |
| <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> | |
| <style> | |
| /* --- Basic page / layout --- */ | |
| html,body{ | |
| height:100%; | |
| margin:0; | |
| background: #700; /* tiny border like your screenshot */ | |
| font-family: "Trebuchet MS", Arial, sans-serif; | |
| -webkit-font-smoothing:antialiased; | |
| -moz-osx-font-smoothing:grayscale; | |
| overflow:hidden; | |
| } | |
| /* Stage */ | |
| .stage{ | |
| width:100%; | |
| height:100%; | |
| display:flex; | |
| align-items:center; | |
| justify-content:center; | |
| position:relative; | |
| box-sizing:border-box; | |
| padding:30px; | |
| } | |
| /* skybox / underwater background */ | |
| .skybox{ | |
| position:absolute; | |
| inset:30px; | |
| border-radius:2px; | |
| overflow:hidden; | |
| z-index:0; | |
| background: | |
| radial-gradient(ellipse at 20% 30%, rgba(0,180,120,0.15), transparent 10%), | |
| radial-gradient(ellipse at 80% 70%, rgba(0,100,255,0.12), transparent 15%), | |
| linear-gradient(180deg,#083b5a 0%, #0b4c52 35%, #1b5f58 60%, #0b2b4a 100%); | |
| box-shadow: inset 0 0 80px rgba(0,0,0,0.6); | |
| transform-origin:center center; | |
| will-change:transform, filter; | |
| } | |
| /* optional external PNG (your demo1 screenshot) */ | |
| .skybox::before{ | |
| content:""; | |
| position:absolute; | |
| inset:-20%; | |
| background-image: url('demo1.png'); | |
| background-size:cover; | |
| background-position:center; | |
| opacity:0.08; /* subtle, to keep demo-like look */ | |
| mix-blend-mode:screen; | |
| filter: contrast(1.1) saturate(1.05); | |
| transition:opacity .6s; | |
| } | |
| /* gentle "sway" plus very slow lap rotation to create non-repeating movement */ | |
| @keyframes sky-sway { | |
| 0% { transform: rotate(-6deg) translateZ(0); } | |
| 40% { transform: rotate( 6deg) translateZ(0); } | |
| 100% { transform: rotate(360deg) translateZ(0); } | |
| } | |
| .skybox { | |
| animation: | |
| sky-sway 40s cubic-bezier(.25,.1,.25,1) infinite; | |
| transform-origin:center center; | |
| } | |
| /* small light ripples overlay */ | |
| .skybox::after{ | |
| content:""; | |
| position:absolute; | |
| inset:0; | |
| background: | |
| radial-gradient(circle at 30% 20%, rgba(255,255,255,0.03), transparent 10%), | |
| radial-gradient(circle at 70% 80%, rgba(180,255,200,0.02), transparent 15%); | |
| mix-blend-mode:overlay; | |
| pointer-events:none; | |
| animation: floaty 12s linear infinite; | |
| } | |
| @keyframes floaty { from {transform:translateY(0)} to {transform:translateY(-18px)} } | |
| /* UI area on top */ | |
| .ui { | |
| position:relative; | |
| z-index:5; | |
| width:80%; | |
| max-width:1200px; | |
| height:80%; | |
| display:flex; | |
| align-items:center; | |
| justify-content:center; | |
| perspective:1100px; | |
| } | |
| /* center container (3D ring) */ | |
| .menu-wrapper{ | |
| width:830px; | |
| height:420px; | |
| position:relative; | |
| display:flex; | |
| align-items:center; | |
| justify-content:center; | |
| transform-style:preserve-3d; | |
| } | |
| /* the ring that will rotate in Y */ | |
| .menu3d{ | |
| position:absolute; | |
| width:100%; | |
| height:100%; | |
| transform-style:preserve-3d; | |
| transition: transform 800ms cubic-bezier(.23,.9,.31,1); | |
| pointer-events:none; | |
| } | |
| /* each item (label + dot) */ | |
| .menu-item{ | |
| position:absolute; | |
| left:50%; | |
| top:48%; | |
| transform-origin:center center; | |
| backface-visibility:hidden; | |
| text-align:center; | |
| pointer-events:none; | |
| will-change:transform,opacity,filter; | |
| } | |
| /* Stylized label text — bright lime gradient + glow + rounded blur border */ | |
| .label { | |
| display:inline-block; | |
| font-size:54px; | |
| letter-spacing:1px; | |
| font-weight:800; | |
| padding:6px 14px; | |
| border-radius:14px; | |
| line-height:1; | |
| text-transform:capitalize; | |
| position:relative; | |
| z-index:3; | |
| color:transparent; | |
| background: linear-gradient(180deg, #c7ff45 0%, #8ecc23 100%); | |
| -webkit-background-clip: text; | |
| background-clip: text; | |
| text-shadow: | |
| 0 0 18px rgba(180,255,120,0.18), | |
| 0 0 8px rgba(0,0,0,0.5); | |
| filter: drop-shadow(0 6px 12px rgba(0,0,0,0.55)); | |
| transform-origin:center center; | |
| } | |
| /* border / glow around label (outer) */ | |
| .label::before{ | |
| content:""; | |
| position:absolute; | |
| left:-8px; right:-8px; top:-6px; bottom:-6px; | |
| border-radius:16px; | |
| z-index:-1; | |
| background:linear-gradient(90deg, rgba(0,0,0,0.18), rgba(255,255,255,0.02)); | |
| filter: blur(6px) saturate(1.1); | |
| opacity:0.6; | |
| } | |
| /* Big DOT separator */ | |
| .dot { | |
| display:inline-block; | |
| width:26px; | |
| height:26px; | |
| margin-left:12px; | |
| border-radius:50%; | |
| background: radial-gradient(circle at 35% 30%, #ffd55c, #ff8b39); | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.6); | |
| vertical-align:middle; | |
| transform:translateY(-6px); | |
| } | |
| /* active appears front and bigger */ | |
| .menu-item.active .label{ | |
| transform: translateZ(30px) scale(1.18); | |
| text-shadow: | |
| 0 0 28px rgba(200,255,120,0.35), 0 2px 10px rgba(0,0,0,0.7); | |
| filter: none; | |
| } | |
| .menu-item.inactive .label{ | |
| opacity:0.35; | |
| transform: translateZ(-60px) scale(.88); | |
| filter: grayscale(.2) blur(0.4px) brightness(.8); | |
| } | |
| /* items behind star must appear partially behind — star uses z-index layering */ | |
| .menu-item.behind{ | |
| opacity:0.42; | |
| transform-origin:center center; | |
| } | |
| /* center starfish (SVG) */ | |
| .starfish-wrap{ | |
| position:absolute; | |
| z-index:2; | |
| left:50%; | |
| top:50%; | |
| transform:translate(-50%,-50%); | |
| width:420px; | |
| height:420px; | |
| pointer-events:none; | |
| display:flex; | |
| align-items:center; | |
| justify-content:center; | |
| transform-style:preserve-3d; | |
| } | |
| .starfish{ | |
| width:100%; | |
| height:100%; | |
| transform-origin:center center; | |
| animation: star-spin 28s linear infinite; | |
| filter: drop-shadow(0 20px 24px rgba(0,0,0,0.6)); | |
| opacity:0.98; | |
| } | |
| @keyframes star-spin{ | |
| from { transform: rotate(0deg); } | |
| to { transform: rotate(360deg); } | |
| } | |
| /* detail area (selected item or inner game name) */ | |
| .detail { | |
| position:absolute; | |
| bottom:18%; | |
| left:50%; | |
| transform:translateX(-50%); | |
| z-index:6; | |
| font-weight:700; | |
| color:#dfffa8; | |
| text-shadow: 0 2px 8px rgba(0,0,0,0.6); | |
| font-size:24px; | |
| display:flex; | |
| gap:10px; | |
| align-items:center; | |
| user-select:none; | |
| } | |
| .detail .title{ | |
| background:linear-gradient(180deg, rgba(255,255,255,0.04), rgba(0,0,0,0.04)); | |
| padding:8px 14px; | |
| border-radius:10px; | |
| border:1px solid rgba(255,255,255,0.03); | |
| box-shadow: inset 0 2px 10px rgba(0,0,0,0.2); | |
| } | |
| /* instructions */ | |
| .help { | |
| position:absolute; | |
| z-index:8; | |
| left:22px; bottom:22px; | |
| color:rgba(255,255,255,0.76); | |
| font-size:12px; | |
| background:rgba(0,0,0,0.25); | |
| padding:8px 12px; | |
| border-radius:6px; | |
| border:1px solid rgba(255,255,255,0.02); | |
| } | |
| /* responsive */ | |
| @media (max-width:900px){ | |
| .menu-wrapper{ width:90%; height:360px;} | |
| .label{ font-size:36px;} | |
| .starfish-wrap{ width:300px; height:300px;} | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="stage"> | |
| <div class="skybox" id="skybox"></div> | |
| <div class="ui"> | |
| <div class="menu-wrapper" aria-hidden="false"> | |
| <!-- rotating ring --> | |
| <div class="menu3d" id="menu3d"> | |
| <!-- items inserted by JS --> | |
| </div> | |
| <!-- starfish in middle --> | |
| <div class="starfish-wrap" aria-hidden="true"> | |
| <!-- Hand-made SVG starfish with a bit of noisy texture --> | |
| <svg class="starfish" viewBox="0 0 600 600" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet"> | |
| <defs> | |
| <radialGradient id="sfGrad" cx="30%" cy="30%"> | |
| <stop offset="0%" stop-color="#ff7a2c"/> | |
| <stop offset="50%" stop-color="#e33b2b"/> | |
| <stop offset="100%" stop-color="#b71a1a"/> | |
| </radialGradient> | |
| <filter id="grain"> | |
| <feTurbulence baseFrequency="0.9" numOctaves="1" stitchTiles="stitch"/> | |
| <feColorMatrix type="saturate" values="0"/> | |
| <feBlend mode="overlay"/> | |
| </filter> | |
| <filter id="soft" x="-50%" y="-50%" width="200%" height="200%"> | |
| <feGaussianBlur stdDeviation="1.6" /> | |
| </filter> | |
| </defs> | |
| <g transform="translate(300,300)"> | |
| <!-- organic star-like shape --> | |
| <path d="M0 -220 C 50 -160 110 -110 200 -90 C 120 -10 150 40 210 120 C 110 60 45 120 5 210 C -40 120 -120 60 -220 120 C -160 40 -120 -10 -200 -90 C -110 -110 -50 -160 0 -220 Z" | |
| fill="url(#sfGrad)" stroke="#8b1515" stroke-width="8" filter="url(#soft)" /> | |
| <!-- spots --> | |
| <g fill="#ffb07a" opacity="0.9"> | |
| <circle cx="-40" cy="-40" r="16"/> | |
| <circle cx="90" cy="-40" r="12"/> | |
| <circle cx="40" cy="40" r="10"/> | |
| <circle cx="-80" cy="60" r="8"/> | |
| <circle cx="130" cy="80" r="9"/> | |
| </g> | |
| <path d="M -20 -40 q 20 -24 40 0" stroke="#a00000" stroke-width="2" fill="none" opacity="0.7"/> | |
| </g> | |
| </svg> | |
| </div> | |
| <!-- detail / current selection text --> | |
| <div class="detail" id="detail"> | |
| <div class="title" id="detailMain">Games</div> | |
| <div class="title" id="detailSub">Tomb Raider 3</div> | |
| </div> | |
| <div class="help"> | |
| Left / Right — rotate menu. <br/> | |
| When <strong>Games</strong> is centered, Left/Right cycle games. <br/> | |
| Enter — toggle selecting (visual only), Esc — reset | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| $(function(){ | |
| // configuration | |
| const mainLabels = ["games","video","music","memory","network"]; | |
| const gamesList = ["Tomb Raider 3","Tekken 3","Crash Bandicoot","Final Fantasy VII","Metal Gear Solid"]; | |
| const radius = 360; // translateZ radius for circle | |
| const menu3d = $('#menu3d'); | |
| let currentIndex = 0; // which mainLabel is front | |
| let currentGameIndex = 0; | |
| let selectedMode = false; // Enter toggles 'selected' (visual) | |
| const n = mainLabels.length; | |
| const angleStep = 360 / n; | |
| // create items | |
| mainLabels.forEach((label, i) => { | |
| const $it = $(` | |
| <div class="menu-item" data-i="${i}"> | |
| <div class="label">${label}</div> | |
| <div class="dot" aria-hidden="true"></div> | |
| </div> | |
| `); | |
| menu3d.append($it); | |
| }); | |
| // position items around circle (rotateY + translateZ) | |
| function layoutItems(){ | |
| menu3d.find('.menu-item').each(function(){ | |
| const i = Number($(this).attr('data-i')); | |
| // compute angle so that index 0 is at front (0deg) | |
| const angle = i * angleStep; | |
| const transform = `rotateY(${angle}deg) translateZ(${radius}px) translateX(-50%)`; | |
| $(this).css('transform', transform); | |
| }); | |
| updateVisual(); | |
| } | |
| layoutItems(); | |
| // set the ring transform to show currentIndex at front | |
| function updateVisual(){ | |
| const rotationY = -currentIndex * angleStep; | |
| menu3d.css('transform', `translateZ(-${radius}px) rotateY(${rotationY}deg)`); | |
| // update classes for items (active/inactive/behind) | |
| menu3d.find('.menu-item').each(function(){ | |
| const i = Number($(this).attr('data-i')); | |
| $(this).removeClass('active inactive behind'); | |
| if(i === currentIndex){ | |
| $(this).addClass('active'); | |
| } else { | |
| // compute shortest angular distance | |
| let d = Math.abs(i - currentIndex); | |
| d = Math.min(d, n - d); | |
| if(d === 1){ | |
| $(this).addClass('inactive'); | |
| } else { | |
| $(this).addClass('behind'); | |
| } | |
| } | |
| }); | |
| // update detail main label and sublabel depending on front | |
| $('#detailMain').text(capitalize(mainLabels[currentIndex])); | |
| if(mainLabels[currentIndex] === 'games'){ | |
| $('#detailSub').text(gamesList[currentGameIndex]); | |
| } else { | |
| $('#detailSub').text(''); | |
| } | |
| } | |
| // keyboard navigation | |
| $(window).on('keydown', function(e){ | |
| const LEFT = 37, RIGHT = 39, ENTER = 13, ESC = 27, UP=38, DOWN=40; | |
| if(e.keyCode === LEFT || e.keyCode === RIGHT){ | |
| e.preventDefault(); | |
| const dir = (e.keyCode === RIGHT) ? 1 : -1; | |
| // If current main label is 'games', left/right cycles gamesList | |
| if(mainLabels[currentIndex] === 'games'){ | |
| // cycle inner games | |
| currentGameIndex = (currentGameIndex + dir + gamesList.length) % gamesList.length; | |
| // visually animate sub text | |
| $('#detailSub').stop(true).animate({ opacity:0 }, 140, function(){ | |
| $(this).text(gamesList[currentGameIndex]).css('opacity',0).animate({opacity:1},300); | |
| }); | |
| // also add subtle star bounce for feedback | |
| $('.starfish').stop(true).animate({ rotation: "+=6" }, { duration:260, step:function(now){$(this).css('transform','rotate('+now+'deg)')}}); | |
| } else { | |
| // rotate main menu | |
| currentIndex = (currentIndex + dir + n) % n; | |
| // reset game index when leaving games | |
| currentGameIndex = 0; | |
| updateVisual(); | |
| } | |
| } else if(e.keyCode === ENTER){ | |
| // toggle selection visual | |
| selectedMode = !selectedMode; | |
| if(selectedMode){ | |
| $('.menu-item.active .label').css('text-shadow','0 0 36px rgba(255,255,120,0.5), 0 3px 14px rgba(0,0,0,0.6)'); | |
| $('#detailMain').css('transform','scale(1.05)'); | |
| } else { | |
| $('.menu-item.active .label').css('text-shadow','0 0 28px rgba(200,255,120,0.35), 0 2px 10px rgba(0,0,0,0.7)'); | |
| $('#detailMain').css('transform','scale(1)'); | |
| } | |
| } else if(e.keyCode === ESC){ | |
| // reset to index 0 | |
| currentIndex = 0; currentGameIndex = 0; selectedMode = false; | |
| updateVisual(); | |
| } else if(e.keyCode === UP || e.keyCode === DOWN){ | |
| // alternative rotation using up/down for convenience | |
| const dir = (e.keyCode === DOWN) ? 1 : -1; | |
| currentIndex = (currentIndex + dir + n) % n; | |
| updateVisual(); | |
| } | |
| }); | |
| // Utility: capitalize | |
| function capitalize(s){ return s.charAt(0).toUpperCase() + s.slice(1); } | |
| // initial visual | |
| updateVisual(); | |
| // responsiveness: recalc radius on resize (so ring still looks right) | |
| $(window).on('resize', function(){ | |
| // simple approach: adjust radius proportional to width | |
| const w = $('.menu-wrapper').width(); | |
| // clamp | |
| const newRadius = Math.max(220, Math.min(520, Math.round(w * 0.45))); | |
| // set radius by updating styles (recompute transforms) | |
| // NOTE: using same variable isn't straightforward; we manually update each item | |
| menu3d.find('.menu-item').each(function(){ | |
| const i = Number($(this).attr('data-i')); | |
| const angle = i * angleStep; | |
| const transform = `rotateY(${angle}deg) translateZ(${newRadius}px) translateX(-50%)`; | |
| $(this).css('transform', transform); | |
| }); | |
| // and the ring translateZ | |
| menu3d.css('transform', function(_, old){ | |
| // compute using currentIndex | |
| const rotationY = -currentIndex * angleStep; | |
| return `translateZ(-${newRadius}px) rotateY(${rotationY}deg)`; | |
| }); | |
| }).trigger('resize'); | |
| // small star wobble occasionally to feel organic | |
| setInterval(()=> { | |
| $('.starfish').animate({opacity:0.96}, 600) | |
| .animate({opacity:1}, 1600); | |
| }, 4500); | |
| // mouse/touch: clicking left/right areas to rotate (for convenience) | |
| // create large invisible left/right click zones | |
| const $leftZone = $('<div>').css({ | |
| position:'absolute', left:0, top:0, bottom:0, width:'35%', | |
| zIndex:9, cursor:'w-resize', opacity:0, pointerEvents:'auto' | |
| }).appendTo('body').on('click', ()=> $(window).trigger($.Event('keydown', { keyCode:37 }))); | |
| const $rightZone = $leftZone.clone().css({left:'65%', right:0}).appendTo('body').on('click', ()=> $(window).trigger($.Event('keydown', { keyCode:39 }))); | |
| // friendly focus to capture keys | |
| $(window).focus(); | |
| }); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment