class-studys 4.0.0 → 6.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/index.html +2028 -488
- package/package.json +1 -1
package/index.html
CHANGED
|
@@ -5,226 +5,756 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>IGNITE OS — the unblocked dashboard</title>
|
|
7
7
|
<meta name="description" content="IGNITE OS: games, movies from Drive and a real web tunnel. Dark, orange, clean.">
|
|
8
|
-
<meta property="og:title" content="IGNITE OS — the unblocked dashboard">
|
|
9
|
-
<meta property="og:description" content="IGNITE OS: games, movies from Drive and a real web tunnel. Dark, orange, clean.">
|
|
10
|
-
<meta property="og:type" content="website">
|
|
11
|
-
<meta name="twitter:card" content="summary_large_image">
|
|
12
8
|
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='14' fill='%230d0906'/%3E%3Ccircle cx='32' cy='32' r='17' fill='%23ff6b1a'/%3E%3Ccircle cx='26' cy='26' r='5' fill='%230d0906'/%3E%3C/svg%3E">
|
|
13
9
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
14
10
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
15
11
|
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800;900&family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
|
|
16
|
-
<style
|
|
17
|
-
/*
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
* {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
:
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
background:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
.brand-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
background:
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
border-radius:
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
.
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
12
|
+
<style>
|
|
13
|
+
/* IGNITE OS styles — embedded */
|
|
14
|
+
/* ═══════════════════════════════════════════════════════════
|
|
15
|
+
IGNITE OS styles — dark orange engine
|
|
16
|
+
═══════════════════════════════════════════════════════════ */
|
|
17
|
+
|
|
18
|
+
:root {
|
|
19
|
+
--bg0: #0a0705; --bg1: #140d08; --bg2: #1c130c;
|
|
20
|
+
--panel: rgba(20, 13, 9, 0.55);
|
|
21
|
+
--panel-solid: #151009;
|
|
22
|
+
--panel-2: rgba(28, 19, 13, 0.85);
|
|
23
|
+
--line: rgba(255, 255, 255, 0.08);
|
|
24
|
+
--line-2: rgba(255, 255, 255, 0.16);
|
|
25
|
+
--text: #f4eee6; --muted: #a29588; --dim: #6f635a;
|
|
26
|
+
--accent: #ff6b1a; --accent2: #ffb26b;
|
|
27
|
+
--on-accent: #140600;
|
|
28
|
+
--glow: 0.7; --blur: 14px; --radius: 18px; --saturate: 1;
|
|
29
|
+
--font: 'Outfit', 'Segoe UI', system-ui, sans-serif;
|
|
30
|
+
--hero-size: clamp(2.6rem, 6.4vw, 5rem);
|
|
31
|
+
--ease: cubic-bezier(.22, .8, .3, 1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
35
|
+
html { scroll-behavior: smooth; }
|
|
36
|
+
|
|
37
|
+
body {
|
|
38
|
+
background: var(--bg0); color: var(--text);
|
|
39
|
+
font-family: var(--font); font-weight: 400;
|
|
40
|
+
min-height: 100vh; overflow-x: hidden; line-height: 1.55;
|
|
41
|
+
}
|
|
42
|
+
body[data-font='grotesk'] { --font: 'Space Grotesk', 'Outfit', sans-serif; }
|
|
43
|
+
body[data-font='mono'] { --font: 'JetBrains Mono', 'Consolas', monospace; }
|
|
44
|
+
body[data-font='system'] { --font: 'Segoe UI', system-ui, sans-serif; }
|
|
45
|
+
|
|
46
|
+
::selection { background: var(--accent); color: var(--on-accent); }
|
|
47
|
+
::-webkit-scrollbar { width: 11px; height: 11px; }
|
|
48
|
+
::-webkit-scrollbar-track { background: #0b0706; }
|
|
49
|
+
::-webkit-scrollbar-thumb {
|
|
50
|
+
background: linear-gradient(var(--accent), var(--accent2));
|
|
51
|
+
border-radius: 9px; border: 3px solid #0b0706;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
a { color: inherit; text-decoration: none; }
|
|
55
|
+
button { font-family: inherit; cursor: pointer; }
|
|
56
|
+
input, select { font-family: inherit; color: inherit; }
|
|
57
|
+
.hidden { display: none !important; }
|
|
58
|
+
|
|
59
|
+
code, kbd {
|
|
60
|
+
font-family: 'JetBrains Mono', monospace;
|
|
61
|
+
background: rgba(255, 255, 255, 0.07);
|
|
62
|
+
border: 1px solid var(--line); padding: 1px 6px;
|
|
63
|
+
border-radius: 6px; font-size: 0.85em; color: var(--accent2);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* ─────────── background engine ─────────── */
|
|
67
|
+
|
|
68
|
+
.bg-wrap {
|
|
69
|
+
position: fixed; inset: 0; z-index: -2;
|
|
70
|
+
background:
|
|
71
|
+
radial-gradient(1200px 600px at 80% -10%, color-mix(in srgb, var(--accent) 14%, transparent), transparent 60%),
|
|
72
|
+
radial-gradient(900px 500px at 10% 110%, color-mix(in srgb, var(--accent2) 10%, transparent), transparent 60%),
|
|
73
|
+
linear-gradient(180deg, var(--bg1), var(--bg0) 60%, var(--bg0));
|
|
74
|
+
filter: saturate(var(--saturate));
|
|
75
|
+
transition: background 0.8s var(--ease);
|
|
76
|
+
}
|
|
77
|
+
body[data-bg='custom'] .bg-wrap {
|
|
78
|
+
background: var(--bgimg, none) center / cover no-repeat fixed, var(--bg0);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.blob {
|
|
82
|
+
position: absolute; border-radius: 50%;
|
|
83
|
+
filter: blur(95px); opacity: calc(var(--glow) * 0.85);
|
|
84
|
+
mix-blend-mode: screen; pointer-events: none;
|
|
85
|
+
animation: drift 26s ease-in-out infinite alternate;
|
|
86
|
+
will-change: transform;
|
|
87
|
+
}
|
|
88
|
+
.blob-a {
|
|
89
|
+
width: 44vw; height: 44vw; left: -8vw; top: -12vh;
|
|
90
|
+
background: radial-gradient(circle, var(--accent), transparent 65%);
|
|
91
|
+
animation-duration: 30s;
|
|
92
|
+
}
|
|
93
|
+
.blob-b {
|
|
94
|
+
width: 36vw; height: 36vw; right: -6vw; top: 30vh;
|
|
95
|
+
background: radial-gradient(circle, color-mix(in srgb, var(--accent) 55%, var(--accent2) 45%), transparent 65%);
|
|
96
|
+
animation-duration: 24s; animation-delay: -6s;
|
|
97
|
+
}
|
|
98
|
+
.blob-c {
|
|
99
|
+
width: 30vw; height: 30vw; left: 28vw; bottom: -14vh;
|
|
100
|
+
background: radial-gradient(circle, var(--accent2), transparent 65%);
|
|
101
|
+
animation-duration: 38s; animation-delay: -14s;
|
|
102
|
+
}
|
|
103
|
+
body[data-bg='grid'] .blob { opacity: calc(var(--glow) * 0.5); }
|
|
104
|
+
body[data-bg='stars'] .blob, body[data-bg='stars'] .bg-grid { display: none; }
|
|
105
|
+
body[data-bg='mesh'] .blob { animation: none; opacity: calc(var(--glow) * 0.55); }
|
|
106
|
+
|
|
107
|
+
@keyframes drift {
|
|
108
|
+
0% { transform: translate3d(0, 0, 0) scale(1); }
|
|
109
|
+
50% { transform: translate3d(6vw, -5vh, 0) scale(1.18); }
|
|
110
|
+
100% { transform: translate3d(-5vw, 4vh, 0) scale(0.94); }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.bg-grid {
|
|
114
|
+
position: absolute; inset: 0;
|
|
115
|
+
background-image:
|
|
116
|
+
linear-gradient(rgba(255, 255, 255, 0.035) 1px, transparent 1px),
|
|
117
|
+
linear-gradient(90deg, rgba(255, 255, 255, 0.035) 1px, transparent 1px);
|
|
118
|
+
background-size: 56px 56px;
|
|
119
|
+
-webkit-mask-image: radial-gradient(ellipse 80% 70% at 50% 30%, #000 20%, transparent 75%);
|
|
120
|
+
mask-image: radial-gradient(ellipse 80% 70% at 50% 30%, #000 20%, transparent 75%);
|
|
121
|
+
animation: gridPan 42s linear infinite alternate; opacity: 0.85;
|
|
122
|
+
}
|
|
123
|
+
body[data-bg='grid'] .bg-grid {
|
|
124
|
+
background-image:
|
|
125
|
+
linear-gradient(color-mix(in srgb, var(--accent) 9%, transparent) 1px, transparent 1px),
|
|
126
|
+
linear-gradient(90deg, color-mix(in srgb, var(--accent) 9%, transparent) 1px, transparent 1px);
|
|
127
|
+
opacity: 1;
|
|
128
|
+
}
|
|
129
|
+
@keyframes gridPan { from { background-position: 0 0; } to { background-position: 56px 112px; } }
|
|
130
|
+
|
|
131
|
+
.bg-noise {
|
|
132
|
+
position: absolute; inset: 0; opacity: 0.05; pointer-events: none;
|
|
133
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
|
|
134
|
+
}
|
|
135
|
+
#bg-canvas { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
|
|
136
|
+
|
|
137
|
+
/* ─────────── nav ─────────── */
|
|
138
|
+
/* ─────────── nav ─────────── */
|
|
139
|
+
|
|
140
|
+
.nav {
|
|
141
|
+
position: sticky; top: 0; z-index: 90;
|
|
142
|
+
background: rgba(10, 7, 5, 0.62);
|
|
143
|
+
backdrop-filter: blur(calc(var(--blur) * 1.4)) saturate(1.4);
|
|
144
|
+
border-bottom: 1px solid var(--line);
|
|
145
|
+
}
|
|
146
|
+
.nav-inner {
|
|
147
|
+
max-width: 1220px; margin: 0 auto; padding: 14px 22px;
|
|
148
|
+
display: flex; align-items: center; gap: 18px;
|
|
149
|
+
}
|
|
150
|
+
.brand { display: flex; align-items: center; gap: 10px; }
|
|
151
|
+
.brand-mark {
|
|
152
|
+
width: 34px; height: 34px; border-radius: 10px;
|
|
153
|
+
display: grid; place-items: center;
|
|
154
|
+
background: linear-gradient(135deg, var(--accent), var(--accent2));
|
|
155
|
+
color: var(--on-accent); font-size: 15px;
|
|
156
|
+
box-shadow: 0 0 22px color-mix(in srgb, var(--accent) 55%, transparent);
|
|
157
|
+
}
|
|
158
|
+
.brand-name { font-weight: 800; font-size: 1.18rem; letter-spacing: 0.4px; }
|
|
159
|
+
.accent-text {
|
|
160
|
+
background: linear-gradient(90deg, var(--accent), var(--accent2));
|
|
161
|
+
-webkit-background-clip: text; background-clip: text; color: transparent;
|
|
162
|
+
}
|
|
163
|
+
.brand-ver {
|
|
164
|
+
font-size: 0.62rem; color: var(--muted); border: 1px solid var(--line);
|
|
165
|
+
padding: 2px 7px; border-radius: 99px; letter-spacing: 1px;
|
|
166
|
+
}
|
|
167
|
+
.nav-links { display: flex; align-items: center; gap: 4px; margin-left: 12px; flex-wrap: wrap; }
|
|
168
|
+
.nav-link {
|
|
169
|
+
padding: 7px 14px; border-radius: 10px; font-size: 0.92rem; font-weight: 600;
|
|
170
|
+
color: var(--muted); transition: 0.25s var(--ease); background: none; border: none;
|
|
171
|
+
}
|
|
172
|
+
.nav-link:hover { color: var(--text); background: rgba(255, 255, 255, 0.05); }
|
|
173
|
+
.btn-link { font: inherit; }
|
|
174
|
+
.btn-link:focus-visible { outline: 2px solid var(--accent); }
|
|
175
|
+
.nav-side { margin-left: auto; display: flex; align-items: center; gap: 14px; }
|
|
176
|
+
.nav-clock {
|
|
177
|
+
font-family: 'JetBrains Mono', monospace; font-size: 0.8rem;
|
|
178
|
+
color: var(--muted); letter-spacing: 1px;
|
|
179
|
+
}
|
|
180
|
+
.profile-pill {
|
|
181
|
+
background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 80%, #000), color-mix(in srgb, var(--accent) 55%, #000));
|
|
182
|
+
color: #ffe9d6; padding: 6px 14px; border-radius: 99px;
|
|
183
|
+
font-size: 0.75rem; font-weight: 800; letter-spacing: 1.5px; cursor: pointer;
|
|
184
|
+
box-shadow: 0 0 16px color-mix(in srgb, var(--accent) 40%, transparent);
|
|
185
|
+
transition: 0.25s var(--ease);
|
|
186
|
+
}
|
|
187
|
+
.profile-pill:hover { transform: translateY(-1px); filter: brightness(1.1); }
|
|
188
|
+
|
|
189
|
+
/* ─────────── layout & hero ─────────── */
|
|
190
|
+
|
|
191
|
+
main { max-width: 1220px; margin: 0 auto; padding: 0 22px 60px; display: grid; gap: 22px; }
|
|
192
|
+
.hero { padding: 76px 8px 10px; }
|
|
193
|
+
|
|
194
|
+
.hero-eyebrow {
|
|
195
|
+
display: inline-flex; align-items: center; gap: 9px;
|
|
196
|
+
font-size: 0.78rem; letter-spacing: 2.5px; text-transform: uppercase;
|
|
197
|
+
color: var(--muted); margin-bottom: 18px;
|
|
198
|
+
}
|
|
199
|
+
.pulse-dot {
|
|
200
|
+
width: 8px; height: 8px; border-radius: 50%;
|
|
201
|
+
background: var(--accent); box-shadow: 0 0 12px var(--accent);
|
|
202
|
+
animation: pulse 1.8s ease-in-out infinite;
|
|
203
|
+
}
|
|
204
|
+
@keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.45; transform: scale(0.75); } }
|
|
205
|
+
|
|
206
|
+
.hero-title {
|
|
207
|
+
font-size: var(--hero-size); font-weight: 800; line-height: 1.04;
|
|
208
|
+
letter-spacing: -1.5px; max-width: 20ch;
|
|
209
|
+
}
|
|
210
|
+
.gradient-text {
|
|
211
|
+
background: linear-gradient(92deg, var(--accent), var(--accent2) 55%, color-mix(in srgb, var(--accent) 40%, #fff));
|
|
212
|
+
-webkit-background-clip: text; background-clip: text; color: transparent;
|
|
213
|
+
filter: drop-shadow(0 0 30px color-mix(in srgb, var(--accent) 35%, transparent));
|
|
214
|
+
}
|
|
215
|
+
.hero-sub { margin-top: 20px; max-width: 62ch; color: var(--muted); font-size: 1.04rem; }
|
|
216
|
+
|
|
217
|
+
.stat-strip { display: flex; gap: 12px; margin-top: 38px; flex-wrap: wrap; }
|
|
218
|
+
.stat {
|
|
219
|
+
background: var(--panel); border: 1px solid var(--line);
|
|
220
|
+
border-radius: var(--radius); padding: 14px 22px; min-width: 130px;
|
|
221
|
+
backdrop-filter: blur(var(--blur));
|
|
222
|
+
display: flex; flex-direction: column; transition: 0.3s var(--ease);
|
|
223
|
+
}
|
|
224
|
+
.stat b {
|
|
225
|
+
font-size: 1.6rem; font-weight: 800;
|
|
226
|
+
background: linear-gradient(90deg, var(--accent), var(--accent2));
|
|
227
|
+
-webkit-background-clip: text; background-clip: text; color: transparent;
|
|
228
|
+
}
|
|
229
|
+
.stat span { font-size: 0.72rem; letter-spacing: 1.6px; text-transform: uppercase; color: var(--muted); }
|
|
230
|
+
.stat:hover { transform: translateY(-3px); border-color: var(--line-2); }
|
|
231
|
+
/* ─────────── panels & buttons ─────────── */
|
|
232
|
+
|
|
233
|
+
.panel {
|
|
234
|
+
background: var(--panel);
|
|
235
|
+
border: 1px solid var(--line);
|
|
236
|
+
border-radius: calc(var(--radius) + 4px);
|
|
237
|
+
padding: 26px;
|
|
238
|
+
backdrop-filter: blur(var(--blur)) saturate(1.3);
|
|
239
|
+
box-shadow: 0 18px 60px rgba(0, 0, 0, 0.35);
|
|
240
|
+
transition: border-color 0.3s var(--ease), transform 0.3s var(--ease);
|
|
241
|
+
scroll-margin-top: 90px;
|
|
242
|
+
}
|
|
243
|
+
.panel:hover { border-color: var(--line-2); }
|
|
244
|
+
.panel-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 20px; }
|
|
245
|
+
.panel-title { font-size: 1.28rem; font-weight: 800; letter-spacing: -0.3px; }
|
|
246
|
+
.panel-sub { color: var(--muted); font-size: 0.92rem; margin-top: 4px; max-width: 66ch; }
|
|
247
|
+
|
|
248
|
+
.badge {
|
|
249
|
+
background: rgba(255, 255, 255, 0.05); color: var(--text);
|
|
250
|
+
border: 1px solid var(--line); border-radius: 99px;
|
|
251
|
+
padding: 6px 14px; font-size: 0.72rem; font-weight: 700;
|
|
252
|
+
letter-spacing: 1.2px; text-transform: uppercase; white-space: nowrap;
|
|
253
|
+
transition: 0.2s var(--ease);
|
|
254
|
+
}
|
|
255
|
+
.badge:hover { border-color: var(--accent); color: var(--accent2); }
|
|
256
|
+
.badge-hot {
|
|
257
|
+
background: color-mix(in srgb, var(--accent) 16%, transparent);
|
|
258
|
+
border-color: color-mix(in srgb, var(--accent) 45%, transparent);
|
|
259
|
+
color: var(--accent2);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.btn {
|
|
263
|
+
border: none; border-radius: 12px; padding: 11px 20px;
|
|
264
|
+
font-weight: 700; font-size: 0.92rem; letter-spacing: 0.2px;
|
|
265
|
+
transition: 0.22s var(--ease);
|
|
266
|
+
}
|
|
267
|
+
.btn-accent {
|
|
268
|
+
background: linear-gradient(135deg, var(--accent), var(--accent2));
|
|
269
|
+
color: var(--on-accent);
|
|
270
|
+
box-shadow: 0 6px 24px color-mix(in srgb, var(--accent) 40%, transparent);
|
|
271
|
+
}
|
|
272
|
+
.btn-accent:hover { transform: translateY(-2px); filter: brightness(1.07); }
|
|
273
|
+
.btn.ghost {
|
|
274
|
+
background: rgba(255, 255, 255, 0.05); color: var(--text);
|
|
275
|
+
border: 1px solid var(--line);
|
|
276
|
+
}
|
|
277
|
+
.btn.ghost:hover { border-color: var(--accent); color: var(--accent2); }
|
|
278
|
+
.btn-sm { padding: 7px 13px; font-size: 0.8rem; border-radius: 10px; }
|
|
279
|
+
.btn-block { width: 100%; }
|
|
280
|
+
.icon-btn {
|
|
281
|
+
background: rgba(255, 255, 255, 0.06); border: 1px solid var(--line);
|
|
282
|
+
color: var(--text); width: 36px; height: 36px; border-radius: 10px;
|
|
283
|
+
display: inline-grid; place-items: center; font-size: 0.85rem;
|
|
284
|
+
transition: 0.2s var(--ease);
|
|
285
|
+
}
|
|
286
|
+
.icon-btn:hover { border-color: var(--accent); color: var(--accent2); }
|
|
287
|
+
|
|
288
|
+
/* ─────────── tunnel ─────────── */
|
|
289
|
+
|
|
290
|
+
.tunnel-form { display: flex; gap: 10px; flex-wrap: wrap; }
|
|
291
|
+
.tunnel-input, .search-input {
|
|
292
|
+
flex: 1 1 320px; min-width: 220px;
|
|
293
|
+
background: rgba(0, 0, 0, 0.32); border: 1px solid var(--line);
|
|
294
|
+
border-radius: 12px; padding: 12px 16px; font-size: 0.95rem;
|
|
295
|
+
outline: none; transition: 0.25s var(--ease);
|
|
296
|
+
}
|
|
297
|
+
.tunnel-input:focus, .search-input:focus {
|
|
298
|
+
border-color: var(--accent);
|
|
299
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
|
|
300
|
+
}
|
|
301
|
+
.chips { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 16px; }
|
|
302
|
+
.chip {
|
|
303
|
+
background: rgba(255, 255, 255, 0.045); border: 1px solid var(--line);
|
|
304
|
+
color: var(--muted); border-radius: 99px; padding: 7px 15px;
|
|
305
|
+
font-size: 0.82rem; font-weight: 600; transition: 0.22s var(--ease);
|
|
306
|
+
}
|
|
307
|
+
.chip:hover {
|
|
308
|
+
color: var(--text); border-color: var(--accent);
|
|
309
|
+
background: color-mix(in srgb, var(--accent) 10%, transparent);
|
|
310
|
+
transform: translateY(-1px);
|
|
311
|
+
}
|
|
312
|
+
.tunnel-tip { margin-top: 16px; font-size: 0.78rem; color: var(--dim); }
|
|
313
|
+
|
|
314
|
+
.local-hint {
|
|
315
|
+
background: color-mix(in srgb, var(--accent) 8%, transparent);
|
|
316
|
+
border: 1px dashed var(--line-2); border-radius: 12px;
|
|
317
|
+
padding: 12px 16px; font-size: 0.85rem; color: var(--muted); margin-bottom: 16px;
|
|
318
|
+
}
|
|
319
|
+
/* ─────────── game cards ─────────── */
|
|
320
|
+
|
|
321
|
+
.game-grid {
|
|
322
|
+
display: grid; gap: 16px;
|
|
323
|
+
grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
|
|
324
|
+
}
|
|
325
|
+
.game-card {
|
|
326
|
+
position: relative; border-radius: var(--radius); overflow: hidden;
|
|
327
|
+
background: var(--panel-2); border: 1px solid var(--line);
|
|
328
|
+
transition: 0.3s var(--ease); cursor: pointer;
|
|
329
|
+
}
|
|
330
|
+
.game-card:hover {
|
|
331
|
+
transform: translateY(-5px); border-color: var(--accent);
|
|
332
|
+
box-shadow: 0 16px 44px rgba(0, 0, 0, 0.5), 0 0 30px color-mix(in srgb, var(--accent) 18%, transparent);
|
|
333
|
+
}
|
|
334
|
+
.game-cover {
|
|
335
|
+
position: relative; height: 130px; overflow: hidden;
|
|
336
|
+
background:
|
|
337
|
+
radial-gradient(120% 130% at 12% 8%, color-mix(in srgb, var(--gcolor) 45%, transparent), transparent 55%),
|
|
338
|
+
radial-gradient(120% 130% at 90% 95%, color-mix(in srgb, var(--accent2) 30%, transparent), transparent 60%),
|
|
339
|
+
linear-gradient(150deg, #241a12, #120a06 70%);
|
|
340
|
+
}
|
|
341
|
+
.game-cover img {
|
|
342
|
+
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
343
|
+
object-fit: cover; filter: saturate(1.1); transition: 0.5s var(--ease);
|
|
344
|
+
z-index: 1;
|
|
345
|
+
}
|
|
346
|
+
.game-card:hover .game-cover img { transform: scale(1.06); }
|
|
347
|
+
.cover-fallback {
|
|
348
|
+
position: absolute; inset: 0; display: grid; place-items: center;
|
|
349
|
+
font-size: 3rem; font-weight: 900;
|
|
350
|
+
color: color-mix(in srgb, var(--gcolor) 70%, #fff);
|
|
351
|
+
text-shadow: 0 0 30px color-mix(in srgb, var(--gcolor) 60%, transparent);
|
|
352
|
+
}
|
|
353
|
+
.game-play {
|
|
354
|
+
position: absolute; right: 12px; bottom: -18px;
|
|
355
|
+
width: 44px; height: 44px; border-radius: 50%;
|
|
356
|
+
background: linear-gradient(135deg, var(--accent), var(--accent2));
|
|
357
|
+
color: var(--on-accent); display: grid; place-items: center;
|
|
358
|
+
font-size: 1rem; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
|
|
359
|
+
transition: 0.25s var(--ease); border: none;
|
|
360
|
+
}
|
|
361
|
+
.game-card:hover .game-play { bottom: 14px; }
|
|
362
|
+
.game-body { padding: 16px 16px 18px; }
|
|
363
|
+
.game-name { font-weight: 800; font-size: 1.02rem; display: flex; align-items: center; gap: 8px; }
|
|
364
|
+
.game-tags { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
|
|
365
|
+
.game-tag {
|
|
366
|
+
font-size: 0.66rem; letter-spacing: 1px; text-transform: uppercase;
|
|
367
|
+
padding: 3px 9px; border-radius: 99px; color: var(--muted);
|
|
368
|
+
background: rgba(255, 255, 255, 0.05); border: 1px solid var(--line);
|
|
369
|
+
}
|
|
370
|
+
.game-desc {
|
|
371
|
+
color: var(--muted); font-size: 0.82rem; margin-top: 8px;
|
|
372
|
+
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
|
|
373
|
+
}
|
|
374
|
+
.game-tools {
|
|
375
|
+
position: absolute; top: 10px; left: 10px; display: flex; gap: 6px;
|
|
376
|
+
opacity: 0; transition: 0.2s var(--ease); z-index: 3;
|
|
377
|
+
}
|
|
378
|
+
.game-card:hover .game-tools { opacity: 1; }
|
|
379
|
+
.game-tools .icon-btn { width: 30px; height: 30px; font-size: 0.72rem; background: rgba(0, 0, 0, 0.55); }
|
|
380
|
+
.local-dot {
|
|
381
|
+
position: absolute; top: 10px; right: 10px;
|
|
382
|
+
background: color-mix(in srgb, var(--gcolor) 85%, #000); color: #fff;
|
|
383
|
+
font-size: 0.6rem; font-weight: 800; letter-spacing: 1px;
|
|
384
|
+
padding: 3px 8px; border-radius: 99px;
|
|
385
|
+
}
|
|
386
|
+
/* ─────────── movies ─────────── */
|
|
387
|
+
|
|
388
|
+
.movie-toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 18px; }
|
|
389
|
+
.movie-toolbar .search-input { flex: 0 1 300px; }
|
|
390
|
+
.genre-chips { display: flex; gap: 6px; flex-wrap: wrap; flex: 1; }
|
|
391
|
+
.genre-chip {
|
|
392
|
+
background: rgba(255, 255, 255, 0.045); border: 1px solid var(--line);
|
|
393
|
+
color: var(--muted); border-radius: 99px; padding: 5px 12px;
|
|
394
|
+
font-size: 0.74rem; font-weight: 700; transition: 0.2s var(--ease);
|
|
395
|
+
}
|
|
396
|
+
.genre-chip.active, .genre-chip:hover {
|
|
397
|
+
color: var(--text); border-color: var(--accent);
|
|
398
|
+
background: color-mix(in srgb, var(--accent) 12%, transparent);
|
|
399
|
+
}
|
|
400
|
+
.select-mini {
|
|
401
|
+
background: rgba(0, 0, 0, 0.32); border: 1px solid var(--line);
|
|
402
|
+
color: var(--text); border-radius: 10px; padding: 9px 12px;
|
|
403
|
+
font-size: 0.82rem; outline: none;
|
|
404
|
+
}
|
|
405
|
+
.select-mini:focus { border-color: var(--accent); }
|
|
406
|
+
|
|
407
|
+
.movie-grid {
|
|
408
|
+
display: grid; gap: 16px;
|
|
409
|
+
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
|
410
|
+
}
|
|
411
|
+
.movie-card {
|
|
412
|
+
position: relative; border-radius: var(--radius); overflow: hidden;
|
|
413
|
+
background: var(--panel-2); border: 1px solid var(--line);
|
|
414
|
+
transition: 0.3s var(--ease); cursor: pointer;
|
|
415
|
+
}
|
|
416
|
+
.movie-card:hover {
|
|
417
|
+
transform: translateY(-5px); border-color: var(--accent);
|
|
418
|
+
box-shadow: 0 14px 40px rgba(0, 0, 0, 0.5), 0 0 26px color-mix(in srgb, var(--accent) 15%, transparent);
|
|
419
|
+
}
|
|
420
|
+
.movie-poster {
|
|
421
|
+
position: relative; aspect-ratio: 2 / 3; overflow: hidden;
|
|
422
|
+
background:
|
|
423
|
+
radial-gradient(100% 120% at 50% 0%, color-mix(in srgb, var(--mcolor) 38%, transparent), transparent 60%),
|
|
424
|
+
linear-gradient(160deg, #241a12, #100a07);
|
|
425
|
+
display: grid; place-items: center;
|
|
426
|
+
}
|
|
427
|
+
.movie-poster img {
|
|
428
|
+
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
429
|
+
object-fit: cover; transition: 0.5s var(--ease);
|
|
430
|
+
z-index: 1;
|
|
431
|
+
}
|
|
432
|
+
.movie-card:hover .movie-poster img { transform: scale(1.06); }
|
|
433
|
+
.movie-fallback {
|
|
434
|
+
font-size: 2.6rem; font-weight: 900;
|
|
435
|
+
color: color-mix(in srgb, var(--mcolor) 75%, #fff);
|
|
436
|
+
text-shadow: 0 0 28px color-mix(in srgb, var(--mcolor) 55%, transparent);
|
|
437
|
+
}
|
|
438
|
+
.movie-play {
|
|
439
|
+
position: absolute; inset: 0; display: grid; place-items: center;
|
|
440
|
+
background: rgba(0, 0, 0, 0.25); opacity: 0; transition: 0.25s var(--ease);
|
|
441
|
+
}
|
|
442
|
+
.movie-play i {
|
|
443
|
+
width: 52px; height: 52px; border-radius: 50%;
|
|
444
|
+
background: linear-gradient(135deg, var(--accent), var(--accent2));
|
|
445
|
+
color: var(--on-accent); display: grid; place-items: center;
|
|
446
|
+
font-style: normal; font-size: 1.1rem;
|
|
447
|
+
box-shadow: 0 10px 30px color-mix(in srgb, var(--accent) 45%, transparent);
|
|
448
|
+
transform: scale(0.7); transition: 0.25s var(--ease);
|
|
449
|
+
}
|
|
450
|
+
.movie-card:hover .movie-play { opacity: 1; }
|
|
451
|
+
.movie-card:hover .movie-play i { transform: scale(1); }
|
|
452
|
+
.movie-info { padding: 12px 13px 14px; }
|
|
453
|
+
.movie-title { font-weight: 800; font-size: 0.92rem; line-height: 1.25; }
|
|
454
|
+
.movie-genre { font-size: 0.7rem; color: var(--muted); letter-spacing: 0.6px; margin-top: 3px; text-transform: uppercase; }
|
|
455
|
+
.movie-status { position: absolute; top: 9px; left: 9px; display: flex; gap: 5px; z-index: 2; }
|
|
456
|
+
.movie-status span {
|
|
457
|
+
background: rgba(0, 0, 0, 0.6); border: 1px solid rgba(255, 255, 255, 0.12);
|
|
458
|
+
color: #fff; font-size: 0.58rem; font-weight: 800; letter-spacing: 0.8px;
|
|
459
|
+
padding: 3px 8px; border-radius: 99px; backdrop-filter: blur(6px);
|
|
460
|
+
}
|
|
461
|
+
.movie-status .fav {
|
|
462
|
+
background: color-mix(in srgb, var(--accent) 75%, #000); border-color: transparent;
|
|
463
|
+
}
|
|
464
|
+
.movie-tools {
|
|
465
|
+
position: absolute; top: 9px; right: 9px; display: flex; gap: 5px;
|
|
466
|
+
opacity: 0; transition: 0.2s var(--ease); z-index: 3;
|
|
467
|
+
}
|
|
468
|
+
.movie-card:hover .movie-tools { opacity: 1; }
|
|
469
|
+
.movie-tools .icon-btn { width: 28px; height: 28px; font-size: 0.68rem; background: rgba(0, 0, 0, 0.55); }
|
|
470
|
+
.movie-cont-pct {
|
|
471
|
+
display: inline-block; margin-top: 6px; padding: 2px 7px;
|
|
472
|
+
font-size: 0.66rem; font-weight: 800; color: var(--accent2);
|
|
473
|
+
background: color-mix(in srgb, var(--accent) 14%, transparent);
|
|
474
|
+
border-radius: 99px;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.empty-state {
|
|
478
|
+
text-align: center; padding: 46px 20px;
|
|
479
|
+
background: rgba(255, 255, 255, 0.02); border: 1px dashed var(--line-2);
|
|
480
|
+
border-radius: var(--radius); margin-top: 8px;
|
|
481
|
+
}
|
|
482
|
+
.empty-art { font-size: 3rem; margin-bottom: 10px; }
|
|
483
|
+
.empty-state h4 { font-size: 1.15rem; margin-bottom: 8px; }
|
|
484
|
+
.empty-state p { color: var(--muted); max-width: 44ch; margin: 0 auto 18px; font-size: 0.92rem; }
|
|
485
|
+
/* ─────────── boot splash ─────────── */
|
|
486
|
+
|
|
487
|
+
.boot {
|
|
488
|
+
position: fixed; inset: 0; z-index: 200;
|
|
489
|
+
background: radial-gradient(90% 90% at 50% 40%, #14100b, #070503 80%);
|
|
490
|
+
display: grid; place-items: center; transition: opacity 0.55s var(--ease);
|
|
491
|
+
}
|
|
492
|
+
.boot.done { opacity: 0; pointer-events: none; }
|
|
493
|
+
.boot-inner { text-align: center; padding: 20px; }
|
|
494
|
+
.boot-logo {
|
|
495
|
+
width: 84px; height: 84px; margin: 0 auto 22px;
|
|
496
|
+
border-radius: 22px; display: grid; place-items: center; font-size: 38px;
|
|
497
|
+
background: linear-gradient(135deg, var(--accent), var(--accent2));
|
|
498
|
+
color: var(--on-accent);
|
|
499
|
+
box-shadow: 0 0 60px color-mix(in srgb, var(--accent) 55%, transparent);
|
|
500
|
+
animation: bootPulse 1.6s ease-in-out infinite;
|
|
501
|
+
}
|
|
502
|
+
@keyframes bootPulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.08); } }
|
|
503
|
+
.boot-title { font-size: 2rem; letter-spacing: 8px; margin-bottom: 16px; }
|
|
504
|
+
.boot-type {
|
|
505
|
+
font-family: 'JetBrains Mono', monospace; font-size: 0.8rem;
|
|
506
|
+
color: var(--accent2); min-height: 1.3em; margin-bottom: 22px;
|
|
507
|
+
}
|
|
508
|
+
.boot-bar {
|
|
509
|
+
width: 260px; height: 5px; border-radius: 99px; margin: 0 auto;
|
|
510
|
+
background: rgba(255, 255, 255, 0.08); overflow: hidden;
|
|
511
|
+
}
|
|
512
|
+
.boot-bar i {
|
|
513
|
+
display: block; height: 100%; width: 0%;
|
|
514
|
+
background: linear-gradient(90deg, var(--accent), var(--accent2));
|
|
515
|
+
border-radius: 99px; transition: width 0.25s ease;
|
|
516
|
+
box-shadow: 0 0 14px var(--accent);
|
|
517
|
+
}
|
|
518
|
+
#boot-skip { margin-top: 26px; }
|
|
519
|
+
|
|
520
|
+
/* ─────────── footer ─────────── */
|
|
521
|
+
|
|
522
|
+
.foot {
|
|
523
|
+
max-width: 1220px; margin: 0 auto; padding: 26px 22px 46px;
|
|
524
|
+
display: flex; justify-content: space-between; align-items: center; gap: 14px;
|
|
525
|
+
flex-wrap: wrap; color: var(--dim); font-size: 0.78rem;
|
|
526
|
+
border-top: 1px solid var(--line); letter-spacing: 0.6px;
|
|
527
|
+
}
|
|
528
|
+
.foot-hint kbd {
|
|
529
|
+
color: var(--accent2); border-color: var(--line-2); font-size: 0.72rem;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/* ─────────── customizer ─────────── */
|
|
533
|
+
|
|
534
|
+
.customizer {
|
|
535
|
+
position: fixed; top: 0; right: 0; bottom: 0; z-index: 120;
|
|
536
|
+
width: min(340px, 92vw);
|
|
537
|
+
background: rgba(16, 11, 8, 0.9);
|
|
538
|
+
backdrop-filter: blur(22px) saturate(1.4);
|
|
539
|
+
border-left: 1px solid var(--line);
|
|
540
|
+
transform: translateX(105%);
|
|
541
|
+
transition: transform 0.4s var(--ease);
|
|
542
|
+
display: flex; flex-direction: column;
|
|
543
|
+
box-shadow: -30px 0 80px rgba(0, 0, 0, 0.5);
|
|
544
|
+
}
|
|
545
|
+
.customizer.open { transform: translateX(0); }
|
|
546
|
+
.customizer-head {
|
|
547
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
548
|
+
padding: 18px 20px; border-bottom: 1px solid var(--line);
|
|
549
|
+
}
|
|
550
|
+
.customizer-head h4 { font-size: 1.05rem; letter-spacing: 0.4px; }
|
|
551
|
+
.cz-scroll { padding: 20px; overflow-y: auto; display: flex; flex-direction: column; gap: 14px; }
|
|
552
|
+
.cz-label {
|
|
553
|
+
font-size: 0.66rem; letter-spacing: 2px; text-transform: uppercase;
|
|
554
|
+
color: var(--muted); font-weight: 700;
|
|
555
|
+
}
|
|
556
|
+
.swatches { display: flex; gap: 10px; flex-wrap: wrap; }
|
|
557
|
+
.swatch {
|
|
558
|
+
width: 38px; height: 38px; border-radius: 12px; border: 2px solid transparent;
|
|
559
|
+
background: var(--sw); box-shadow: 0 0 18px color-mix(in srgb, var(--sw) 45%, transparent);
|
|
560
|
+
transition: 0.2s var(--ease);
|
|
561
|
+
}
|
|
562
|
+
.swatch:hover { transform: scale(1.12); }
|
|
563
|
+
.swatch.active { border-color: #fff; transform: scale(1.12); }
|
|
564
|
+
.cz-row { display: flex; align-items: center; gap: 12px; }
|
|
565
|
+
input[type='color'] {
|
|
566
|
+
width: 46px; height: 34px; border: 1px solid var(--line); border-radius: 9px;
|
|
567
|
+
background: transparent; padding: 2px; cursor: pointer;
|
|
568
|
+
}
|
|
569
|
+
.cz-hex {
|
|
570
|
+
font-family: 'JetBrains Mono', monospace; font-size: 0.78rem; color: var(--accent2);
|
|
571
|
+
}
|
|
572
|
+
input[type='range'] {
|
|
573
|
+
width: 100%; accent-color: var(--accent); cursor: pointer;
|
|
574
|
+
}
|
|
575
|
+
.cz-toggles { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
576
|
+
.cz-toggle { display: inline-flex; align-items: center; gap: 6px; font-size: 0.82rem; color: var(--muted); }
|
|
577
|
+
.cz-toggle input { accent-color: var(--accent); width: 15px; height: 15px; }
|
|
578
|
+
.cz-note { font-size: 0.72rem; color: var(--dim); text-align: center; }
|
|
579
|
+
/* ─────────── modals ─────────── */
|
|
580
|
+
|
|
581
|
+
.modal {
|
|
582
|
+
position: fixed; inset: 0; z-index: 150;
|
|
583
|
+
background: rgba(5, 3, 2, 0.82);
|
|
584
|
+
backdrop-filter: blur(14px);
|
|
585
|
+
display: grid; place-items: center;
|
|
586
|
+
animation: fadeIn 0.25s var(--ease);
|
|
587
|
+
}
|
|
588
|
+
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
589
|
+
|
|
590
|
+
.launcher-frame-wrap, .player-stage {
|
|
591
|
+
width: 100%; height: 100vh; overflow: hidden;
|
|
592
|
+
}
|
|
593
|
+
.launcher-bar, .player-bar {
|
|
594
|
+
position: absolute; top: 0; left: 0; right: 0; z-index: 5;
|
|
595
|
+
display: flex; align-items: center; gap: 10px;
|
|
596
|
+
padding: 10px 16px;
|
|
597
|
+
background: rgba(10, 7, 5, 0.75);
|
|
598
|
+
backdrop-filter: blur(14px);
|
|
599
|
+
border-bottom: 1px solid var(--line);
|
|
600
|
+
}
|
|
601
|
+
.launcher-title, .player-title {
|
|
602
|
+
font-weight: 700; font-size: 0.88rem; white-space: nowrap;
|
|
603
|
+
overflow: hidden; text-overflow: ellipsis; max-width: 40vw;
|
|
604
|
+
color: var(--accent2);
|
|
605
|
+
}
|
|
606
|
+
.launcher-actions { margin-left: auto; display: flex; gap: 7px; align-items: center; }
|
|
607
|
+
.launcher-actions .launcher-title { max-width: 30vw; }
|
|
608
|
+
#launcher-modal .modal-inner { width: 100%; }
|
|
609
|
+
#launcher-modal { display: flex; }
|
|
610
|
+
#launcher-modal .launcher-frame-wrap {
|
|
611
|
+
margin-top: 0; height: calc(100vh);
|
|
612
|
+
display: flex;
|
|
613
|
+
}
|
|
614
|
+
#launcher-frame {
|
|
615
|
+
flex: 1; border: none; height: 100%; width: 100%;
|
|
616
|
+
background: #000;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
#player-modal { display: flex; }
|
|
620
|
+
.player-bar { position: absolute; }
|
|
621
|
+
.player-stage {
|
|
622
|
+
width: 100%; display: grid; place-items: center;
|
|
623
|
+
padding-top: 58px;
|
|
624
|
+
}
|
|
625
|
+
.player-stage video {
|
|
626
|
+
max-width: 84vw; max-height: calc(100vh - 80px); border-radius: var(--radius);
|
|
627
|
+
box-shadow: 0 30px 90px rgba(0, 0, 0, 0.6); outline: none;
|
|
628
|
+
}
|
|
629
|
+
.player-stage iframe {
|
|
630
|
+
width: 84vw; height: calc(84vw * 0.5625);
|
|
631
|
+
max-height: calc(100vh - 90px); border: none; border-radius: var(--radius);
|
|
632
|
+
background: #000;
|
|
633
|
+
}
|
|
634
|
+
@media (max-height: 720px) {
|
|
635
|
+
.player-stage iframe { height: calc(100vh - 90px); }
|
|
636
|
+
}
|
|
637
|
+
.player-error {
|
|
638
|
+
max-width: 46ch; text-align: center; color: var(--muted);
|
|
639
|
+
padding: 30px; border-radius: var(--radius);
|
|
640
|
+
background: var(--panel-2); border: 1px dashed var(--line-2);
|
|
641
|
+
}
|
|
642
|
+
.player-error a { color: var(--accent2); text-decoration: underline; }
|
|
643
|
+
|
|
644
|
+
.form-card {
|
|
645
|
+
width: min(440px, 92vw); background: var(--panel-solid);
|
|
646
|
+
border: 1px solid var(--line); border-radius: calc(var(--radius) + 6px);
|
|
647
|
+
padding: 26px; display: grid; gap: 15px;
|
|
648
|
+
max-height: 90vh; overflow-y: auto;
|
|
649
|
+
}
|
|
650
|
+
.form-card h4 { font-size: 1.2rem; }
|
|
651
|
+
.form-field { display: grid; gap: 6px; }
|
|
652
|
+
.form-field label { font-size: 0.78rem; color: var(--muted); font-weight: 600; }
|
|
653
|
+
.form-field input {
|
|
654
|
+
background: rgba(0, 0, 0, 0.32); border: 1px solid var(--line);
|
|
655
|
+
border-radius: 10px; padding: 11px 14px; font-size: 0.92rem; outline: none;
|
|
656
|
+
transition: 0.22s var(--ease);
|
|
657
|
+
}
|
|
658
|
+
.form-field input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent); }
|
|
659
|
+
.form-row { display: flex; justify-content: flex-end; gap: 10px; }
|
|
660
|
+
|
|
661
|
+
/* ─────────── toasts ─────────── */
|
|
662
|
+
|
|
663
|
+
#toast-zone {
|
|
664
|
+
position: fixed; right: 18px; bottom: 18px; z-index: 300;
|
|
665
|
+
display: flex; flex-direction: column; gap: 10px; pointer-events: none;
|
|
666
|
+
}
|
|
667
|
+
.toast {
|
|
668
|
+
background: rgba(18, 12, 8, 0.92);
|
|
669
|
+
border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
|
|
670
|
+
border-left: 3px solid var(--accent);
|
|
671
|
+
color: var(--text); padding: 12px 18px; border-radius: 12px;
|
|
672
|
+
font-size: 0.85rem; max-width: 340px;
|
|
673
|
+
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5), 0 0 20px color-mix(in srgb, var(--accent) 15%, transparent);
|
|
674
|
+
backdrop-filter: blur(12px);
|
|
675
|
+
animation: toastIn 0.35s var(--ease);
|
|
676
|
+
}
|
|
677
|
+
.toast.out { animation: toastOut 0.35s var(--ease) forwards; }
|
|
678
|
+
@keyframes toastIn { from { transform: translateX(120%); opacity: 0; } to { transform: none; opacity: 1; } }
|
|
679
|
+
@keyframes toastOut { to { transform: translateX(120%); opacity: 0; } }
|
|
680
|
+
|
|
681
|
+
/* ─────────── turbo / confetti ─────────── */
|
|
682
|
+
|
|
683
|
+
.turbo { animation: turboBg 0.5s ease infinite alternate; }
|
|
684
|
+
@keyframes turboBg {
|
|
685
|
+
to { filter: hue-rotate(32deg) saturate(1.6); }
|
|
686
|
+
}
|
|
687
|
+
.confetti-piece {
|
|
688
|
+
position: fixed; width: 9px; height: 14px; z-index: 400;
|
|
689
|
+
border-radius: 2px; pointer-events: none;
|
|
690
|
+
animation: confettiFall linear forwards;
|
|
691
|
+
}
|
|
692
|
+
@keyframes confettiFall {
|
|
693
|
+
0% { transform: translateY(-10vh) rotate(0deg); opacity: 1; }
|
|
694
|
+
100% { transform: translateY(110vh) rotate(720deg); opacity: 0.4; }
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/* ─────────── work mode ─────────── */
|
|
698
|
+
|
|
699
|
+
body[data-work='on'] .hero,
|
|
700
|
+
body[data-work='on'] #tunnel,
|
|
701
|
+
body[data-work='on'] #games,
|
|
702
|
+
body[data-work='on'] #movies,
|
|
703
|
+
body[data-work='on'] #continue-panel,
|
|
704
|
+
body[data-work='on'] .foot-hint,
|
|
705
|
+
body[data-work='on'] a[href='#tunnel'],
|
|
706
|
+
body[data-work='on'] a[href='#games'],
|
|
707
|
+
body[data-work='on'] a[href='#movies'] { display: none; }
|
|
708
|
+
body[data-work='on'] #work-view { display: block; }
|
|
709
|
+
|
|
710
|
+
#work-view { scroll-margin-top: 90px; }
|
|
711
|
+
.work-grid { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
|
|
712
|
+
.work-card {
|
|
713
|
+
background: var(--panel-2); border: 1px solid var(--line);
|
|
714
|
+
border-radius: var(--radius); padding: 18px;
|
|
715
|
+
}
|
|
716
|
+
.work-card h4 { font-size: 0.95rem; margin: 0 0 12px; color: var(--muted); }
|
|
717
|
+
.focus-timer {
|
|
718
|
+
font-family: 'JetBrains Mono', monospace; font-size: 1.9rem; font-weight: 700;
|
|
719
|
+
color: var(--accent2); letter-spacing: 1px;
|
|
720
|
+
}
|
|
721
|
+
.timer-controls { display: flex; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; }
|
|
722
|
+
.todo-list { list-style: none; padding: 0; margin-top: 14px; max-height: 220px; overflow-y: auto; }
|
|
723
|
+
.todo-list li {
|
|
724
|
+
display: flex; align-items: center; gap: 10px; padding: 8px 12px;
|
|
725
|
+
border-radius: 10px; background: rgba(255, 255, 255, 0.045); margin-bottom: 6px;
|
|
726
|
+
}
|
|
727
|
+
.todo-list li.done span { text-decoration: line-through; opacity: 0.55; }
|
|
728
|
+
.todo-list input[type='checkbox'] { accent-color: var(--accent); width: 16px; height: 16px; cursor: pointer; }
|
|
729
|
+
.todo-list .icon-btn { width: 26px; height: 26px; font-size: 0.66rem; }
|
|
730
|
+
|
|
731
|
+
/* ─────────── responsive ─────────── */
|
|
732
|
+
|
|
733
|
+
@media (max-width: 720px) {
|
|
734
|
+
.nav-links { display: none; }
|
|
735
|
+
.hero { padding-top: 46px; }
|
|
736
|
+
.game-grid { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); }
|
|
737
|
+
.movie-grid { grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); }
|
|
738
|
+
.panel { padding: 18px; }
|
|
739
|
+
.player-stage video { max-width: 96vw; }
|
|
740
|
+
.player-stage iframe { width: 96vw; }
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
@media (prefers-reduced-motion: reduce) {
|
|
744
|
+
*, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }
|
|
745
|
+
}
|
|
746
|
+
body[data-anim='off'] *, body[data-anim='off'] *::before, body[data-anim='off'] *::after {
|
|
747
|
+
animation: none !important;
|
|
748
|
+
scroll-behavior: auto;
|
|
749
|
+
}
|
|
214
750
|
</style>
|
|
751
|
+
|
|
752
|
+
<base target="_blank">
|
|
753
|
+
<base target="_blank">
|
|
215
754
|
</head>
|
|
216
755
|
<body data-bg="aurora">
|
|
217
756
|
|
|
218
|
-
<!--
|
|
219
|
-
<div id="security-warning">
|
|
220
|
-
<div>
|
|
221
|
-
<h2>⚠️ UNAUTHORIZED ACCESS DETECTED ⚠️</h2>
|
|
222
|
-
<p>Developer tools usage is not permitted.</p>
|
|
223
|
-
<p>This activity has been logged.</p>
|
|
224
|
-
</div>
|
|
225
|
-
</div>
|
|
226
|
-
|
|
227
|
-
<!-- Background layers -->
|
|
757
|
+
<!-- background layers -->
|
|
228
758
|
<div class="bg-wrap" aria-hidden="true">
|
|
229
759
|
<canvas id="bg-canvas"></canvas>
|
|
230
760
|
<div class="blob blob-a"></div>
|
|
@@ -234,7 +764,7 @@ code, kbd {
|
|
|
234
764
|
<div class="bg-noise"></div>
|
|
235
765
|
</div>
|
|
236
766
|
|
|
237
|
-
<!--
|
|
767
|
+
<!-- boot splash -->
|
|
238
768
|
<div id="boot" class="boot">
|
|
239
769
|
<div class="boot-inner">
|
|
240
770
|
<div class="boot-logo">▲</div>
|
|
@@ -245,7 +775,7 @@ code, kbd {
|
|
|
245
775
|
</div>
|
|
246
776
|
</div>
|
|
247
777
|
|
|
248
|
-
<!--
|
|
778
|
+
<!-- nav -->
|
|
249
779
|
<header class="nav" id="nav">
|
|
250
780
|
<div class="nav-inner">
|
|
251
781
|
<a class="brand" href="#top">
|
|
@@ -256,356 +786,1366 @@ code, kbd {
|
|
|
256
786
|
<nav class="nav-links" id="nav-links">
|
|
257
787
|
<a href="#games" class="nav-link">Games</a>
|
|
258
788
|
<a href="#movies" class="nav-link">Movies</a>
|
|
259
|
-
<button class="nav-link btn-link" id="nav-work" type="button">Work</button>
|
|
789
|
+
<button class="nav-link btn-link" id="nav-work" type="button" title="Work Mode hides the vault (hotkey H)">Work</button>
|
|
260
790
|
<button class="nav-link btn-link" id="nav-customize" type="button">Customize</button>
|
|
261
791
|
</nav>
|
|
262
792
|
<div class="nav-side">
|
|
263
793
|
<span class="nav-clock" id="nav-clock">--:--</span>
|
|
264
|
-
<span class="profile-pill" id="profile-pill">DRIVER</span>
|
|
794
|
+
<span class="profile-pill" id="profile-pill" title="Your driver name — click to change">DRIVER</span>
|
|
265
795
|
</div>
|
|
266
796
|
</div>
|
|
267
797
|
</header>
|
|
268
798
|
|
|
269
799
|
<main id="top">
|
|
270
|
-
|
|
271
|
-
|
|
800
|
+
<!-- hero -->
|
|
801
|
+
<!-- work mode view -->
|
|
802
|
+
<section class="panel hidden" id="work-view">
|
|
272
803
|
<div class="panel-head">
|
|
273
|
-
<div>
|
|
274
|
-
|
|
275
|
-
|
|
804
|
+
<div><h3 class="panel-title">📖 Study Focus</h3>
|
|
805
|
+
<p class="panel-sub">Focus timer + task list. Work Mode hides the rest of the vault — press <kbd>H</kbd> to switch.</p></div>
|
|
806
|
+
<div class="focus-timer" id="focus-timer">25:00</div>
|
|
807
|
+
</div>
|
|
808
|
+
<div class="work-grid">
|
|
809
|
+
<div class="work-card">
|
|
810
|
+
<h4>Focus timer</h4>
|
|
811
|
+
<div class="timer-controls">
|
|
812
|
+
<button class="btn ghost btn-sm" id="timer-start" type="button">▶ start</button>
|
|
813
|
+
<button class="btn ghost btn-sm" id="timer-pause" type="button">⏸ pause</button>
|
|
814
|
+
<button class="btn ghost btn-sm" id="timer-reset" type="button">⟳ reset</button>
|
|
815
|
+
</div>
|
|
816
|
+
<select id="timer-length" class="select-mini">
|
|
817
|
+
<option value="1500">25:00 pomodoro</option>
|
|
818
|
+
<option value="600">10:00</option>
|
|
819
|
+
<option value="1800">30:00</option>
|
|
820
|
+
<option value="3600">60:00</option>
|
|
821
|
+
</select>
|
|
276
822
|
</div>
|
|
823
|
+
<div class="work-card">
|
|
824
|
+
<h4>Task list</h4>
|
|
825
|
+
<form class="tunnel-form" id="todo-form">
|
|
826
|
+
<input id="todo-input" class="tunnel-input" type="text" placeholder="add a task…" maxlength="90">
|
|
827
|
+
<button class="btn btn-accent btn-sm" type="submit">+</button>
|
|
828
|
+
</form>
|
|
829
|
+
<ul class="todo-list" id="todo-list"></ul>
|
|
830
|
+
<p class="cz-note">saved in your browser</p>
|
|
831
|
+
</div>
|
|
832
|
+
</div>
|
|
833
|
+
</section>
|
|
834
|
+
<section class="hero">
|
|
835
|
+
<div class="hero-eyebrow"><span class="pulse-dot"></span> systems online</div>
|
|
836
|
+
<h2 class="hero-title">Everything, <span class="accent-text gradient-text">unlocked.</span></h2>
|
|
837
|
+
<p class="hero-sub">IGNITE OS is a dashboard for browser games and movies streamed straight from your Google Drive.</p>
|
|
838
|
+
<div class="stat-strip" id="stat-strip">
|
|
839
|
+
<div class="stat"><b id="stat-games">0</b><span>games</span></div>
|
|
840
|
+
<div class="stat"><b id="stat-movies">0</b><span>movies</span></div>
|
|
841
|
+
<div class="stat"><b id="stat-local">0</b><span>local</span></div>
|
|
842
|
+
<div class="stat"><b id="stat-last">—</b><span>last launch</span></div>
|
|
843
|
+
</div>
|
|
844
|
+
</section>
|
|
845
|
+
|
|
846
|
+
<!-- games -->
|
|
847
|
+
<section class="panel" id="games">
|
|
848
|
+
<div class="panel-head">
|
|
849
|
+
<div><h3 class="panel-title">🎮 Game Vault</h3>
|
|
850
|
+
<p class="panel-sub">Play proxied web games or drop your own into <code>public/local-games/</code>.</p></div>
|
|
277
851
|
<span class="badge" id="games-edit-badge">+ new</span>
|
|
278
852
|
</div>
|
|
853
|
+
<div id="local-hint" class="local-hint hidden">
|
|
854
|
+
📁 no local games yet — drop any HTML5 game folder into <code>public/local-games/</code> and hit refresh. They run fully offline.
|
|
855
|
+
</div>
|
|
279
856
|
<div class="game-grid" id="game-grid"></div>
|
|
280
857
|
</section>
|
|
858
|
+
|
|
859
|
+
<!-- movies -->
|
|
860
|
+
<section class="panel" id="movies">
|
|
861
|
+
<div class="panel-head">
|
|
862
|
+
<div><h3 class="panel-title">🍿 Movies · from your Drive</h3>
|
|
863
|
+
<p class="panel-sub">Paste any Google Drive share link (file <em>or</em> folder) — IGNITE OS streams it in a full player.</p></div>
|
|
864
|
+
<span class="badge" id="movies-add">+ movie</span>
|
|
865
|
+
</div>
|
|
866
|
+
<div class="movie-toolbar">
|
|
867
|
+
<input id="movie-search" class="search-input" type="text" placeholder="search movies…">
|
|
868
|
+
<div class="genre-chips" id="genre-chips"></div>
|
|
869
|
+
<select id="movie-sort" class="select-mini">
|
|
870
|
+
<option value="added">newest first</option>
|
|
871
|
+
<option value="title">title A→Z</option>
|
|
872
|
+
<option value="watched">unwatched first</option>
|
|
873
|
+
</select>
|
|
874
|
+
</div>
|
|
875
|
+
<div class="movie-grid" id="movie-grid"></div>
|
|
876
|
+
<div class="empty-state hidden" id="movies-empty">
|
|
877
|
+
<div class="empty-art">🍻</div>
|
|
878
|
+
<h4>Your library is empty</h4>
|
|
879
|
+
<p>Hit <b>+ movie</b> and paste a Google Drive link, or load the demo shelf to see the player in action.</p>
|
|
880
|
+
<button class="btn ghost" id="demo-movies">load demo shelf</button>
|
|
881
|
+
</div>
|
|
882
|
+
</section>
|
|
883
|
+
|
|
884
|
+
<!-- continue watching -->
|
|
885
|
+
<section class="panel hidden" id="continue-panel">
|
|
886
|
+
<div class="panel-head"><div><h3 class="panel-title">▶ Continue watching</h3></div></div>
|
|
887
|
+
<div class="movie-grid" id="continue-grid"></div>
|
|
888
|
+
</section>
|
|
889
|
+
|
|
281
890
|
</main>
|
|
282
891
|
|
|
283
|
-
|
|
284
|
-
<
|
|
892
|
+
<footer class="foot">
|
|
893
|
+
<span>IGNITE OS · unblocked engine</span>
|
|
894
|
+
<span id="foot-clock"></span>
|
|
895
|
+
<span class="foot-hint"><kbd>/</kbd> search movies <kbd>G</kbd> games <kbd>M</kbd> movies <kbd>C</kbd> customize</span>
|
|
896
|
+
</footer>
|
|
897
|
+
<!-- customizer -->
|
|
898
|
+
<aside class="customizer" id="customizer" aria-label="Customizer">
|
|
899
|
+
<div class="customizer-head">
|
|
900
|
+
<h4>Customizer</h4>
|
|
901
|
+
<button class="icon-btn" id="cz-close" title="close">✕</button>
|
|
902
|
+
</div>
|
|
903
|
+
<div class="cz-scroll">
|
|
904
|
+
<label class="cz-label">theme preset</label>
|
|
905
|
+
<div class="swatches" id="cz-swatches">
|
|
906
|
+
<button class="swatch" data-preset="ember" style="--sw:#ff6b1a" title="Ember"></button>
|
|
907
|
+
<button class="swatch" data-preset="cobalt" style="--sw:#3b82f6" title="Cobalt"></button>
|
|
908
|
+
<button class="swatch" data-preset="violet" style="--sw:#a855f7" title="Violet"></button>
|
|
909
|
+
<button class="swatch" data-preset="inferno" style="--sw:#ff3d3d" title="Inferno"></button>
|
|
910
|
+
<button class="swatch" data-preset="ocean" style="--sw:#22d3ee" title="Ocean"></button>
|
|
911
|
+
<button class="swatch" data-preset="mint" style="--sw:#34d399" title="Mint"></button>
|
|
912
|
+
</div>
|
|
913
|
+
<label class="cz-label" for="cz-accent">accent color</label>
|
|
914
|
+
<div class="cz-row">
|
|
915
|
+
<input type="color" id="cz-accent" value="#ff6b1a">
|
|
916
|
+
<span class="cz-hex" id="cz-accent-hex">#ff6b1a</span>
|
|
917
|
+
</div>
|
|
918
|
+
<label class="cz-label" for="cz-accent2">secondary accent</label>
|
|
919
|
+
<div class="cz-row">
|
|
920
|
+
<input type="color" id="cz-accent2" value="#ffb26b">
|
|
921
|
+
<span class="cz-hex" id="cz-accent2-hex">#ffb26b</span>
|
|
922
|
+
</div>
|
|
923
|
+
<label class="cz-label" for="cz-bgtype">background</label>
|
|
924
|
+
<select id="cz-bgtype" class="select-mini">
|
|
925
|
+
<option value="aurora">aurora · animated</option>
|
|
926
|
+
<option value="grid">glow grid</option>
|
|
927
|
+
<option value="stars">night sky</option>
|
|
928
|
+
<option value="mesh">static mesh</option>
|
|
929
|
+
<option value="custom">custom image</option>
|
|
930
|
+
</select>
|
|
931
|
+
<div class="cz-row hidden" id="cz-bgurl-row">
|
|
932
|
+
<input id="cz-bgurl" class="search-input" type="text" placeholder="https://… background image URL">
|
|
933
|
+
</div>
|
|
934
|
+
<label class="cz-label" for="cz-bg-hue">background hue</label>
|
|
935
|
+
<input type="range" id="cz-bg-hue" min="0" max="360" value="28">
|
|
936
|
+
<label class="cz-label" for="cz-glow">glow intensity</label>
|
|
937
|
+
<input type="range" id="cz-glow" min="0" max="100" value="70">
|
|
938
|
+
<label class="cz-label" for="cz-blur">card blur</label>
|
|
939
|
+
<input type="range" id="cz-blur" min="0" max="30" value="14">
|
|
940
|
+
<label class="cz-label" for="cz-radius">card radius</label>
|
|
941
|
+
<input type="range" id="cz-radius" min="4" max="32" value="18">
|
|
942
|
+
<label class="cz-label" for="cz-font">font</label>
|
|
943
|
+
<select id="cz-font" class="select-mini">
|
|
944
|
+
<option value="outfit">Outfit</option>
|
|
945
|
+
<option value="grotesk">Space Grotesk</option>
|
|
946
|
+
<option value="mono">JetBrains Mono</option>
|
|
947
|
+
<option value="system">System</option>
|
|
948
|
+
</select>
|
|
949
|
+
<label class="cz-label">effects</label>
|
|
950
|
+
<div class="cz-toggles">
|
|
951
|
+
<label class="cz-toggle"><input type="checkbox" id="cz-particles" checked><span>particles</span></label>
|
|
952
|
+
<label class="cz-toggle"><input type="checkbox" id="cz-grid" checked><span>grid</span></label>
|
|
953
|
+
<label class="cz-toggle"><input type="checkbox" id="cz-noise" checked><span>noise</span></label>
|
|
954
|
+
<label class="cz-toggle"><input type="checkbox" id="cz-anim" checked><span>animations</span></label>
|
|
955
|
+
<label class="cz-toggle"><input type="checkbox" id="cz-sound"><span>sound fx</span></label>
|
|
956
|
+
<label class="cz-toggle"><input type="checkbox" id="cz-work"><span>work mode (H)</span></label>
|
|
957
|
+
</div>
|
|
958
|
+
<input id="cz-name" class="search-input" type="text" maxlength="20" placeholder="DRIVER">
|
|
959
|
+
<button class="btn ghost btn-block" id="cz-reset">reset everything</button>
|
|
960
|
+
<p class="cz-note">everything saves to your browser automatically</p>
|
|
961
|
+
</div>
|
|
962
|
+
</aside>
|
|
963
|
+
<!-- modals -->
|
|
964
|
+
<div class="modal hidden" id="launcher-modal" role="dialog" aria-modal="true">
|
|
285
965
|
<div class="launcher-bar">
|
|
286
966
|
<button class="icon-btn" id="launcher-back">←</button>
|
|
287
967
|
<span class="launcher-title" id="launcher-title">—</span>
|
|
288
968
|
<div class="launcher-actions">
|
|
289
969
|
<button class="btn ghost btn-sm" id="launcher-refresh">⟳</button>
|
|
970
|
+
<button class="btn ghost btn-sm" id="launcher-fs">⚬ fullscreen</button>
|
|
290
971
|
<button class="icon-btn" id="launcher-close">✕</button>
|
|
291
972
|
</div>
|
|
292
973
|
</div>
|
|
293
974
|
<div class="launcher-frame-wrap">
|
|
294
|
-
<iframe id="launcher-frame" sandbox="allow-scripts allow-same-origin allow-popups"></iframe>
|
|
975
|
+
<iframe id="launcher-frame" sandbox="allow-scripts allow-same-origin allow-popups allow-presentation allow-pointer-lock allow-forms allow-modals allow-downloads allow-top-navigation-by-user-activation"></iframe>
|
|
976
|
+
</div>
|
|
977
|
+
</div>
|
|
978
|
+
|
|
979
|
+
<div class="modal hidden" id="player-modal" role="dialog" aria-modal="true">
|
|
980
|
+
<div class="player-bar">
|
|
981
|
+
<button class="icon-btn" id="player-back">←</button>
|
|
982
|
+
<span class="launcher-title" id="player-title">—</span>
|
|
983
|
+
<div class="launcher-actions">
|
|
984
|
+
<button class="btn ghost btn-sm" id="player-toggle" title="switch player engine">⇤ engine</button>
|
|
985
|
+
<button class="btn ghost btn-sm" id="player-mark">✓ watched</button>
|
|
986
|
+
<button class="icon-btn" id="player-close">✕</button>
|
|
987
|
+
</div>
|
|
295
988
|
</div>
|
|
989
|
+
<div class="player-stage" id="player-stage"></div>
|
|
990
|
+
</div>
|
|
991
|
+
|
|
992
|
+
<div class="modal hidden" id="form-modal" role="dialog" aria-modal="true">
|
|
993
|
+
<form class="form-card" id="form-card">
|
|
994
|
+
<h4 id="form-title">Add movie</h4>
|
|
995
|
+
<div class="form-field">
|
|
996
|
+
<label>Title</label>
|
|
997
|
+
<input id="form-name" type="text" maxlength="80" required>
|
|
998
|
+
</div>
|
|
999
|
+
<div class="form-field">
|
|
1000
|
+
<label>Description <i>(optional)</i></label>
|
|
1001
|
+
<input id="form-desc" type="text" maxlength="200">
|
|
1002
|
+
</div>
|
|
1003
|
+
<div class="form-field">
|
|
1004
|
+
<label>Google Drive link · file or folder · or any direct video URL</label>
|
|
1005
|
+
<input id="form-link" type="text" placeholder="https://drive.google.com/file/d/…/view">
|
|
1006
|
+
</div>
|
|
1007
|
+
<div class="form-field">
|
|
1008
|
+
<label>Genre <i>(optional)</i></label>
|
|
1009
|
+
<input id="form-genre" type="text" placeholder="Action, Racing, Sci-Fi…">
|
|
1010
|
+
</div>
|
|
1011
|
+
<div class="form-field">
|
|
1012
|
+
<label>Poster image URL <i>(optional)</i></label>
|
|
1013
|
+
<input id="form-poster" type="text" placeholder="https://… cover art (Drive thumbnails work too)">
|
|
1014
|
+
</div>
|
|
1015
|
+
<div class="form-row">
|
|
1016
|
+
<button type="button" class="btn ghost" id="form-cancel">cancel</button>
|
|
1017
|
+
<button type="submit" class="btn btn-accent" id="form-submit">save</button>
|
|
1018
|
+
</div>
|
|
1019
|
+
<p class="cz-note" id="form-parse-hint"></p>
|
|
1020
|
+
</form>
|
|
296
1021
|
</div>
|
|
297
1022
|
|
|
1023
|
+
<div id="toast-zone"></div>
|
|
1024
|
+
|
|
298
1025
|
<script>
|
|
299
|
-
|
|
300
|
-
|
|
1026
|
+
/* IGNITE OS data — embedded */
|
|
1027
|
+
/* ═══════════════════════════════════════════════════════════════════
|
|
1028
|
+
IGNITE OS — data layer
|
|
1029
|
+
Games, theme presets and demo movies. The default games all play
|
|
1030
|
+
through the IGNITE OS proxy (/go/ form) or straight from /lg/ (local).
|
|
1031
|
+
Want more games? Just append entries here.
|
|
1032
|
+
═══════════════════════════════════════════════════════════════════ */
|
|
1033
|
+
|
|
1034
|
+
const THEME_PRESETS = {
|
|
1035
|
+
ember: { accent: '#ff6b1a', accent2: '#ffb26b', bgHue: 28, bgType: 'aurora' },
|
|
1036
|
+
cobalt: { accent: '#3b82f6', accent2: '#93c5fd', bgHue: 205, bgType: 'aurora' },
|
|
1037
|
+
violet: { accent: '#a855f7', accent2: '#d8b4fe', bgHue: 268, bgType: 'aurora' },
|
|
1038
|
+
inferno: { accent: '#ff3d3d', accent2: '#ffb347', bgHue: 4, bgType: 'grid' },
|
|
1039
|
+
ocean: { accent: '#22d3ee', accent2: '#a5f3fc', bgHue: 188, bgType: 'aurora' },
|
|
1040
|
+
mint: { accent: '#34d399', accent2: '#a7f3d0', bgHue: 150, bgType: 'aurora' },
|
|
1041
|
+
};
|
|
1042
|
+
|
|
1043
|
+
const DEFAULT_GAMES = [
|
|
1044
|
+
{
|
|
1045
|
+
id: 'Cookie Clicker', name: 'Cookie Clicker TEST',
|
|
1046
|
+
url: 'https://unpkg.com/edcube-cookie-clicker@5.0.0/index.html',
|
|
1047
|
+
cover: 'data:image/jpeg;base64,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',// ← optional image URL — letters disappear when set
|
|
1048
|
+
desc: 'cool',
|
|
1049
|
+
tags: ['cookie', '2D'], color: '#22d3ee',
|
|
1050
|
+
},
|
|
1051
|
+
{
|
|
1052
|
+
id: 'recoil', name: 'Recoil',
|
|
1053
|
+
url: 'https://generic-alpaca-325321.framer.app/',
|
|
1054
|
+
cover: 'https://fancade.com/images/5E9F40DA7F9C9FA8.jpg', // ← optional image URL — letters disappear when set
|
|
1055
|
+
desc: 'recoil like drivve mad',
|
|
1056
|
+
tags: ['closed-world', 'action', '2D'], color: '#ff6b1a',
|
|
1057
|
+
},
|
|
1058
|
+
{
|
|
1059
|
+
id: 'retro ball', name: 'Retro Ball',
|
|
1060
|
+
url: 'https://objectstorage.us-ashburn-1.oraclecloud.com/n/idzgyfi7awwr/b/bucket-20231001-1518-rb1.0/o/index.html',
|
|
1061
|
+
cover: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTfQEUbXopS95V30bru2UdgdystMD8ElzCDFzH6k6_pUQ&s', // ← optional image URL — letters disappear when set
|
|
1062
|
+
desc: 'touchdown',
|
|
1063
|
+
tags: ['sports', 'action'], color: '#a855f7',
|
|
1064
|
+
},
|
|
1065
|
+
{
|
|
1066
|
+
id: 'drift boss', name: 'Drift Boss',
|
|
1067
|
+
url: 'https://www.mathplayground.com/drift-boss-v3/index.html',
|
|
1068
|
+
cover: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTMW_L-ZhbnvWj1lwNrWeAGDaK0rG0lCw52zSIsTfTmWNZ9oOVhXvQ_oJRA&s=10', // ← optional image URL — letters disappear when set
|
|
1069
|
+
desc: 'drive',
|
|
1070
|
+
tags: ['car', 'arcade', '2D'], color: '#3b82f6',
|
|
1071
|
+
},
|
|
1072
|
+
{
|
|
1073
|
+
id: 'tiny fishing', name: 'Tiny fishing',
|
|
1074
|
+
url: 'https://generic-alpaca-325321.framer.app/page-3',
|
|
1075
|
+
cover: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR-I2lTQx2HkSPrPeyCNmAnGGd_c29Um34tPc8TpiyJjg&s=10', // ← optional image URL — letters disappear when set
|
|
1076
|
+
desc: 'works trust',
|
|
1077
|
+
tags: ['slow but works', '3D'], color: '#facc15',
|
|
1078
|
+
},
|
|
1079
|
+
{
|
|
1080
|
+
id: 'A Small world Cup', name: 'A Small World Cup',
|
|
1081
|
+
url: 'https://script.google.com/a/macros/mcpsmd.net/s/AKfycbwSK5IqLXbZppMz9znIJVycB4BZCHodE3uSgUF4C1fReEqmMSDYua81Ew7Rp-Cn85gojg/exec',
|
|
1082
|
+
cover: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTY4AGQkfiAEPBItQdxMlfKm0Wz7eUOzt-SsZUGejt7cA&s=10', // ← optional image URL — letters disappear when set
|
|
1083
|
+
desc: 'Soccer Skills — Euro 2021 edition. Curl it into the top corner.',
|
|
1084
|
+
tags: ['soccer', 'sports'], color: '#34d399',
|
|
1085
|
+
},
|
|
1086
|
+
{
|
|
1087
|
+
id: 'Air Hockey CUP', name: 'Air Hockey CUP',
|
|
1088
|
+
url: 'https://script.google.com/macros/s/AKfycbw7VnhYKWITlJo9p8qyT8ExOfvnjvizq57_jj7MzDnyJWTuHtNbee3NHoEoRhuS6eqo/exec',
|
|
1089
|
+
cover: 'https://play-lh.googleusercontent.com/EFevQ0zkoUL5FEPIsI8-yFKoK6QCyu3TSZRXEJiLkRaWrCidW3b6ZKNiNzuvfeaLKW9FyHeY3WYh4RR839Qanck', // ← optional image URL — letters disappear when set
|
|
1090
|
+
desc: 'basic hockey',
|
|
1091
|
+
tags: ['hockey', 'sports'], color: '#f43f5e',
|
|
1092
|
+
},
|
|
1093
|
+
{
|
|
1094
|
+
id: 'Slope REMAKE', name: 'Slope REMAKE',
|
|
1095
|
+
url: 'https://script.google.com/a/macros/mcpsmd.net/s/AKfycbyendSzfWzjivGUAoNgH5QDpJIxJDyJvSYwXWxcV_8gl9xkdTMV2BvDpjiWNw5pGaVT/exec',
|
|
1096
|
+
cover: 'https://play-lh.googleusercontent.com/Ev5gkPTONr-1k1r7erT0iiW-pUZoGUH_6FVQdD91e9ghSAqZRMTaFVGQW5Z6PLOfFgxZnJhdEBE2z-TgfZ1bsg', // ← optional image URL — letters disappear when set
|
|
1097
|
+
desc: 'a rolling ball ',
|
|
1098
|
+
tags: ['slope','3D'], color: '#fb7185',
|
|
1099
|
+
},
|
|
1100
|
+
{
|
|
1101
|
+
id: 'Subway Surfers', name: 'Subway Surfers',
|
|
1102
|
+
url: 'https://unpkg.com/subwaystpetersburg@1.0.0/index.html',
|
|
1103
|
+
cover: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTzwmhMeCmyvXSpjVLHUUCok1FZhyrP-D9klmlq8Pt-Ww&s=10', // ← optional image URL — letters disappear when set
|
|
1104
|
+
desc: 'run from cop ',
|
|
1105
|
+
tags: ['endless','cop'], color: '#fb7185',
|
|
1106
|
+
},
|
|
1107
|
+
{
|
|
1108
|
+
id: 'Bacon May Die', name: 'Bacon May Die',
|
|
1109
|
+
url: 'https://unpkg.com/schooluk-bacon-may-die@1.0.0/index.html',
|
|
1110
|
+
cover: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRG8Ra7wjHyaMeLnHp70ql98oi25b33UsPI63N-Jwbapw&s=10',
|
|
1111
|
+
desc: 'brawler — fight your way through.',
|
|
1112
|
+
tags: ['brawler','action'], color: '#fb7185',
|
|
1113
|
+
},
|
|
1114
|
+
{
|
|
1115
|
+
id: 'paper-fly', name: 'Paper Fly',
|
|
1116
|
+
url: 'https://unpkg.com/papergmfly@1.0.0/index.html',
|
|
1117
|
+
cover: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQQFa7pNRwbmpuk0rwFYmYYjwAdFMI6qH18R1rO_rd8XQ&s=10',
|
|
1118
|
+
desc: 'fold, flick and fly.',
|
|
1119
|
+
tags: ['arcade','2D'], color: '#22d3ee',
|
|
1120
|
+
},
|
|
1121
|
+
{
|
|
1122
|
+
id: 'slope-505', name: 'Slope 505',
|
|
1123
|
+
url: 'https://unpkg.com/sloeplus@1.0.0/index.html',
|
|
1124
|
+
cover: 'https://play-lh.googleusercontent.com/Ev5gkPTONr-1k1r7erT0iiW-pUZoGUH_6FVQdD91e9ghSAqZRMTaFVGQW5Z6PLOfFgxZnJhdEBE2z-TgfZ1bsg',
|
|
1125
|
+
desc: 'the rolling ball, remade.',
|
|
1126
|
+
tags: ['slope','3D'], color: '#fb7185',
|
|
1127
|
+
},
|
|
1128
|
+
{
|
|
1129
|
+
id: 'boat-game', name: 'Boat Game',
|
|
1130
|
+
url: 'https://unpkg.com/boateman@1.0.0/index.html',
|
|
1131
|
+
cover: 'https://play-lh.googleusercontent.com/k80-V4CxUpsoNUTiZCasfAF4yTxy428anUQiM_AieHnLRMq2qmCPIHIYWT8dYATGyo1eTjZLZEa3aa3b4kyingA',
|
|
1132
|
+
desc: 'captain your boat.',
|
|
1133
|
+
tags: ['boat','arcade'], color: '#3b82f6',
|
|
1134
|
+
},
|
|
1135
|
+
{
|
|
1136
|
+
id: '1v1-lol', name: '1v1.lol',
|
|
1137
|
+
url: 'https://unpkg.com/edcube-1v1-lol@5.0.0/index.html',
|
|
1138
|
+
cover: 'https://play-lh.googleusercontent.com/NAE7nd8mxERLlT-eIM68aEQi1RthPUzVd8KN_C-RPmvDgBLJ-C_kBHrBQFYPi8wQwr_nkxGUadoS0xStbU_jc7s=w240-h480-rw',
|
|
1139
|
+
desc: 'build and battle, 1v1.',
|
|
1140
|
+
tags: ['shooter','multiplayer'], color: '#22d3ee',
|
|
1141
|
+
},
|
|
1142
|
+
{
|
|
1143
|
+
id: 'crossy-road', name: 'Crossy Road',
|
|
1144
|
+
url: 'https://unpkg.com/edcube-crossy-road@5.0.0/index.html',
|
|
1145
|
+
cover: 'https://play-lh.googleusercontent.com/LX__Y8tKCvkvEgboZtQfDsvYiNFa6kwWSpqObipZI2MLK5rFVd5tEU8xgMNoMj1ICOf9TO7PFoFmiKGeIpf1=w240-h480-rw',
|
|
1146
|
+
desc: 'cross the road, dodge the traffic.',
|
|
1147
|
+
tags: ['arcade','crossing'], color: '#facc15',
|
|
1148
|
+
},
|
|
1149
|
+
{
|
|
1150
|
+
id: 'basketball-stars', name: 'Basketball Stars',
|
|
1151
|
+
url: 'https://unpkg.com/edcube-basketball-stars@5.0.0/index.html',
|
|
1152
|
+
cover: 'https://m.media-amazon.com/images/I/81BL1Hx2KuL.png',
|
|
1153
|
+
desc: 'sink threes and take the crown.',
|
|
1154
|
+
tags: ['sports','basketball'], color: '#ff6b1a',
|
|
1155
|
+
},
|
|
1156
|
+
{
|
|
1157
|
+
id: 'bitlife', name: 'Bitlife',
|
|
1158
|
+
url: 'https://unpkg.com/edcube-bitlife@5.0.0/index.html',
|
|
1159
|
+
cover: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRZdo5cBqkv1uAcfCofpC-WAViocRNLlysohwHMV2Lqzw&s=10',
|
|
1160
|
+
desc: 'live a whole life, one tap at a time.',
|
|
1161
|
+
tags: ['sim','life'], color: '#34d399',
|
|
1162
|
+
},
|
|
1163
|
+
{
|
|
1164
|
+
id: 'tag', name: 'Tag',
|
|
1165
|
+
url: 'https://unpkg.com/edcube-tag@1.0.0/index.html',
|
|
1166
|
+
cover: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSFwGQ9vG6ZifxDpWHtuu8oSzYqll79xZ9rW_KwPwzCdA&s',
|
|
1167
|
+
desc: "don't get caught.",
|
|
1168
|
+
tags: ['platformer','2D'], color: '#facc15',
|
|
1169
|
+
},
|
|
1170
|
+
{
|
|
1171
|
+
id: 'block-blast', name: 'Block Blast',
|
|
1172
|
+
url: 'https://unpkg.com/edcube-block-blast@5.0.0/index.html',
|
|
1173
|
+
cover: 'https://imgs.crazygames.com/block-puzzle-master_1x1/20250924025352/block-puzzle-master_1x1-cover?format=auto&quality=70&metadata=none',
|
|
1174
|
+
desc: 'blast blocks, clear lines.',
|
|
1175
|
+
tags: ['puzzle'], color: '#a855f7',
|
|
1176
|
+
},
|
|
1177
|
+
{
|
|
1178
|
+
id: 'cut-the-rope', name: 'Cut the Rope',
|
|
1179
|
+
url: 'https://unpkg.com/edcube-cut-the-rope@5.0.0/index.html',
|
|
1180
|
+
cover: 'https://play-lh.googleusercontent.com/2bTmHHM7DBMVe7fLh16Em-J-tVBMFGL2HJCxPAMNF7gAYIfU6EZoYG1__lQ0cvFMEyp3GU9CGijZzEB1qJHF',
|
|
1181
|
+
desc: 'feed the monster, cut the rope.',
|
|
1182
|
+
tags: ['puzzle'], color: '#34d399',
|
|
1183
|
+
},
|
|
1184
|
+
];
|
|
1185
|
+
|
|
1186
|
+
const DEMO_MOVIES = [
|
|
1187
|
+
{ title: 'Spiderman: Far from home ', genre: 'Spiderman · Open Movie', desc: 'spiderman goes in new york idk',
|
|
1188
|
+
source: 'url', videoUrl: 'https://drive.google.com/file/d/1t62YY5V2WFqPA801cDECww1EQYk4fMnk/preview?t=11.299',
|
|
1189
|
+
poster: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSjdXbHSbJUSaJdgj45vDisJUiPj1qljxkOPbfojU3M1w&s=10' },
|
|
1190
|
+
{ title: 'Spiderman: Home comming', genre: 'spider · Open Movie', desc: 'wtf.',
|
|
1191
|
+
source: 'url', videoUrl: 'https://drive.google.com/file/d/1hUIx05G3N_ucg8SwdZHRt_5MdqLQZtai/preview?t=6',
|
|
1192
|
+
poster: 'https://resizing.flixster.com/-XZAfHZM39UwaGJIFWKAE8fS0ak=/v3/t/assets/p12798844_p_v8_ao.jpg' },
|
|
1193
|
+
{ title: 'F1', genre: ' Open Movie', desc: 'idk anything about it.',
|
|
1194
|
+
source: 'url', videoUrl: 'https://drive.google.com/file/d/1k_zRDt4Rn_9FotYJUfrhJZ0ZDKfwqQk1/preview',
|
|
1195
|
+
poster: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSr2SgmDLkFhLGuF1CnP_xJv1stiKvK027DkN6m6wdqdg&s=10' },
|
|
1196
|
+
{ title: 'OBBSESSION', genre: ' Open Movie', desc: 'idk this movie either',
|
|
1197
|
+
source: 'url', videoUrl: 'https://drive.google.com/file/d/1FEN16LwM5ZzTBSbBtkY95LrIzlsHXFx7/preview',
|
|
1198
|
+
poster: 'https://images.contentstack.io/v3/assets/blt13adb7e2033fcee5/blt5c8bcbfee5dade5c/6a2b47a8c284bac540ef3353/Obsession_keyart_poster_2000x3000.jpg?width=2560' },
|
|
1199
|
+
];
|
|
1200
|
+
|
|
1201
|
+
/* IGNITE OS app — embedded */
|
|
1202
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
1203
|
+
IGNITE OS — dashboard engine (zero deps)
|
|
1204
|
+
═══════════════════════════════════════════════════════════════ */
|
|
1205
|
+
(function () {
|
|
301
1206
|
'use strict';
|
|
302
1207
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
1208
|
+
const $ = (id) => document.getElementById(id);
|
|
1209
|
+
|
|
1210
|
+
// ── persistence ──
|
|
1211
|
+
const K = {
|
|
1212
|
+
games: 'emby.games.v1', movies: 'emby.movies.v2',
|
|
1213
|
+
prefs: 'emby.prefs.v1', profile: 'emby.profile.v1',
|
|
1214
|
+
last: 'emby.last.v1', overrides: 'emby.overrides.v1',
|
|
1215
|
+
};
|
|
1216
|
+
const store = {
|
|
1217
|
+
get(key, fallback) {
|
|
1218
|
+
try { const v = JSON.parse(localStorage.getItem(key)); return v === null || v === undefined ? fallback : v; }
|
|
1219
|
+
catch { return fallback; }
|
|
1220
|
+
},
|
|
1221
|
+
set(key, val) {
|
|
1222
|
+
try { localStorage.setItem(key, JSON.stringify(val)); } catch { /* ignore */ }
|
|
1223
|
+
},
|
|
1224
|
+
};
|
|
1225
|
+
|
|
1226
|
+
// ── state ──
|
|
1227
|
+
const state = {
|
|
1228
|
+
games: [],
|
|
1229
|
+
localGames: [],
|
|
1230
|
+
movies: store.get(K.movies, []),
|
|
1231
|
+
overrides: store.get(K.overrides, {}),
|
|
1232
|
+
prefs: Object.assign({
|
|
1233
|
+
preset: 'ember', accent: '#ff6b1a', accent2: '#ffb26b',
|
|
1234
|
+
bgType: 'aurora', bgHue: 28, bgUrl: '', glow: 70, blur: 14,
|
|
1235
|
+
radius: 18, font: 'outfit', particles: true, grid: true,
|
|
1236
|
+
noise: true, anim: true, sound: false, work: false,
|
|
1237
|
+
}, store.get(K.prefs, {})),
|
|
1238
|
+
profile: Object.assign({ name: 'DRIVER' }, store.get(K.profile, {})),
|
|
1239
|
+
movieFilter: '', movieGenre: '', movieSort: 'added',
|
|
1240
|
+
playerMovie: null, playerEngine: 'video',
|
|
1241
|
+
launcherUrl: '', formEditId: null, formMode: 'movie',
|
|
1242
|
+
keys: [], turbo: false,
|
|
1243
|
+
};
|
|
1244
|
+
|
|
1245
|
+
// ── proxy url helpers ──
|
|
1246
|
+
const serverBase = () => {
|
|
1247
|
+
try { return String(localStorage.getItem('emby.server') || '').replace(/\/+$/, ''); }
|
|
1248
|
+
catch { return ''; }
|
|
1249
|
+
};
|
|
1250
|
+
const proxify = (raw) => {
|
|
1251
|
+
let u = (raw || '').trim();
|
|
1252
|
+
if (!u) return '';
|
|
1253
|
+
if (!/^[a-z][a-z0-9+.-]*:/i.test(u)) u = 'https://' + u;
|
|
1254
|
+
return u;
|
|
1255
|
+
};
|
|
1256
|
+
const tunnel = (raw) => proxify(raw, '/go/');
|
|
1257
|
+
|
|
1258
|
+
// ── google drive parser ──
|
|
1259
|
+
const DRIVE_ID = /[A-Za-z0-9_-]{28,}/;
|
|
1260
|
+
function parseDrive(input) {
|
|
1261
|
+
const s = (input || '').trim();
|
|
1262
|
+
if (!s) return { kind: 'none' };
|
|
1263
|
+
if (/^[A-Za-z0-9_-]{28,}$/.test(s)) return { kind: 'file', id: s };
|
|
1264
|
+
let u;
|
|
1265
|
+
try { u = new URL(s); } catch { return { kind: 'url', url: s }; }
|
|
1266
|
+
const host = u.hostname.toLowerCase();
|
|
1267
|
+
if (host === 'drive.google.com' || host === 'drive.usercontent.google.com' || host === 'docs.google.com') {
|
|
1268
|
+
const path = u.pathname;
|
|
1269
|
+
const mFile = path.match(/\/file\/d\/([^/]+)/);
|
|
1270
|
+
const mFolder = path.match(/\/drive\/folders\/([^/]+)/);
|
|
1271
|
+
const idQ = u.searchParams.get('id');
|
|
1272
|
+
const folderQ = u.searchParams.get('folder');
|
|
1273
|
+
if (mFolder || folderQ) return { kind: 'folder', id: (mFolder ? mFolder[1] : folderQ) };
|
|
1274
|
+
if (mFile) return { kind: 'file', id: mFile[1] };
|
|
1275
|
+
if (idQ) return { kind: 'file', id: idQ };
|
|
1276
|
+
if (DRIVE_ID.test(path)) return { kind: 'file', id: path.match(DRIVE_ID)[0] };
|
|
315
1277
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
1278
|
+
return { kind: 'url', url: s };
|
|
1279
|
+
}
|
|
1280
|
+
const drivePreview = (id) => 'https://drive.google.com/file/d/' + id + '/preview';
|
|
1281
|
+
const driveRaw = (id) => 'https://drive.usercontent.google.com/download?id=' + id + '&export=download&confirm=t';
|
|
1282
|
+
const driveFolder = (id) => 'https://drive.google.com/embeddedfolderview?id=' + id + '#list';
|
|
1283
|
+
const driveThumb = (id) => 'https://drive.google.com/thumbnail?id=' + id + '&sz=w1000';
|
|
1284
|
+
|
|
1285
|
+
// ── toast ──
|
|
1286
|
+
function toast(msg, ms) {
|
|
1287
|
+
const zone = $('toast-zone');
|
|
1288
|
+
const t = document.createElement('div');
|
|
1289
|
+
t.className = 'toast'; t.textContent = msg;
|
|
1290
|
+
zone.appendChild(t);
|
|
1291
|
+
setTimeout(() => { t.classList.add('out'); setTimeout(() => t.remove(), 400); }, ms || 2600);
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
// ── micro sound fx (WebAudio, optional) ──
|
|
1295
|
+
let audioCtx = null;
|
|
1296
|
+
function beep(freq, dur, type) {
|
|
1297
|
+
if (!state.prefs.sound) return;
|
|
1298
|
+
try {
|
|
1299
|
+
audioCtx = audioCtx || new (window.AudioContext || window.webkitAudioContext)();
|
|
1300
|
+
const o = audioCtx.createOscillator(), g = audioCtx.createGain();
|
|
1301
|
+
o.type = type || 'sine'; o.frequency.value = freq;
|
|
1302
|
+
g.gain.setValueAtTime(0.06, audioCtx.currentTime);
|
|
1303
|
+
g.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + (dur || 0.12));
|
|
1304
|
+
o.connect(g); g.connect(audioCtx.destination);
|
|
1305
|
+
o.start(); o.stop(audioCtx.currentTime + (dur || 0.12));
|
|
1306
|
+
} catch { /* ignore */ }
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
// ── confetti ──
|
|
1310
|
+
function confetti(n) {
|
|
1311
|
+
const colors = ['#ff6b1a', '#ffb26b', '#ffd54f', '#ff8a65', '#ffffff', '#ffab91'];
|
|
1312
|
+
for (let i = 0; i < (n || 60); i++) {
|
|
1313
|
+
const p = document.createElement('i');
|
|
1314
|
+
p.className = 'confetti-piece';
|
|
1315
|
+
p.style.left = Math.random() * 100 + 'vw';
|
|
1316
|
+
p.style.background = colors[(Math.random() * colors.length) | 0];
|
|
1317
|
+
p.style.animationDuration = (1.6 + Math.random() * 1.8) + 's';
|
|
1318
|
+
p.style.animationDelay = (Math.random() * 0.35) + 's';
|
|
1319
|
+
document.body.appendChild(p);
|
|
1320
|
+
setTimeout(() => p.remove(), 4200);
|
|
323
1321
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
window.IGNITE = { tunnel, proxify };
|
|
1325
|
+
// ═══════════ theme / prefs ═══════════
|
|
1326
|
+
|
|
1327
|
+
const hexToRgb = (hex) => {
|
|
1328
|
+
const h = hex.replace('#', '');
|
|
1329
|
+
const n = parseInt(h.length === 3 ? h.split('').map((c) => c + c).join('') : h, 16);
|
|
1330
|
+
return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 };
|
|
1331
|
+
};
|
|
1332
|
+
|
|
1333
|
+
function applyPrefs() {
|
|
1334
|
+
const p = state.prefs;
|
|
1335
|
+
const root = document.documentElement.style;
|
|
1336
|
+
root.setProperty('--accent', p.accent);
|
|
1337
|
+
root.setProperty('--accent2', p.accent2);
|
|
1338
|
+
root.setProperty('--glow', Math.max(0.05, p.glow / 100));
|
|
1339
|
+
root.setProperty('--blur', p.blur + 'px');
|
|
1340
|
+
root.setProperty('--radius', p.radius + 'px');
|
|
1341
|
+
root.setProperty('--bg-h', p.bgHue);
|
|
1342
|
+
root.setProperty('--bg0', 'hsl(' + p.bgHue + ' 42% 3%)');
|
|
1343
|
+
root.setProperty('--bg1', 'hsl(' + p.bgHue + ' 40% 7%)');
|
|
1344
|
+
root.setProperty('--bg2', 'hsl(' + p.bgHue + ' 38% 11%)');
|
|
1345
|
+
|
|
1346
|
+
const body = document.body;
|
|
1347
|
+
body.dataset.bg = p.bgType;
|
|
1348
|
+
body.dataset.font = p.font;
|
|
1349
|
+
body.dataset.anim = p.anim ? 'on' : 'off';
|
|
1350
|
+
body.dataset.work = p.work ? 'on' : 'off';
|
|
1351
|
+
if (p.bgType === 'custom' && p.bgUrl) {
|
|
1352
|
+
body.style.setProperty('--bgimg', 'url("' + p.bgUrl.replace(/"/g, '%22') + '")');
|
|
1353
|
+
} else {
|
|
1354
|
+
body.style.removeProperty('--bgimg');
|
|
1355
|
+
}
|
|
1356
|
+
document.querySelector('.bg-grid').style.display = p.grid ? '' : 'none';
|
|
1357
|
+
document.querySelector('.bg-noise').style.display = p.noise ? '' : 'none';
|
|
1358
|
+
|
|
1359
|
+
// sync customizer UI
|
|
1360
|
+
$('cz-accent').value = p.accent;
|
|
1361
|
+
$('cz-accent-hex').textContent = p.accent;
|
|
1362
|
+
$('cz-accent2').value = p.accent2;
|
|
1363
|
+
$('cz-accent2-hex').textContent = p.accent2;
|
|
1364
|
+
$('cz-bgtype').value = p.bgType;
|
|
1365
|
+
$('cz-bgurl').value = p.bgUrl;
|
|
1366
|
+
$('cz-bg-hue').value = p.bgHue;
|
|
1367
|
+
$('cz-glow').value = p.glow;
|
|
1368
|
+
$('cz-blur').value = p.blur;
|
|
1369
|
+
$('cz-radius').value = p.radius;
|
|
1370
|
+
$('cz-font').value = p.font;
|
|
1371
|
+
$('cz-particles').checked = p.particles;
|
|
1372
|
+
$('cz-grid').checked = p.grid;
|
|
1373
|
+
$('cz-noise').checked = p.noise;
|
|
1374
|
+
$('cz-anim').checked = p.anim;
|
|
1375
|
+
$('cz-sound').checked = p.sound;
|
|
1376
|
+
$('cz-work').checked = !!p.work;
|
|
1377
|
+
document.title = p.work ? 'Homework Hub' : 'IGNITE OS — the unblocked dashboard';
|
|
1378
|
+
const bnameTag = document.querySelector('.brand-name');
|
|
1379
|
+
if (bnameTag) bnameTag.innerHTML = p.work
|
|
1380
|
+
? 'Homework<span class="accent-text">Hub</span>'
|
|
1381
|
+
: 'Ignite<span class="accent-text">OS</span>';
|
|
1382
|
+
$('cz-bgurl-row').classList.toggle('hidden', p.bgType !== 'custom');
|
|
1383
|
+
document.querySelectorAll('#cz-swatches .swatch').forEach((sw) => {
|
|
1384
|
+
sw.classList.toggle('active', sw.dataset.preset === p.preset);
|
|
337
1385
|
});
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
let re = /./;
|
|
341
|
-
re.toString = re.toString.toString();
|
|
342
|
-
try { console.log('%c', re); } catch (e) { triggerSecurity(); }
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// Enhanced keyboard and context menu blocking
|
|
346
|
-
function initSecurityProtection() {
|
|
347
|
-
// Block all inspection shortcuts
|
|
348
|
-
document.addEventListener('keydown', function(e) {
|
|
349
|
-
const forbidden = [
|
|
350
|
-
e.key === 'F12',
|
|
351
|
-
(e.ctrlKey || e.metaKey) && e.shiftKey && ['I', 'C', 'J', 'K'].includes(e.key.toUpperCase()),
|
|
352
|
-
(e.ctrlKey || e.metaKey) && ['U', 'S', 'A', 'F', 'G', 'H', 'P'].includes(e.key.toUpperCase()),
|
|
353
|
-
e.ctrlKey && e.shiftKey && e.key.toUpperCase() === 'C',
|
|
354
|
-
e.key === 'F1' || e.key === 'F3' || e.key === 'F5',
|
|
355
|
-
(e.ctrlKey || e.metaKey) && e.key === 'r'
|
|
356
|
-
];
|
|
357
|
-
|
|
358
|
-
if (forbidden.some(Boolean)) {
|
|
359
|
-
e.preventDefault();
|
|
360
|
-
e.stopPropagation();
|
|
361
|
-
e.stopImmediatePropagation();
|
|
362
|
-
triggerSecurity();
|
|
363
|
-
return false;
|
|
364
|
-
}
|
|
365
|
-
}, true);
|
|
1386
|
+
store.set(K.prefs, p);
|
|
1387
|
+
}
|
|
366
1388
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
triggerSecurity();
|
|
372
|
-
return false;
|
|
373
|
-
}, true);
|
|
374
|
-
|
|
375
|
-
// Block text selection
|
|
376
|
-
document.addEventListener('selectstart', function(e) {
|
|
377
|
-
if (!['INPUT', 'TEXTAREA'].includes(e.target.tagName)) {
|
|
378
|
-
e.preventDefault();
|
|
379
|
-
return false;
|
|
380
|
-
}
|
|
381
|
-
}, true);
|
|
382
|
-
|
|
383
|
-
// Block drag operations
|
|
384
|
-
document.addEventListener('dragstart', function(e) {
|
|
385
|
-
e.preventDefault();
|
|
386
|
-
return false;
|
|
387
|
-
}, true);
|
|
1389
|
+
function savePref(patch) {
|
|
1390
|
+
Object.assign(state.prefs, patch);
|
|
1391
|
+
applyPrefs();
|
|
1392
|
+
store.set(K.prefs, state.prefs);
|
|
388
1393
|
}
|
|
389
1394
|
|
|
390
|
-
function
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
// Clear the page content
|
|
396
|
-
setTimeout(() => {
|
|
397
|
-
document.body.innerHTML = '<div style="color:#ff4444; text-align:center; padding:50px; font-size:24px;">Access Denied</div>';
|
|
398
|
-
}, 3000);
|
|
399
|
-
|
|
400
|
-
// Infinite alert loop (commented out to avoid user annoyance)
|
|
401
|
-
// setInterval(() => alert('Unauthorized access detected!'), 100);
|
|
402
|
-
}
|
|
1395
|
+
function toggleCustomizer(open) {
|
|
1396
|
+
$('customizer').classList.toggle('open', open);
|
|
1397
|
+
if (open) { $('cz-name').value = state.profile.name; $('cz-name').focus(); }
|
|
1398
|
+
beep(open ? 440 : 320, 0.07);
|
|
403
1399
|
}
|
|
404
|
-
// Encrypted and obfuscated game data
|
|
405
|
-
// This data is base64 encoded and ROT13 encrypted to hide the URLs
|
|
406
|
-
const encryptedGameData = 'W3siaWQiOiJDb29raWUgQ2xpY2tlciIsIm5hbWUiOiJDb29raWUgQ2xpY2tlciBURVNUIiwidXJsIjoiaHR0cHM6XC9cL2hhY2tmZ2cuY29tXC9lZGN1YmUtY29va2llLWNsaWNrZXJANS4wLjBcL2luZGV4Lmh0bWwiLCJjb3ZlciI6ImRhdGE6aW1hZ2VcL2pwZWc7YmFzZTY0LFwvOWpcLzRBQVFTa1pKUmdBQkFRQUFBUUFCQUFEXC8yd0NFQUFrR0J4TVRFaFVURXhNVkZoVVhHQmdZR0JnWEdCZ1lHQm9WRnhnWUZ4b2VHaDNZSUNnZ0dob2xIUmNkSVRFaEpTa3JMaTR1R0I4ek9ETXROeWd0TGlCQ2dvS0RnME9NeEFRR2kwbElDWXRMUzh2THkwdExTMHRMUzB0TFMwdExTMHRMUzB0TFMwdExTMHRMUzB0TFMwdExTMHRMUzB0TFMwdExTMHRMUzB0TFMwdExTMHRMUzB0TGZcL0FBQUVJQU9FQTRRTUJJZ0FDRVEEREE9UUhcL3hBQWNBQUFDSWdNQkFRQUFBQUFBQUFBQUFBQUJBZ01EQkFVR0J3Y0pDZ3NGXC9FQUI0SUJEUUVEQWdRSUJBNVRVbE1TQUFBQkFnVWdCWGpMXC9BQlI0aUJCUUVGQndnS0RCQUZHaEVJSHhJRUNRc0xFZzhSRml3OUloUVVIQ3dPRnhvZkRoOFFKekZMTGlwUVFBZ3hCQWNIQnhJUERoOFNIUzBzSlI0akxUZzRLemNnRkNRc1JWaFNjQjhqRndNdUV4UWNGZzlcL0FSb2VHUUVGR3dVSURCZ1FKand3aERDZ3dCRVJCRUNnVUxGQkVCQndNSEl4TVJOUzlSUERnNExTOXJLelVzTXpQQkFRRWRBZ1VBUVhJSURnWkFSUUVGQndNREFBQUFBQUFBQUFBQUFBQUJBQUlCQXdBRUJRY0FDQUFBQUFBQUFBQUFBQUFBZ01CQkFBRkJnY0lDUW9MXC9BQWNBUUpHQVNBeEhBTUJFVWNIQVVnS0J3RWJBeFlTSkVGSkVsUWJlaG9pRnhSWUZDSW9HWG9jRmg4akhoWWJFaFNjXC9BREFBQUFBQUFBQUFBQUFBQUJBQUlCQXdBRUJRY0lCaHNcL0FBZ0FBZ0lJQndnQ0JBRWNBUUlGQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBIiwiZGVzYyI6ImNvb2wiLCJ0YWdzIjpbImNvb2tpZSIsIjJEIl0sImNvbG9yIjoiIzIyZDNlZSJ9LHsiaWQiOiJyZWNvaWwiLCJuYW1lIjoiUmVjb2lsIiwidXJsIjoiaHR0cHM6XC9cL3R2cWVldnAtbnlybnB4LTMyNTMyMS5senZudmUubnBwXC8iLCJjb3ZlciI6Imh0dHBzOlwvXC92bmFwbnFyLnBienZcL3Z6bnR2ZlwvNUU5RjQwREE3RjlDOUZBOC5qY3QiLCJkZXNjIjoicmVjb2lsIGxpa2UgZHJpdnZlIG1hZCIsInRhZ3MiOlsiY2xvc2VkLXdvcmxkIiwiYWN0aW9uIiwiMkQiXSwiY29sb3IiOiIjZmY2YjFhIn0seyJpZCI6InJldHJvIGJhbGwiLCJuYW1lIjoiUmV0cm8gQmFsbCIsInVybCI6Imh0dHBzOlwvXC9ianpwcGZmZXZ0dGZvbS5oZi1uZnN1bnJuLTEub2VucHBsc2ticHFudWYuYnpuXC9hXC92cXpwZm5kdy1pMjMxMDAxLTE1MTgtb3IxLjBcL2JcL29ocGJwZi0yMDIzMTAwMS0xNTE4LXJvMS4wXC9iXC92YXFyaC5hZ3p5IiwiY292ZXIiOiJodHRwczpcL1wvcmFwZWxjZ3JxLTA6Ly93d3cuTG5nem4uZ3F3LnBieVwvZnJucHByZlwvcT1nYWE5UmNUZkVFVWJYb3BROTVWMzBicnUyVWRnZHlzdE1EOEVsekNERnpINms2X3BVUSZzPTEwIiwiZGVzYyI6InRvdWNoZG93biIsInRhZ3MiOlsic3BvcnRzIiwiYWN0aW9uIl0sImNvbG9yIjoiI2E4NTVmNyJ9XQ==';
|
|
407
1400
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
1401
|
+
function bindCustomizer() {
|
|
1402
|
+
$('cz-swatches').addEventListener('click', (e) => {
|
|
1403
|
+
const sw = e.target.closest('.swatch');
|
|
1404
|
+
if (!sw) return;
|
|
1405
|
+
const preset = THEME_PRESETS[sw.dataset.preset];
|
|
1406
|
+
if (!preset) return;
|
|
1407
|
+
Object.assign(state.prefs, preset, { preset: sw.dataset.preset });
|
|
1408
|
+
applyPrefs(); store.set(K.prefs, state.prefs);
|
|
1409
|
+
beep(620, 0.09);
|
|
1410
|
+
toast('theme: ' + sw.dataset.preset, 1400);
|
|
1411
|
+
});
|
|
1412
|
+
$('cz-accent').addEventListener('input', (e) => savePref({ accent: e.target.value, preset: null }));
|
|
1413
|
+
$('cz-accent2').addEventListener('input', (e) => savePref({ accent2: e.target.value, preset: null }));
|
|
1414
|
+
$('cz-bgtype').addEventListener('change', (e) => savePref({ bgType: e.target.value, preset: null }));
|
|
1415
|
+
$('cz-bgurl').addEventListener('change', (e) => savePref({ bgUrl: e.target.value.trim() }));
|
|
1416
|
+
$('cz-bg-hue').addEventListener('input', (e) => savePref({ bgHue: Number(e.target.value) }));
|
|
1417
|
+
$('cz-glow').addEventListener('input', (e) => savePref({ glow: Number(e.target.value) }));
|
|
1418
|
+
$('cz-blur').addEventListener('input', (e) => savePref({ blur: Number(e.target.value) }));
|
|
1419
|
+
$('cz-radius').addEventListener('input', (e) => savePref({ radius: Number(e.target.value) }));
|
|
1420
|
+
$('cz-font').addEventListener('change', (e) => savePref({ font: e.target.value }));
|
|
1421
|
+
$('cz-particles').addEventListener('change', (e) => savePref({ particles: e.target.checked }));
|
|
1422
|
+
$('cz-grid').addEventListener('change', (e) => savePref({ grid: e.target.checked }));
|
|
1423
|
+
$('cz-noise').addEventListener('change', (e) => savePref({ noise: e.target.checked }));
|
|
1424
|
+
$('cz-anim').addEventListener('change', (e) => savePref({ anim: e.target.checked }));
|
|
1425
|
+
$('cz-sound').addEventListener('change', (e) => savePref({ sound: e.target.checked }));
|
|
1426
|
+
$('cz-work').addEventListener('change', (e) => setWorkMode(e.target.checked));
|
|
1427
|
+
$('cz-name').addEventListener('change', (e) => {
|
|
1428
|
+
state.profile.name = (e.target.value || 'DRIVER').toUpperCase().slice(0, 20);
|
|
1429
|
+
store.set(K.profile, state.profile);
|
|
1430
|
+
$('profile-pill').textContent = state.profile.name;
|
|
1431
|
+
});
|
|
1432
|
+
$('cz-reset').addEventListener('click', () => {
|
|
1433
|
+
localStorage.removeItem(K.prefs);
|
|
1434
|
+
localStorage.removeItem(K.profile);
|
|
1435
|
+
location.reload();
|
|
412
1436
|
});
|
|
1437
|
+
$('nav-customize').addEventListener('click', () => toggleCustomizer(true));
|
|
1438
|
+
$('cz-close').addEventListener('click', () => toggleCustomizer(false));
|
|
413
1439
|
}
|
|
1440
|
+
// ═══════════ background canvas ═══════════
|
|
414
1441
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
1442
|
+
const canvas = $('bg-canvas');
|
|
1443
|
+
const ctx = canvas.getContext('2d');
|
|
1444
|
+
let motes = [], starMode = false;
|
|
1445
|
+
function initMotes() {
|
|
1446
|
+
starMode = state.prefs.bgType === 'stars';
|
|
1447
|
+
const count = starMode ? 130 : Math.min(72, Math.floor(canvas.width / 22));
|
|
1448
|
+
motes = Array.from({ length: count }, () => ({
|
|
1449
|
+
x: Math.random() * canvas.width, y: Math.random() * canvas.height,
|
|
1450
|
+
r: starMode ? (Math.random() * 1.4 + 0.4) : (Math.random() * 2 + 0.6),
|
|
1451
|
+
vx: (Math.random() - 0.5) * (starMode ? 0.15 : 0.35),
|
|
1452
|
+
vy: (Math.random() - 0.5) * (starMode ? 0.12 : 0.28),
|
|
1453
|
+
tw: Math.random() * Math.PI * 2, tws: 0.01 + Math.random() * 0.03,
|
|
1454
|
+
hue: Math.random() * 40 - 20,
|
|
1455
|
+
}));
|
|
1456
|
+
}
|
|
1457
|
+
function drawMotes() {
|
|
1458
|
+
if (!state.prefs.particles) { ctx.clearRect(0, 0, canvas.width, canvas.height); return; }
|
|
1459
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
1460
|
+
const rgb = hexToRgb(state.prefs.accent);
|
|
1461
|
+
for (const m of motes) {
|
|
1462
|
+
m.x += m.vx; m.y += m.vy; m.tw += m.tws;
|
|
1463
|
+
if (m.x < -8) m.x = canvas.width + 8; if (m.x > canvas.width + 8) m.x = -8;
|
|
1464
|
+
if (m.y < -8) m.y = canvas.height + 8; if (m.y > canvas.height + 8) m.y = -8;
|
|
1465
|
+
const alpha = starMode ? (0.3 + 0.7 * Math.abs(Math.sin(m.tw))) : (0.1 + 0.42 * Math.abs(Math.sin(m.tw)));
|
|
1466
|
+
ctx.beginPath();
|
|
1467
|
+
ctx.arc(m.x, m.y, m.r, 0, Math.PI * 2);
|
|
1468
|
+
ctx.fillStyle = starMode
|
|
1469
|
+
? 'rgba(255,255,255,' + alpha + ')'
|
|
1470
|
+
: 'hsla(' + (state.prefs.bgHue + m.hue) + ' 90% 65% / ' + alpha + ')';
|
|
1471
|
+
ctx.fill();
|
|
424
1472
|
}
|
|
425
1473
|
}
|
|
1474
|
+
function bgLoop() {
|
|
1475
|
+
drawMotes();
|
|
1476
|
+
requestAnimationFrame(bgLoop);
|
|
1477
|
+
}
|
|
1478
|
+
window.addEventListener('resize', () => {
|
|
1479
|
+
canvas.width = window.innerWidth; canvas.height = window.innerHeight;
|
|
1480
|
+
initMotes();
|
|
1481
|
+
});
|
|
1482
|
+
// ═══════════ game vault ═══════════
|
|
426
1483
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
.
|
|
434
|
-
.
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
// Safe game loading with additional protection
|
|
438
|
-
function loadGames() {
|
|
439
|
-
if (!protectionActive) return [];
|
|
440
|
-
|
|
441
|
-
const games = decodeGameData();
|
|
442
|
-
return games.map(game => ({
|
|
443
|
-
...game,
|
|
444
|
-
url: deobfuscateUrl(game.url),
|
|
445
|
-
// Add timestamp to prevent caching of URLs
|
|
446
|
-
_loaded: Date.now()
|
|
447
|
-
}));
|
|
1484
|
+
const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) =>
|
|
1485
|
+
({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
|
1486
|
+
|
|
1487
|
+
function mergeGames() {
|
|
1488
|
+
const base = DEFAULT_GAMES.map((g) => Object.assign({}, g, state.overrides[g.id] || {}));
|
|
1489
|
+
const extra = Object.keys(state.overrides)
|
|
1490
|
+
.filter((id) => !DEFAULT_GAMES.some((g) => g.id === id))
|
|
1491
|
+
.map((id) => state.overrides[id])
|
|
1492
|
+
.filter((g) => g && g.name && g.url);
|
|
1493
|
+
return base.concat(extra);
|
|
448
1494
|
}
|
|
449
1495
|
|
|
450
|
-
// Game rendering with obfuscated data access
|
|
451
1496
|
function renderGames() {
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
1497
|
+
const games = mergeGames();
|
|
1498
|
+
const local = state.localGames.map((name) => ({
|
|
1499
|
+
id: 'local-' + name,
|
|
1500
|
+
name: name.replace(/[_-]+/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()),
|
|
1501
|
+
url: serverBase() + '/lg/' + name + '/', local: true, desc: 'Runs 100% offline from IGNITE OS.',
|
|
1502
|
+
tags: ['local'], color: '#ff9f6b',
|
|
1503
|
+
}));
|
|
1504
|
+
state.games = games.concat(local);
|
|
1505
|
+
$('local-hint').classList.toggle('hidden', local.length > 0);
|
|
1506
|
+
$('stat-games').textContent = games.length;
|
|
1507
|
+
$('stat-local').textContent = local.length;
|
|
1508
|
+
$('stat-movies').textContent = state.movies.length;
|
|
1509
|
+
$('movies-empty').classList.toggle('hidden', state.movies.length > 0);
|
|
1510
|
+
|
|
1511
|
+
const wrap = $('game-grid');
|
|
1512
|
+
wrap.innerHTML = '';
|
|
1513
|
+
state.games.forEach((g) => {
|
|
459
1514
|
const card = document.createElement('div');
|
|
460
1515
|
card.className = 'game-card';
|
|
461
|
-
card.style.setProperty('--gcolor',
|
|
462
|
-
card.
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
<div class="game-
|
|
466
|
-
|
|
467
|
-
<button class="
|
|
468
|
-
</div>
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
<div class="
|
|
472
|
-
<
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
1516
|
+
card.style.setProperty('--gcolor', g.color || '#ff6b1a');
|
|
1517
|
+
card.dataset.id = g.id;
|
|
1518
|
+
const cover = g.cover ? '<img src="' + esc(g.cover) + '" alt="" loading="lazy" onerror="this.remove()">' : '';
|
|
1519
|
+
const tools = g.local ? '' :
|
|
1520
|
+
'<div class="game-tools">' +
|
|
1521
|
+
'<button class="icon-btn" data-act="edit" title="edit">✎</button>' +
|
|
1522
|
+
'<button class="icon-btn" data-act="del" title="remove">✖</button>' +
|
|
1523
|
+
'</div>';
|
|
1524
|
+
card.innerHTML =
|
|
1525
|
+
'<div class="game-cover">' +
|
|
1526
|
+
(g.cover ? '' : '<div class="cover-fallback">' + esc(g.name.slice(0, 2).toUpperCase()) + '</div>') + cover +
|
|
1527
|
+
(g.local ? '<span class="local-dot">OFFLINE</span>' : '') + tools +
|
|
1528
|
+
'<button class="game-play" data-act="play" title="play">▶</button>' +
|
|
1529
|
+
'</div>' +
|
|
1530
|
+
'<div class="game-body">' +
|
|
1531
|
+
'<div class="game-name">' + esc(g.name) + '</div>' +
|
|
1532
|
+
'<div class="game-tags">' + (g.tags || []).map((t) => '<span class="game-tag">' + esc(t) + '</span>').join('') + '</div>' +
|
|
1533
|
+
(g.desc ? '<div class="game-desc">' + esc(g.desc) + '</div>' : '') +
|
|
1534
|
+
'</div>';
|
|
1535
|
+
wrap.appendChild(card);
|
|
477
1536
|
});
|
|
478
1537
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
1538
|
+
|
|
1539
|
+
// ── launcher modal ──
|
|
1540
|
+
function openLauncher(title, src) {
|
|
1541
|
+
$('launcher-title').textContent = title;
|
|
1542
|
+
$('launcher-frame').src = src;
|
|
1543
|
+
$('launcher-modal').classList.remove('hidden');
|
|
1544
|
+
document.body.style.overflow = 'hidden';
|
|
1545
|
+
beep(520, 0.08);
|
|
1546
|
+
}
|
|
1547
|
+
function closeLauncher() {
|
|
1548
|
+
$('launcher-frame').src = 'about:blank';
|
|
1549
|
+
$('launcher-modal').classList.add('hidden');
|
|
1550
|
+
document.body.style.overflow = '';
|
|
1551
|
+
}
|
|
1552
|
+
function bindGames() {
|
|
1553
|
+
$('game-grid').addEventListener('click', (e) => {
|
|
1554
|
+
const card = e.target.closest('.game-card');
|
|
1555
|
+
if (!card) return;
|
|
1556
|
+
const g = state.games.find((x) => x.id === card.dataset.id);
|
|
1557
|
+
if (!g) return;
|
|
1558
|
+
const act = e.target.closest('[data-act]');
|
|
1559
|
+
if (act && act.dataset.act === 'edit' && !g.local) { showGameForm(g); return; }
|
|
1560
|
+
if (act && act.dataset.act === 'del' && !g.local) {
|
|
1561
|
+
if (confirm('Remove "' + g.name + '" from your vault?')) {
|
|
1562
|
+
delete state.overrides[g.id];
|
|
1563
|
+
store.set(K.overrides, state.overrides);
|
|
1564
|
+
renderGames(); toast('game removed');
|
|
1565
|
+
}
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
const url = g.local ? g.url : tunnel(g.url);
|
|
1569
|
+
store.set(K.last, { name: g.name, ts: Date.now() });
|
|
1570
|
+
$('stat-last').textContent = g.name.slice(0, 12);
|
|
1571
|
+
openLauncher(g.name, url);
|
|
1572
|
+
});
|
|
1573
|
+
$('games-edit-badge').addEventListener('click', () => showGameForm(null));
|
|
1574
|
+
$('launcher-close').addEventListener('click', closeLauncher);
|
|
1575
|
+
$('launcher-back').addEventListener('click', () => { try { $('launcher-frame').contentWindow.history.back(); } catch (e) { /* ignore */ } });
|
|
1576
|
+
$('launcher-refresh').addEventListener('click', () => { $('launcher-frame').src = $('launcher-frame').src; });
|
|
1577
|
+
$('launcher-fs').addEventListener('click', () => {
|
|
1578
|
+
const fr = $('launcher-frame');
|
|
1579
|
+
if (fr.requestFullscreen) fr.requestFullscreen().catch(() => {});
|
|
1580
|
+
});
|
|
1581
|
+
$('launcher-modal').addEventListener('click', (e) => {
|
|
1582
|
+
if (e.target === $('launcher-modal')) closeLauncher();
|
|
1583
|
+
});
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
// ── shared add/edit form (games + movies) ──
|
|
1587
|
+
function showForm(mode, item) {
|
|
1588
|
+
state.formMode = mode;
|
|
1589
|
+
state.formEditId = item ? item.id : null;
|
|
1590
|
+
$('form-title').textContent = (item ? 'Edit ' : ('Add ')) + (mode === 'game' ? 'game' : 'movie');
|
|
1591
|
+
$('form-name').value = item ? item.name || item.title || '' : '';
|
|
1592
|
+
$('form-desc').value = item ? item.desc || '' : '';
|
|
1593
|
+
$('form-link').value = item ? (mode === 'game' ? item.url : (item.url || item.driveId || '')) : '';
|
|
1594
|
+
$('form-genre').value = item ? (mode === 'game' ? (item.tags || []).join(', ') : (item.genre || '')) : '';
|
|
1595
|
+
$('form-poster').value = item ? (item.cover || item.poster || '') : '';
|
|
1596
|
+
$('form-name').placeholder = mode === 'game' ? 'game title' : 'movie title';
|
|
1597
|
+
$('form-link').placeholder = mode === 'game'
|
|
1598
|
+
? 'https://somesite.com/game — proxied automatically'
|
|
1599
|
+
: 'https://drive.google.com/file/d/…/view';
|
|
1600
|
+
$('form-parse-hint').textContent = '';
|
|
1601
|
+
$('form-modal').classList.remove('hidden');
|
|
1602
|
+
setTimeout(() => $('form-name').focus(), 60);
|
|
1603
|
+
}
|
|
1604
|
+
function closeForm() {
|
|
1605
|
+
$('form-modal').classList.add('hidden');
|
|
1606
|
+
state.formEditId = null;
|
|
1607
|
+
}
|
|
1608
|
+
// ═══════════ form handlers ═══════════
|
|
1609
|
+
|
|
1610
|
+
const uid = () => 'm' + Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
|
|
1611
|
+
|
|
1612
|
+
function bindForm() {
|
|
1613
|
+
$('form-cancel').addEventListener('click', closeForm);
|
|
1614
|
+
$('form-modal').addEventListener('click', (e) => { if (e.target === $('form-modal')) closeForm(); });
|
|
1615
|
+
$('form-card').addEventListener('submit', (e) => {
|
|
1616
|
+
e.preventDefault();
|
|
1617
|
+
const mode = state.formMode;
|
|
1618
|
+
const name = $('form-name').value.trim();
|
|
1619
|
+
const desc = $('form-desc').value.trim();
|
|
1620
|
+
const genre = $('form-genre').value.trim();
|
|
1621
|
+
const poster = $('form-poster').value.trim();
|
|
1622
|
+
const link = $('form-link').value.trim();
|
|
1623
|
+
if (!name) { toast('give it a name'); return; }
|
|
1624
|
+
if (mode === 'game') {
|
|
1625
|
+
if (!link) { toast('paste a game URL — it plays through the tunnel'); return; }
|
|
1626
|
+
const id = state.formEditId || uid().replace('m', 'g');
|
|
1627
|
+
const old = state.overrides[id] || {};
|
|
1628
|
+
state.overrides[id] = Object.assign({}, old, {
|
|
1629
|
+
id, name, url: link, desc,
|
|
1630
|
+
tags: genre ? genre.split(',').map((t) => t.trim()).filter(Boolean) : ['custom'],
|
|
1631
|
+
cover: poster || undefined, color: old.color || pickColor(name),
|
|
1632
|
+
});
|
|
1633
|
+
store.set(K.overrides, state.overrides);
|
|
1634
|
+
renderGames();
|
|
1635
|
+
toast('game saved: ' + name); beep(660, 0.09);
|
|
1636
|
+
} else {
|
|
1637
|
+
if (!link) { toast('paste a Google Drive link or video URL'); return; }
|
|
1638
|
+
const parsed = parseDrive(link);
|
|
1639
|
+
const movie = {
|
|
1640
|
+
id: state.formEditId || uid(),
|
|
1641
|
+
title: name, desc, genre: genre || 'uncategorized',
|
|
1642
|
+
poster: poster || (parsed.kind === 'file' ? driveThumb(parsed.id) : ''),
|
|
1643
|
+
source: parsed.kind, driveId: parsed.id || undefined,
|
|
1644
|
+
url: parsed.url || undefined,
|
|
1645
|
+
fav: false, watched: false, progress: 0, added: Date.now(),
|
|
1646
|
+
};
|
|
1647
|
+
const idx = state.movies.findIndex((m) => m.id === movie.id);
|
|
1648
|
+
if (idx > -1) Object.assign(state.movies[idx], movie); else state.movies.unshift(movie);
|
|
1649
|
+
store.set(K.movies, state.movies);
|
|
1650
|
+
renderMovies(); renderGames();
|
|
1651
|
+
toast('movie added: ' + name); beep(660, 0.09);
|
|
1652
|
+
}
|
|
1653
|
+
closeForm();
|
|
1654
|
+
});
|
|
1655
|
+
|
|
1656
|
+
$('form-link').addEventListener('input', () => {
|
|
1657
|
+
const p = parseDrive($('form-link').value);
|
|
1658
|
+
const h = $('form-parse-hint');
|
|
1659
|
+
if (p.kind === 'file') h.textContent = 'Drive file detected ✓ (id ' + p.id.slice(0, 6) + '…)';
|
|
1660
|
+
else if (p.kind === 'folder') h.textContent = 'Drive folder detected ✓ — embeds the folder listing';
|
|
1661
|
+
else if (p.kind === 'url') h.textContent = 'direct video URL — plays in the built-in player';
|
|
1662
|
+
else h.textContent = 'paste a Google Drive share link or a direct video URL';
|
|
1663
|
+
});
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
const pickColor = (name) => {
|
|
1667
|
+
const palette = ['#ff6b1a', '#3b82f6', '#a855f7', '#22d3ee', '#34d399', '#facc15', '#fb7185'];
|
|
1668
|
+
let h = 0; for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) | 0;
|
|
1669
|
+
return palette[Math.abs(h) % palette.length];
|
|
498
1670
|
};
|
|
1671
|
+
// ═══════════ movie library ═══════════
|
|
499
1672
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
1673
|
+
function renderMovies() {
|
|
1674
|
+
const q = state.movieFilter.toLowerCase();
|
|
1675
|
+
let list = state.movies.filter((m) =>
|
|
1676
|
+
(!q || m.title.toLowerCase().includes(q)) &&
|
|
1677
|
+
(!state.movieGenre || (m.genre || '').toLowerCase().includes(state.movieGenre.toLowerCase())));
|
|
1678
|
+
if (state.movieSort === 'title') list = list.slice().sort((a, b) => a.title.localeCompare(b.title));
|
|
1679
|
+
else if (state.movieSort === 'watched') list = list.slice().sort((a, b) => Number(a.watched) - Number(b.watched));
|
|
1680
|
+
else list = list.slice().sort((a, b) => (b.added || 0) - (a.added || 0));
|
|
1681
|
+
|
|
1682
|
+
const genres = ['all'];
|
|
1683
|
+
state.movies.forEach((m) => { const g = (m.genre || '').trim(); if (g && !genres.includes(g)) genres.push(g); });
|
|
1684
|
+
$('genre-chips').innerHTML = genres.map((g) =>
|
|
1685
|
+
'<button class="genre-chip' + ((g === state.movieGenre) || (g === 'all' && !state.movieGenre) ? ' active' : '') +
|
|
1686
|
+
'" data-g="' + esc(g) + '">' + esc(g) + '</button>').join('');
|
|
1687
|
+
|
|
1688
|
+
const wrap = $('movie-grid');
|
|
1689
|
+
wrap.innerHTML = '';
|
|
1690
|
+
list.forEach((m) => wrap.appendChild(movieCard(m)));
|
|
1691
|
+
$('movies-empty').classList.toggle('hidden', state.movies.length > 0);
|
|
1692
|
+
|
|
1693
|
+
const cont = state.movies.filter((m) => m.progress > 7 && m.progress < 95 && !m.watched);
|
|
1694
|
+
$('continue-panel').classList.toggle('hidden', cont.length === 0);
|
|
1695
|
+
if (cont.length) {
|
|
1696
|
+
const cw = $('continue-grid');
|
|
1697
|
+
cw.innerHTML = '';
|
|
1698
|
+
cont.forEach((m) => {
|
|
1699
|
+
const c = movieCard(m);
|
|
1700
|
+
const pct = document.createElement('span');
|
|
1701
|
+
pct.className = 'movie-cont-pct';
|
|
1702
|
+
pct.textContent = Math.round(m.progress) + '%';
|
|
1703
|
+
c.querySelector('.movie-info').appendChild(pct);
|
|
1704
|
+
cw.appendChild(c);
|
|
513
1705
|
});
|
|
514
1706
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
function movieCard(m) {
|
|
1710
|
+
const card = document.createElement('div');
|
|
1711
|
+
card.className = 'movie-card';
|
|
1712
|
+
card.dataset.id = m.id;
|
|
1713
|
+
card.style.setProperty('--mcolor', m.color || '#ff6b1a');
|
|
1714
|
+
const posterFallback = m.driveId ? 'this.onerror=null;this.src=\'https://drive.google.com/thumbnail?id=' + m.driveId + '&sz=w1000\';' : 'this.remove()';const poster = (m.poster || m.driveId) ? '<img src="' + esc(m.poster || ('https://drive.google.com/thumbnail?id=' + m.driveId + '&sz=w1000')) + '" alt="" loading="lazy" referrerpolicy="no-referrer" onerror="' + posterFallback + '">' : '';
|
|
1715
|
+
const st = [];
|
|
1716
|
+
if (m.fav) st.push('<span class="fav">★</span>');
|
|
1717
|
+
if (m.watched) st.push('<span>✓</span>');
|
|
1718
|
+
else if (m.progress > 7) st.push('<span>' + Math.round(m.progress) + '%</span>');
|
|
1719
|
+
card.innerHTML =
|
|
1720
|
+
'<div class="movie-poster">' +
|
|
1721
|
+
(m.poster ? '' : '<div class="movie-fallback">' + esc((m.title || '?').slice(0, 2).toUpperCase()) + '</div>') + poster +
|
|
1722
|
+
'<div class="movie-play"><i>▶</i></div>' +
|
|
1723
|
+
'<div class="movie-status">' + st.join('') + '</div>' +
|
|
1724
|
+
'<div class="movie-tools">' +
|
|
1725
|
+
'<button class="icon-btn" data-act="fav" title="favorite">★</button>' +
|
|
1726
|
+
'<button class="icon-btn" data-act="watched" title="watched">✓</button>' +
|
|
1727
|
+
'<button class="icon-btn" data-act="edit" title="edit">✎</button>' +
|
|
1728
|
+
'<button class="icon-btn" data-act="del" title="remove">✖</button>' +
|
|
1729
|
+
'</div>' +
|
|
1730
|
+
'</div>' +
|
|
1731
|
+
'<div class="movie-info">' +
|
|
1732
|
+
'<div class="movie-title">' + esc(m.title) + '</div>' +
|
|
1733
|
+
'<div class="movie-genre">' + esc(m.genre || '') + '</div>' +
|
|
1734
|
+
'</div>';
|
|
1735
|
+
return card;
|
|
1736
|
+
}
|
|
1737
|
+
// ═══════════ movie player ═══════════
|
|
1738
|
+
|
|
1739
|
+
function openPlayer(movie) {
|
|
1740
|
+
// normalize any pasted Google Drive link (incl. /preview URLs) into an embedded id
|
|
1741
|
+
if (movie.source === 'url' && movie.url) {
|
|
1742
|
+
const p = parseDrive(movie.url);
|
|
1743
|
+
if (p.kind === 'file') {
|
|
1744
|
+
movie.driveId = p.id;
|
|
1745
|
+
movie.source = 'file';
|
|
1746
|
+
if (!movie.poster) movie.poster = driveThumb(p.id);
|
|
1747
|
+
} else if (p.kind === 'folder') {
|
|
1748
|
+
movie.driveId = p.id;
|
|
1749
|
+
movie.source = 'folder';
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
state.playerMovie = movie;
|
|
1753
|
+
// embedded preview by default for Drive files — reliable, in-page, no new tab
|
|
1754
|
+
state.playerEngine = movie.driveId ? 'preview' : 'video';
|
|
1755
|
+
$('player-title').textContent = movie.title;
|
|
1756
|
+
$('player-toggle').classList.toggle('hidden', !movie.driveId);
|
|
1757
|
+
$('player-modal').classList.remove('hidden');
|
|
1758
|
+
document.body.style.overflow = 'hidden';
|
|
1759
|
+
renderPlayerStage();
|
|
1760
|
+
beep(440, 0.08);
|
|
1761
|
+
}
|
|
1762
|
+
function closePlayer() {
|
|
1763
|
+
$('player-stage').innerHTML = '';
|
|
1764
|
+
state.playerMovie = null;
|
|
1765
|
+
$('player-modal').classList.add('hidden');
|
|
1766
|
+
document.body.style.overflow = '';
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
function renderPlayerStage() {
|
|
1770
|
+
const m = state.playerMovie;
|
|
1771
|
+
const stage = $('player-stage');
|
|
1772
|
+
stage.innerHTML = '';
|
|
1773
|
+
if (!m) return;
|
|
1774
|
+
|
|
1775
|
+
// drive folder -> embedded folder listing, in-page
|
|
1776
|
+
if (m.source === 'folder' && m.driveId) {
|
|
1777
|
+
stage.innerHTML = '<iframe src="' + driveFolder(m.driveId) +
|
|
1778
|
+
'" allow="autoplay; fullscreen; encrypted-media; picture-in-picture" allowfullscreen></iframe>';
|
|
1779
|
+
return;
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
// engine 1: direct video stream (raw drive download, or a plain direct URL)
|
|
1783
|
+
if (state.playerEngine === 'video' && (m.driveId || m.url)) {
|
|
1784
|
+
const v = document.createElement('video');
|
|
1785
|
+
v.controls = true; v.autoplay = true; v.playsInline = true;
|
|
1786
|
+
if (m.poster) v.poster = m.poster;
|
|
1787
|
+
v.src = m.driveId ? driveRaw(m.driveId) : m.url;
|
|
1788
|
+
if (m.progress > 7 && m.progress < 95 && m.driveId) {
|
|
1789
|
+
v.addEventListener('loadedmetadata', () => { try { v.currentTime = (m.progress / 100) * v.duration; } catch (e) { /* ignore */ } });
|
|
1790
|
+
}
|
|
1791
|
+
v.addEventListener('timeupdate', () => {
|
|
1792
|
+
if (!v.duration || Number.isNaN(v.duration)) return;
|
|
1793
|
+
const pct = (v.currentTime / v.duration) * 100;
|
|
1794
|
+
const m2 = state.movies.find((x) => x.id === m.id);
|
|
1795
|
+
if (m2 && Math.abs(m2.progress - pct) > 0.5) {
|
|
1796
|
+
m2.progress = Math.min(98, pct);
|
|
1797
|
+
store.set(K.movies, state.movies);
|
|
522
1798
|
}
|
|
523
1799
|
});
|
|
1800
|
+
let settled = false;
|
|
1801
|
+
const fallback = (why) => {
|
|
1802
|
+
if (settled || !m.driveId) return;
|
|
1803
|
+
settled = true;
|
|
1804
|
+
state.playerEngine = 'preview';
|
|
1805
|
+
toast('direct stream unavailable (' + why + ') — embedded player engaged');
|
|
1806
|
+
renderPlayerStage();
|
|
1807
|
+
};
|
|
1808
|
+
v.addEventListener('error', () => fallback('stream blocked'));
|
|
1809
|
+
// some setups stall on an HTML interstitial instead of firing error — force a check
|
|
1810
|
+
setTimeout(() => {
|
|
1811
|
+
if (!settled && v.readyState < 2 && !v.duration) fallback('no stream');
|
|
1812
|
+
}, 7000);
|
|
1813
|
+
stage.appendChild(v);
|
|
1814
|
+
return;
|
|
524
1815
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
1816
|
+
|
|
1817
|
+
// engine 2: embedded Google preview player — plays fully in-page, never a new tab
|
|
1818
|
+
if (m.driveId) {
|
|
1819
|
+
stage.innerHTML = '<iframe src="' + drivePreview(m.driveId) +
|
|
1820
|
+
'" allow="autoplay; fullscreen; encrypted-media; picture-in-picture" allowfullscreen></iframe>';
|
|
1821
|
+
} else if (m.url) {
|
|
1822
|
+
stage.innerHTML = '<iframe src="' + esc(m.url) +
|
|
1823
|
+
'" allow="autoplay; fullscreen" allowfullscreen></iframe>';
|
|
1824
|
+
} else {
|
|
1825
|
+
const box = document.createElement('div');
|
|
1826
|
+
box.className = 'player-error';
|
|
1827
|
+
box.innerHTML = '<h4>Nothing to play</h4><p>This entry has no usable link. Edit it and paste a Google Drive share link.</p>';
|
|
1828
|
+
stage.appendChild(box);
|
|
530
1829
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
function bindMovies() {
|
|
1833
|
+
const onGrid = (e) => {
|
|
1834
|
+
const card = e.target.closest('.movie-card');
|
|
1835
|
+
if (!card) return;
|
|
1836
|
+
const m = state.movies.find((x) => x.id === card.dataset.id);
|
|
1837
|
+
if (!m) return;
|
|
1838
|
+
const act = e.target.closest('[data-act]');
|
|
1839
|
+
if (act) {
|
|
1840
|
+
const a = act.dataset.act;
|
|
1841
|
+
if (a === 'fav') { m.fav = !m.fav; store.set(K.movies, state.movies); renderMovies(); toast(m.fav ? '★ favorited' : '★ removed', 1200); }
|
|
1842
|
+
else if (a === 'watched') { m.watched = !m.watched; m.progress = m.watched ? 100 : m.progress; store.set(K.movies, state.movies); renderMovies(); toast(m.watched ? 'marked watched' : 'marked unwatched', 1200); }
|
|
1843
|
+
else if (a === 'edit') { showForm('movie', m); }
|
|
1844
|
+
else if (a === 'del') {
|
|
1845
|
+
if (confirm('Remove "' + m.title + '"?')) {
|
|
1846
|
+
state.movies = state.movies.filter((x) => x.id !== m.id);
|
|
1847
|
+
store.set(K.movies, state.movies);
|
|
1848
|
+
renderMovies(); toast('movie removed');
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
openPlayer(m);
|
|
1854
|
+
};
|
|
1855
|
+
$('movie-grid').addEventListener('click', onGrid);
|
|
1856
|
+
$('continue-grid').addEventListener('click', onGrid);
|
|
1857
|
+
|
|
1858
|
+
$('movies-add').addEventListener('click', () => showForm('movie', null));
|
|
1859
|
+
$('movie-search').addEventListener('input', (e) => { state.movieFilter = e.target.value; renderMovies(); });
|
|
1860
|
+
$('genre-chips').addEventListener('click', (e) => {
|
|
1861
|
+
const chip = e.target.closest('.genre-chip');
|
|
1862
|
+
if (!chip) return;
|
|
1863
|
+
state.movieGenre = chip.dataset.g === 'all' ? '' : chip.dataset.g;
|
|
1864
|
+
renderMovies();
|
|
1865
|
+
});
|
|
1866
|
+
$('movie-sort').addEventListener('change', (e) => { state.movieSort = e.target.value; renderMovies(); });
|
|
1867
|
+
$('demo-movies').addEventListener('click', () => {
|
|
1868
|
+
const have = state.movies.map((m) => m.title);
|
|
1869
|
+
DEMO_MOVIES.forEach((dm) => {
|
|
1870
|
+
if (!have.includes(dm.title)) {
|
|
1871
|
+
state.movies.push(Object.assign({}, dm, {
|
|
1872
|
+
id: uid(), url: dm.videoUrl, fav: false, watched: false, progress: 0, added: Date.now(),
|
|
1873
|
+
}));
|
|
538
1874
|
}
|
|
539
1875
|
});
|
|
1876
|
+
store.set(K.movies, state.movies);
|
|
1877
|
+
renderMovies(); renderGames();
|
|
1878
|
+
toast('demo shelf loaded');
|
|
1879
|
+
confetti(40);
|
|
1880
|
+
});
|
|
1881
|
+
|
|
1882
|
+
$('player-close').addEventListener('click', closePlayer);
|
|
1883
|
+
$('player-back').addEventListener('click', closePlayer);
|
|
1884
|
+
$('player-modal').addEventListener('click', (e) => { if (e.target === $('player-modal')) closePlayer(); });
|
|
1885
|
+
$('player-toggle').addEventListener('click', () => {
|
|
1886
|
+
state.playerEngine = state.playerEngine === 'video' ? 'preview' : 'video';
|
|
1887
|
+
renderPlayerStage();
|
|
1888
|
+
toast('player engine: ' + state.playerEngine);
|
|
1889
|
+
});
|
|
1890
|
+
$('player-mark').addEventListener('click', () => {
|
|
1891
|
+
if (!state.playerMovie) return;
|
|
1892
|
+
const m = state.movies.find((x) => x.id === state.playerMovie.id);
|
|
1893
|
+
if (m) { m.watched = true; m.progress = 100; store.set(K.movies, state.movies); renderMovies(); toast('✓ ' + m.title + ' marked watched'); }
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
// ═══════════ tunnel ═══════════
|
|
1897
|
+
|
|
1898
|
+
function bindTunnel() { /* tunnel removed — nothing to bind */ }
|
|
1899
|
+
|
|
1900
|
+
// ═══════════ hotkeys & konami ═══════════
|
|
1901
|
+
|
|
1902
|
+
function bindHotkeys() {
|
|
1903
|
+
const konami = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a'];
|
|
1904
|
+
window.addEventListener('keydown', (e) => {
|
|
1905
|
+
const tag = (document.activeElement || {}).tagName;
|
|
1906
|
+
const typing = tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT';
|
|
1907
|
+
if (!typing) {
|
|
1908
|
+
if (e.key === '/') { e.preventDefault(); $('movie-search').focus(); return; }
|
|
1909
|
+
if (e.key === 'g' || e.key === 'G') { $('games').scrollIntoView({ behavior: 'smooth' }); return; }
|
|
1910
|
+
if (e.key === 'm' || e.key === 'M') { $('movies').scrollIntoView({ behavior: 'smooth' }); return; }
|
|
1911
|
+
if (e.key === 'h' || e.key === 'H') { setWorkMode(!state.prefs.work); return; }
|
|
1912
|
+
if (e.key === 'c' || e.key === 'C') { toggleCustomizer(!$('customizer').classList.contains('open')); return; }
|
|
1913
|
+
}
|
|
1914
|
+
if (e.key === 'Escape') {
|
|
1915
|
+
closeLauncher(); closePlayer(); closeForm(); toggleCustomizer(false);
|
|
1916
|
+
}
|
|
1917
|
+
state.keys.push(e.key);
|
|
1918
|
+
if (state.keys.length > konami.length) state.keys.shift();
|
|
1919
|
+
if (state.keys.join(',') === konami.join(',')) {
|
|
1920
|
+
state.keys = [];
|
|
1921
|
+
turboTime();
|
|
1922
|
+
}
|
|
1923
|
+
});
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
function turboTime() {
|
|
1927
|
+
if (state.turbo) return;
|
|
1928
|
+
state.turbo = true;
|
|
1929
|
+
document.body.classList.add('turbo');
|
|
1930
|
+
confetti(90);
|
|
1931
|
+
toast('▲▲▼▼◄►◄►BA — TURBO MODE');
|
|
1932
|
+
beep(880, 0.5, 'square');
|
|
1933
|
+
setTimeout(() => { document.body.classList.remove('turbo'); state.turbo = false; }, 8000);
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
// ═══════════ games form helper ═══════════
|
|
1937
|
+
|
|
1938
|
+
function showGameForm(g) { showForm('game', g); }
|
|
1939
|
+
|
|
1940
|
+
// ═══════════ work mode ═══════════
|
|
1941
|
+
|
|
1942
|
+
const work = {
|
|
1943
|
+
secs: 1500, left: 1500, running: false, timerId: null,
|
|
1944
|
+
todos: store.get('emby.work.v1', []),
|
|
1945
|
+
};
|
|
1946
|
+
const fmtTime = (s) => {
|
|
1947
|
+
s = Math.max(0, Math.round(s));
|
|
1948
|
+
return String(Math.floor(s / 60)).padStart(2, '0') + ':' + String(s % 60).padStart(2, '0');
|
|
1949
|
+
};
|
|
1950
|
+
function renderWorkTimer() { $('focus-timer').textContent = fmtTime(work.left); }
|
|
1951
|
+
function workTick() {
|
|
1952
|
+
if (!work.running) return;
|
|
1953
|
+
work.left -= 1;
|
|
1954
|
+
if (work.left <= 0) {
|
|
1955
|
+
work.left = 0; work.running = false;
|
|
1956
|
+
clearInterval(work.timerId);
|
|
1957
|
+
renderWorkTimer();
|
|
1958
|
+
toast('focus session done');
|
|
1959
|
+
beep(880, 0.6, 'square');
|
|
1960
|
+
confetti(24);
|
|
1961
|
+
return;
|
|
540
1962
|
}
|
|
1963
|
+
renderWorkTimer();
|
|
1964
|
+
}
|
|
1965
|
+
function renderTodos() {
|
|
1966
|
+
$('todo-list').innerHTML = work.todos.map((t, i) =>
|
|
1967
|
+
'<li class="' + (t.done ? 'done' : '') + '">' +
|
|
1968
|
+
'<input type="checkbox" ' + (t.done ? 'checked ' : '') + 'data-i="' + i + '">' +
|
|
1969
|
+
'<span>' + esc(t.text) + '</span>' +
|
|
1970
|
+
'<button class="icon-btn" data-i="' + i + '" title="delete">✖</button></li>').join('');
|
|
541
1971
|
}
|
|
1972
|
+
const saveTodos = () => store.set('emby.work.v1', work.todos);
|
|
542
1973
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
if (
|
|
556
|
-
|
|
557
|
-
|
|
1974
|
+
function setWorkMode(v) {
|
|
1975
|
+
state.prefs.work = !!v;
|
|
1976
|
+
applyPrefs();
|
|
1977
|
+
store.set(K.prefs, state.prefs);
|
|
1978
|
+
if (v) { closeLauncher(); closePlayer(); closeForm(); toggleCustomizer(false); }
|
|
1979
|
+
toast(v ? 'Work Mode — vault hidden' : 'Work Mode off');
|
|
1980
|
+
beep(v ? 392 : 330, 0.07);
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
function bindWork() {
|
|
1984
|
+
$('nav-work').addEventListener('click', () => setWorkMode(!state.prefs.work));
|
|
1985
|
+
$('timer-start').addEventListener('click', () => {
|
|
1986
|
+
if (work.left <= 0) work.left = work.secs;
|
|
1987
|
+
work.running = true; clearInterval(work.timerId);
|
|
1988
|
+
work.timerId = setInterval(workTick, 1000);
|
|
1989
|
+
renderWorkTimer();
|
|
1990
|
+
});
|
|
1991
|
+
$('timer-pause').addEventListener('click', () => {
|
|
1992
|
+
work.running = false; clearInterval(work.timerId);
|
|
1993
|
+
});
|
|
1994
|
+
$('timer-reset').addEventListener('click', () => {
|
|
1995
|
+
work.running = false; clearInterval(work.timerId);
|
|
1996
|
+
work.left = work.secs; renderWorkTimer();
|
|
1997
|
+
});
|
|
1998
|
+
$('timer-length').addEventListener('change', (e) => {
|
|
1999
|
+
work.secs = Number(e.target.value); work.left = work.secs; renderWorkTimer();
|
|
2000
|
+
});
|
|
2001
|
+
$('todo-form').addEventListener('submit', (e) => {
|
|
2002
|
+
e.preventDefault();
|
|
2003
|
+
const v = $('todo-input').value.trim();
|
|
2004
|
+
if (!v) return;
|
|
2005
|
+
work.todos.push({ text: v, done: false });
|
|
2006
|
+
$('todo-input').value = '';
|
|
2007
|
+
saveTodos(); renderTodos(); beep(520, 0.06);
|
|
2008
|
+
});
|
|
2009
|
+
$('todo-list').addEventListener('change', (e) => {
|
|
2010
|
+
const i = Number(e.target.dataset.i);
|
|
2011
|
+
if (work.todos[i] !== undefined) { work.todos[i].done = e.target.checked; saveTodos(); renderTodos(); }
|
|
2012
|
+
});
|
|
2013
|
+
$('todo-list').addEventListener('click', (e) => {
|
|
2014
|
+
if (!e.target.classList.contains('icon-btn')) return;
|
|
2015
|
+
const i = Number(e.target.dataset.i);
|
|
2016
|
+
if (work.todos[i] !== undefined) { work.todos.splice(i, 1); saveTodos(); renderTodos(); }
|
|
2017
|
+
});
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
// ═══════════ clocks ═══════════
|
|
2021
|
+
|
|
2022
|
+
function tick() {
|
|
2023
|
+
const d = new Date();
|
|
2024
|
+
const t = d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
2025
|
+
$('nav-clock').textContent = t;
|
|
2026
|
+
$('foot-clock').textContent = new Date().toLocaleDateString([], { weekday: 'short', month: 'short', day: 'numeric' }) + ' · ' + t;
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
// ═══════════ boot splash ═══════════
|
|
2030
|
+
|
|
2031
|
+
const BOOT_LINES = [
|
|
2032
|
+
'boot ignite OS v1.4 …',
|
|
2033
|
+
'game vault … loading',
|
|
2034
|
+
'games vault … ready',
|
|
2035
|
+
'movies (drive) … ready',
|
|
2036
|
+
'weapons hot. have fun, ' + state.profile.name + '.',
|
|
2037
|
+
];
|
|
2038
|
+
|
|
2039
|
+
function runBoot() {
|
|
2040
|
+
const typeEl = $('boot-type');
|
|
2041
|
+
const fill = $('boot-fill');
|
|
2042
|
+
const total = 3000;
|
|
2043
|
+
const start = performance.now();
|
|
2044
|
+
let line = -1;
|
|
2045
|
+
let finished = false;
|
|
2046
|
+
const step = () => {
|
|
2047
|
+
const p = Math.min(1, (performance.now() - start) / total);
|
|
2048
|
+
fill.style.width = Math.round(p * 100) + '%';
|
|
2049
|
+
const targetLine = Math.floor(p * BOOT_LINES.length);
|
|
2050
|
+
if (targetLine > line) {
|
|
2051
|
+
line = targetLine;
|
|
2052
|
+
typeEl.textContent = BOOT_LINES[Math.min(line, BOOT_LINES.length - 1)];
|
|
558
2053
|
}
|
|
559
|
-
|
|
2054
|
+
if (p < 1 && state.prefs.anim) requestAnimationFrame(step);
|
|
2055
|
+
else finishBoot();
|
|
2056
|
+
};
|
|
2057
|
+
const finishBoot = () => {
|
|
2058
|
+
if (finished) return;
|
|
2059
|
+
finished = true;
|
|
2060
|
+
fill.style.width = '100%';
|
|
2061
|
+
typeEl.textContent = 'ready. pull up.';
|
|
2062
|
+
setTimeout(() => {
|
|
2063
|
+
const b = $('boot');
|
|
2064
|
+
if (b) { b.classList.add('done'); setTimeout(() => b.remove(), 600); }
|
|
2065
|
+
confetti(26);
|
|
2066
|
+
}, 320);
|
|
2067
|
+
};
|
|
2068
|
+
// failsafe: hard-exit the splash no matter what after 3.5s
|
|
2069
|
+
setTimeout(finishBoot, 3500);
|
|
2070
|
+
if (!state.prefs.anim) { typeEl.textContent = 'ready.'; finishBoot(); return; }
|
|
2071
|
+
requestAnimationFrame(step);
|
|
2072
|
+
$('boot-skip').addEventListener('click', () => { finishBoot(); const b = $('boot'); if (b) setTimeout(() => b.remove(), 300); });
|
|
560
2073
|
}
|
|
561
2074
|
|
|
562
|
-
//
|
|
563
|
-
Object.defineProperty(window, 'loadGames', {
|
|
564
|
-
value: loadGames,
|
|
565
|
-
writable: false,
|
|
566
|
-
configurable: false
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
Object.defineProperty(window, 'decodeGameData', {
|
|
570
|
-
value: function() { triggerSecurity(); },
|
|
571
|
-
writable: false,
|
|
572
|
-
configurable: false
|
|
573
|
-
});
|
|
2075
|
+
// ═══════════ init ═══════════
|
|
574
2076
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
2077
|
+
async function fetchLocalGames() {
|
|
2078
|
+
try {
|
|
2079
|
+
const res = await fetch(serverBase() + '/lg/');
|
|
2080
|
+
const names = await res.json();
|
|
2081
|
+
state.localGames = names.sort();
|
|
2082
|
+
renderGames();
|
|
2083
|
+
} catch { /* server missing — ignore */ }
|
|
580
2084
|
}
|
|
581
2085
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
2086
|
+
function seedDemo() {
|
|
2087
|
+
if (state.movies.length) return;
|
|
2088
|
+
state.movies = DEMO_MOVIES.map((dm) => Object.assign({}, dm, {
|
|
2089
|
+
id: uid(), url: dm.videoUrl, fav: false, watched: false, progress: 0, added: Date.now(),
|
|
2090
|
+
}));
|
|
2091
|
+
store.set(K.movies, state.movies);
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
function init() {
|
|
2095
|
+
seedDemo();
|
|
2096
|
+
// repair movies saved by older versions: extract drive ids, fix posters
|
|
2097
|
+
state.movies.forEach((mv) => {
|
|
2098
|
+
if (mv.source === 'url' && mv.url) {
|
|
2099
|
+
const p = parseDrive(mv.url);
|
|
2100
|
+
if (p.kind === 'file') { mv.driveId = p.id; mv.source = 'file'; }
|
|
2101
|
+
else if (p.kind === 'folder') { mv.driveId = p.id; mv.source = 'folder'; }
|
|
587
2102
|
}
|
|
588
|
-
|
|
589
|
-
}
|
|
590
|
-
|
|
2103
|
+
if (mv.driveId && !mv.poster) mv.poster = driveThumb(mv.driveId);
|
|
2104
|
+
});
|
|
2105
|
+
store.set(K.movies, state.movies);
|
|
2106
|
+
$('profile-pill').textContent = state.profile.name;
|
|
2107
|
+
$('profile-pill').addEventListener('click', () => toggleCustomizer(true));
|
|
2108
|
+
applyPrefs();
|
|
2109
|
+
bindCustomizer();
|
|
2110
|
+
bindGames();
|
|
2111
|
+
bindMovies();
|
|
2112
|
+
bindForm();
|
|
2113
|
+
bindTunnel();
|
|
2114
|
+
bindHotkeys();
|
|
2115
|
+
bindWork();
|
|
2116
|
+
renderWorkTimer();
|
|
2117
|
+
renderTodos();
|
|
2118
|
+
renderGames();
|
|
2119
|
+
renderMovies();
|
|
2120
|
+
tick(); setInterval(tick, 1000);
|
|
2121
|
+
canvas.width = window.innerWidth; canvas.height = window.innerHeight;
|
|
2122
|
+
initMotes();
|
|
2123
|
+
bgLoop();
|
|
2124
|
+
const last = store.get(K.last, {});
|
|
2125
|
+
$('stat-last').textContent = last.name ? last.name.slice(0, 12) : '—';
|
|
2126
|
+
fetchLocalGames();
|
|
2127
|
+
runBoot();
|
|
2128
|
+
}
|
|
591
2129
|
|
|
592
|
-
//
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
if (
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
2130
|
+
// keep it all in-page: block view-source, devtools & right-click menu
|
|
2131
|
+
document.addEventListener('contextmenu', (e) => { e.preventDefault(); });
|
|
2132
|
+
window.addEventListener('contextmenu', (e) => { e.preventDefault(); return false; });
|
|
2133
|
+
document.oncontextmenu = () => false;
|
|
2134
|
+
window.addEventListener('keydown', (e) => {
|
|
2135
|
+
const k = (e.key || '').toLowerCase();
|
|
2136
|
+
const combo = e.ctrlKey || e.metaKey;
|
|
2137
|
+
if (combo && (k === 'u' || k === 's' || k === 'p')) { e.preventDefault(); e.stopPropagation(); return false; }
|
|
2138
|
+
if (combo && e.shiftKey && (k === 'i' || k === 'j' || k === 'c' || k === 'k' || k === 'u')) { e.preventDefault(); e.stopPropagation(); return false; }
|
|
2139
|
+
if (e.key === 'F12') { e.preventDefault(); e.stopPropagation(); return false; }
|
|
2140
|
+
}, true);
|
|
2141
|
+
// if someone opens a view-source: tab, bounce them back to the real page
|
|
2142
|
+
if (location.protocol === 'view-source:') { /* noop guard */ }
|
|
2143
|
+
if (document.documentElement && location.href.startsWith('view-source:')) {
|
|
2144
|
+
location.replace(location.href.replace('view-source:', ''));
|
|
2145
|
+
}
|
|
606
2146
|
|
|
2147
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
607
2148
|
})();
|
|
608
2149
|
</script>
|
|
609
|
-
|
|
610
2150
|
</body>
|
|
611
2151
|
</html>
|