diff --git a/app.js b/app.js new file mode 100644 index 0000000..f7f16dd --- /dev/null +++ b/app.js @@ -0,0 +1,178 @@ +'use strict'; + +// ── Category metadata ─────────────────────────────────────────────────────── +const CAT = { + all: { label: 'All Games', color: '#7c3aed', emoji: '🎮' }, + action: { label: 'Action', color: '#ef4444', emoji: '⚡' }, + adventure: { label: 'Adventure', color: '#fb923c', emoji: '🗺️' }, + horror: { label: 'Horror', color: '#8b5cf6', emoji: '👻' }, + idle: { label: 'Idle', color: '#84cc16', emoji: '💰' }, + multiplayer: { label: 'Multiplayer', color: '#ec4899', emoji: '👥' }, + music: { label: 'Music', color: '#14b8a6', emoji: '🎵' }, + platformer: { label: 'Platformer', color: '#f97316', emoji: '🏃' }, + puzzle: { label: 'Puzzle', color: '#06b6d4', emoji: '🧩' }, + racing: { label: 'Racing', color: '#f59e0b', emoji: '🏎️' }, + rpg: { label: 'RPG', color: '#a855f7', emoji: '⚔️' }, + shooter: { label: 'Shooter', color: '#f43f5e', emoji: '🔫' }, + sports: { label: 'Sports', color: '#10b981', emoji: '🏀' }, + strategy: { label: 'Strategy', color: '#3b82f6', emoji: '♟️' }, + other: { label: 'Other', color: '#64748b', emoji: '✨' }, +}; + +// ── State ─────────────────────────────────────────────────────────────────── +let activeCat = 'all'; +let searchTerm = ''; + +// ── DOM refs ──────────────────────────────────────────────────────────────── +const $ = id => document.getElementById(id); +const grid = $('grid'); +const empty = $('empty'); +const overlay = $('overlay'); +const frame = $('game-frame'); +const loader = $('loader'); +const catBar = $('cat-bar'); +const searchEl = $('search'); +const clearBtn = $('search-clear'); + +// ── Category bar ──────────────────────────────────────────────────────────── +function buildCatBar() { + const counts = {}; + GAMES.forEach(g => { counts[g.cat] = (counts[g.cat] || 0) + 1; }); + + catBar.innerHTML = Object.entries(CAT).map(([id, m]) => { + const n = id === 'all' ? GAMES.length : (counts[id] || 0); + if (id !== 'all' && !n) return ''; + return ``; + }).join(''); + + catBar.addEventListener('click', e => { + const btn = e.target.closest('.cat-pill'); + if (!btn) return; + catBar.querySelectorAll('.cat-pill').forEach(b => b.classList.remove('active')); + btn.classList.add('active'); + activeCat = btn.dataset.cat; + render(); + }); +} + +// ── Game grid ─────────────────────────────────────────────────────────────── +function render() { + const lower = searchTerm.toLowerCase(); + const list = GAMES.filter(g => + (activeCat === 'all' || g.cat === activeCat) && + (!lower || g.name.toLowerCase().includes(lower)) + ); + + const n = list.length; + $('header-count').textContent = `${n} game${n === 1 ? '' : 's'}`; + + if (!n) { + grid.innerHTML = ''; + empty.classList.remove('hidden'); + return; + } + + empty.classList.add('hidden'); + + grid.innerHTML = list.map(g => { + const m = CAT[g.cat]; + const idx = GAMES.indexOf(g); + const bg = `linear-gradient(145deg, ${m.color}2e 0%, ${m.color}0a 100%)`; + return `
+
+ ${g.thumb + ? `${g.name}` + : `${m.emoji}` + } +
+ + + + +
+
+
+
${g.name}
+ ${m.label} +
+
`; + }).join(''); +} + +// Delegated click — attached once +grid.addEventListener('click', e => { + const card = e.target.closest('.card'); + if (card) openGame(+card.dataset.idx); +}); + +// ── Game loading ───────────────────────────────────────────────────────────── +function openGame(idx) { + const g = GAMES[idx]; + const m = CAT[g.cat]; + + $('overlay-name').textContent = g.name; + const badge = $('overlay-badge'); + badge.textContent = m.label; + badge.style.background = m.color + '22'; + badge.style.color = m.color; + + overlay.classList.remove('hidden'); + loader.style.display = 'flex'; + frame.style.opacity = '0'; + frame.src = ''; + + // Load directly — iframe runs in the game's own origin so same-origin + // fetches (Unity data files, Construct 2 assets, etc.) are never blocked by CORS + frame.onload = () => { + loader.style.display = 'none'; + frame.style.opacity = '1'; + }; + frame.src = g.url; +} + +function closeGame() { + overlay.classList.add('hidden'); + frame.src = ''; + loader.style.display = 'none'; + frame.style.opacity = '0'; +} + +function goFullscreen() { + const el = frame; + (el.requestFullscreen || el.webkitRequestFullscreen || el.mozRequestFullScreen + || function(){}).call(el); +} + +// ── Search ────────────────────────────────────────────────────────────────── +searchEl.addEventListener('input', e => { + searchTerm = e.target.value; + clearBtn.classList.toggle('hidden', !searchTerm); + render(); +}); + +clearBtn.addEventListener('click', () => { + searchEl.value = ''; + searchTerm = ''; + clearBtn.classList.add('hidden'); + searchEl.focus(); + render(); +}); + +// ── Controls ──────────────────────────────────────────────────────────────── +$('close-btn').addEventListener('click', closeGame); +$('fs-btn').addEventListener('click', goFullscreen); + +document.addEventListener('keydown', e => { + if (!overlay.classList.contains('hidden')) { + if (e.key === 'Escape') closeGame(); + if (e.key === 'f' || e.key === 'F') goFullscreen(); + } +}); + +// ── Boot ──────────────────────────────────────────────────────────────────── +buildCatBar(); +render(); diff --git a/games.js b/games.js new file mode 100644 index 0000000..ad1f8e9 --- /dev/null +++ b/games.js @@ -0,0 +1,206 @@ +// URL helpers — GitHub Pages URLs (game runs in its own origin, CORS-safe) +const UGS = n => `https://bubbls.github.io/UGS-Assets/${encodeURIComponent(n)}/`; +const NG = n => `https://neruvy.github.io/neruvy-games/games/${n}/`; +const NP = n => `https://neruvy.github.io/web-port/${n}/`; +const GP = n => `https://genizy.github.io/web-port/${n}/`; +const MT = n => `https://mathtut0r1ng.github.io/gamespage/games/${n}/`; + +const GAMES = [ + + // ── UGS-ASSETS (bubbls/UGS-Assets) ─────────────────────────────────────── + { name: "1 Date Danger", url: UGS("1-date-danger"), cat: "horror" }, + { name: "10 Minutes Till Dawn", url: UGS("10minutestilldawn"), cat: "shooter" }, + { name: "2 Minute Football", url: UGS("2 minute football"), cat: "sports" }, + { name: "2-3-4 Player Games", url: UGS("2-3-4-player-game"), cat: "multiplayer" }, + { name: "2 DOOM", url: UGS("2doom"), cat: "action" }, + { name: "3Dash", url: UGS("3dash"), cat: "platformer" }, + { name: "5B", url: UGS("5b"), cat: "other" }, + { name: "Antimatter Dimensions", url: UGS("Antimatter Dimensions"), cat: "idle" }, + { name: "Bumper Cars Soccer", url: UGS("BUMPER CARS SOCCER"), cat: "sports" }, + { name: "Blightborne", url: UGS("Blightborne"), cat: "rpg" }, + { name: "Burrito Bison", url: UGS("BurritoBison-main"), cat: "action" }, + { name: "Endacopia", url: UGS("Endacopia"), cat: "horror" }, + { name: "Five Nights at Wario's", url: UGS("FNAW-main"), cat: "horror" }, + { name: "Five Nights at Osaka's", url: UGS("Five Nights at Osaka's"), cat: "horror" }, + { name: "Insomniary", url: UGS("Insomniary"), cat: "horror" }, + { name: "Stickman GTA", url: UGS("STICKMAN GTA"), cat: "action" }, + { name: "Stickman Clash", url: UGS("Stickman Clash"), cat: "action" }, + { name: "A Day in the Office", url: UGS("a day in the office"), cat: "other" }, + { name: "Adventure Capitalist", url: UGS("adventure-capitalist"), cat: "idle" }, + { name: "Ages of Conflict", url: UGS("ages of conflict"), cat: "strategy" }, + { name: "Airline Tycoon Idle", url: UGS("airline-tycoon-idle"), cat: "idle" }, + { name: "Alien Sky Invasion", url: UGS("alien sky invasion"), cat: "shooter" }, + { name: "Amazing Rope Police", url: UGS("amazing-strange-rope-police-vice-spider"), cat: "action" }, + { name: "Amidst the Sky", url: UGS("amidst the sky"), cat: "platformer" }, + { name: "Angry Birds", url: UGS("angry-bird"), cat: "puzzle" }, + { name: "Apes vs Helium", url: UGS("apesvshelium"), cat: "action" }, + { name: "Arcade Volley", url: UGS("arcade-volley"), cat: "sports" }, + { name: "Archesspalago", url: UGS("archesspalago"), cat: "puzzle" }, + { name: "Astro Survivors", url: UGS("astro survivors"), cat: "shooter" }, + { name: "Aviamaster", url: UGS("aviamaster"), cat: "strategy" }, + { name: "Babel Tower", url: UGS("babel tower"), cat: "puzzle" }, + { name: "Backrooms 2D", url: UGS("backrooms 2D"), cat: "horror" }, + { name: "Bacon May Die", url: UGS("bacon may die"), cat: "action" }, + { name: "Bad Parenting", url: UGS("bad-parenting"), cat: "platformer" }, + { name: "Baldi's Basics", url: UGS("baldis-basics"), cat: "horror" }, + { name: "Baldi's Decomp", url: UGS("baldi decomp"), cat: "horror" }, + { name: "Ballistic", url: UGS("ballistic"), cat: "shooter" }, + { name: "Banana Poker", url: UGS("banana poker"), cat: "other" }, + { name: "Barry Has a Secret", url: UGS("barry has a secret"), cat: "horror" }, + { name: "Baseball Bros", url: UGS("baseball bros"), cat: "sports" }, + { name: "Basket Stars", url: UGS("basket stars"), cat: "sports" }, + { name: "Basketball Superstars", url: UGS("basketball-superstars"), cat: "sports" }, + { name: "Beach Boxing Sim", url: UGS("beach-boxing-sim"), cat: "sports" }, + { name: "Bearsus", url: UGS("bearsus"), cat: "action" }, + { name: "Ben 10 Super Slammer", url: UGS("ben 10 super slammer"), cat: "action" }, + { name: "BFNSU", url: UGS("bfnsu"), cat: "horror" }, + { name: "Big 2048", url: UGS("big 2048"), cat: "puzzle" }, + { name: "Big Flappy Tower", url: UGS("big flappy tower"), cat: "platformer" }, + { name: "Block Miner", url: UGS("block-miner"), cat: "idle" }, + { name: "BlockPost", url: UGS("blockpost"), cat: "shooter" }, + { name: "Blumgi Racers", url: UGS("blumgi racers"), cat: "racing" }, + { name: "Blumgi Rocket", url: UGS("blumgi-rocket"), cat: "racing" }, + { name: "Bounce Back", url: UGS("bounce back"), cat: "puzzle" }, + { name: "Bouncy Basketball", url: UGS("bouncy basketball"), cat: "sports" }, + { name: "Bouncy Motors", url: UGS("bouncy motors"), cat: "racing" }, + { name: "Bounty of One", url: UGS("bounty of one"), cat: "rpg" }, + { name: "Brawl 3D", url: UGS("brawl-3d"), cat: "multiplayer" }, + { name: "Bullet Force", url: UGS("bullet-force-multiplayer"), cat: "shooter" }, + { name: "Capybara Clicker", url: UGS("capybara clicker"), cat: "idle" }, + { name: "Cat Mario", url: UGS("cat mario"), cat: "platformer" }, + { name: "Cats Love Cake 2", url: UGS("cats love cake 2"), cat: "puzzle" }, + { name: "Cave Chaos 2", url: UGS("cave chaos 2"), cat: "platformer" }, + { name: "Cheese Chompers 3", url: UGS("cheese chompers 3"), cat: "action" }, + { name: "Chicken Gun", url: UGS("chicken-gun"), cat: "shooter" }, + { name: "Choppy Orc", url: UGS("choppy orc"), cat: "platformer" }, + { name: "CircloO 2", url: UGS("circlo02"), cat: "puzzle" }, + { name: "Clash of Vikings", url: UGS("clashofvikings"), cat: "strategy" }, + { name: "Cookie Clicker", url: UGS("cookieclicker"), cat: "idle" }, + { name: "Crazy Cars", url: UGS("crazy cars"), cat: "racing" }, + { name: "Crazy Chicken 3D", url: UGS("crazy chicken 3D"), cat: "shooter" }, + { name: "Creature Card Idle", url: UGS("creature-card-idle"), cat: "idle" }, + { name: "CS:GO Surf", url: UGS("csgo surf"), cat: "action" }, + { name: "Dandy's World Clicker", url: UGS("dandys-world-clicker"), cat: "idle" }, + { name: "D-Blox", url: UGS("dblox"), cat: "puzzle" }, + { name: "Deepest Sword", url: UGS("deepest sword"), cat: "rpg" }, + { name: "Demon Bluff", url: UGS("demon-bluff"), cat: "horror" }, + { name: "Die in the Dungeon", url: UGS("die in the dungeon"), cat: "rpg" }, + { name: "Dimension Incident", url: UGS("dimension-incident"), cat: "horror" }, + { name: "Doge Miner 2", url: UGS("doge miner 2"), cat: "idle" }, + { name: "Doodle Jump", url: UGS("doodle jump"), cat: "platformer" }, + { name: "Down the Mountain", url: UGS("down the mountain"), cat: "action" }, + { name: "Dragon", url: UGS("dragon"), cat: "action" }, + { name: "Drift Hunters", url: UGS("drift-hunters"), cat: "racing" }, + { name: "Drunken Duel", url: UGS("drunken duel"), cat: "multiplayer" }, + { name: "Duck Life 6", url: UGS("duck life 6"), cat: "rpg" }, + { name: "Duck Life Battle", url: UGS("duck life battle"), cat: "rpg" }, + { name: "Dungeon Deck", url: UGS("dungeondeck"), cat: "strategy" }, + { name: "Eagle Ride", url: UGS("eagle ride"), cat: "racing" }, + { name: "Edy's Car Simulator", url: UGS("edys car simulator"), cat: "racing" }, + { name: "Eggy Car", url: UGS("eggy car"), cat: "racing" }, + { name: "Elastic Man", url: UGS("elasticman"), cat: "other" }, + { name: "Escalating Duel", url: UGS("escalating-duel"), cat: "action" }, + { name: "Escape Road 2", url: UGS("escaperoad2city"), cat: "racing" }, + + // ── NERUVY GAMES (Neruvy/neruvy-games) ─────────────────────────────────── + { name: "1v1.LOL", url: NG("1v1lol"), cat: "shooter" }, + { name: "2048", url: NG("2048"), cat: "puzzle" }, + { name: "ARC", url: NG("ARC"), cat: "action" }, + { name: "Bob the Robber 2", url: NG("bob-the-robber-2"), cat: "adventure" }, + { name: "Drive Mad", url: NG("drive-mad"), cat: "racing" }, + { name: "Granny", url: NG("granny"), cat: "horror" }, + { name: "Moto X3M", url: NG("moto-x3m"), cat: "racing" }, + { name: "Moto X3M 2", url: NG("motox3m2"), cat: "racing" }, + { name: "Poly Track", url: NG("poly-track"), cat: "racing" }, + { name: "Run 3", url: NG("run-3"), cat: "platformer" }, + { name: "Slope", url: NG("slope"), cat: "action" }, + { name: "Smash Karts", url: NG("smash-karts"), cat: "racing" }, + { name: "Snow Rider 3D", url: NG("snow-rider"), cat: "racing" }, + { name: "Survival Race", url: NG("survival-race"), cat: "racing" }, + { name: "Time Shooter 3: SWAT", url: NG("time-shooter-3-swat"), cat: "shooter" }, + { name: "Vex 3", url: NG("vex3"), cat: "platformer" }, + { name: "Vex 4", url: NG("vex4"), cat: "platformer" }, + { name: "Vex 5", url: NG("vex5"), cat: "platformer" }, + { name: "Vex 6", url: NG("vex6"), cat: "platformer" }, + { name: "Vex 7", url: NG("vex7"), cat: "platformer" }, + + // ── WEB PORTS — Neruvy (Neruvy/web-port) ───────────────────────────────── + { name: "Amanda the Adventurer", url: NP("amanda-the-adventurer"), cat: "horror" }, + { name: "Andy's Apple Farm", url: NP("andys-apple-farm"), cat: "adventure" }, + { name: "Baldi's Basics Plus", url: NP("baldi-plus"), cat: "horror" }, + { name: "Baldi's Basics Remastered", url: NP("baldi-remaster"), cat: "horror" }, + { name: "Bendy and the Ink Machine", url: NP("bendy"), cat: "horror" }, + { name: "BERGENTRUCK", url: NP("bergentruck"), cat: "adventure" }, + { name: "BLOODMONEY!", url: NP("bloodmoney"), cat: "action" }, + { name: "Buckshot Roulette", url: NP("buckshot-roulette"), cat: "horror" }, + { name: "Class of '09", url: NP("class-of-09"), cat: "adventure" }, + { name: "Cuphead", url: NP("cuphead"), cat: "action" }, + { name: "Dead Plate", url: NP("dead-plate"), cat: "horror" }, + { name: "The Deadseat", url: NP("deadseat"), cat: "horror" }, + { name: "Deltarune", url: NP("deltatraveler"), cat: "rpg" }, + { name: "Do Not Take This Cat Home", url: NP("donottakethiscathome"), cat: "horror" }, + { name: "Fears to Fathom", url: NP("fears-to-fathom"), cat: "horror" }, + { name: "Getting Over It", url: NP("getting-over-it"), cat: "platformer" }, + { name: "Happy Sheepies", url: NP("happy-sheepies"), cat: "puzzle" }, + { name: "Hotline Miami", url: NP("hotline-miami"), cat: "action" }, + { name: "Human Expenditure Program", url: NP("human-expenditure-program"), cat: "horror" }, + { name: "Jelly Drift", url: NP("jelly-drift"), cat: "racing" }, + { name: "Karlson", url: NP("karlson"), cat: "action" }, + { name: "Kindergarten", url: NP("kindergarten"), cat: "adventure" }, + { name: "Lacy's Flash Games", url: NP("lacysflashgames"), cat: "other" }, + { name: "Milkman Karlson", url: NP("milkman-karlson"), cat: "action" }, + { name: "OMORI", url: NP("omori-fixed"), cat: "rpg" }, + { name: "People Playground", url: NP("people-playground"), cat: "other" }, + { name: "Pizza Tower", url: NP("pizza-tower"), cat: "platformer" }, + { name: "Raft", url: NP("raft"), cat: "adventure" }, + { name: "Slender: The 8 Pages", url: NP("slender"), cat: "horror" }, + { name: "Speed Stars", url: NP("speed-stars"), cat: "racing" }, + { name: "That's Not My Neighbor", url: NP("thats-not-my-neighbor"), cat: "horror" }, + { name: "The Man in the Window", url: NP("the-man-in-the-window"), cat: "horror" }, + { name: "ULTRAKILL", url: NP("ultrakill"), cat: "action" }, + { name: "Undertale Yellow", url: NP("undertale-yellow"), cat: "rpg" }, + { name: "WebFishing", url: NP("web-fishing"), cat: "other" }, + { name: "Yandere Simulator", url: NP("yandere-simulator"), cat: "action" }, + { name: "Yume Nikki", url: NP("yume-nikki"), cat: "rpg" }, + + // ── WEB PORTS — Genizy exclusives (genizy/web-port) ────────────────────── + { name: "Minesweeper Plus", url: GP("minesweeperplus"), cat: "puzzle" }, + { name: "Schoolboy Runaway", url: GP("schoolboy-runaway"), cat: "action" }, + { name: "Sonic.exe", url: GP("sonic.exe"), cat: "horror" }, + { name: "Tattletail", url: GP("tattletail"), cat: "horror" }, + { name: "Witch Heart", url: GP("witch-heart"), cat: "rpg" }, + + // ── MATHTUT0R1NG (mathtut0r1ng.github.io) ──────────────────────────────── + { name: "A Dance of Fire and Ice", url: MT("a-dance-of-fire-and-ice"), cat: "music" }, + { name: "Amaze", url: MT("amaze"), cat: "puzzle" }, + { name: "Aquapark.io", url: MT("aquapark.io"), cat: "sports" }, + { name: "Basket Random", url: MT("basket-random"), cat: "sports" }, + { name: "Basketball Stars", url: MT("basketball-stars"), cat: "sports" }, + { name: "Block Blast", url: MT("block-blast"), cat: "puzzle" }, + { name: "Bouncemasters", url: MT("bouncemasters"), cat: "action" }, + { name: "Bowmasters", url: MT("bowmasters"), cat: "action" }, + { name: "Bridge Race", url: MT("bridge-race"), cat: "racing" }, + { name: "Clash Royale", url: MT("clash"), cat: "strategy" }, + { name: "Crossy Road", url: MT("crossy-road"), cat: "platformer" }, + { name: "Eaglercraft 1.12.2", url: MT("eaglercraft-1.12.2"), cat: "other" }, + { name: "Five Nights at Freddy's", url: MT("fnaf"), cat: "horror" }, + { name: "Five Nights at Freddy's 2", url: MT("fnaf2"), cat: "horror" }, + { name: "Five Nights at Freddy's 3", url: MT("fnaf3"), cat: "horror" }, + { name: "Five Nights at Freddy's 4", url: MT("fnaf4"), cat: "horror" }, + { name: "Friday Night Funkin'", url: MT("friday-night-funkin"), cat: "music" }, + { name: "Hill Climb Racing Lite", url: MT("hill-climb-racing-lite"), cat: "racing" }, + { name: "Hollow Knight", url: MT("hollow-knight"), cat: "platformer" }, + { name: "Paper.io 2", url: MT("paper.io-2"), cat: "multiplayer" }, + { name: "Pixel Gun Survival", url: MT("pixel-gun-survival"), cat: "shooter" }, + { name: "R.E.P.O.", url: MT("repo"), cat: "other" }, + { name: "RE:RUN", url: MT("re-run"), cat: "action" }, + { name: "Slither.io", url: MT("slither.io"), cat: "multiplayer" }, + { name: "Solar Smash", url: MT("solar-smash"), cat: "other" }, + { name: "Spiral Roll", url: MT("spiral-roll"), cat: "puzzle" }, + { name: "Sprunki", url: MT("sprunki"), cat: "music" }, + { name: "Super Mario 64", url: MT("super-mario-64"), cat: "platformer" }, + { name: "The Impossible Quiz", url: MT("the-impossible-quiz"), cat: "puzzle" }, + { name: "The World's Hardest Game", url: MT("the-worlds-hardest-game"), cat: "platformer" }, + { name: "They Are Coming", url: MT("they-are-coming"), cat: "shooter" }, + +]; diff --git a/index.html b/index.html new file mode 100644 index 0000000..0a44770 --- /dev/null +++ b/index.html @@ -0,0 +1,89 @@ + + + + + + OMG Games + + + + + + + + + +
+ + +
+
+ +
+ + + + + + + + diff --git a/style.css b/style.css new file mode 100644 index 0000000..955dc26 --- /dev/null +++ b/style.css @@ -0,0 +1,431 @@ +/* ── Variables ─────────────────────────────────────────────────────────── */ +:root { + --bg: #07070e; + --surface: #0d0d1a; + --surface2: #121224; + --surface3: #16162c; + --border: #1a1a30; + --border2: #22223a; + --primary: #7c3aed; + --primary2: #6d28d9; + --accent: #a78bfa; + --glow: #7c3aed28; + --text: #ededf8; + --text-dim: #8888b0; + --text-muted: #444468; + + --header-h: 58px; + --catbar-h: 52px; + --radius: 10px; + --card-min: 190px; +} + +*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } + +html { height: 100%; } + +body { + min-height: 100%; + background: var(--bg); + color: var(--text); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Inter', system-ui, sans-serif; + font-size: 14px; + overflow-x: hidden; +} + +button { font-family: inherit; } + +/* ── Header ────────────────────────────────────────────────────────────── */ +#header { + position: fixed; + top: 0; left: 0; right: 0; + height: var(--header-h); + background: var(--surface); + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + gap: 12px; + padding: 0 16px; + z-index: 50; +} + +.header-left { display: flex; align-items: center; flex-shrink: 0; } +.header-center { flex: 1; max-width: 520px; margin: 0 auto; } +.header-right { flex-shrink: 0; } + +/* Logo */ +.logo { + display: flex; + align-items: center; + gap: 9px; + user-select: none; +} + +.logo-badge { + background: var(--primary); + color: #fff; + font-size: 11px; + font-weight: 900; + letter-spacing: 1.5px; + padding: 4px 9px; + border-radius: 6px; +} + +.logo-text { + font-size: 18px; + font-weight: 700; + color: var(--text); + letter-spacing: -0.2px; +} + +/* Search */ +.search-wrap { + position: relative; + width: 100%; +} + +.search-icon { + position: absolute; + left: 11px; + top: 50%; + transform: translateY(-50%); + width: 16px; + height: 16px; + color: var(--text-muted); + pointer-events: none; +} + +#search { + width: 100%; + background: var(--surface2); + border: 1px solid var(--border2); + color: var(--text); + padding: 8px 34px 8px 34px; + border-radius: 8px; + font-size: 13.5px; + outline: none; + transition: border-color 0.15s, background 0.15s; + font-family: inherit; +} + +#search::placeholder { color: var(--text-muted); } +#search:focus { border-color: var(--primary); background: var(--surface3); } + +#search-clear { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + color: var(--text-muted); + cursor: pointer; + padding: 4px; + display: flex; + border-radius: 4px; +} +#search-clear svg { width: 13px; height: 13px; } +#search-clear:hover { color: var(--text); } + +/* Count badge */ +#header-count { + font-size: 12px; + color: var(--text-muted); + white-space: nowrap; +} + +/* ── Category Bar ──────────────────────────────────────────────────────── */ +#cat-bar { + position: fixed; + top: var(--header-h); + left: 0; right: 0; + height: var(--catbar-h); + background: var(--surface); + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + gap: 8px; + padding: 0 16px; + overflow-x: auto; + overflow-y: hidden; + z-index: 49; + scrollbar-width: none; +} +#cat-bar::-webkit-scrollbar { display: none; } + +.cat-pill { + display: flex; + align-items: center; + gap: 6px; + padding: 6px 14px; + border-radius: 20px; + font-size: 13px; + font-weight: 500; + white-space: nowrap; + cursor: pointer; + border: 1px solid var(--border2); + background: var(--surface2); + color: var(--text-dim); + transition: all 0.15s; + user-select: none; +} + +.cat-pill:hover { + background: var(--surface3); + color: var(--text); + border-color: var(--border2); +} + +.cat-pill.active { + background: var(--primary); + border-color: var(--primary); + color: #fff; +} + +.cat-pill-emoji { font-size: 14px; line-height: 1; } +.cat-pill-count { + font-size: 11px; + opacity: 0.7; + font-variant-numeric: tabular-nums; +} + +/* ── Main / Grid ───────────────────────────────────────────────────────── */ +#main { + margin-top: calc(var(--header-h) + var(--catbar-h)); + padding: 20px 16px 32px; + min-height: calc(100vh - var(--header-h) - var(--catbar-h)); +} + +#grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(var(--card-min), 1fr)); + gap: 10px; +} + +/* ── Game Card ─────────────────────────────────────────────────────────── */ +.card { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + overflow: hidden; + cursor: pointer; + transition: transform 0.15s, border-color 0.15s, box-shadow 0.15s; + position: relative; +} + +.card:hover { + transform: translateY(-3px); + border-color: var(--primary); + box-shadow: 0 8px 28px var(--glow); +} + +.card-thumb { + width: 100%; + aspect-ratio: 16 / 9; + display: flex; + align-items: center; + justify-content: center; + position: relative; + overflow: hidden; +} + +.card-thumb img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + transition: transform 0.2s; +} + +.card:hover .card-thumb img { transform: scale(1.04); } + +.card-emoji { + font-size: 2.2rem; + line-height: 1; + filter: drop-shadow(0 2px 10px #0008); + user-select: none; + pointer-events: none; +} + +/* Play button overlay */ +.card-play { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.38); + opacity: 0; + transition: opacity 0.15s; +} + +.card:hover .card-play { opacity: 1; } + +.card-play svg { + width: 36px; + height: 36px; + color: #fff; + filter: drop-shadow(0 2px 8px #0006); +} + +.card-body { + padding: 9px 11px 11px; +} + +.card-name { + font-size: 13px; + font-weight: 600; + color: var(--text); + line-height: 1.35; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-bottom: 5px; +} + +.cat-badge { + display: inline-block; + font-size: 10px; + font-weight: 600; + padding: 2px 8px; + border-radius: 5px; + letter-spacing: 0.2px; +} + +/* ── Empty State ───────────────────────────────────────────────────────── */ +#empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 10px; + padding: 100px 20px; +} +.empty-icon { font-size: 3.5rem; } +.empty-msg { font-size: 18px; font-weight: 600; color: var(--text-dim); } +.empty-sub { font-size: 13px; color: var(--text-muted); } + +/* ── Game Overlay ──────────────────────────────────────────────────────── */ +#overlay { + position: fixed; + inset: 0; + background: var(--bg); + z-index: 200; + display: flex; + flex-direction: column; +} + +#overlay.hidden { display: none; } + +#overlay-bar { + height: 50px; + background: var(--surface); + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 14px; + gap: 10px; + flex-shrink: 0; +} + +#overlay-left { + display: flex; + align-items: center; + gap: 10px; + min-width: 0; + flex: 1; +} + +#overlay-right { display: flex; gap: 6px; flex-shrink: 0; } + +#close-btn, #fs-btn { + background: var(--surface2); + border: 1px solid var(--border2); + color: var(--text-dim); + border-radius: 7px; + cursor: pointer; + width: 34px; + height: 34px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + transition: background 0.12s, color 0.12s, border-color 0.12s; +} +#close-btn svg, #fs-btn svg { width: 15px; height: 15px; } +#close-btn:hover, #fs-btn:hover { + background: var(--surface3); + border-color: var(--border2); + color: var(--text); +} + +#overlay-name { + font-size: 14px; + font-weight: 600; + color: var(--text); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +#overlay-badge { + font-size: 10.5px; + font-weight: 600; + padding: 2px 8px; + border-radius: 5px; + white-space: nowrap; + flex-shrink: 0; +} + +#frame-wrap { + flex: 1; + position: relative; + overflow: hidden; +} + +#game-frame { + width: 100%; + height: 100%; + border: none; + display: block; + transition: opacity 0.2s; +} + +/* Loading state */ +#loader { + position: absolute; + inset: 0; + display: none; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 16px; + background: var(--bg); + z-index: 2; +} + +.spinner { + width: 40px; + height: 40px; + border: 3px solid var(--border2); + border-top-color: var(--primary); + border-radius: 50%; + animation: spin 0.75s linear infinite; +} + +.loader-text { + font-size: 13px; + color: var(--text-muted); +} + +@keyframes spin { to { transform: rotate(360deg); } } + +/* ── Scrollbar ─────────────────────────────────────────────────────────── */ +::-webkit-scrollbar { width: 5px; height: 5px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 3px; } +::-webkit-scrollbar-thumb:hover { background: var(--primary); } + +/* ── Utilities ─────────────────────────────────────────────────────────── */ +.hidden { display: none !important; }