Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions public/images/games_logo_dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/components/fullscreen-button.astro
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const { targetId } = Astro.props;
type="button"
data-fullscreen-for={targetId}
aria-live="polite"
class="rounded-lg border border-gray-300 bg-white px-4 py-1.5 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-100 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
class="rounded-lg border border-gray-300 bg-white px-4 py-1.5 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-100 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-200 dark:hover:bg-neutral-800"
>
⛶ Full screen
</button>
Expand Down
2 changes: 1 addition & 1 deletion src/components/gametile.astro
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ const altText = `${Game} unblocked - play online free`;
decoding="async"
class="aspect-[3/2] h-auto w-full max-w-[300px] rounded-xl transition-transform duration-300 hover:scale-105 hover:shadow-lg active:scale-95"
/>
<p class="text-gray-800 transition-colors hover:text-black">{Game}</p>
<p class="text-gray-800 dark:text-neutral-200 transition-colors hover:text-black dark:hover:text-white">{Game}</p>
</a>
73 changes: 61 additions & 12 deletions src/layout/layout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const moreGames = (() => {
<title>{gameTitle}</title>
<meta name="description" content={gameDescription} />
<meta name="robots" content="index, follow, max-image-preview:large" />
<meta name="theme-color" content="#ffffff" />
<meta name="theme-color" id="theme-color" content="#ffffff" />
<link rel="canonical" href={pageUrl} />

{
Expand Down Expand Up @@ -226,20 +226,46 @@ const moreGames = (() => {
<script is:inline defer src="/_vercel/speed-insights/script.js"></script>
<link href="/pagefind/pagefind-component-ui.css" rel="stylesheet" />
<script src="/pagefind/pagefind-component-ui.js" type="module" is:inline></script>
<script is:inline>
// ponytail: class toggle beats media query — persists user choice
try {
var t = localStorage.getItem("theme");
var dark = t === "dark" || (!t && matchMedia("(prefers-color-scheme: dark)").matches);
if (dark) document.documentElement.classList.add("dark");
var m = document.getElementById("theme-color");
if (m) m.setAttribute("content", dark ? "#0a0a0a" : "#ffffff");
} catch {}
</script>
</head>
<body>
<body class="bg-white text-gray-900 dark:bg-neutral-950 dark:text-neutral-100">
<nav class="flex items-center justify-between">
<a href="/">
<img
src="/images/games_logo_light.svg"
alt="Superfun Games - Unblocked Games Home"
width="300"
class="m-5"
class="m-5 block dark:hidden"
/>
<img
src="/images/games_logo_dark.svg"
alt="Superfun Games - Unblocked Games Home"
width="300"
class="m-5 hidden dark:block"
/>
</a>
<div class="mr-5 flex gap-4 text-sm items-center">
<a href="/" class="hover:text-black text-gray-800 transition-colors">Home</a>
<a href="/dmca" class="hover:text-black text-gray-800 transition-colors">DMCA</a>
<a href="/" class="hover:text-black text-gray-800 dark:text-neutral-200 dark:hover:text-white transition-colors">Home</a>
<a href="/dmca" class="hover:text-black text-gray-800 dark:text-neutral-200 dark:hover:text-white transition-colors">DMCA</a>
<button
type="button"
id="theme-toggle"
aria-label="Switch to dark theme"
aria-pressed="false"
class="rounded-lg border border-gray-300 bg-white px-2 py-1 dark:border-neutral-700 dark:bg-neutral-900 hover:bg-gray-100 dark:hover:bg-neutral-800 transition-colors"
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-gray-700 dark:hidden"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-neutral-200 hidden dark:block"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>
</button>
<pagefind-searchbox></pagefind-searchbox>
</div>
</nav>
Expand All @@ -251,10 +277,10 @@ const moreGames = (() => {
Play {Game} Unblocked on Chromebook
</h1>
<div class="py-2">
<p class="text-sm text-gray-500 mb-1 line-clamp-3">
<p class="text-sm text-gray-500 dark:text-neutral-400 mb-1 line-clamp-3">
{gameDescription}
</p>
<p class="text-xs text-gray-400 font-medium">
<p class="text-xs text-gray-400 dark:text-neutral-500 font-medium">
✓ {isFlash
? "No Flash plugin required — works on school Chromebooks via Ruffle emulator"
: "Works on school Chromebooks — no downloads needed"}
Expand All @@ -268,8 +294,8 @@ const moreGames = (() => {
{Game && !isHome && (
<div class="mx-auto max-w-[1200px] px-6 pt-2">
<!-- More Games cross-linking (dynamically generated) -->
<nav aria-label="More unblocked games" class="border-t border-gray-200 mt-4 py-4">
<h2 class="text-sm font-semibold mb-2 text-gray-700">More Unblocked Games</h2>
<nav aria-label="More unblocked games" class="border-t border-gray-200 dark:border-neutral-800 mt-4 py-4">
<h2 class="text-sm font-semibold mb-2 text-gray-700 dark:text-neutral-300">More Unblocked Games</h2>
<div class="grid grid-cols-[repeat(auto-fill,minmax(min(100%,280px),1fr))] gap-[10px]">
{moreGames.map((s) => (
<Gametile Game={gamesMap[s].title} Path={`/games/${s}`} Image={gamesMap[s].image} />
Expand All @@ -280,13 +306,36 @@ const moreGames = (() => {
)}

<footer>
<p class="ml-6 mb-4 text-sm text-gray-600">
<p class="ml-6 mb-4 text-sm text-gray-600 dark:text-neutral-400">
All games sourced from <a
class="hover:underline underline-offset-2 text-blue-600"
class="hover:underline underline-offset-2 text-blue-600 dark:text-blue-400"
href="https://archive.org">Internet Archive</a
> · <a class="hover:underline underline-offset-2 text-blue-600" href="/about">About</a>
> · <a class="hover:underline underline-offset-2 text-blue-600 dark:text-blue-400" href="/about">About</a>
</p>
</footer>
<script is:inline>
// ponytail: icons swap via dark: classes, JS only flips the class
(function () {
function sync() {
var dark = document.documentElement.classList.contains("dark");
var m = document.getElementById("theme-color");
if (m) m.setAttribute("content", dark ? "#0a0a0a" : "#ffffff");
var b = document.getElementById("theme-toggle");
if (b) {
b.setAttribute("aria-pressed", dark ? "true" : "false");
b.setAttribute("aria-label", dark ? "Switch to light theme" : "Switch to dark theme");
}
}
document.addEventListener("click", function (e) {
var b = e.target && e.target.closest ? e.target.closest("#theme-toggle") : null;
if (!b) return;
var dark = document.documentElement.classList.toggle("dark");
try { localStorage.setItem("theme", dark ? "dark" : "light"); } catch {}
sync();
});
sync();
})();
</script>
{
seo && slug && (
<script define:vars={{ slug, title: seo.title, image: seo.image }} is:inline>
Expand Down
4 changes: 2 additions & 2 deletions src/pages/404.astro
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ const pageTitle = "Page Not Found - Superfun Games";
<Layout pageTitle={pageTitle}>
<div class="mx-auto max-w-[600px] p-6 text-center">
<h1 class="text-2xl font-bold mb-2">Game not found</h1>
<p class="mb-4 text-gray-600">
<p class="mb-4 text-gray-600 dark:text-neutral-400">
That page doesn't exist. Head back to the homepage to pick a game.
</p>
<a href="/" class="text-blue-600 hover:underline">Back to all games</a>
<a href="/" class="text-blue-600 dark:text-blue-400 hover:underline">Back to all games</a>
</div>
</Layout>
6 changes: 3 additions & 3 deletions src/pages/about.astro
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ const pageTitle = "About - Superfun Games Unblocked Games";
<Layout pageTitle={pageTitle}>
<div class="mx-auto max-w-[800px] p-6">
<h1 data-pagefind-body class="text-2xl font-bold mb-4">About Superfun Games</h1>
<p data-pagefind-body class="mb-4 text-sm text-gray-700">
<p data-pagefind-body class="mb-4 text-sm text-gray-700 dark:text-neutral-300">
Superfun Games is a free collection of classic Flash games and browser
games that run without Flash Player. Flash games run through the Ruffle
emulator (WebAssembly) directly in modern browsers including Chrome on
school Chromebooks. No downloads or plugins needed.
</p>
<p class="mb-4 text-sm text-gray-700">
<p class="mb-4 text-sm text-gray-700 dark:text-neutral-300">
All games are sourced from the Internet Archive. The site is a static
Astro build maintained at
<a class="text-blue-600 hover:underline" href="https://github.com/noahgsolomon/unblocked-games">github.com/noahgsolomon/unblocked-games</a>.
<a class="text-blue-600 dark:text-blue-400 hover:underline" href="https://github.com/noahgsolomon/unblocked-games">github.com/noahgsolomon/unblocked-games</a>.
</p>
</div>
</Layout>
2 changes: 1 addition & 1 deletion src/pages/dmca.astro
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const pageTitle = "DMCA Notice - Superfun Games Unblocked Games";
---

<Layout pageTitle={pageTitle}>
<div class="mx-auto max-w-[800px] p-6">
<div class="mx-auto max-w-[800px] p-6 dark:text-neutral-300">
<h1 class="text-2xl font-bold mb-4">DMCA Notice</h1>
<p class="mb-4">
If you believe that any copyright infringement exists on the Site, please
Expand Down
6 changes: 3 additions & 3 deletions src/pages/games/[slug].astro
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,13 @@ const seo = gamesMap[slug];
</div>
</div>
) : (
<p class="p-6 text-center text-gray-600">Game not available.</p>
<p class="p-6 text-center text-gray-600 dark:text-neutral-400">Game not available.</p>
)
}
{seo.guide && (
<section class="mx-auto max-w-[1200px] px-6 pb-6">
<h2 class="text-sm font-semibold mb-1 text-gray-700">How to play {seo.title}</h2>
<p data-pagefind-body class="text-sm text-gray-600">{seo.guide}</p>
<h2 class="text-sm font-semibold mb-1 text-gray-700 dark:text-neutral-300">How to play {seo.title}</h2>
<p data-pagefind-body class="text-sm text-gray-600 dark:text-neutral-400">{seo.guide}</p>
</section>
)}
</Layout>
Expand Down
16 changes: 8 additions & 8 deletions src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,26 @@ const popularGames = defaultGames.slice(0, POPULAR_COUNT);
<h1 data-pagefind-body class="text-2xl font-bold mb-2 m-5">
Play Unblocked Games on Your School Chromebook - Working 2026
</h1>
<p class="text-sm text-gray-600 mb-4 m-5">
<p class="text-sm text-gray-600 dark:text-neutral-400 mb-4 m-5">
Play classic Flash games and browser games on your school Chromebook without
needing any plugins or downloads. All games run directly in your Chrome
browser via Ruffle emulation - no Flash Player required. Relive your
favorite nostalgic games like <a
href="/games/happy-wheels"
class="text-blue-600 hover:underline">Happy Wheels</a
>, <a href="/games/plants-vs-zombies" class="text-blue-600 hover:underline"
class="text-blue-600 dark:text-blue-400 hover:underline">Happy Wheels</a
>, <a href="/games/plants-vs-zombies" class="text-blue-600 dark:text-blue-400 hover:underline"
>Plants vs Zombies</a
>, <a
href="/games/strike-force-heroes"
class="text-blue-600 hover:underline">Strike Force Heroes</a
class="text-blue-600 dark:text-blue-400 hover:underline">Strike Force Heroes</a
>, and more. Updated and working in 2026.
</p>

<div class="flex flex-wrap gap-2 m-5 mb-4 text-sm">
<h2 class="font-semibold text-gray-700">Popular:</h2>
<h2 class="font-semibold text-gray-700 dark:text-neutral-300">Popular:</h2>
{
popularGames.map((slug) => (
<a href={"/games/" + slug} class="text-blue-600 hover:underline"
<a href={"/games/" + slug} class="text-blue-600 dark:text-blue-400 hover:underline"
>{gamesMap[slug].title}</a
>
))
Expand All @@ -46,7 +46,7 @@ const popularGames = defaultGames.slice(0, POPULAR_COUNT);
class="grid grid-cols-[repeat(auto-fill,minmax(min(100%,280px),1fr))] gap-[10px]"
>
</div>
<hr class="mx-5 my-4 border-gray-400" />
<hr class="mx-5 my-4 border-gray-400 dark:border-neutral-700" />
</section>
<script is:inline define:vars={{ valid: defaultGames }}>
(function () {
Expand All @@ -70,7 +70,7 @@ const popularGames = defaultGames.slice(0, POPULAR_COUNT);
img.className =
"aspect-[3/2] h-auto w-full max-w-[300px] rounded-xl transition-transform duration-300 hover:scale-105 hover:shadow-lg active:scale-95";
var p = document.createElement("p");
p.className = "text-gray-800 transition-colors hover:text-black";
p.className = "text-gray-800 dark:text-neutral-200 transition-colors hover:text-black dark:hover:text-white";
p.textContent = g.t;
a.appendChild(img);
a.appendChild(p);
Expand Down
41 changes: 41 additions & 0 deletions src/styles/styles.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
@import "tailwindcss";

@custom-variant dark (&:where(.dark, .dark *));

/* flash.astro — dynamic states injected by JS */
.flash-loading {
background: #f0f0f0;
Expand All @@ -9,6 +11,11 @@
padding: 40px;
}

.dark .flash-loading {
background: #1f2937;
color: #d1d5db;
}

.flash-error {
background: #fff3f3;
border: 1px solid #ffcccc;
Expand All @@ -17,3 +24,37 @@
font-size: 1rem;
padding: 40px 24px;
}

.dark .flash-error {
background: #450a0a;
border-color: #7f1d1d;
color: #fca5a5;
}

/* pagefind component UI has no dark theme — override its vars ourselves */
/* ponytail: html.dark beats pagefind's :root regardless of stylesheet order */
html.dark {
color-scheme: dark;
--pf-text: #f5f5f5;
--pf-text-secondary: #d4d4d4;
--pf-text-muted: #a3a3a3;
--pf-background: #171717;
--pf-border: #404040;
--pf-border-focus: #737373;
--pf-skeleton: #262626;
--pf-skeleton-shine: #404040;
--pf-hover: #262626;
--pf-mark: #f5f5f5;
--pf-scroll-shadow: rgba(0, 0, 0, 0.4);
--pf-shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.4);
--pf-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.5);
--pf-shadow-lg: 0 16px 48px rgba(0, 0, 0, 0.6);
--pf-error-bg: #450a0a;
--pf-error-border: #7f1d1d;
--pf-error-text: #fca5a5;
--pf-error-text-secondary: #f87171;
--pf-outline-focus: #60a5fa;
--pf-modal-backdrop: rgba(0, 0, 0, 0.7);
--pf-icon-search: url("data:image/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.7549 11.255H11.9649L11.6849 10.985C12.6649 9.845 13.2549 8.365 13.2549 6.755C13.2549 3.165 10.3449 0.255005 6.75488 0.255005C3.16488 0.255005 0.254883 3.165 0.254883 6.755C0.254883 10.345 3.16488 13.255 6.75488 13.255C8.36488 13.255 9.84488 12.665 10.9849 11.685L11.2549 11.965V12.755L16.2549 17.745L17.7449 16.255L12.7549 11.255ZM6.75488 11.255C4.26488 11.255 2.25488 9.245 2.25488 6.755C2.25488 4.26501 4.26488 2.255 6.75488 2.255C9.24488 2.255 11.2549 4.26501 11.2549 6.755C11.2549 9.245 9.24488 11.255 6.75488 11.255Z' fill='%23f5f5f5'/%3E%3C/svg%3E%0A");
--pf-icon-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%23f5f5f5' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
Loading