Skip to content

Instantly share code, notes, and snippets.

@EncodeTheCode
Created June 8, 2026 11:48
Show Gist options
  • Select an option

  • Save EncodeTheCode/403cd3f2406ca30355de23b2254215f6 to your computer and use it in GitHub Desktop.

Select an option

Save EncodeTheCode/403cd3f2406ca30355de23b2254215f6 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.0" />
<title>MGS1 Weapon Conveyor UI Recreation</title>
<style>
:root {
--bg: #ffffff;
--panel: #050505;
--text: #8d8d8d;
--accent: #ff9f12;
--selected-glow: rgba(255, 175, 40, 0.92);
--slot-w: 230px;
--slot-h: 84px;
--shadow: 0 10px 28px rgba(0,0,0,.22);
}
html, body {
width: 100%;
height: 100%;
margin: 0;
overflow: hidden;
background: var(--bg);
font-family: Arial, Helvetica, sans-serif;
}
.hint {
position: fixed;
left: 20px;
top: 18px;
color: #333;
font-size: 14px;
line-height: 1.45;
opacity: 0.72;
user-select: none;
z-index: 20;
}
.controls {
position: fixed;
left: 20px;
bottom: 18px;
width: min(360px, calc(100vw - 40px));
background: rgba(248,248,248,.94);
border: 1px solid rgba(0,0,0,.08);
border-radius: 14px;
padding: 14px 14px 12px;
box-shadow: 0 10px 26px rgba(0,0,0,.10);
z-index: 20;
user-select: none;
backdrop-filter: blur(6px);
}
.controls h2 {
margin: 0 0 10px;
font-size: 14px;
letter-spacing: .02em;
color: #1a1a1a;
}
.control {
display: grid;
grid-template-columns: 1fr auto;
gap: 8px 10px;
margin-bottom: 10px;
align-items: center;
font-size: 12px;
color: #222;
}
.control label {
display: block;
margin-bottom: 4px;
font-weight: 600;
}
.control input[type="range"] {
width: 100%;
}
.control .value {
min-width: 68px;
text-align: right;
font-variant-numeric: tabular-nums;
color: #444;
}
.control .full {
grid-column: 1 / -1;
}
.stage {
position: relative;
width: 100vw;
height: 100vh;
perspective: 900px;
}
.weapon {
position: absolute;
width: var(--slot-w);
height: var(--slot-h);
transform-origin: center center;
will-change: transform, opacity, filter;
}
.weapon .card {
position: absolute;
inset: 0;
background: linear-gradient(180deg, #080808 0%, #020202 100%);
box-shadow: var(--shadow);
overflow: hidden;
border-radius: 0;
transition: filter 180ms ease, box-shadow 180ms ease;
}
.weapon .card::before {
content: "";
position: absolute;
inset: 0;
background:
radial-gradient(ellipse at 65% 35%, rgba(62, 116, 122, 0.20), transparent 42%),
linear-gradient(90deg, rgba(255,255,255,.04), rgba(255,255,255,0));
mix-blend-mode: screen;
pointer-events: none;
}
.ammo {
position: absolute;
left: 17px;
top: 28px;
font-size: 25px;
font-weight: 700;
letter-spacing: 1px;
color: #8e8e8e;
text-shadow: 0 1px 0 rgba(0,0,0,.45);
}
.gun {
position: absolute;
right: 13px;
top: 7px;
font-size: 42px;
filter: saturate(1.15);
opacity: 0.95;
transform: translateZ(0);
text-shadow: 0 0 1px rgba(255,255,255,.18);
}
.bars {
position: absolute;
right: 11px;
bottom: 10px;
display: flex;
gap: 3px;
align-items: flex-end;
}
.bar {
width: 10px;
background: linear-gradient(180deg, #ffc53b 0%, #e28f00 100%);
box-shadow: 0 0 0 1px rgba(0,0,0,.38) inset;
}
.bar.small { height: 16px; background: linear-gradient(180deg, #223758 0%, #18263b 100%); }
.bar.med { height: 23px; }
.bar.tall { height: 29px; }
.weapon.selected .card {
outline: 1px solid rgba(255, 166, 0, .25);
box-shadow:
0 0 0 1px rgba(255, 170, 0, .22) inset,
0 0 18px rgba(255, 160, 25, .18),
var(--shadow);
animation: pulse 250ms ease-in-out infinite alternate;
}
@keyframes pulse {
from { filter: brightness(1.00); }
to { filter: brightness(1.16); }
}
@media (max-width: 760px) {
:root { --slot-w: 185px; --slot-h: 68px; }
.ammo { font-size: 22px; top: 22px; }
.gun { font-size: 34px; top: 8px; }
.bar { width: 8px; }
.controls { width: calc(100vw - 40px); }
}
</style>
</head>
<body>
<div class="hint">
Hold A = left / previous<br>
Hold D = right / next<br>
Release to stop instantly<br>
Enter = flash selected item for 3 seconds
</div>
<div class="stage" id="stage"></div>
<script>
const stage = document.getElementById('stage');
const settings = {
moveDuration: 411,
repeatDelay: 400,
stagger: 80,
leftBoost: 38,
};
const slots = [
{ x: 68, y: 480, z: 10 },
{ x: 315, y: 480, z: 11 },
{ x: 560, y: 480, z: 12 },
{ x: 560, y: 360, z: 14 },
{ x: 560, y: 240, z: 15 },
];
const items = [
{ name: 'SOCOM', ammo: '23/25', bullets: 11 },
{ name: 'FA-MAS', ammo: '23/25', bullets: 11 },
{ name: 'NIKITA', ammo: '23/25', bullets: 11 },
{ name: 'RATIONS', ammo: '23/25', bullets: 11 },
{ name: 'MINE', ammo: '23/25', bullets: 11 },
];
const els = items.map((item) => {
const el = document.createElement('div');
el.className = 'weapon';
el.innerHTML = `
<div class="card">
<div class="ammo">${item.ammo}</div>
<div class="gun">▣</div>
<div class="bars">
<div class="bar small"></div>
${Array.from({ length: item.bullets }, (_, n) => `<div class="bar ${n < 1 ? 'small' : 'med'}"></div>`).join('')}
</div>
</div>
`;
stage.appendChild(el);
return el;
});
// order[slotIndex] = itemIndex.
let order = [0, 1, 2, 3, 4];
let selectedItemIndex = order[2];
let flashTimer = null;
let heldKey = null;
let holdTimer = null;
let repeatTimer = null;
function readSettings() {
// Fixed tuning values are used directly; no UI controls are shown.
}
function getCenterOffset() {
return {
x: Math.max(0, (window.innerWidth - 800) / 2),
y: Math.max(0, (window.innerHeight - 600) / 2)
};
}
function slotDuration(slotIndex, direction) {
const base = settings.moveDuration;
const leftBoost = settings.leftBoost;
// Slightly faster motion on the lower-left side to preserve the MGS-like feel.
if (slotIndex === 0) return Math.max(60, base - leftBoost * 2);
if (slotIndex === 1) return Math.max(60, base - leftBoost);
if (slotIndex === 2) return base;
if (slotIndex === 3) return base + 8;
return base + 14;
}
function render({ teleportItemIndex = -1, direction = 0 } = {}) {
const { x: centerX, y: centerY } = getCenterOffset();
for (let slotIndex = 0; slotIndex < slots.length; slotIndex++) {
const itemIndex = order[slotIndex];
const el = els[itemIndex];
const slot = slots[slotIndex];
const isSelected = itemIndex === selectedItemIndex;
const x = centerX + slot.x;
const y = centerY + slot.y;
const delay = slotIndex * settings.stagger + (direction === -1 ? Math.max(0, 6 - slotIndex * 2) : 0);
const duration = slotDuration(slotIndex, direction);
el.style.transition = `transform ${duration}ms cubic-bezier(.22,.86,.2,1) ${delay}ms, opacity 180ms ease, filter 180ms ease`;
el.style.transform = `translate3d(${x}px, ${y}px, 0) scale(1)`;
el.style.zIndex = String(slot.z + (isSelected ? 100 : 0));
el.style.opacity = '1';
el.classList.toggle('selected', isSelected);
if (itemIndex === teleportItemIndex) {
el.style.transition = 'none';
el.style.transform = `translate3d(${x}px, ${y}px, 0) scale(1)`;
el.offsetHeight;
}
}
}
function step(direction) {
// direction: +1 = D / right, -1 = A / left.
// Single action only; no input queue. Holding triggers repeated single actions.
let teleportItemIndex = -1;
if (direction === 1) {
teleportItemIndex = order[0];
order = order.slice(1).concat(order[0]);
} else {
teleportItemIndex = order[order.length - 1];
order = [order[order.length - 1]].concat(order.slice(0, -1));
}
selectedItemIndex = order[2];
render({ teleportItemIndex, direction });
}
function startHold(key) {
if (heldKey === key) return;
stopHold();
heldKey = key;
const direction = key === 'd' ? 1 : -1;
step(direction);
holdTimer = window.setTimeout(() => {
repeatTimer = window.setInterval(() => {
step(direction);
}, settings.repeatDelay);
}, settings.repeatDelay);
}
function stopHold() {
heldKey = null;
if (holdTimer !== null) {
clearTimeout(holdTimer);
holdTimer = null;
}
if (repeatTimer !== null) {
clearInterval(repeatTimer);
repeatTimer = null;
}
}
function flashSelected() {
clearTimeout(flashTimer);
els.forEach(el => el.classList.remove('selected'));
const selectedEl = els[selectedItemIndex];
if (!selectedEl) return;
selectedEl.classList.add('selected');
flashTimer = setTimeout(() => {
selectedEl.classList.remove('selected');
}, 3000);
}
window.addEventListener('keydown', (e) => {
const key = e.key.toLowerCase();
if (key === 'a' || key === 'd') {
e.preventDefault();
if (!e.repeat) startHold(key);
} else if (e.key === 'Enter') {
flashSelected();
}
});
window.addEventListener('keyup', (e) => {
const key = e.key.toLowerCase();
if (key === 'a' || key === 'd') {
stopHold();
}
});
window.addEventListener('blur', stopHold);
window.addEventListener('resize', () => render());
stage.addEventListener('click', flashSelected);
readSettings();
render();
flashSelected();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment