PageSourceSearch

https://ubghyper.github.io/assets/js/utils.js

js ubghyper.github.io collected 2026-10-03 10:06:48 UTC 18,356 bytes, 153 lines download raw bytes

1// ── Theme Utilities ──────────────────────────────────────────
2const html = document.documentElement;
3const SUN  = '<svg class="icon" aria-hidden="true"><use href="#icon-sun"/></svg>';
4const MOON = '<svg class="icon" aria-hidden="true"><use href="#icon-moon"/></svg>';
5
6function initTheme() {
7  const themeBtn = document.getElementById('themeToggle');
8  if (!themeBtn) return;
9  const savedTheme = localStorage.getItem('theme') || 'light';
10  html.setAttribute('data-theme', savedTheme);
11  themeBtn.innerHTML = savedTheme === 'dark' ? SUN : MOON;
12  
13  themeBtn.addEventListener('click', toggleTheme);
14}
15
16function toggleTheme() {
17  const t = html.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
18  html.setAttribute('data-theme', t);
19  localStorage.setItem('theme', t);
20  const btn = document.getElementById('themeToggle');
21  if (btn) btn.innerHTML = t === 'dark' ? SUN : MOON;
22  // Re-render chart if available
23  if (window.waitForChart && window.renderChart) {
24    window.waitForChart().then(window.renderChart).catch(() => {});
25  }
26}
27
28// ── String & Format Utilities ────────────────────────────────
29function cap(s) {
30  return s.charAt(0).toUpperCase() + s.slice(1);
31}
32
33function fmtTime(s) {
34  return String(Math.floor(s / 60)).padStart(2, '0') + ':' + String(s % 60).padStart(2, '0');
35}
36
37// ── Constants ─────────────────────────────────────────────────
38const _isLocal = location.hostname === 'localhost' || location.hostname === '127.0.0.1';
39// ?cdn=PORT loads every game from http://localhost:PORT (scripts/serve-local-games.js).
40// Remembered for the tab, because grid cards link to /games/{slug}.html without it.
41// Local hosts only: the live site never reads it.
42const _localPort = _isLocal ? (() => {
43  const q = new URLSearchParams(location.search).get('cdn');
44  try {
45    if (q) sessionStorage.setItem('ubg_cdn', q);
46    return q || sessionStorage.getItem('ubg_cdn');
47  } catch (e) { return q; }
48})() : null;
49
50// ── Game shards ───────────────────────────────────────────────
51// Game files live in separate "shard" repos under the same GitHub account. A
52// project repo named `X` is served by GitHub Pages at ubghyper.github.io/X/,
53// so every shard sits on the same origin and a game folder resolves to
54//   https://ubghyper.github.io/{shard}/{slug}/
55// A game names its shard with an optional `shard` field in games.json; entries
56// without one live in the original GameList.github.io shard. Adding a new shard
57// therefore requires no code change — only the field on the new games.
58const SHARD_ORIGIN  = 'https://ubghyper.github.io';
59const SHARD_DEFAULT = 'GameList.github.io';
60
61// ── GENERATED by scripts/gen-play-shell.js — do not edit by hand ──────────────
62// Shard repo -> its pinned jsDelivr mirror. Thumbnails resolve through here
63// rather than github.io: on any non-github.io host, github.io is blocked by most
64// school filters, so thumbnails served from it render as a grid of empty boxes.
65const SHARD_CDN = {
66  "GameList.github.io": "https://cdn.jsdelivr.net/gh/UBGHyper/GameList.github.io@422a72130124f24fc1ef366ef3307e1ad2671ad4",
67  "gamelist2.github.io": "https://cdn.jsdelivr.net/gh/freeonlinewebtools/gamelist2.github.io@408d7c4e1177fe8926c754144ad3b2a50ba340fb",
68  "gamelist3.github.io": "https://cdn.jsdelivr.net/gh/freeonlinewebtools/gamelist3.github.io@985669be9312518503ffd8d19816f16a9c98120f",
69  "gamelist4.github.io": "https://cdn.jsdelivr.net/gh/freeonlinewebtools/gamelist4.github.io@16488774e354c4ba5dcd1759fe85f1b3b20a42ff",
70  "gamelist5.github.io": "https://cdn.jsdelivr.net/gh/freeonlinewebtools/gamelist5.github.io@1c5c32f73d8e0d0198a4890a90087537b9cc8c42",
71  "gamelist6.github.io": "https://cdn.jsdelivr.net/gh/freeonlinewebtools/gamelist6.github.io@9055fb30d4da4715e618263a5cf225b1937e8a42",
72  "gamelist7.github.io": "https://cdn.jsdelivr.net/gh/freeonlinewebtools/gamelist7.github.io@f49b5c32c35ee48ab179ec5a719d95ec7f
720a6d19",
73  "gamelist8.github.io": "https://cdn.jsdelivr.net/gh/freeonlinewebtools/gamelist8.github.io@7e098d51d78090f262a5d2b4ddee3a062b2f95cf",
74  "gamelist9.github.io": "https://cdn.jsdelivr.net/gh/freeonlinewebtools/gamelist9.github.io@0ee4fe7925333a3dff0b4d9dcefe7c534002d656",
75  "gamelist10.github.io": "https://cdn.jsdelivr.net/gh/freeonlinewebtools/gamelist10.github.io@d5fea0594796d79bcba69576156b82e2d22ab2d1",
76  "gamelist11.github.io": "https://cdn.jsdelivr.net/gh/freeonlinewebtools/gamelist11.github.io@6d18f59c8ece07d60618ca35e18de93943326a94",
77  "gamelist12.github.io": "https://cdn.jsdelivr.net/gh/freeonlinewebtools/gamelist12.github.io@425bcd1a664b8f9d63fd5ef6f155a1c178b9b4a4",
78  "gamelist13.github.io": "https://cdn.jsdelivr.net/gh/freeonlinewebtools/gamelist13.github.io@deaaef4b48f030554c652fd52c3b9d79d4602aee"
79};
80// Games that actually have a shell at /g/{slug}/. Built from what was written,
81// not from manifest eligibility — 8 games are eligible yet skipped (no entry
82// html, oversized entry) and listing those would point the iframe at nothing.
83const PLAY_SHELLS = new Set(["2048","3D-Bowling","8-Ball-Billiards-Classic","A-Dance-of-Fire-and-Ice","A-Game-About-Feeding-A-Black-Hole","ABaF","Achievement-Unlocked","Achievement-Unlocked-2","Achievement-Unlocked-3","Adventure","Adventure-Drivers","Age-Of-War","Age-Of-War-2","Alien-Hominid","Amanda-The-Adventurer","Angry-Birds-2","Anton-Blast-Demo-v12","Apes-vs-Helium","Archery-World-Tour","AttackHole","Awesome-Cars","Awesome-Planes","Awesome-Tanks","Awesome-Tanks-2","BALDI","BART-BLAST-INDEV-VERSION","BASEBALL-BROS","BERGENTRUCK-201X","BTD-4","Bacon-May-Die","Bad-Ice-Cream","Bad-Ice-Cream-2","Bad-Ice-Cream-3","Bad-Time-Simulator-Sans-Fight","Baldis-Basics-The-Ultra-Decompile","Ball-Blast-HTML5","Bartender-The-Right-Mix","Basket-Bros","Basket-Champs","Basketball-FRVR","Basketball-Legends-2020","Basketball-Line","Basketball-Stars","Bejeweled","Bendy","Big-Tower-Tiny-Square-2","Billards","Bit-Planes","Bitlife","Blackjack","Blade-Ball","Bloons-TD-5","Bloons-Tower-Defence","Bloons-Tower-Defence-2","Bloons-Tower-Defence-3","Bloxorz","Bob-The-Robber","Bobs-Onslaught","Bomb-Pass","Bottle-Jump-3D","Bouncemasters","Boxhead","Boxing-Random","Brawl-Stars","BreakLock","Breaking-The-Bank","Bubble-Tanks-Arena","Bubble-Tanks-TD-1","Bubble-Tanks-TD-2","Buckshot-Roulete","Burrito-Bison","Burrito-Bison-Revenge","Buster-Jam","CSGO","Cactus-McCoy-2","Canabalt","Candy-Crush","Cannon-Balls-3D","Cat-Connection-V2","Cat-Gunner","Cave-Story","Celeste","Cell-Machine","Chat-Room","CheeseRolling","Chess-Classic","Chiikawa-Puzzle","Choppy-Orc","Christmas-Massacre","Chromium-BSU","Clash-Of-Tanks","Clash-Of-Vikings","Clicker-Heroes","Cluster-Rush","ColorWaterSort3D","Cooking-Mama","Core-Ball-Level-4","Count-Masters","Crash-Bandicoot","Crazy-Cars","CrazyCattle3d","CrazyChicken3D","Crossing-The-Pit","Crossy-Road","Crush-The-Castle","Crush-The-Castle-2","Cuphead","Cursed-Treasure","Cursed-Treasure-2","Curve-Rush","Curveball","Cut-The-Rope","Cut-The-Rope-Holiday","Cut-The-Rope-Time-Travel","DONT-YOU-LECTURE-ME-WITH-YOUR-THIRTY-DOLLAR-WEBSITE","Dadish","Dadish-2","Dadish-3","Dan-The-Man","Death-Run-3D","Deep-Sleep-1","Deep-Sleep-2","Deep-Sleep-3","Destroy-The-Car-3D","Dice-a-Million","Dig-Deep","Dig-To-China","Dogeminer","Dont-Escape-1","Dont-Escape-2","Dont-Escape-3","Dont-Whack-Your-Boss","Doodle-Jump","Doom","Doom-3","Dragon-Vs-Bricks","Draw-the-Hill","DrawJoust","Dreadhead-Parkour","Drift-Boss","Drive-Mad","Driven-Wild","Duck-Life","Duck-Life-2","Duck-Life-3","Duck-Life-4","Duck-Life-5","Eaglercraft","Earn-To-Die","Earn-To-Die-2","Earn-to-Die-2-Exodus","Effing-Worms","Eggy-Car","Elastic-Face","ElementalMasters","Endless-Aethos","Endoparasitic","Endoparasitic-2","Escape-Road","Escape-Road-2","Escape-Road-City","Escaping-The-Prison","Evil-Glitch","EvoWarsio","FNAE","FNAF-1","FNAF-2","FNAF-3","FNAF-4","FNAF-Pizzeria-Simulator","FNAF-Sister-Location","FNAF-UCN","FNAF-World","FNF-Cyber-Sensation-Friday-Night-Funkin","FNF-SOFT-Online-Friday-Night-Funkin","FNF-VS-Shaggy","Factory-Balls","Factory-Balls-2","Factory-Balls-3","Factory-Balls-4","Fallout","Fancy-Pants-Adventures","Fashion-Battle","Fat-Cat","Fear-Assessment","Fears-To-F
83athom-Home-Alone","Fears-to-Fathom","Final-Earth-2","Fireboy-and-Watergirl","Fireboy-and-Watergirl-2","Fireboy-and-Watergirl-3","Fireboy-and-Watergirl-4","Fireboy-and-Watergirl-5","Fireboy-and-Watergirl-6","Fishing-Frenzy","Five-Nights-at-Candys-1","Five-Nights-at-Candys-2","Five-Nights-at-Freddys-4-Halloween","Five-Nights-at-Freddys-Pizzeria-Simulator","Five-Nights-at-Shreks-Hotel","Flappy-Bird","Flappy-Bird-Multiplayer","Flappy-Dunk","Flash-Flight-Simulator-1","ForkNSausage","Friday-Night-Funkin-AKAGE","Friday-Night-Funkin-FNF-vs-Tricky-Version-2-Phase-3-w-CHEAT-BOT","Friday-Night-Funkin-Lullaby","Friday-Night-Funkin-Plus-Hatsune-Miku","Friday-Night-Funkin-Psych-Engine","Friday-Night-Funkin-Sarventes-Mid-Fight-Masses","Friday-Night-Funkin-Vs-Nonsense","Funny-Puzzles","Fused-240","Gabriels-Awesome-Schoolhouse","Game-Inside-a-Game","Geometry-Dash-Lite","George-and-the-Printer","Getaway-Shootout","Getting-Over-It","Gobble","Gods-Flesh","Granny","Granny-2","Granny-3","Guess-Their-Answer","Gun-Mayham-2","Gun-Mayham-Redux","GunBuild","Gunblood","HAYAI","HCR-Lite-10","Half-Life","Half-Life-Opposing-Force","Happy-Fishing","Happy-Wheels","HappySheepies","Harvestio","Heartbreak-Havoc","Hedgehog-Launch","HexGL","Hextris","HighHeels","Highway-Racer-2","Hit-Single-Real","Hobo-1","Hobo-2","Hobo-3","Hobo-4","Hobo-5","Hobo-6","Hobo-7","Hoop-Royale","Hotline-Miami","House-of-Hazards","Hyper-Sandbox","I-WANNA-BE-THE-GUY-THE-GAME-THE-MOVIE","Ice-Dodo","Idle-Breakout","Idle-Dice","Idle-Mining-Empire","Indie-Cross","Interactive-Buddy","Into-Space-1","Into-Space-2","Into-Space-3","Iron-Lung","Iron-Snout","Jacksmith","Jelly-Drift","Jelly-Mario","Jelly-Truck","Jetpack-Joyride","Johnny-Upgrade","Just-Fall","Just-Shapes-and-Beats","Karlson","Kindergarten","Kindergarten-2","Kitchen-Bazar","KittyToy","Knights-Arena","Krunker","LETHAL-APE","Laceys-Flash-Games","Learn-To-Fly-2","Learn-To-Fly-3","Level-Devil","Line-Rider-2","Little-Alchemy-2","Little-Runmo-The-Game-13","Lobotomy-Corporation","Look-Outside","Love-Letters","Luminous-Strike","Madness-Project-Nexus","Magic-Tiles-3","Maze-Speedrun","Melon-Playground","Meteor-60-Seconds","Midnight-Shift-12","Milkman-Karlson","Mindustry","Minecraft","Minecraft-Case-Simulator","Minecraft-Online","Minesweeper-Plus","Mini-Metro","Mob-control","Monkey-Mart","Monster-Tracks","Motherload","Moto-X3M-Pool-Party","Moto3XM","Moto3XM-2","MotoX3M-6-Spooky-Land","Mountain-Bike-Racer","Mutilate-A-Doll-Two","Needy-Streamer-Overload","Ninja-vs-EVILCORP","Om-Nom-Run","One-Night-At-Kim-Jong-Uns","OshiOshiPunch","Outhold","OvO","OvO-2","OvO-3","Overburden","PEAK","Pac-Man","Pandemic","Pandemic-2","Papa-Bakes","Papa-Burg","Papa-Cpck","Papa-Htdog","Papa-Scoop","Papas-Cheese","Papas-Donut","Papas-Louie-1","Papas-Louie-2","Papas-Louie-3","Papas-Pasta","Papas-Pizzeria","Papas-PnCk","Papas-Sushi","Papas-Wing","Paper-IO-2","Parking-Rush","Peaks-of-Yore","People-Playground","Pibby-Apocalypse","Pico-vs-uberkids","Picos-school","Picos-school-2","Pizza-Tower","Plague-Inc","Poly-Art-3D","Polytrack-Modded","Polytrack-New","Polytrack-Old","Portal-Flash","Pou","Power-Hover","Powermanga","RERUN","Race-Master","Raft","Raft-Wars","Raft-Wars-2","Ragdoll-Archers","Rainbow-Obby-v03x","Raze","Raze-2","Red-ball","Red-ball-2","Red-ball-3","Red-ball-4","Repo","Retro-Bowl","Retro-Bowl-College","Riddle-School-1","Riddle-School-2","Riddle-School-3","Riddle-School-4","Riddle-School-5","Riddle-School-Transfer","Riddle-School-Transfer-2","Rogue-Sergeant-The-Final-Operation","Rolling-Sky","Rolly-Vortex","Rooftop-Snipers","Rooftop-Snipers-2","Run","Run-2","Run-3","RunRich","SANDTRIS","SLENDYTUBBIES-1","SMB1-Web-Gameplay","Sandstone","Scary-Shawarma-3D","Schoolboy-Runaway","Scratch-Inc","Sentry-Knight","Shady-Bears","Shapez","Shell-Shockers","Shopping-Cart-Hero","Shopping-Cart-Hero-2","Shopping-Cart-Hero-3","Side-Effects","Sift-Heads-1","Sift-Heads-2","Sift-Heads-3","Sift-Heads-4","Sift-Heads-5","Skyball","Slender","Slendytubbies-II-V22","Slider","Slime-Rancher","Slope","Slope-2","Slope-Plus","Slow-Roads","Snail-Bob","Snail-Bob-2","Snail-Bob-3","Snail-Bob-4","Snail-B
83ob-5","Snail-Bob-6","Snail-Bob-7","Snail-Bob-8","Sniper-Assassin","Sniper-assassin-2","Sniper-assassin-3","Sniper-assassin-4","Sniper-assassin-5","Snow-Rider-3D","SnowBattleio","Soccer-Masters-Euro-2020","Soccer-Random","Sonic-2-Communitys-Cut","Sonic-3-AIR","Space-Waves","Spaceflight-Simulator","Spelunky","SpelunkyClassicHD","SpiderDoll","Sprunki","Stardew-Valley","Starfighter","Stealing-The-Diamond","Stick-Madness","Stick-Madness-2","Stick-Merge","Stick-Merge-2","Stick-RPG","Stick-Slasher","Stick-War-1","Stick-War-2","Stickman-Arena","Stickman-Boost","Stickman-Climb","Stickman-Destruction-OnlineGamesio","Stickman-Duel","Stickman-Hook","Storm-The-House","Storm-The-House-2","Strikeforce-Heroes","Strikeforce-Kitty","Strikeforce-Kitty-2","SuikaGame","Super-Mario-63","Super-Mario-Wonder","Super-Meat-Boy","Super-Smash-Flash","Sushi-Roll","Swords-And-Souls","TABS","TG-Playground","Tag","Tallio","Tasty-Pastry-Party","Tattletail","Temple-Run-2","Terraria","Territorialio","Territory-War","Tetris","Thats-Not-My-Neighbor","The-Deadseat","The-Final-Earth-2","The-Impossible-Quiz-2","The-Impossible-Quiz-Deluxe","The-Man-In-The-Window","There-Is-No-Game","This-Is-The-Only-Level","Three-Goblets","Time-Shooter","Time-Shooter-2","Time-Shooter-3","Tiny-Fishing","Tomb-of-the-Mask","Toss-the-turtle","Tower-Crash-3D","Tower-Square","Trigger-Rally","Troll-Face-Quest","Troll-Face-Quest-2","Troll-Face-Quest-3","Troll-Face-Quest-4","Troll-Face-Quest-5","Tunnel-Rush","Turbo-Stars","Ultrakill","Ultrapool","Undertale","Undertale-Yellow","Unfair-mario","Unity-Web-Player-DELTATRAVELER","Unity-Web-Player-Dimension-incident","Unity-Web-Player-FISH","Unity-Web-Player-Flying-Gorilla","Unity-Web-Player-Goblin-Goopmaxxing","Unity-Web-Player-MiSide","Unity-Web-Player-Roulette-Hero","Unity-Web-Player-Shift-at-Midnight","Unity-Web-Player-Swordfight","Untitled-Goose-Game","UvuvwevwevweOnyetenvewv","Vectoroids","Vena","Vex-1","Vex-2","Vex-3","Vex-3-Xmas","Vex-4","Vex-5","Vex-6","Vex-7","Vex-8","Vex-Challenges","Vex-X3M","Vex-X3M-2","Violetland","Volley-Random","Vs-QT-Mod","WeBecomeWhatWeBehold","Weapon-Upgrade-Rush","WebFishing","WebOsu-20","Wheely","Wheely-2","Wheely-3","Wheely-4","Wheely-5","Wheely-6","Wheely-7","Wheely-8","WhosYourDaddy","Witch-Heart","Wordle","Worlds-Hardest-Game","Worlds-Hardest-Game-3","Worlds-Hardest-Game-4","Worst-Time-Simulator","YOMI-HUSTLE","Zombotron","Zombotron-2","beatblock","bob-the-robber-2","circloO","circloO-2-Unblocked-ubg235-Poki","gunspin","hollowknight","journey-downhill","n-gon","qwop","sandspiel","shapez-Demo-Factory-Automation-Game","slitherio"]);
84// ── END GENERATED ────────────────────────────────────────────────────────────
85
86const IMG_BASE = (_isLocal && _localPort)
87  ? `http://localhost:${_localPort}`
88  : `${SHARD_ORIGIN}/${SHARD_DEFAULT}`;
89
90/** Base URL of the shard a game is hosted in.
91    `shard` may be a bare repo name (served from SHARD_ORIGIN) or a full base URL —
92    the numbered shards live on a different account, e.g.
93    "https://freeonlinewebtools.github.io/gamelist9.github.io". */
94function gameBase(g) {
95  if (_isLocal && _localPort) return `http://localhost:${_localPort}`;
96  const shard = (g && g.shard) || SHARD_DEFAULT;
97  return shard.includes('://') ? shard.replace(/\/$/, '') : `${SHARD_ORIGIN}/${shard}`;
98}
99/** The game's folder on its shard. Still the fallback iframe target. */
100function gameFolder(g) { return `${gameBase(g)}/${g.url}/`; }
101
102/** The shard's pinned jsDelivr mirror, or '' when it has none. */
103function gameCdnBase(g) {
104  if (_isLocal && _localPort) return '';
105  const shard = (g && g.shard) || SHARD_DEFAULT;
106  const repo = shard.includes('://') ? shard.replace(/\/$/, '').split('/').pop() : shard;
107  return SHARD_CDN[repo] || '';
108}
109
110/** The game's thumbnail, or '' when it has no image.
111    Served from the CDN wherever possible. Thumbnails used to resolve to
112    github.io, which most school filters block — so on the Render host every
113    grid, shelf and recommendation rendered as empty boxes even though the page
114    itself loaded fine. */
115function gameThumb(g) {
116  if (!g || !g.image) return '';
117  const cdn = gameCdnBase(g);
118  return cdn ? `${cdn}/${g.url}/${g.image}` : `${gameFolder(g)}${g.image}`;
119}
120
121/** What the player iframe loads.
122    Prefers the local shell at /g/{slug}/, whose assets come from jsDelivr, so no
123    github.io is involved at all — that is what makes the Render host usable.
124    Two deliberate limits:
125      · only for games that actually have a shell; the other ~98 would 404.
126      · not while on github.io. There it buys no reachability (the page itself
127        already came from github.io) and it would move the game to a new origin,
128        orphaning localStorage saves for the ~394 games currently running on the
129        freeonlinewebtools origin. No upside, real downside. */
130function gamePlayUrl(g) {
131  if (!g) return '';
132  if (g.embed) return g.embed;
133  const onGithubPages = location.hostname.endsWith('github.io');
134  if (!onGithubPages && !(_isLocal && _localPort) && PLAY_SHELLS.has(g.url)) {
135    return `/g/${encodeURIComponent(g.url)}/`;
136  }
137  return gameFolder(g);
138}
139
140const PT_KEY     = 'ubg_pt';
141const FAV_KEY    = 'ubg_fav';
142const FAV_LEG    = 'favorites';
143const RATING_KEY = 'ubg_ratings';
144
145// ── Favorites Utilities ───────────────────────────────────────
146const favSet = new Set(JSON.parse(localStorage.getItem(FAV_KEY) || localStorage.getItem(FAV_LEG) || '[]'));
147
148function saveFavs() {
149  localStorage.setItem(FAV_KEY, JSON.stringify([...favSet]));
150}
151
152// Initialize theme on script load
153initTheme();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.