A sleek theme picker for Blooket. Press Alt+T (or Cmd+T on Mac) to open.
// ==UserScript==
// @name Blooket Themer
// @namespace https://greasyfork.org/
// @version 1.0.4
// @description A sleek theme picker for Blooket. Press Alt+T (or Cmd+T on Mac) to open.
// @author You
// @match https://*.blooket.com/*
// @run-at document-end
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
(function () {
'use strict';
// ─── THEMES ────────────────────────────────────────────────────────────────
// Each theme has:
// vars — CSS vars for the GUI panel itself
// navbar — Blooket top navbar background
// bg — page background color
// bgPattern — blooket background pattern tint (usually same as bg but lighter)
// btn — primary button color
// btnText — button text color
// text — general page text
// surface — card/surface background
const THEMES = [
{
id: 'default',
name: 'Default',
emoji: '⬜',
description: 'Original Blooket look',
navbar: null, // null = remove override (restore original)
bg: null,
btn: null,
btnText: null,
text: null,
surface: null,
vars: {
'--bt-primary': '#4f46e5', '--bt-secondary': '#7c3aed',
'--bt-accent': '#f59e0b', '--bt-bg': '#f8fafc',
'--bt-surface': '#ffffff', '--bt-text': '#1e293b',
'--bt-border': '#e2e8f0', '--bt-shadow': 'rgba(79,70,229,0.15)',
},
},
{
id: 'ocean',
name: 'Ocean Blue',
emoji: '🌊',
description: 'Deep & calm blues',
navbar: '#0369a1',
bg: '#0f172a',
btn: '#0ea5e9',
btnText: '#ffffff',
text: '#e0f2fe',
surface: '#1e293b',
vars: {
'--bt-primary': '#0ea5e9', '--bt-secondary': '#0284c7',
'--bt-accent': '#38bdf8', '--bt-bg': '#0f172a',
'--bt-surface': '#1e293b', '--bt-text': '#e0f2fe',
'--bt-border': '#334155', '--bt-shadow': 'rgba(14,165,233,0.25)',
},
},
{
id: 'crimson',
name: 'Crimson',
emoji: '🔴',
description: 'Bold, fiery reds',
navbar: '#b91c1c',
bg: '#fef2f2',
btn: '#ef4444',
btnText: '#ffffff',
text: '#450a0a',
surface: '#ffffff',
vars: {
'--bt-primary': '#ef4444', '--bt-secondary': '#dc2626',
'--bt-accent': '#fca5a5', '--bt-bg': '#fef2f2',
'--bt-surface': '#ffffff', '--bt-text': '#450a0a',
'--bt-border': '#fecaca', '--bt-shadow': 'rgba(239,68,68,0.2)',
},
},
{
id: 'shadow',
name: 'Red & Black',
emoji: '🖤',
description: 'Dark with red accents',
navbar: '#1a0000',
bg: '#0a0a0a',
btn: '#e11d48',
btnText: '#ffffff',
text: '#fafafa',
surface: '#171717',
vars: {
'--bt-primary': '#e11d48', '--bt-secondary': '#be123c',
'--bt-accent': '#fb7185', '--bt-bg': '#0a0a0a',
'--bt-surface': '#171717', '--bt-text': '#fafafa',
'--bt-border': '#3f0d1a', '--bt-shadow': 'rgba(225,29,72,0.3)',
},
},
{
id: 'forest',
name: 'Forest',
emoji: '🌿',
description: 'Earthy greens',
navbar: '#166534',
bg: '#f0fdf4',
btn: '#16a34a',
btnText: '#ffffff',
text: '#14532d',
surface: '#ffffff',
vars: {
'--bt-primary': '#16a34a', '--bt-secondary': '#15803d',
'--bt-accent': '#86efac', '--bt-bg': '#f0fdf4',
'--bt-surface': '#ffffff', '--bt-text': '#14532d',
'--bt-border': '#bbf7d0', '--bt-shadow': 'rgba(22,163,74,0.2)',
},
},
{
id: 'sunset',
name: 'Sunset',
emoji: '🌅',
description: 'Warm oranges & pinks',
navbar: '#9a3412',
bg: '#1c0a00',
btn: '#f97316',
btnText: '#ffffff',
text: '#fef3c7',
surface: '#2d1206',
vars: {
'--bt-primary': '#f97316', '--bt-secondary': '#ec4899',
'--bt-accent': '#fbbf24', '--bt-bg': '#1c0a00',
'--bt-surface': '#2d1206', '--bt-text': '#fef3c7',
'--bt-border': '#7c2d12', '--bt-shadow': 'rgba(249,115,22,0.3)',
},
},
{
id: 'candy',
name: 'Candy',
emoji: '🍬',
description: 'Sweet pastel vibes',
navbar: '#a21caf',
bg: '#fdf4ff',
btn: '#d946ef',
btnText: '#ffffff',
text: '#4a044e',
surface: '#ffffff',
vars: {
'--bt-primary': '#d946ef', '--bt-secondary': '#a855f7',
'--bt-accent': '#f0abfc', '--bt-bg': '#fdf4ff',
'--bt-surface': '#ffffff', '--bt-text': '#4a044e',
'--bt-border': '#f5d0fe', '--bt-shadow': 'rgba(217,70,239,0.2)',
},
},
{
id: 'midnight',
name: 'Midnight',
emoji: '🌙',
description: 'Pure dark mode',
navbar: '#1e1b4b',
bg: '#030712',
btn: '#6366f1',
btnText: '#ffffff',
text: '#f9fafb',
surface: '#111827',
vars: {
'--bt-primary': '#6366f1', '--bt-secondary': '#8b5cf6',
'--bt-accent': '#a5b4fc', '--bt-bg': '#030712',
'--bt-surface': '#111827', '--bt-text': '#f9fafb',
'--bt-border': '#1f2937', '--bt-shadow': 'rgba(99,102,241,0.25)',
},
},
];
// ─── CSS INJECTION ──────────────────────────────────────────────────────────
GM_addStyle(`
:root {
--bt-primary: #4f46e5;
--bt-secondary: #7c3aed;
--bt-accent: #f59e0b;
--bt-bg: #f8fafc;
--bt-surface: #ffffff;
--bt-text: #1e293b;
--bt-border: #e2e8f0;
--bt-shadow: rgba(79,70,229,0.15);
}
/* ── GUI Panel ── */
#bt-panel {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0.92);
z-index: 999999;
width: 360px;
background: var(--bt-surface);
border: 1.5px solid var(--bt-border);
border-radius: 18px;
box-shadow: 0 24px 60px var(--bt-shadow), 0 4px 16px rgba(0,0,0,0.12);
font-family: 'Segoe UI', 'SF Pro Display', system-ui, sans-serif;
color: var(--bt-text);
opacity: 0;
pointer-events: none;
transition: opacity 0.22s cubic-bezier(.4,0,.2,1),
transform 0.22s cubic-bezier(.4,0,.2,1);
overflow: hidden;
}
#bt-panel.bt-open {
opacity: 1;
pointer-events: all;
transform: translate(-50%, -50%) scale(1);
}
/* Header */
#bt-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 18px 20px 14px;
border-bottom: 1.5px solid var(--bt-border);
background: linear-gradient(135deg, var(--bt-primary), var(--bt-secondary));
}
#bt-header-left {
display: flex;
align-items: center;
gap: 10px;
}
#bt-logo {
width: 30px;
height: 30px;
border-radius: 8px;
background: rgba(255,255,255,0.25);
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
}
#bt-title {
font-size: 15px;
font-weight: 700;
color: #fff;
letter-spacing: -0.3px;
}
#bt-subtitle {
font-size: 11px;
color: rgba(255,255,255,0.75);
margin-top: 1px;
}
#bt-close {
width: 28px;
height: 28px;
border-radius: 50%;
background: rgba(255,255,255,0.18);
border: none;
cursor: pointer;
color: #fff;
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
line-height: 1;
transition: background 0.15s;
}
#bt-close:hover { background: rgba(255,255,255,0.32); }
/* Theme grid */
#bt-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
padding: 16px;
}
.bt-theme-card {
border-radius: 12px;
border: 2px solid var(--bt-border);
padding: 12px;
cursor: pointer;
background: var(--bt-bg);
transition: border-color 0.15s, transform 0.12s, box-shadow 0.15s;
position: relative;
overflow: hidden;
}
.bt-theme-card:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px var(--bt-shadow);
}
.bt-theme-card.bt-active {
border-color: var(--bt-primary);
box-shadow: 0 0 0 3px var(--bt-shadow);
}
.bt-theme-card.bt-active::after {
content: '✓';
position: absolute;
top: 6px;
right: 8px;
font-size: 11px;
font-weight: 800;
color: var(--bt-primary);
}
.bt-card-swatch {
height: 28px;
border-radius: 7px;
margin-bottom: 8px;
}
.bt-card-name {
font-size: 12.5px;
font-weight: 700;
color: var(--bt-text);
}
.bt-card-desc {
font-size: 10.5px;
color: var(--bt-text);
opacity: 0.55;
margin-top: 2px;
}
.bt-card-emoji {
font-size: 14px;
margin-bottom: 4px;
display: block;
}
/* Footer */
#bt-footer {
padding: 10px 16px 14px;
text-align: center;
font-size: 10.5px;
color: var(--bt-text);
opacity: 0.4;
border-top: 1.5px solid var(--bt-border);
}
/* Overlay */
#bt-overlay {
position: fixed;
inset: 0;
z-index: 999998;
background: rgba(0,0,0,0.35);
backdrop-filter: blur(2px);
opacity: 0;
pointer-events: none;
transition: opacity 0.22s;
}
#bt-overlay.bt-open {
opacity: 1;
pointer-events: all;
}
/* Toast */
#bt-toast {
position: fixed;
bottom: 28px;
left: 50%;
transform: translateX(-50%) translateY(16px);
z-index: 9999999;
background: var(--bt-primary);
color: #fff;
padding: 9px 20px;
border-radius: 999px;
font-size: 13px;
font-weight: 600;
font-family: 'Segoe UI', system-ui, sans-serif;
box-shadow: 0 4px 18px var(--bt-shadow);
opacity: 0;
pointer-events: none;
transition: opacity 0.2s, transform 0.2s;
}
#bt-toast.bt-show {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
`);
// ─── BUILD UI ───────────────────────────────────────────────────────────────
const overlay = document.createElement('div');
overlay.id = 'bt-overlay';
document.body.appendChild(overlay);
const panel = document.createElement('div');
panel.id = 'bt-panel';
panel.innerHTML = `
<div id="bt-header">
<div id="bt-header-left">
<div id="bt-logo">🎨</div>
<div>
<div id="bt-title">Blooket Themer</div>
<div id="bt-subtitle">Pick your vibe</div>
</div>
</div>
<button id="bt-close" title="Close (Alt+T)">✕</button>
</div>
<div id="bt-grid"></div>
<div id="bt-footer">Alt+B / ⌘+B to toggle · Themes save automatically</div>
`;
document.body.appendChild(panel);
const toast = document.createElement('div');
toast.id = 'bt-toast';
document.body.appendChild(toast);
// ─── POPULATE GRID ─────────────────────────────────────────────────────────
const grid = panel.querySelector('#bt-grid');
let activeId = GM_getValue('bt-theme', 'default');
function buildGrid() {
grid.innerHTML = '';
THEMES.forEach(theme => {
const card = document.createElement('div');
card.className = 'bt-theme-card' + (theme.id === activeId ? ' bt-active' : '');
card.dataset.id = theme.id;
// Build a mini gradient swatch from the theme vars
const { '--bt-primary': p, '--bt-secondary': s, '--bt-accent': a } = theme.vars;
card.innerHTML = `
<span class="bt-card-emoji">${theme.emoji}</span>
<div class="bt-card-swatch" style="background: linear-gradient(120deg, ${p}, ${s}, ${a});"></div>
<div class="bt-card-name">${theme.name}</div>
<div class="bt-card-desc">${theme.description}</div>
`;
card.addEventListener('click', () => applyTheme(theme.id));
grid.appendChild(card);
});
}
// ─── APPLY THEME ───────────────────────────────────────────────────────────
function applyTheme(id) {
const theme = THEMES.find(t => t.id === id);
if (!theme) return;
activeId = id;
GM_setValue('bt-theme', id);
const root = document.documentElement;
Object.entries(theme.vars).forEach(([k, v]) => root.style.setProperty(k, v));
buildGrid();
showToast(`${theme.emoji} ${theme.name} applied!`);
}
// ─── TOAST ─────────────────────────────────────────────────────────────────
let toastTimer;
function showToast(msg) {
toast.textContent = msg;
toast.classList.add('bt-show');
clearTimeout(toastTimer);
toastTimer = setTimeout(() => toast.classList.remove('bt-show'), 2200);
}
// ─── OPEN / CLOSE ──────────────────────────────────────────────────────────
function openPanel() {
panel.classList.add('bt-open');
overlay.classList.add('bt-open');
}
function closePanel() {
panel.classList.remove('bt-open');
overlay.classList.remove('bt-open');
}
function togglePanel() {
panel.classList.contains('bt-open') ? closePanel() : openPanel();
}
panel.querySelector('#bt-close').addEventListener('click', closePanel);
overlay.addEventListener('click', closePanel);
// ─── KEYBOARD SHORTCUT ─────────────────────────────────────────────────────
window.addEventListener('keydown', (e) => {
const isMac = navigator.platform.toUpperCase().includes('MAC');
const trigger = isMac
? (e.metaKey && !e.altKey && (e.key === 'b' || e.key === 'B'))
: (e.altKey && (e.key === 'b' || e.key === 'B'));
if (trigger) {
e.preventDefault();
e.stopPropagation();
togglePanel();
}
}, true); // useCapture=true so it fires before React can swallow it
// ─── INIT ───────────────────────────────────────────────────────────────────
buildGrid();
applyTheme(activeId); // restore saved theme on load
})();