easter-egg-quest 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +115 -0
- package/dist/easter-egg-quest.es.js +4997 -0
- package/dist/easter-egg-quest.umd.js +1 -0
- package/dist/three.module-BYIS7JD4.mjs +30588 -0
- package/dist/types/GameController.d.ts +67 -0
- package/dist/types/config.d.ts +5 -0
- package/dist/types/core/EventBus.d.ts +11 -0
- package/dist/types/core/StateMachine.d.ts +22 -0
- package/dist/types/entry/HiddenEntry.d.ts +43 -0
- package/dist/types/index.d.ts +30 -0
- package/dist/types/input/InputTracker.d.ts +55 -0
- package/dist/types/narrative/Script.d.ts +7 -0
- package/dist/types/rendering/FallbackRenderer.d.ts +27 -0
- package/dist/types/rendering/HUDRenderer.d.ts +28 -0
- package/dist/types/rendering/NarrativeRenderer.d.ts +29 -0
- package/dist/types/rendering/OverlayManager.d.ts +20 -0
- package/dist/types/rendering/PageBreather.d.ts +29 -0
- package/dist/types/rendering/PageReactor.d.ts +18 -0
- package/dist/types/rendering/ResultsRenderer.d.ts +41 -0
- package/dist/types/rendering/ShrineRenderer.d.ts +32 -0
- package/dist/types/rendering/ThreeRenderer.d.ts +147 -0
- package/dist/types/scoring/Persistence.d.ts +26 -0
- package/dist/types/scoring/ScoringEngine.d.ts +37 -0
- package/dist/types/stages/MotionStage.d.ts +34 -0
- package/dist/types/stages/RhythmStage.d.ts +42 -0
- package/dist/types/stages/StillnessStage.d.ts +32 -0
- package/dist/types/types.d.ts +202 -0
- package/dist/types/utils/helpers.d.ts +29 -0
- package/package.json +49 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { FinalScore, StageResult, InputSnapshot } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Scoring engine — tracks all competition metrics and computes the
|
|
4
|
+
* predictability score at game end.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ScoringEngine {
|
|
7
|
+
private _startTime;
|
|
8
|
+
private _entryTime;
|
|
9
|
+
private _stageResults;
|
|
10
|
+
/** Raw activity counters for predictability analysis. */
|
|
11
|
+
private _extraClicks;
|
|
12
|
+
private _chaoticMoveFrames;
|
|
13
|
+
private _hesitations;
|
|
14
|
+
private _failedAttempts;
|
|
15
|
+
private _overcorrections;
|
|
16
|
+
start(): void;
|
|
17
|
+
recordEntryFound(): void;
|
|
18
|
+
recordStageResult(result: StageResult): void;
|
|
19
|
+
/** Called each frame to analyse input patterns for predictability. */
|
|
20
|
+
analyseFrame(snap: InputSnapshot): void;
|
|
21
|
+
/** Increment extra clicks (clicks outside meaningful interaction). */
|
|
22
|
+
recordExtraClick(): void;
|
|
23
|
+
/** Record hesitation events (start-stop-start within 600 ms). */
|
|
24
|
+
recordHesitation(): void;
|
|
25
|
+
recordOvercorrection(): void;
|
|
26
|
+
computeFinalScore(inputStats?: {
|
|
27
|
+
totalClicks: number;
|
|
28
|
+
totalScrolls: number;
|
|
29
|
+
totalKeyPresses: number;
|
|
30
|
+
totalDistance: number;
|
|
31
|
+
maxVelocity: number;
|
|
32
|
+
}): FinalScore;
|
|
33
|
+
private _computeBehaviorProfile;
|
|
34
|
+
private _pickTitle;
|
|
35
|
+
private _computePredictability;
|
|
36
|
+
private _labelForScore;
|
|
37
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ResolvedConfig, NarrativeScript, StageResult, StageStatus, StageHandler } from '../types';
|
|
2
|
+
import type { InputTracker } from '../input/InputTracker';
|
|
3
|
+
import type { EventBus } from '../core/EventBus';
|
|
4
|
+
/**
|
|
5
|
+
* Stage 2 — Motion
|
|
6
|
+
*
|
|
7
|
+
* The user must keep moving continuously for the configured duration.
|
|
8
|
+
* Stopping for too long resets progress. Continuous motion creates
|
|
9
|
+
* luminous trails and eventually crystallizes the second egg.
|
|
10
|
+
*/
|
|
11
|
+
export declare class MotionStage implements StageHandler {
|
|
12
|
+
private config;
|
|
13
|
+
private script;
|
|
14
|
+
private input;
|
|
15
|
+
private bus;
|
|
16
|
+
private _status;
|
|
17
|
+
private _startTime;
|
|
18
|
+
private _fades;
|
|
19
|
+
private _attempts;
|
|
20
|
+
private _movingAccum;
|
|
21
|
+
private _lastUpdateTime;
|
|
22
|
+
private _narrativeIndex;
|
|
23
|
+
private _lastFadeNarrativeTime;
|
|
24
|
+
private _introPlayed;
|
|
25
|
+
/** How long without movement before the user "fades" (ms). */
|
|
26
|
+
private readonly FADE_THRESHOLD_MS;
|
|
27
|
+
constructor(config: ResolvedConfig, script: NarrativeScript, input: InputTracker, bus: EventBus);
|
|
28
|
+
start(): Promise<void>;
|
|
29
|
+
update(_dt: number): StageStatus;
|
|
30
|
+
cleanup(): void;
|
|
31
|
+
getResult(): StageResult;
|
|
32
|
+
private _handleFade;
|
|
33
|
+
private _wait;
|
|
34
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ResolvedConfig, NarrativeScript, StageResult, StageStatus, StageHandler } from '../types';
|
|
2
|
+
import type { InputTracker } from '../input/InputTracker';
|
|
3
|
+
import type { EventBus } from '../core/EventBus';
|
|
4
|
+
/**
|
|
5
|
+
* Stage 3 — Rhythm
|
|
6
|
+
*
|
|
7
|
+
* Synthesizes stillness and motion. The user must alternate between
|
|
8
|
+
* moving and pausing in a natural rhythm. Neither pure stillness nor
|
|
9
|
+
* pure constant motion works. The ideal cycle is ~3–4 s move, ~2–3 s pause.
|
|
10
|
+
*
|
|
11
|
+
* When the user's rhythm approaches coherence, the page "breathes" in sync.
|
|
12
|
+
*/
|
|
13
|
+
export declare class RhythmStage implements StageHandler {
|
|
14
|
+
private config;
|
|
15
|
+
private script;
|
|
16
|
+
private input;
|
|
17
|
+
private bus;
|
|
18
|
+
private _status;
|
|
19
|
+
private _startTime;
|
|
20
|
+
private _attempts;
|
|
21
|
+
private _narrativeIndex;
|
|
22
|
+
private _lastNarrativeTime;
|
|
23
|
+
private _introPlayed;
|
|
24
|
+
private _goodCycles;
|
|
25
|
+
private _totalCycles;
|
|
26
|
+
private _lastPhaseCount;
|
|
27
|
+
private _bestAccuracy;
|
|
28
|
+
/** Ideal move duration range (ms). */
|
|
29
|
+
private readonly IDEAL_MOVE_MIN;
|
|
30
|
+
private readonly IDEAL_MOVE_MAX;
|
|
31
|
+
/** Ideal pause duration range (ms). */
|
|
32
|
+
private readonly IDEAL_PAUSE_MIN;
|
|
33
|
+
private readonly IDEAL_PAUSE_MAX;
|
|
34
|
+
constructor(config: ResolvedConfig, script: NarrativeScript, input: InputTracker, bus: EventBus);
|
|
35
|
+
start(): Promise<void>;
|
|
36
|
+
update(_dt: number): StageStatus;
|
|
37
|
+
cleanup(): void;
|
|
38
|
+
getResult(): StageResult;
|
|
39
|
+
private _evaluateCycle;
|
|
40
|
+
private _showReaction;
|
|
41
|
+
private _wait;
|
|
42
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ResolvedConfig, NarrativeScript, StageResult, StageStatus, StageHandler } from '../types';
|
|
2
|
+
import type { InputTracker } from '../input/InputTracker';
|
|
3
|
+
import type { EventBus } from '../core/EventBus';
|
|
4
|
+
/**
|
|
5
|
+
* Stage 1 — Stillness
|
|
6
|
+
*
|
|
7
|
+
* The user must remain completely still for the configured duration.
|
|
8
|
+
* Any mouse movement, click, scroll, or keypress resets progress.
|
|
9
|
+
* As the user approaches success, we emit visual-softening events.
|
|
10
|
+
* On success, emits 'egg:reveal' with egg index 0.
|
|
11
|
+
*/
|
|
12
|
+
export declare class StillnessStage implements StageHandler {
|
|
13
|
+
private config;
|
|
14
|
+
private script;
|
|
15
|
+
private input;
|
|
16
|
+
private bus;
|
|
17
|
+
private _status;
|
|
18
|
+
private _startTime;
|
|
19
|
+
private _breaks;
|
|
20
|
+
private _attempts;
|
|
21
|
+
private _lastBreakNarrativeTime;
|
|
22
|
+
private _narrativeIndex;
|
|
23
|
+
private _bestProgress;
|
|
24
|
+
private _introPlayed;
|
|
25
|
+
constructor(config: ResolvedConfig, script: NarrativeScript, input: InputTracker, bus: EventBus);
|
|
26
|
+
start(): Promise<void>;
|
|
27
|
+
update(_dt: number): StageStatus;
|
|
28
|
+
cleanup(): void;
|
|
29
|
+
getResult(): StageResult;
|
|
30
|
+
private _handleBreak;
|
|
31
|
+
private _wait;
|
|
32
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
export interface EasterEggQuestConfig {
|
|
2
|
+
theme?: 'auto' | 'light' | 'dark' | ThemeConfig;
|
|
3
|
+
hiddenEntry?: HiddenEntryConfig;
|
|
4
|
+
hud?: boolean | HUDConfig;
|
|
5
|
+
shrine?: boolean | ShrineConfig;
|
|
6
|
+
sounds?: boolean;
|
|
7
|
+
stageDurations?: StageDurationsConfig;
|
|
8
|
+
narrative?: Partial<NarrativeScript>;
|
|
9
|
+
renderer?: '3d' | '2d' | 'auto';
|
|
10
|
+
scoring?: ScoringConfig;
|
|
11
|
+
accessibility?: AccessibilityConfig;
|
|
12
|
+
callbacks?: EventCallbacks;
|
|
13
|
+
}
|
|
14
|
+
export interface HiddenEntryConfig {
|
|
15
|
+
mode: 'auto' | 'manual';
|
|
16
|
+
selector?: string;
|
|
17
|
+
excludeSelectors?: string[];
|
|
18
|
+
}
|
|
19
|
+
export interface HUDConfig {
|
|
20
|
+
enabled?: boolean;
|
|
21
|
+
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
22
|
+
}
|
|
23
|
+
export interface ShrineConfig {
|
|
24
|
+
enabled?: boolean;
|
|
25
|
+
position?: 'bottom-right' | 'bottom-left' | 'bottom-center';
|
|
26
|
+
}
|
|
27
|
+
export interface StageDurationsConfig {
|
|
28
|
+
stillnessMs?: number;
|
|
29
|
+
motionMs?: number;
|
|
30
|
+
rhythmCycles?: number;
|
|
31
|
+
}
|
|
32
|
+
export interface ThemeConfig {
|
|
33
|
+
overlayBg?: string;
|
|
34
|
+
textColor?: string;
|
|
35
|
+
textSecondary?: string;
|
|
36
|
+
accent?: string;
|
|
37
|
+
accentGlow?: string;
|
|
38
|
+
hudBg?: string;
|
|
39
|
+
hudText?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface ScoringConfig {
|
|
42
|
+
enableLocal?: boolean;
|
|
43
|
+
leaderboardAdapter?: LeaderboardAdapter;
|
|
44
|
+
}
|
|
45
|
+
export interface AccessibilityConfig {
|
|
46
|
+
reducedMotion?: boolean | 'auto';
|
|
47
|
+
highContrast?: boolean;
|
|
48
|
+
disableHiddenEntry?: boolean;
|
|
49
|
+
subtitlesLog?: boolean;
|
|
50
|
+
}
|
|
51
|
+
export interface EventCallbacks {
|
|
52
|
+
onInit?: () => void;
|
|
53
|
+
onEntryFound?: () => void;
|
|
54
|
+
onStageStart?: (stage: GameStage) => void;
|
|
55
|
+
onEggFound?: (eggIndex: number) => void;
|
|
56
|
+
onStageComplete?: (stage: GameStage, result: StageResult) => void;
|
|
57
|
+
onFinaleStart?: () => void;
|
|
58
|
+
onComplete?: (score: FinalScore) => void;
|
|
59
|
+
onDestroy?: () => void;
|
|
60
|
+
}
|
|
61
|
+
export interface ResolvedConfig {
|
|
62
|
+
theme: ResolvedTheme;
|
|
63
|
+
hiddenEntry: {
|
|
64
|
+
mode: 'auto' | 'manual';
|
|
65
|
+
selector?: string;
|
|
66
|
+
excludeSelectors: string[];
|
|
67
|
+
};
|
|
68
|
+
hud: {
|
|
69
|
+
enabled: boolean;
|
|
70
|
+
position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
71
|
+
};
|
|
72
|
+
shrine: {
|
|
73
|
+
enabled: boolean;
|
|
74
|
+
position: 'bottom-right' | 'bottom-left' | 'bottom-center';
|
|
75
|
+
};
|
|
76
|
+
sounds: boolean;
|
|
77
|
+
stageDurations: {
|
|
78
|
+
stillnessMs: number;
|
|
79
|
+
motionMs: number;
|
|
80
|
+
rhythmCycles: number;
|
|
81
|
+
};
|
|
82
|
+
renderer: '3d' | '2d' | 'auto';
|
|
83
|
+
scoring: {
|
|
84
|
+
enableLocal: boolean;
|
|
85
|
+
leaderboardAdapter?: LeaderboardAdapter;
|
|
86
|
+
};
|
|
87
|
+
accessibility: {
|
|
88
|
+
reducedMotion: boolean;
|
|
89
|
+
highContrast: boolean;
|
|
90
|
+
disableHiddenEntry: boolean;
|
|
91
|
+
subtitlesLog: boolean;
|
|
92
|
+
};
|
|
93
|
+
callbacks: EventCallbacks;
|
|
94
|
+
}
|
|
95
|
+
export interface ResolvedTheme {
|
|
96
|
+
overlayBg: string;
|
|
97
|
+
textColor: string;
|
|
98
|
+
textSecondary: string;
|
|
99
|
+
accent: string;
|
|
100
|
+
accentGlow: string;
|
|
101
|
+
hudBg: string;
|
|
102
|
+
hudText: string;
|
|
103
|
+
}
|
|
104
|
+
export interface NarrativeScript {
|
|
105
|
+
hiddenEntryHints: string[];
|
|
106
|
+
entryConfirmation: string[];
|
|
107
|
+
stage1Intro: string[];
|
|
108
|
+
stage1Reactions: string[];
|
|
109
|
+
stage1Success: string[];
|
|
110
|
+
stage2Intro: string[];
|
|
111
|
+
stage2Reactions: string[];
|
|
112
|
+
stage2Success: string[];
|
|
113
|
+
stage3Intro: string[];
|
|
114
|
+
stage3Reactions: string[];
|
|
115
|
+
stage3Success: string[];
|
|
116
|
+
finale: string[];
|
|
117
|
+
results: string[];
|
|
118
|
+
}
|
|
119
|
+
export type GameStage = 'idle' | 'entry' | 'stage1-intro' | 'stage1-active' | 'stage1-success' | 'stage2-intro' | 'stage2-active' | 'stage2-success' | 'stage3-intro' | 'stage3-active' | 'stage3-success' | 'finale' | 'results' | 'complete';
|
|
120
|
+
export interface GameState {
|
|
121
|
+
stage: GameStage;
|
|
122
|
+
eggsFound: number;
|
|
123
|
+
startTime: number;
|
|
124
|
+
stageStartTime: number;
|
|
125
|
+
isPaused: boolean;
|
|
126
|
+
eggs: [EggState, EggState, EggState];
|
|
127
|
+
}
|
|
128
|
+
export interface EggState {
|
|
129
|
+
found: boolean;
|
|
130
|
+
foundTime?: number;
|
|
131
|
+
stageTime?: number;
|
|
132
|
+
}
|
|
133
|
+
export interface StageResult {
|
|
134
|
+
stageTime: number;
|
|
135
|
+
attempts: number;
|
|
136
|
+
breaks?: number;
|
|
137
|
+
fades?: number;
|
|
138
|
+
rhythmAccuracy?: number;
|
|
139
|
+
}
|
|
140
|
+
export interface FinalScore {
|
|
141
|
+
eggsFound: number;
|
|
142
|
+
totalTime: number;
|
|
143
|
+
entryTime: number;
|
|
144
|
+
egg1Time: number;
|
|
145
|
+
egg2Time: number;
|
|
146
|
+
egg3Time: number;
|
|
147
|
+
stillnessBreaks: number;
|
|
148
|
+
motionFades: number;
|
|
149
|
+
rhythmAccuracy: number;
|
|
150
|
+
predictabilityScore: number;
|
|
151
|
+
predictabilityLabel: string;
|
|
152
|
+
stageResults: StageResult[];
|
|
153
|
+
behaviorProfile: BehaviorProfile;
|
|
154
|
+
totalClicks: number;
|
|
155
|
+
totalScrolls: number;
|
|
156
|
+
totalKeyPresses: number;
|
|
157
|
+
totalDistance: number;
|
|
158
|
+
eggSeed: number;
|
|
159
|
+
}
|
|
160
|
+
export interface LeaderboardAdapter {
|
|
161
|
+
submitScore(score: FinalScore): Promise<void>;
|
|
162
|
+
getLeaderboard(): Promise<LeaderboardEntry[]>;
|
|
163
|
+
}
|
|
164
|
+
export interface LeaderboardEntry {
|
|
165
|
+
name: string;
|
|
166
|
+
totalTime: number;
|
|
167
|
+
predictabilityScore: number;
|
|
168
|
+
date: string;
|
|
169
|
+
}
|
|
170
|
+
export interface InputSnapshot {
|
|
171
|
+
isMoving: boolean;
|
|
172
|
+
lastMoveTime: number;
|
|
173
|
+
lastStillTime: number;
|
|
174
|
+
mouseX: number;
|
|
175
|
+
mouseY: number;
|
|
176
|
+
velocity: number;
|
|
177
|
+
totalClicks: number;
|
|
178
|
+
totalScrolls: number;
|
|
179
|
+
totalKeyPresses: number;
|
|
180
|
+
totalDistance: number;
|
|
181
|
+
maxVelocity: number;
|
|
182
|
+
}
|
|
183
|
+
export interface BehaviorProfile {
|
|
184
|
+
title: string;
|
|
185
|
+
dominantAction: string;
|
|
186
|
+
mousePersonality: string;
|
|
187
|
+
pace: string;
|
|
188
|
+
}
|
|
189
|
+
export interface MovementPhase {
|
|
190
|
+
type: 'move' | 'still';
|
|
191
|
+
startTime: number;
|
|
192
|
+
endTime: number;
|
|
193
|
+
duration: number;
|
|
194
|
+
}
|
|
195
|
+
export type StageStatus = 'active' | 'complete';
|
|
196
|
+
export interface StageHandler {
|
|
197
|
+
start(): Promise<void>;
|
|
198
|
+
update(dt: number): StageStatus;
|
|
199
|
+
cleanup(): void;
|
|
200
|
+
getResult(): StageResult;
|
|
201
|
+
}
|
|
202
|
+
export type ThreeModule = typeof import('three');
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM utility helpers. All are safe to call in non-browser environments
|
|
3
|
+
* (they degrade gracefully).
|
|
4
|
+
*/
|
|
5
|
+
/** Check if an element is visible in the viewport and has a non-zero size. */
|
|
6
|
+
export declare function isElementVisible(el: Element): boolean;
|
|
7
|
+
/** Check if an element's text suggests a dangerous / transactional action. */
|
|
8
|
+
export declare function isDangerousElement(el: Element): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Query safe clickable elements that are eligible for the hidden entry.
|
|
11
|
+
* Filters out dangerous elements, invisible ones, and elements too small to click.
|
|
12
|
+
*/
|
|
13
|
+
export declare function findEligibleEntryElements(containerSelector?: string, excludeSelectors?: string[]): Element[];
|
|
14
|
+
/** Pick a random element from an array. */
|
|
15
|
+
export declare function pickRandom<T>(arr: T[]): T | undefined;
|
|
16
|
+
/** Create an element inside an optional Shadow DOM host. */
|
|
17
|
+
export declare function createElement<K extends keyof HTMLElementTagNameMap>(tag: K, attrs?: Record<string, string>, styles?: Partial<CSSStyleDeclaration>): HTMLElementTagNameMap[K];
|
|
18
|
+
/** Clamp a value between min and max. */
|
|
19
|
+
export declare function clamp(val: number, min: number, max: number): number;
|
|
20
|
+
/** Linear interpolation. */
|
|
21
|
+
export declare function lerp(a: number, b: number, t: number): number;
|
|
22
|
+
/** Format milliseconds as mm:ss. */
|
|
23
|
+
export declare function formatTime(ms: number): string;
|
|
24
|
+
/** Smooth ease-in-out (cubic). */
|
|
25
|
+
export declare function easeInOutCubic(t: number): number;
|
|
26
|
+
/** Smooth ease-out (quad). */
|
|
27
|
+
export declare function easeOutQuad(t: number): number;
|
|
28
|
+
/** Detect prefers-reduced-motion. */
|
|
29
|
+
export declare function prefersReducedMotion(): boolean;
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "easter-egg-quest",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A hidden Easter-themed narrative mini-game overlay for any website",
|
|
5
|
+
"main": "dist/easter-egg-quest.umd.js",
|
|
6
|
+
"module": "dist/easter-egg-quest.es.js",
|
|
7
|
+
"types": "dist/types/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/easter-egg-quest.es.js",
|
|
11
|
+
"require": "./dist/easter-egg-quest.umd.js",
|
|
12
|
+
"types": "./dist/types/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"dev": "vite",
|
|
20
|
+
"build": "vite build && tsc --emitDeclarationOnly",
|
|
21
|
+
"preview": "vite preview",
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"prepublishOnly": "npm run build"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"three": "^0.160.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/three": "^0.160.0",
|
|
30
|
+
"terser": "^5.46.1",
|
|
31
|
+
"typescript": "^5.3.0",
|
|
32
|
+
"vite": "^5.0.0"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"easter",
|
|
36
|
+
"egg",
|
|
37
|
+
"game",
|
|
38
|
+
"overlay",
|
|
39
|
+
"interactive",
|
|
40
|
+
"threejs",
|
|
41
|
+
"mini-game",
|
|
42
|
+
"easter-egg"
|
|
43
|
+
],
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "https://github.com/example/easter-egg-quest"
|
|
48
|
+
}
|
|
49
|
+
}
|