mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-28 15:01:34 +08:00
Compare commits
1 Commits
codex-port
...
feat/easte
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f85501538 |
109
skills/creative/easter-egg-chrome-extension/SKILL.md
Normal file
109
skills/creative/easter-egg-chrome-extension/SKILL.md
Normal file
@@ -0,0 +1,109 @@
|
||||
---
|
||||
name: easter-egg-chrome-extension
|
||||
description: Build a Chrome extension that hides colored Easter eggs on every webpage. Eggs spawn at random intervals, can be clicked to collect, and each collection triggers a random page distortion effect that stacks permanently until refresh. Includes rare rabbit easter egg mechanic and persistent egg counter.
|
||||
tags: [chrome-extension, easter, fun, javascript, css]
|
||||
triggers:
|
||||
- easter egg chrome extension
|
||||
- easter egg hunt browser
|
||||
- page distortion extension
|
||||
- fun chrome plugin
|
||||
---
|
||||
|
||||
# Easter Egg Hunt Chrome Extension
|
||||
|
||||
## Overview
|
||||
A Chrome Manifest V3 extension that:
|
||||
- Hides **colored eggs** (🥚 with CSS hue-rotate filters) at random positions on every page
|
||||
- Eggs appear/disappear at random intervals (3-30s cycles)
|
||||
- Clicking an egg: confetti burst + **random page distortion effect that STACKS and persists till refresh**
|
||||
- **Rare rabbit** (~6% spawn rate): needs 5 clicks to catch (hops away each click), rewards +3 eggs and opens a special URL
|
||||
- Persistent egg counter via `chrome.storage.local`
|
||||
- Paw print cursor trail
|
||||
- Popup UI showing count, manual spawn button, reset
|
||||
|
||||
## File Structure
|
||||
```
|
||||
easter-egg-extension/
|
||||
├── manifest.json # Manifest V3, permissions: activeTab, scripting, storage
|
||||
├── content.js # Main egg hunt engine (content script)
|
||||
├── easter.css # Animations, HUD, confetti, trail styles
|
||||
├── background.js # Minimal service worker
|
||||
├── popup.html # Extension popup UI
|
||||
├── popup.js # Popup logic (count display, spawn/reset buttons)
|
||||
└── icons/
|
||||
├── egg48.png # Generated via PIL
|
||||
└── egg128.png
|
||||
```
|
||||
|
||||
## Build Steps
|
||||
|
||||
1. **Create directory**: `mkdir -p ~/easter-egg-extension/icons`
|
||||
|
||||
2. **Write all source files** from templates:
|
||||
- `templates/manifest.json`
|
||||
- `templates/content.js`
|
||||
- `templates/easter.css`
|
||||
- `templates/background.js`
|
||||
- `templates/popup.html`
|
||||
- `templates/popup.js`
|
||||
|
||||
3. **Generate icons** with Python PIL:
|
||||
```python
|
||||
from PIL import Image, ImageDraw
|
||||
def make_egg_icon(size, path):
|
||||
img = Image.new('RGBA', (size, size), (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(img)
|
||||
pad = size // 8
|
||||
cx, cy = size // 2, size // 2 + pad // 3
|
||||
rx = size // 2 - pad
|
||||
ry = int(rx * 1.25)
|
||||
top = cy - ry
|
||||
draw.ellipse([pad, top, size - pad, cy + ry], fill=(255, 235, 200, 255))
|
||||
stripe_y = cy - ry // 6
|
||||
stripe_h = size // 10
|
||||
for x in range(pad + 4, size - pad - 4):
|
||||
dx = (x - cx) / rx
|
||||
if abs(dx) < 0.92:
|
||||
band_color = [(168, 130, 234, 220), (236, 72, 153, 200), (102, 126, 234, 220)]
|
||||
idx = (x // (size // 6)) % 3
|
||||
draw.rectangle([x, stripe_y, x + 1, stripe_y + stripe_h], fill=band_color[idx])
|
||||
for sx, sy in [(cx - rx//3, cy - ry//3), (cx + rx//4, cy - ry//2)]:
|
||||
r = max(1, size // 30)
|
||||
draw.ellipse([sx-r, sy-r, sx+r, sy+r], fill=(255, 255, 255, 200))
|
||||
img.save(path)
|
||||
|
||||
make_egg_icon(48, 'icons/egg48.png')
|
||||
make_egg_icon(128, 'icons/egg128.png')
|
||||
```
|
||||
|
||||
4. **Install in Chrome**:
|
||||
- Navigate to `chrome://extensions`
|
||||
- Enable "Developer mode" (top right toggle)
|
||||
- Click "Load unpacked" → select the extension directory
|
||||
- Refresh open tabs with Cmd+R / Ctrl+R
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
### Egg Coloring
|
||||
Uses CSS `hue-rotate()` + `saturate()` + `brightness()` filters on the 🥚 emoji. 9 color presets: purple, blue, green, pink, red, yellow, sky, teal, orange.
|
||||
|
||||
### Stacking Effects
|
||||
Each effect injects a NEW `<style>` tag with a unique ID (`easter-fx-N`). Animation keyframes also get unique names (`easter-shake-N`). This means effects never clobber each other — they pile up into cumulative chaos. Only a page refresh clears them.
|
||||
|
||||
### 20 Page Effects
|
||||
Flip, earthquake, invert, shrink, tilt, blur, rainbow, Comic Sans, spin, bounce, film noir, stretch, mirror, wobble, max saturation, pixelate, drift, sepia, zoom, trippy.
|
||||
|
||||
### Rabbit Mechanic
|
||||
- ~6% spawn chance (weight 3 vs egg weight 50)
|
||||
- Needs 5 clicks; each click makes it hop to a random position
|
||||
- Gets double the normal visible time before auto-vanishing
|
||||
- Catching it: +3 eggs, confetti, opens configurable URL (default: Hermes Agent GitHub)
|
||||
|
||||
## Reload After Changes
|
||||
`chrome://extensions` → click 🔄 on the extension card → Cmd+R on open tabs
|
||||
|
||||
## Pitfalls
|
||||
- Content scripts don't auto-reload — must reload extension AND refresh tabs
|
||||
- `chrome.storage.local` needs "storage" permission in manifest
|
||||
- CSS filters on `html` element can conflict when multiple effects target it — using `!important` and injected stylesheets handles this
|
||||
- Emoji rendering varies by OS — colored egg filters look best on macOS/Windows, may look different on Linux
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate Easter egg icons for the Chrome extension."""
|
||||
import sys
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
def make_egg_icon(size, path):
|
||||
img = Image.new('RGBA', (size, size), (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(img)
|
||||
pad = size // 8
|
||||
cx, cy = size // 2, size // 2 + pad // 3
|
||||
rx = size // 2 - pad
|
||||
ry = int(rx * 1.25)
|
||||
top = cy - ry
|
||||
# Egg body
|
||||
draw.ellipse([pad, top, size - pad, cy + ry], fill=(255, 235, 200, 255))
|
||||
# Decorative stripe
|
||||
stripe_y = cy - ry // 6
|
||||
stripe_h = size // 10
|
||||
for x in range(pad + 4, size - pad - 4):
|
||||
dx = (x - cx) / rx
|
||||
if abs(dx) < 0.92:
|
||||
band_color = [(168, 130, 234, 220), (236, 72, 153, 200), (102, 126, 234, 220)]
|
||||
idx = (x // (size // 6)) % 3
|
||||
draw.rectangle([x, stripe_y, x + 1, stripe_y + stripe_h], fill=band_color[idx])
|
||||
# Sparkle dots
|
||||
for sx, sy in [(cx - rx//3, cy - ry//3), (cx + rx//4, cy - ry//2)]:
|
||||
r = max(1, size // 30)
|
||||
draw.ellipse([sx-r, sy-r, sx+r, sy+r], fill=(255, 255, 255, 200))
|
||||
img.save(path)
|
||||
print(f'Saved {path} ({size}x{size})')
|
||||
|
||||
if __name__ == "__main__":
|
||||
outdir = sys.argv[1] if len(sys.argv) > 1 else "icons"
|
||||
make_egg_icon(48, f'{outdir}/egg48.png')
|
||||
make_egg_icon(128, f'{outdir}/egg128.png')
|
||||
@@ -0,0 +1,4 @@
|
||||
// Service worker — mostly passthrough, keeps extension alive
|
||||
chrome.runtime.onInstalled.addListener(() => {
|
||||
console.log("🥚 Easter Egg Hunt installed! Happy Easter~!");
|
||||
});
|
||||
625
skills/creative/easter-egg-chrome-extension/templates/content.js
Normal file
625
skills/creative/easter-egg-chrome-extension/templates/content.js
Normal file
@@ -0,0 +1,625 @@
|
||||
(() => {
|
||||
"use strict";
|
||||
|
||||
// ── Config ──────────────────────────────────────────────
|
||||
const CFG = {
|
||||
// How long (ms) before first egg can appear after page load
|
||||
initialDelayMin: 3000,
|
||||
initialDelayMax: 12000,
|
||||
// How long an egg stays visible before vanishing on its own
|
||||
visibleMin: 5000,
|
||||
visibleMax: 15000,
|
||||
// Cooldown between eggs (after one vanishes or is collected)
|
||||
cooldownMin: 8000,
|
||||
cooldownMax: 30000,
|
||||
// Max eggs that can be on screen at once
|
||||
maxSimultaneous: 1,
|
||||
// Chance (0-1) that a second egg spawns alongside the first
|
||||
doubleSpawnChance: 0.1,
|
||||
};
|
||||
|
||||
// Spawnable collectibles — eggs get colored via CSS filter
|
||||
const SPAWN_TYPES = [
|
||||
// Colored eggs (vast majority)
|
||||
{ emoji: "🥚", type: "egg", weight: 50 },
|
||||
// Rare rabbit — requires multiple clicks, drops a special golden egg
|
||||
{ emoji: "🐰", type: "rabbit", weight: 3 },
|
||||
];
|
||||
|
||||
const RABBIT_CLICKS_NEEDED = 5; // clicks to catch the rabbit
|
||||
|
||||
// Hue rotations + saturations to color the eggs
|
||||
const EGG_COLORS = [
|
||||
{ filter: "hue-rotate(280deg) saturate(2) brightness(0.95)", name: "purple" },
|
||||
{ filter: "hue-rotate(180deg) saturate(2.5) brightness(0.95)", name: "blue" },
|
||||
{ filter: "hue-rotate(100deg) saturate(2) brightness(0.9)", name: "green" },
|
||||
{ filter: "hue-rotate(330deg) saturate(2.5) brightness(0.95)", name: "pink" },
|
||||
{ filter: "hue-rotate(0deg) saturate(3) brightness(0.85)", name: "red" },
|
||||
{ filter: "hue-rotate(60deg) saturate(2) brightness(0.95)", name: "yellow" },
|
||||
{ filter: "hue-rotate(200deg) saturate(1.8) brightness(1.05)", name: "sky" },
|
||||
{ filter: "hue-rotate(150deg) saturate(2.2) brightness(0.9)", name: "teal" },
|
||||
{ filter: "hue-rotate(20deg) saturate(2.5) brightness(0.9)", name: "orange" },
|
||||
];
|
||||
|
||||
function pickWeighted(items) {
|
||||
const total = items.reduce((s, i) => s + i.weight, 0);
|
||||
let r = Math.random() * total;
|
||||
for (const item of items) {
|
||||
r -= item.weight;
|
||||
if (r <= 0) return item;
|
||||
}
|
||||
return items[0];
|
||||
}
|
||||
|
||||
const COLLECT_MESSAGES = [
|
||||
"You found an egg~! 🎉",
|
||||
"Egg get!! ★",
|
||||
"Another one for the basket~!",
|
||||
"Eagle-eyed egg hunter! 👀",
|
||||
"Basket grows heavier... 🧺",
|
||||
"Easter magic~! ✨",
|
||||
"Egg-cellent find!",
|
||||
];
|
||||
|
||||
const RABBIT_HIT_MESSAGES = [
|
||||
"The bunny hops! Keep clicking!",
|
||||
"Almost got it~!",
|
||||
"It's getting tired...!",
|
||||
"One more hop!",
|
||||
];
|
||||
|
||||
const RABBIT_CAUGHT_MESSAGE = "🌟 The bunny dropped a GOLDEN EGG! 🌟";
|
||||
|
||||
// ── State ───────────────────────────────────────────────
|
||||
let eggsOnScreen = 0;
|
||||
let eggCount = 0;
|
||||
let scoreEl = null;
|
||||
let toastTimeout = null;
|
||||
let scheduledTimeout = null;
|
||||
let paused = false;
|
||||
|
||||
// ── Persistence via chrome.storage ──────────────────────
|
||||
function loadCount(cb) {
|
||||
if (chrome?.storage?.local) {
|
||||
chrome.storage.local.get("easterEggCount", (res) => {
|
||||
eggCount = res.easterEggCount || 0;
|
||||
cb();
|
||||
});
|
||||
} else {
|
||||
eggCount = parseInt(localStorage.getItem("easterEggCount") || "0", 10);
|
||||
cb();
|
||||
}
|
||||
}
|
||||
|
||||
function saveCount() {
|
||||
if (chrome?.storage?.local) {
|
||||
chrome.storage.local.set({ easterEggCount: eggCount });
|
||||
} else {
|
||||
localStorage.setItem("easterEggCount", String(eggCount));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Helpers ─────────────────────────────────────────────
|
||||
const rand = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
const pick = (arr) => arr[Math.floor(Math.random() * arr.length)];
|
||||
|
||||
// ── Score HUD ───────────────────────────────────────────
|
||||
function ensureScoreHUD() {
|
||||
if (scoreEl && document.body.contains(scoreEl)) {
|
||||
updateScoreText();
|
||||
return;
|
||||
}
|
||||
scoreEl = document.createElement("div");
|
||||
scoreEl.className = "easter-score";
|
||||
updateScoreText();
|
||||
document.body.appendChild(scoreEl);
|
||||
}
|
||||
|
||||
function updateScoreText() {
|
||||
if (!scoreEl) return;
|
||||
scoreEl.textContent = `🧺 ${eggCount} egg${eggCount !== 1 ? "s" : ""}`;
|
||||
}
|
||||
|
||||
function pulseScore() {
|
||||
if (!scoreEl) return;
|
||||
scoreEl.style.transform = "scale(1.3)";
|
||||
setTimeout(() => { scoreEl.style.transform = "scale(1)"; }, 300);
|
||||
}
|
||||
|
||||
// ── Toast notification ──────────────────────────────────
|
||||
function showToast(msg) {
|
||||
// Remove old toast if any
|
||||
const old = document.querySelector(".easter-toast");
|
||||
if (old) old.remove();
|
||||
clearTimeout(toastTimeout);
|
||||
|
||||
const t = document.createElement("div");
|
||||
t.className = "easter-toast";
|
||||
t.textContent = msg;
|
||||
Object.assign(t.style, {
|
||||
position: "fixed",
|
||||
bottom: "65px",
|
||||
right: "20px",
|
||||
background: "rgba(255,255,255,0.95)",
|
||||
color: "#5a3e7a",
|
||||
padding: "10px 20px",
|
||||
borderRadius: "14px",
|
||||
fontFamily: "'Segoe UI', system-ui, sans-serif",
|
||||
fontSize: "14px",
|
||||
fontWeight: "600",
|
||||
zIndex: "9999999",
|
||||
boxShadow: "0 4px 20px rgba(0,0,0,0.15)",
|
||||
border: "2px solid #fecfef",
|
||||
opacity: "0",
|
||||
transform: "translateY(10px)",
|
||||
transition: "all 0.3s ease",
|
||||
pointerEvents: "none",
|
||||
});
|
||||
document.body.appendChild(t);
|
||||
|
||||
// Animate in
|
||||
requestAnimationFrame(() => {
|
||||
t.style.opacity = "1";
|
||||
t.style.transform = "translateY(0)";
|
||||
});
|
||||
|
||||
toastTimeout = setTimeout(() => {
|
||||
t.style.opacity = "0";
|
||||
t.style.transform = "translateY(10px)";
|
||||
setTimeout(() => t.remove(), 300);
|
||||
}, 2200);
|
||||
}
|
||||
|
||||
// ── Confetti burst ──────────────────────────────────────
|
||||
function burstConfetti(x, y) {
|
||||
const container = document.createElement("div");
|
||||
container.className = "easter-confetti";
|
||||
document.body.appendChild(container);
|
||||
|
||||
const colors = [
|
||||
"#ff9a9e", "#fecfef", "#a8edea", "#fed6e3",
|
||||
"#667eea", "#ffd89b", "#b8f5b0", "#c3aed6",
|
||||
];
|
||||
|
||||
for (let i = 0; i < 25; i++) {
|
||||
const p = document.createElement("div");
|
||||
p.className = "confetti-piece";
|
||||
const angle = (Math.PI * 2 * i) / 25;
|
||||
const velocity = rand(60, 160);
|
||||
const endX = x + Math.cos(angle) * velocity;
|
||||
const endY = y + Math.sin(angle) * velocity;
|
||||
Object.assign(p.style, {
|
||||
left: x + "px",
|
||||
top: y + "px",
|
||||
background: pick(colors),
|
||||
width: rand(6, 12) + "px",
|
||||
height: rand(8, 16) + "px",
|
||||
borderRadius: rand(0, 1) ? "50%" : "2px",
|
||||
animationDuration: rand(800, 1400) + "ms",
|
||||
});
|
||||
// Override the keyframe with direct style animation
|
||||
p.animate(
|
||||
[
|
||||
{ transform: "translate(0,0) rotate(0deg)", opacity: 1 },
|
||||
{
|
||||
transform: `translate(${Math.cos(angle) * velocity}px, ${Math.sin(angle) * velocity + 80}px) rotate(${rand(360, 720)}deg)`,
|
||||
opacity: 0,
|
||||
},
|
||||
],
|
||||
{ duration: rand(800, 1400), easing: "cubic-bezier(0,.8,.5,1)", fill: "forwards" }
|
||||
);
|
||||
container.appendChild(p);
|
||||
}
|
||||
|
||||
setTimeout(() => container.remove(), 1600);
|
||||
}
|
||||
|
||||
// ── Spawn an egg ────────────────────────────────────────
|
||||
function spawnEgg() {
|
||||
if (paused || eggsOnScreen >= CFG.maxSimultaneous) {
|
||||
scheduleNext();
|
||||
return;
|
||||
}
|
||||
|
||||
// Pick a random position within the VISIBLE viewport
|
||||
// But avoid the very edges and the score HUD area
|
||||
const margin = 60;
|
||||
const vw = window.innerWidth;
|
||||
const vh = window.innerHeight;
|
||||
const x = rand(margin, vw - margin);
|
||||
const y = rand(margin, vh - margin - 80); // avoid bottom-right HUD
|
||||
|
||||
const spawn = pickWeighted(SPAWN_TYPES);
|
||||
const egg = document.createElement("div");
|
||||
egg.className = "easter-egg-hidden";
|
||||
egg.textContent = spawn.emoji;
|
||||
egg.setAttribute("aria-label", "Easter egg! Click to collect!");
|
||||
|
||||
// Color the egg with a random hue if it's an egg type
|
||||
if (spawn.type === "egg") {
|
||||
const color = pick(EGG_COLORS);
|
||||
egg.style.filter = color.filter;
|
||||
}
|
||||
|
||||
// Position fixed so it's in viewport no matter scroll
|
||||
Object.assign(egg.style, {
|
||||
position: "fixed",
|
||||
left: x + "px",
|
||||
top: y + "px",
|
||||
opacity: "0",
|
||||
transform: "scale(0)",
|
||||
transition: "opacity 0.6s ease, transform 0.4s ease",
|
||||
});
|
||||
|
||||
document.body.appendChild(egg);
|
||||
eggsOnScreen++;
|
||||
|
||||
// Fade in
|
||||
requestAnimationFrame(() => {
|
||||
egg.style.opacity = "1";
|
||||
egg.style.transform = "scale(1)";
|
||||
});
|
||||
|
||||
// Store type for behavior
|
||||
egg._spawnType = spawn.type;
|
||||
egg._rabbitClicks = 0;
|
||||
|
||||
// Click handler
|
||||
egg.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (spawn.type === "rabbit") {
|
||||
handleRabbitClick(egg, e.clientX, e.clientY);
|
||||
} else {
|
||||
collectEgg(egg, e.clientX, e.clientY);
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-vanish — rabbits get extra time since they need multiple clicks
|
||||
const vanishDelay = spawn.type === "rabbit"
|
||||
? rand(CFG.visibleMax, CFG.visibleMax * 2)
|
||||
: rand(CFG.visibleMin, CFG.visibleMax);
|
||||
const vanishTimer = setTimeout(() => {
|
||||
if (egg.parentNode) {
|
||||
egg.style.opacity = "0";
|
||||
egg.style.transform = "scale(0)";
|
||||
setTimeout(() => {
|
||||
egg.remove();
|
||||
eggsOnScreen = Math.max(0, eggsOnScreen - 1);
|
||||
}, 600);
|
||||
}
|
||||
scheduleNext();
|
||||
}, vanishDelay);
|
||||
|
||||
// Store timer on element so we can cancel on collect
|
||||
egg._vanishTimer = vanishTimer;
|
||||
}
|
||||
|
||||
// ── Rabbit multi-click handler ───────────────────────────
|
||||
function handleRabbitClick(el, cx, cy) {
|
||||
el._rabbitClicks++;
|
||||
|
||||
if (el._rabbitClicks >= RABBIT_CLICKS_NEEDED) {
|
||||
// Caught! Cancel vanish timer, do the golden egg reveal
|
||||
clearTimeout(el._vanishTimer);
|
||||
burstConfetti(cx, cy);
|
||||
showToast(RABBIT_CAUGHT_MESSAGE);
|
||||
|
||||
// Animate rabbit out
|
||||
el.style.transform = "scale(1.8) rotate(30deg)";
|
||||
el.style.opacity = "0";
|
||||
setTimeout(() => {
|
||||
el.remove();
|
||||
eggsOnScreen = Math.max(0, eggsOnScreen - 1);
|
||||
// Open Hermes Agent page
|
||||
window.open("https://github.com/nousresearch/hermes-agent", "_blank");
|
||||
}, 600);
|
||||
|
||||
// Bonus: count it as a special egg
|
||||
eggCount += 3;
|
||||
saveCount();
|
||||
updateScoreText();
|
||||
pulseScore();
|
||||
scheduleNext();
|
||||
return;
|
||||
}
|
||||
|
||||
// Not caught yet — hop to a new random position
|
||||
const margin = 60;
|
||||
const vw = window.innerWidth;
|
||||
const vh = window.innerHeight;
|
||||
const nx = rand(margin, vw - margin);
|
||||
const ny = rand(margin, vh - margin - 80);
|
||||
|
||||
// Quick hop animation
|
||||
el.style.transition = "left 0.2s ease, top 0.2s ease, transform 0.15s ease";
|
||||
el.style.transform = "scale(1.3) translateY(-15px)";
|
||||
setTimeout(() => {
|
||||
el.style.left = nx + "px";
|
||||
el.style.top = ny + "px";
|
||||
el.style.transform = "scale(1)";
|
||||
}, 150);
|
||||
|
||||
// Show progress toast
|
||||
const remaining = RABBIT_CLICKS_NEEDED - el._rabbitClicks;
|
||||
if (remaining <= 3) {
|
||||
showToast(RABBIT_HIT_MESSAGES[Math.min(el._rabbitClicks - 1, RABBIT_HIT_MESSAGES.length - 1)]);
|
||||
} else {
|
||||
showToast(`🐰 The bunny hops away! (${el._rabbitClicks}/${RABBIT_CLICKS_NEEDED})`);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Page distortion effects (STACK permanently till refresh) ──
|
||||
let fxCounter = 0;
|
||||
|
||||
function injectStyle(css) {
|
||||
const s = document.createElement("style");
|
||||
s.id = "easter-fx-" + (fxCounter++);
|
||||
s.textContent = css;
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
|
||||
const PAGE_EFFECTS = [
|
||||
{
|
||||
name: "🙃 Page flipped!",
|
||||
apply() {
|
||||
document.documentElement.style.transform = "rotate(180deg)";
|
||||
document.documentElement.style.transformOrigin = "center center";
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "🫨 Earthquake!!",
|
||||
apply() {
|
||||
injectStyle(`
|
||||
@keyframes easter-shake-${fxCounter} {
|
||||
0%, 100% { transform: translate(0,0) rotate(0deg); }
|
||||
10% { transform: translate(-8px, 4px) rotate(-1deg); }
|
||||
20% { transform: translate(6px, -6px) rotate(1.5deg); }
|
||||
30% { transform: translate(-4px, 8px) rotate(-0.5deg); }
|
||||
40% { transform: translate(8px, -2px) rotate(1deg); }
|
||||
50% { transform: translate(-6px, -4px) rotate(-1.5deg); }
|
||||
60% { transform: translate(4px, 6px) rotate(0.5deg); }
|
||||
70% { transform: translate(-8px, 2px) rotate(-1deg); }
|
||||
80% { transform: translate(6px, -8px) rotate(1.5deg); }
|
||||
90% { transform: translate(-4px, 4px) rotate(-0.5deg); }
|
||||
}
|
||||
body { animation: easter-shake-${fxCounter} 0.15s infinite !important; }
|
||||
`);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "🔮 Colors inverted!",
|
||||
apply() { injectStyle(`html { filter: invert(1) !important; }`); },
|
||||
},
|
||||
{
|
||||
name: "🔬 Honey I shrunk the page!",
|
||||
apply() { injectStyle(`body { transform: scale(0.4) !important; transform-origin: center top; }`); },
|
||||
},
|
||||
{
|
||||
name: "📐 Reality tilted!",
|
||||
apply() { injectStyle(`body { transform: perspective(600px) rotateY(15deg) rotateX(5deg) !important; transform-origin: center center; }`); },
|
||||
},
|
||||
{
|
||||
name: "😵💫 Who needs glasses?",
|
||||
apply() { injectStyle(`html { filter: blur(4px) !important; }`); },
|
||||
},
|
||||
{
|
||||
name: "🌈 Rainbow mode!",
|
||||
apply() {
|
||||
const n = fxCounter;
|
||||
injectStyle(`
|
||||
@keyframes easter-rainbow-${n} { 0% { filter: hue-rotate(0deg); } 100% { filter: hue-rotate(360deg); } }
|
||||
html { animation: easter-rainbow-${n} 0.8s linear infinite !important; }
|
||||
`);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "✏️ Comic Sans activated!",
|
||||
apply() { injectStyle(`* { font-family: "Comic Sans MS", "Comic Sans", cursive !important; }`); },
|
||||
},
|
||||
{
|
||||
name: "🌀 SPEEN!",
|
||||
apply() {
|
||||
const n = fxCounter;
|
||||
injectStyle(`
|
||||
@keyframes easter-spin-${n} { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
||||
html { animation: easter-spin-${n} 2s linear infinite !important; transform-origin: center center; }
|
||||
`);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "🏀 Boing boing boing!",
|
||||
apply() {
|
||||
const n = fxCounter;
|
||||
injectStyle(`
|
||||
@keyframes easter-bounce-${n} {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
25% { transform: translateY(-30px); }
|
||||
50% { transform: translateY(0); }
|
||||
75% { transform: translateY(-15px); }
|
||||
}
|
||||
body > * { animation: easter-bounce-${n} 0.6s ease infinite !important; }
|
||||
body > *:nth-child(odd) { animation-delay: 0.15s !important; }
|
||||
`);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "🎬 Film noir!",
|
||||
apply() { injectStyle(`html { filter: grayscale(1) contrast(1.3) !important; }`); },
|
||||
},
|
||||
{
|
||||
name: "🪗 S T R E T C H",
|
||||
apply() { injectStyle(`body { transform: scaleX(1.6) scaleY(0.6) !important; transform-origin: center top; }`); },
|
||||
},
|
||||
{
|
||||
name: "🪞 Mirror world!",
|
||||
apply() { injectStyle(`body { transform: scaleX(-1) !important; }`); },
|
||||
},
|
||||
{
|
||||
name: "🌊 Wobbly!",
|
||||
apply() {
|
||||
const n = fxCounter;
|
||||
injectStyle(`
|
||||
@keyframes easter-wobble-${n} {
|
||||
0%, 100% { transform: skewX(0deg) skewY(0deg); }
|
||||
25% { transform: skewX(4deg) skewY(2deg); }
|
||||
50% { transform: skewX(-3deg) skewY(-1deg); }
|
||||
75% { transform: skewX(2deg) skewY(3deg); }
|
||||
}
|
||||
body { animation: easter-wobble-${n} 0.5s ease-in-out infinite !important; }
|
||||
`);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "🎨 MAXIMUM COLOR!",
|
||||
apply() { injectStyle(`html { filter: saturate(8) brightness(1.1) !important; }`); },
|
||||
},
|
||||
{
|
||||
name: "👾 Pixel mode!",
|
||||
apply() {
|
||||
injectStyle(`html { image-rendering: pixelated !important; transform: scale(0.3); transform-origin: top left; width: 333.33vw; height: 333.33vh; }`);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "💨 Drifting away...",
|
||||
apply() {
|
||||
const n = fxCounter;
|
||||
injectStyle(`
|
||||
@keyframes easter-drift-${n} { from { transform: translateX(0) rotate(0deg); } to { transform: translateX(-60vw) rotate(-8deg); } }
|
||||
body { animation: easter-drift-${n} 4s ease-in-out forwards !important; }
|
||||
`);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "📜 Ye olde webpage!",
|
||||
apply() { injectStyle(`html { filter: sepia(1) brightness(0.9) !important; }`); },
|
||||
},
|
||||
{
|
||||
name: "🔍 ENHANCE!",
|
||||
apply() { injectStyle(`body { transform: scale(2.5) !important; transform-origin: center top; }`); },
|
||||
},
|
||||
{
|
||||
name: "🍄 Trippy!!",
|
||||
apply() {
|
||||
const n = fxCounter;
|
||||
injectStyle(`
|
||||
@keyframes easter-trip-${n} {
|
||||
0% { filter: hue-rotate(0deg) contrast(1) blur(0px); }
|
||||
25% { filter: hue-rotate(90deg) contrast(1.5) blur(1px); }
|
||||
50% { filter: hue-rotate(180deg) contrast(2) blur(2px); }
|
||||
75% { filter: hue-rotate(270deg) contrast(1.5) blur(1px); }
|
||||
100% { filter: hue-rotate(360deg) contrast(1) blur(0px); }
|
||||
}
|
||||
html { animation: easter-trip-${n} 1.5s ease-in-out infinite !important; }
|
||||
`);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
function triggerRandomEffect() {
|
||||
const effect = pick(PAGE_EFFECTS);
|
||||
effect.apply();
|
||||
showToast(effect.name);
|
||||
}
|
||||
|
||||
function collectEgg(egg, cx, cy) {
|
||||
clearTimeout(egg._vanishTimer);
|
||||
|
||||
eggCount++;
|
||||
saveCount();
|
||||
updateScoreText();
|
||||
pulseScore();
|
||||
burstConfetti(cx, cy);
|
||||
triggerRandomEffect();
|
||||
|
||||
egg.style.transform = "scale(1.6) rotate(20deg)";
|
||||
egg.style.opacity = "0";
|
||||
setTimeout(() => {
|
||||
egg.remove();
|
||||
eggsOnScreen = Math.max(0, eggsOnScreen - 1);
|
||||
}, 500);
|
||||
|
||||
scheduleNext();
|
||||
}
|
||||
|
||||
// ── Scheduling ──────────────────────────────────────────
|
||||
function scheduleNext() {
|
||||
clearTimeout(scheduledTimeout);
|
||||
const delay = rand(CFG.cooldownMin, CFG.cooldownMax);
|
||||
scheduledTimeout = setTimeout(() => {
|
||||
spawnEgg();
|
||||
if (Math.random() < CFG.doubleSpawnChance) {
|
||||
const maxBak = CFG.maxSimultaneous;
|
||||
CFG.maxSimultaneous = 2;
|
||||
setTimeout(() => spawnEgg(), rand(500, 2000));
|
||||
setTimeout(() => { CFG.maxSimultaneous = maxBak; }, 3000);
|
||||
}
|
||||
}, delay);
|
||||
}
|
||||
|
||||
// ── Bunny cursor trail ──────────────────────────────────
|
||||
let lastTrail = 0;
|
||||
function initCursorTrail() {
|
||||
document.addEventListener("mousemove", (e) => {
|
||||
const now = Date.now();
|
||||
if (now - lastTrail < 300) return;
|
||||
lastTrail = now;
|
||||
if (Math.random() > 0.2) return;
|
||||
|
||||
const trail = document.createElement("span");
|
||||
trail.className = "bunny-trail";
|
||||
trail.textContent = pick(["🐾", "·", "🐾", "✿", "·"]);
|
||||
Object.assign(trail.style, {
|
||||
left: e.clientX + "px",
|
||||
top: e.clientY + "px",
|
||||
});
|
||||
document.body.appendChild(trail);
|
||||
setTimeout(() => trail.remove(), 1000);
|
||||
});
|
||||
}
|
||||
|
||||
// ── Visibility handling (pause when tab hidden) ─────────
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
if (document.hidden) {
|
||||
paused = true;
|
||||
clearTimeout(scheduledTimeout);
|
||||
} else {
|
||||
paused = false;
|
||||
scheduleNext();
|
||||
}
|
||||
});
|
||||
|
||||
// ── Listen for messages from popup ──────────────────────
|
||||
if (chrome?.runtime?.onMessage) {
|
||||
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||
if (msg.type === "getCount") {
|
||||
sendResponse({ count: eggCount });
|
||||
} else if (msg.type === "resetCount") {
|
||||
eggCount = 0;
|
||||
saveCount();
|
||||
updateScoreText();
|
||||
sendResponse({ count: 0 });
|
||||
} else if (msg.type === "spawnNow") {
|
||||
spawnEgg();
|
||||
sendResponse({ ok: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ── Boot ────────────────────────────────────────────────
|
||||
function init() {
|
||||
loadCount(() => {
|
||||
ensureScoreHUD();
|
||||
initCursorTrail();
|
||||
const firstDelay = rand(CFG.initialDelayMin, CFG.initialDelayMax);
|
||||
scheduledTimeout = setTimeout(() => spawnEgg(), firstDelay);
|
||||
});
|
||||
}
|
||||
|
||||
if (document.body) {
|
||||
init();
|
||||
} else {
|
||||
document.addEventListener("DOMContentLoaded", init);
|
||||
}
|
||||
})();
|
||||
208
skills/creative/easter-egg-chrome-extension/templates/easter.css
Normal file
208
skills/creative/easter-egg-chrome-extension/templates/easter.css
Normal file
@@ -0,0 +1,208 @@
|
||||
/* ===== Easter Egg Hunt Styles ===== */
|
||||
|
||||
/* Hidden eggs scattered on page */
|
||||
.easter-egg-hidden {
|
||||
position: absolute;
|
||||
font-size: 28px;
|
||||
cursor: pointer;
|
||||
z-index: 99999;
|
||||
transition: transform 0.3s ease, opacity 0.3s ease;
|
||||
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));
|
||||
animation: egg-wobble 2s ease-in-out infinite;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.easter-egg-hidden:hover {
|
||||
transform: scale(1.4) rotate(10deg) !important;
|
||||
}
|
||||
|
||||
@keyframes egg-wobble {
|
||||
0%, 100% { transform: rotate(-3deg); }
|
||||
50% { transform: rotate(3deg); }
|
||||
}
|
||||
|
||||
/* Egg found celebration */
|
||||
.easter-egg-found {
|
||||
animation: egg-found 0.8s ease forwards;
|
||||
}
|
||||
|
||||
@keyframes egg-found {
|
||||
0% { transform: scale(1); opacity: 1; }
|
||||
30% { transform: scale(1.8) rotate(20deg); opacity: 1; }
|
||||
100% { transform: scale(0) rotate(360deg); opacity: 0; }
|
||||
}
|
||||
|
||||
/* Easter banner */
|
||||
.easter-banner {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 30%, #a8edea 60%, #fed6e3 100%);
|
||||
color: #5a3e7a;
|
||||
text-align: center;
|
||||
padding: 10px 20px;
|
||||
font-family: 'Segoe UI', system-ui, sans-serif;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
z-index: 999999;
|
||||
box-shadow: 0 3px 15px rgba(0,0,0,0.15);
|
||||
transform: translateY(-100%);
|
||||
animation: banner-slide 0.5s ease forwards;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.easter-banner .close-banner {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.2s;
|
||||
background: none;
|
||||
border: none;
|
||||
color: #5a3e7a;
|
||||
}
|
||||
|
||||
.easter-banner .close-banner:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@keyframes banner-slide {
|
||||
to { transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* Egg reveal popup */
|
||||
.easter-reveal {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) scale(0);
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
padding: 30px 40px;
|
||||
z-index: 9999999;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
||||
text-align: center;
|
||||
font-family: 'Segoe UI', system-ui, sans-serif;
|
||||
animation: reveal-pop 0.5s ease forwards;
|
||||
max-width: 420px;
|
||||
border: 3px solid #fecfef;
|
||||
}
|
||||
|
||||
.easter-reveal h2 {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 22px;
|
||||
color: #5a3e7a;
|
||||
}
|
||||
|
||||
.easter-reveal p {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 15px;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.easter-reveal .egg-emoji {
|
||||
font-size: 50px;
|
||||
display: block;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.easter-reveal button {
|
||||
background: linear-gradient(135deg, #a8edea, #fed6e3);
|
||||
border: none;
|
||||
padding: 10px 28px;
|
||||
border-radius: 25px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #5a3e7a;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.easter-reveal button:hover {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 4px 15px rgba(168, 237, 234, 0.5);
|
||||
}
|
||||
|
||||
@keyframes reveal-pop {
|
||||
to { transform: translate(-50%, -50%) scale(1); }
|
||||
}
|
||||
|
||||
.easter-overlay {
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(0,0,0,0.4);
|
||||
z-index: 9999998;
|
||||
animation: fade-in 0.3s ease forwards;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
/* Confetti burst */
|
||||
.easter-confetti {
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
pointer-events: none;
|
||||
z-index: 99999999;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.confetti-piece {
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 14px;
|
||||
border-radius: 2px;
|
||||
animation: confetti-fall linear forwards;
|
||||
}
|
||||
|
||||
@keyframes confetti-fall {
|
||||
0% { transform: translateY(-20px) rotate(0deg); opacity: 1; }
|
||||
100% { transform: translateY(100vh) rotate(720deg); opacity: 0; }
|
||||
}
|
||||
|
||||
/* Bunny cursor trail */
|
||||
.bunny-trail {
|
||||
position: fixed;
|
||||
pointer-events: none;
|
||||
z-index: 999999;
|
||||
font-size: 20px;
|
||||
animation: trail-fade 1s ease forwards;
|
||||
}
|
||||
|
||||
@keyframes trail-fade {
|
||||
0% { opacity: 0.8; transform: scale(1) translateY(0); }
|
||||
100% { opacity: 0; transform: scale(0.3) translateY(-30px); }
|
||||
}
|
||||
|
||||
/* Score counter */
|
||||
.easter-score {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
color: white;
|
||||
padding: 12px 20px;
|
||||
border-radius: 30px;
|
||||
font-family: 'Segoe UI', system-ui, sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
z-index: 999999;
|
||||
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
|
||||
animation: score-pop 0.5s ease;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@keyframes score-pop {
|
||||
0% { transform: scale(0); }
|
||||
70% { transform: scale(1.1); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Easter Egg Hunt 🥚",
|
||||
"version": "1.0.0",
|
||||
"description": "Generates site-relevant Easter eggs and transforms pages with festive surprises!",
|
||||
"permissions": ["activeTab", "scripting", "storage"],
|
||||
"action": {
|
||||
"default_popup": "popup.html",
|
||||
"default_icon": {
|
||||
"48": "icons/egg48.png",
|
||||
"128": "icons/egg128.png"
|
||||
}
|
||||
},
|
||||
"icons": {
|
||||
"48": "icons/egg48.png",
|
||||
"128": "icons/egg128.png"
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["<all_urls>"],
|
||||
"js": ["content.js"],
|
||||
"css": ["easter.css"],
|
||||
"run_at": "document_idle"
|
||||
}
|
||||
],
|
||||
"background": {
|
||||
"service_worker": "background.js"
|
||||
}
|
||||
}
|
||||
147
skills/creative/easter-egg-chrome-extension/templates/popup.html
Normal file
147
skills/creative/easter-egg-chrome-extension/templates/popup.html
Normal file
@@ -0,0 +1,147 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
width: 280px;
|
||||
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
|
||||
background: linear-gradient(165deg, #fef9ff 0%, #f0e6ff 50%, #e8faf8 100%);
|
||||
color: #3d2c5e;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
padding: 20px 16px 12px;
|
||||
}
|
||||
|
||||
.header .big-egg {
|
||||
font-size: 48px;
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
animation: bounce 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-8px); }
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(135deg, #667eea, #a855f7, #ec4899);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.header .subtitle {
|
||||
font-size: 12px;
|
||||
color: #9880b8;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.counter-section {
|
||||
text-align: center;
|
||||
padding: 16px;
|
||||
margin: 0 16px;
|
||||
background: rgba(255,255,255,0.7);
|
||||
border-radius: 16px;
|
||||
border: 1.5px solid rgba(168, 130, 234, 0.2);
|
||||
}
|
||||
|
||||
.counter-section .label {
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1.5px;
|
||||
color: #9880b8;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.counter-section .count {
|
||||
font-size: 40px;
|
||||
font-weight: 800;
|
||||
background: linear-gradient(135deg, #667eea, #a855f7);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.counter-section .eggs-label {
|
||||
font-size: 13px;
|
||||
color: #7c6a9e;
|
||||
}
|
||||
|
||||
.actions {
|
||||
padding: 14px 16px 16px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.actions button {
|
||||
flex: 1;
|
||||
padding: 10px 8px;
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn-spawn {
|
||||
background: linear-gradient(135deg, #a8edea, #fed6e3);
|
||||
color: #5a3e7a;
|
||||
}
|
||||
|
||||
.btn-spawn:hover {
|
||||
transform: scale(1.03);
|
||||
box-shadow: 0 3px 12px rgba(168, 237, 234, 0.5);
|
||||
}
|
||||
|
||||
.btn-reset {
|
||||
background: rgba(0,0,0,0.05);
|
||||
color: #9880b8;
|
||||
}
|
||||
|
||||
.btn-reset:hover {
|
||||
background: rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
padding: 0 16px 14px;
|
||||
font-size: 11px;
|
||||
color: #b8a5d4;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<span class="big-egg">🥚</span>
|
||||
<h1>Easter Egg Hunt</h1>
|
||||
<div class="subtitle">eggs hide on every page you visit ✨</div>
|
||||
</div>
|
||||
|
||||
<div class="counter-section">
|
||||
<div class="label">collected</div>
|
||||
<div class="count" id="count">0</div>
|
||||
<div class="eggs-label">eggs found</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button class="btn-spawn" id="spawnBtn">🥚 Spawn Egg Now</button>
|
||||
<button class="btn-reset" id="resetBtn">Reset</button>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
🐰 Happy Easter! Eggs appear randomly~
|
||||
</div>
|
||||
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,43 @@
|
||||
const countEl = document.getElementById("count");
|
||||
const spawnBtn = document.getElementById("spawnBtn");
|
||||
const resetBtn = document.getElementById("resetBtn");
|
||||
|
||||
// Get current egg count from content script
|
||||
function refreshCount() {
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
||||
if (!tabs[0]?.id) return;
|
||||
chrome.tabs.sendMessage(tabs[0].id, { type: "getCount" }, (res) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
// Content script not loaded on this page — read from storage
|
||||
chrome.storage.local.get("easterEggCount", (data) => {
|
||||
countEl.textContent = data.easterEggCount || 0;
|
||||
});
|
||||
return;
|
||||
}
|
||||
countEl.textContent = res?.count ?? 0;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
spawnBtn.addEventListener("click", () => {
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
||||
if (!tabs[0]?.id) return;
|
||||
chrome.tabs.sendMessage(tabs[0].id, { type: "spawnNow" });
|
||||
spawnBtn.textContent = "🐣 Spawned!";
|
||||
setTimeout(() => { spawnBtn.textContent = "🥚 Spawn Egg Now"; }, 1200);
|
||||
});
|
||||
});
|
||||
|
||||
resetBtn.addEventListener("click", () => {
|
||||
if (!confirm("Reset your egg count to 0?")) return;
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
||||
if (!tabs[0]?.id) return;
|
||||
chrome.tabs.sendMessage(tabs[0].id, { type: "resetCount" }, () => {
|
||||
countEl.textContent = "0";
|
||||
});
|
||||
});
|
||||
chrome.storage.local.set({ easterEggCount: 0 });
|
||||
countEl.textContent = "0";
|
||||
});
|
||||
|
||||
refreshCount();
|
||||
Reference in New Issue
Block a user