class-studys 7.0.1 → 7.0.2

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.
Files changed (2) hide show
  1. package/index.html +299 -114
  2. package/package.json +1 -1
package/index.html CHANGED
@@ -21,8 +21,8 @@ button{font-family:inherit;cursor:pointer;border:none;background:none;color:inhe
21
21
  /* ---------- particles ---------- */
22
22
  #particles{position:fixed;inset:0;z-index:0;pointer-events:none}
23
23
 
24
- /* ---------- ambient glow (tiny bit of orange) ---------- */
25
- .glow{position:fixed;z-index:0;pointer-events:none;filter:blur(120px);opacity:.14;border-radius:50%;
24
+ /* ---------- ambient glow ---------- */
25
+ .glow{position:fixed;z-index:0;pointer-events:none;filter:blur(120px);opacity:.13;border-radius:50%;
26
26
  background:var(--accent);animation:glowFloat 14s ease-in-out infinite alternate}
27
27
  .glow.g1{width:55vw;height:55vw;top:-20vw;right:-15vw}
28
28
  .glow.g2{width:40vw;height:40vw;bottom:-15vw;left:-10vw;animation-delay:-7s}
@@ -41,91 +41,123 @@ button{font-family:inherit;cursor:pointer;border:none;background:none;color:inhe
41
41
  .glass::before{content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
42
42
  background:linear-gradient(135deg,rgba(255,255,255,.14),transparent 40%,transparent 70%,rgba(255,255,255,.05))}
43
43
 
44
- /* ---------- HOME ---------- */
45
- #home{padding:6vh 6vw 150px;overflow-y:auto;scrollbar-width:none;align-items:center;text-align:center;display:flex;flex-direction:column}
44
+ /* ============================================================
45
+ HOME — left-aligned hero, long scrollable page
46
+ ============================================================ */
47
+ #home{overflow-y:auto;scrollbar-width:none;display:block;padding:0}
48
+ #proxy{padding:0}
49
+ #proxyFrame{flex:1;width:100%;height:100%;border:none;background:#000}
46
50
  #home::-webkit-scrollbar{display:none}
47
- .home-left{max-width:900px;width:100%;display:flex;flex-direction:column;align-items:center;animation:riseIn 1s cubic-bezier(.22,1,.36,1) both}
48
- @keyframes riseIn{from{opacity:0;transform:translateY(40px)}to{opacity:1;transform:none}}
51
+
52
+ .hero{min-height:100vh;display:flex;flex-direction:column;justify-content:center;align-items:flex-start;
53
+ padding:12vh 7vw 10vh;position:relative;text-align:left;overflow:hidden}
54
+ /* left edge accent beam */
55
+ .hero::before{content:"";position:absolute;left:0;top:15%;bottom:15%;width:3px;border-radius:3px;
56
+ background:linear-gradient(180deg,transparent,var(--accent),transparent);
57
+ animation:beamPulse 4s ease-in-out infinite;opacity:.7}
58
+ @keyframes beamPulse{0%,100%{opacity:.35;filter:blur(1px)}50%{opacity:.9;filter:blur(0)}}
59
+
49
60
  .os-badge{display:inline-flex;align-items:center;gap:8px;padding:6px 16px;border-radius:99px;
50
61
  font-size:12px;letter-spacing:.25em;text-transform:uppercase;color:var(--accent);
51
- border:1px solid color-mix(in srgb,var(--accent) 40%,transparent);margin-bottom:24px;
52
- animation:pulseBadge 3s ease-in-out infinite}
62
+ border:1px solid color-mix(in srgb,var(--accent) 40%,transparent);margin-bottom:26px;
63
+ animation:slideLeft .8s cubic-bezier(.22,1,.36,1) both, pulseBadge 3s ease-in-out infinite 1s}
64
+ @keyframes slideLeft{from{opacity:0;transform:translateX(-46px)}to{opacity:1;transform:none}}
53
65
  @keyframes pulseBadge{0%,100%{box-shadow:0 0 0 0 color-mix(in srgb,var(--accent) 35%,transparent)}
54
66
  50%{box-shadow:0 0 24px 4px color-mix(in srgb,var(--accent) 25%,transparent)}}
55
67
  .os-badge .dot{width:7px;height:7px;border-radius:50%;background:var(--accent);animation:blink 1.6s infinite}
56
68
  @keyframes blink{50%{opacity:.25}}
57
- #bigTitle{font-size:clamp(56px,9vw,124px);font-weight:800;line-height:.95;letter-spacing:-.03em;
58
- background:linear-gradient(180deg,#fff 30%,#8a8a8a);-webkit-background-clip:text;background-clip:text;
59
- -webkit-text-fill-color:transparent;animation:titleShimmer 6s ease-in-out infinite}
60
- #bigTitle span{background:linear-gradient(135deg,var(--accent),color-mix(in srgb,var(--accent) 55%,#fff));
61
- -webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
62
- @keyframes titleShimmer{0%,100%{filter:drop-shadow(0 0 0 transparent)}50%{filter:drop-shadow(0 0 34px color-mix(in srgb,var(--accent) 30%,transparent))}}
63
- #rotator{height:28px;margin-top:22px;font-size:clamp(15px,1.6vw,20px);color:#9a9a9a;overflow:hidden;position:relative;width:100%}
64
- #rotator .line{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:10px;
65
- opacity:0;transform:translateY(24px);transition:opacity .6s,transform .6s cubic-bezier(.22,1,.36,1)}
69
+
70
+ #bigTitle{font-size:clamp(60px,10vw,138px);font-weight:800;line-height:.93;letter-spacing:-.035em;
71
+ display:flex;flex-direction:column;align-items:flex-start}
72
+ #bigTitle {
73
+ font-size: clamp(60px, 10vw, 138px);
74
+ font-weight: 800;
75
+ line-height: 1;
76
+ letter-spacing: -0.035em;
77
+ display: flex;
78
+ flex-direction: row;
79
+ align-items: center;
80
+ gap: 0.2em;
81
+ white-space: nowrap;
82
+ }
83
+
84
+ #rotator{height:30px;margin-top:26px;font-size:clamp(15px,1.6vw,20px);color:#9a9a9a;overflow:hidden;position:relative;width:min(560px,80vw)}
85
+ #rotator .line{position:absolute;inset:0;display:flex;align-items:center;gap:10px;
86
+ opacity:0;transform:translateY(26px);transition:opacity .6s,transform .6s cubic-bezier(.22,1,.36,1)}
66
87
  #rotator .line.on{opacity:1;transform:none}
67
- #rotator .line.off{opacity:0;transform:translateY(-24px)}
68
- #rotator .tick{width:22px;height:2px;background:var(--accent);border-radius:2px;flex:none}
88
+ #rotator .line.off{opacity:0;transform:translateY(-26px)}
89
+ #rotator .tick{width:22px;height:2px;background:var(--accent);border-radius:2px;flex:none;
90
+ box-shadow:0 0 10px var(--accent)}
69
91
 
70
- /* home widget row */
71
- .widget-row{display:flex;gap:16px;margin-top:44px;flex-wrap:wrap;justify-content:center;animation:riseIn 1.1s .15s cubic-bezier(.22,1,.36,1) both}
92
+ /* widget row */
93
+ .widget-row{display:flex;gap:16px;margin-top:48px;flex-wrap:wrap;animation:slideLeft 1s .55s cubic-bezier(.22,1,.36,1) both}
72
94
  .widget{padding:18px 22px;display:flex;align-items:center;gap:16px;min-width:200px;
95
+ animation:floaty 5.5s ease-in-out infinite;animation-delay:var(--fd,0s);
73
96
  transition:transform .35s cubic-bezier(.22,1,.36,1),box-shadow .35s}
74
- .widget:hover{transform:translateY(-5px);box-shadow:0 16px 44px rgba(0,0,0,.6),0 0 30px color-mix(in srgb,var(--accent) 12%,transparent)}
97
+ .widget:hover{transform:translateY(-6px) scale(1.03);box-shadow:0 16px 44px rgba(0,0,0,.6),0 0 30px color-mix(in srgb,var(--accent) 14%,transparent)}
98
+ @keyframes floaty{0%,100%{translate:0 0}50%{translate:0 -7px}}
75
99
  .widget .wicon{font-size:30px;filter:drop-shadow(0 0 12px color-mix(in srgb,var(--accent) 45%,transparent))}
76
100
  .widget .wlabel{font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:#888}
77
101
  .widget .wvalue{font-size:20px;font-weight:700;margin-top:2px}
78
- /* battery ring */
79
102
  .ring{transform:rotate(-90deg)}
80
103
  .ring .bg{stroke:rgba(255,255,255,.1)}
81
- .ring .fg{stroke:var(--accent);stroke-linecap:round;transition:stroke-dashoffset 1s cubic-bezier(.22,1,.36,1)}
104
+ .ring .fg{stroke:var(--accent);stroke-linecap:round;transition:stroke-dashoffset 1s cubic-bezier(.22,1,.36,1);
105
+ filter:drop-shadow(0 0 6px var(--accent))}
82
106
 
83
- /* home nav */
84
- .home-nav{display:flex;gap:14px;margin-top:46px;flex-wrap:wrap;justify-content:center;animation:riseIn 1.2s .3s cubic-bezier(.22,1,.36,1) both}
85
- .nav-btn{padding:14px 30px;border-radius:18px;font-size:15px;font-weight:600;letter-spacing:.04em;
107
+ /* nav */
108
+ .home-nav{display:flex;gap:14px;margin-top:50px;flex-wrap:wrap;animation:slideLeft 1.1s .7s cubic-bezier(.22,1,.36,1) both}
109
+ .nav-btn{padding:15px 30px;border-radius:18px;font-size:15px;font-weight:600;letter-spacing:.04em;
86
110
  display:flex;align-items:center;gap:10px;transition:all .3s cubic-bezier(.22,1,.36,1)}
87
111
  .nav-btn:hover{transform:translateY(-4px) scale(1.03);border-color:color-mix(in srgb,var(--accent) 55%,transparent);
88
112
  box-shadow:0 12px 34px rgba(0,0,0,.55),0 0 26px color-mix(in srgb,var(--accent) 18%,transparent)}
89
113
  .nav-btn .arr{transition:transform .3s}
90
- .nav-btn:hover .arr{transform:translateX(5px)}
114
+ .nav-btn:hover .arr{transform:translateX(6px)}
91
115
  .nav-btn.primary{background:linear-gradient(135deg,var(--accent),color-mix(in srgb,var(--accent) 70%,#000));
92
116
  color:#0a0a0a;box-shadow:0 8px 26px color-mix(in srgb,var(--accent) 35%,transparent)}
93
117
 
94
- /* ---------- home library marquee ---------- */
95
- .library{width:100%;max-width:1200px;margin-top:80px;display:flex;flex-direction:column;gap:20px;
96
- animation:riseIn 1.3s .4s cubic-bezier(.22,1,.36,1) both}
97
- .library .lib-head{font-size:11px;letter-spacing:.4em;text-transform:uppercase;color:#666;
98
- display:flex;align-items:center;justify-content:center;gap:16px}
99
- .library .lib-head::before,.library .lib-head::after{content:"";width:70px;height:1px;
100
- background:linear-gradient(90deg,transparent,rgba(255,255,255,.25))}
101
- .library .lib-head::after{background:linear-gradient(90deg,rgba(255,255,255,.25),transparent)}
102
- .rail-window{overflow:hidden;width:100%;position:relative;padding:6px 0;
118
+ /* scroll cue */
119
+ .scroll-cue{position:absolute;left:50%;bottom:26px;transform:translateX(-50%);display:flex;flex-direction:column;
120
+ align-items:center;gap:6px;color:#666;font-size:10px;letter-spacing:.35em;text-transform:uppercase;
121
+ animation:fadeIn 1.4s 1.6s both}
122
+ .scroll-cue .chev{width:12px;height:12px;border-right:2px solid var(--accent);border-bottom:2px solid var(--accent);
123
+ transform:rotate(45deg);animation:chevBounce 1.6s ease-in-out infinite}
124
+ @keyframes chevBounce{0%,100%{transform:rotate(45deg) translate(0,0);opacity:.5}50%{transform:rotate(45deg) translate(5px,5px);opacity:1}}
125
+ @keyframes fadeIn{from{opacity:0}to{opacity:1}}
126
+
127
+ /* ---------- library marquee ---------- */
128
+ .library{width:100%;padding:9vh 0 16vh;display:flex;flex-direction:column;gap:24px;
129
+ animation:fadeIn 1s both}
130
+ .library .lib-title{padding:0 7vw;font-size:clamp(28px,4vw,48px);font-weight:800;letter-spacing:-.02em;display:flex;align-items:baseline;gap:16px}
131
+ .library .lib-title em{font-style:normal;color:var(--accent)}
132
+ .library .lib-title .n{font-size:13px;color:#666;font-weight:600;letter-spacing:.2em}
133
+ .library .lib-sub{padding:0 7vw;color:#777;font-size:14px;margin-top:-12px}
134
+ .rail-window{overflow:hidden;width:100%;position:relative;padding:8px 0;
103
135
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 7%,#000 93%,transparent);
104
136
  mask-image:linear-gradient(90deg,transparent,#000 7%,#000 93%,transparent)}
105
- .rail-track{display:flex;gap:18px;width:max-content;padding-right:18px;
106
- animation:railScroll 42s linear infinite}
107
- .rail-track.rev{animation-direction:reverse;animation-duration:50s}
137
+ .rail-track{display:flex;gap:20px;width:max-content;padding-right:20px;animation:railScroll 42s linear infinite}
138
+ .rail-track.rev{animation-direction:reverse;animation-duration:52s}
108
139
  .rail-window:hover .rail-track{animation-play-state:paused}
109
140
  @keyframes railScroll{from{transform:translateX(0)}to{transform:translateX(-50%)}}
110
- .rail-game{width:92px;height:92px;flex:none;border-radius:24px;overflow:hidden;
141
+ .rail-game{width:96px;height:96px;flex:none;border-radius:26px;overflow:hidden;
111
142
  position:relative;border:1px solid var(--glass-border);cursor:pointer;
112
- transition:transform .35s cubic-bezier(.34,1.56,.64,1),box-shadow .35s}
113
- .rail-game:hover{transform:scale(1.15) rotate(-3deg);box-shadow:0 10px 30px rgba(0,0,0,.6),0 0 24px var(--gc,#ff6b1a)}
143
+ transition:transform .35s cubic-bezier(.34,1.56,.64,1),box-shadow .35s;animation:floaty 6s ease-in-out infinite;animation-delay:var(--fd,0s)}
144
+ .rail-game:hover{transform:scale(1.16) rotate(-4deg);animation-play-state:paused;
145
+ box-shadow:0 12px 34px rgba(0,0,0,.65),0 0 26px var(--gc,#ff6b1a)}
114
146
  .rail-game img{width:100%;height:100%;object-fit:cover;display:block}
115
147
 
116
- /* ---------- section headers (games / movies) ---------- */
148
+ /* section headers */
117
149
  .hub{padding:34px 5vw 130px;overflow-y:auto;scrollbar-width:none}
118
150
  .hub::-webkit-scrollbar{display:none}
119
151
  .hub-head{display:flex;align-items:flex-end;justify-content:space-between;margin-bottom:34px;
120
- animation:riseIn .7s cubic-bezier(.22,1,.36,1) both}
152
+ animation:slideLeft .7s cubic-bezier(.22,1,.36,1) both}
121
153
  .hub-head h2{font-size:clamp(34px,4.5vw,58px);font-weight:800;letter-spacing:-.02em}
122
154
  .hub-head h2 em{font-style:normal;color:var(--accent)}
123
155
  .hub-head .count{font-size:13px;color:#777;letter-spacing:.15em;text-transform:uppercase}
124
156
 
125
- /* games grid: rows of 5, icons only */
157
+ /* games grid: rows of 5 */
126
158
  .games-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:22px}
127
159
  @media(max-width:1100px){.games-grid{grid-template-columns:repeat(4,1fr)}}
128
- @media(max-width:800px){.games-grid{grid-template-columns:repeat(3,1fr)}}
160
+ @media(max-width:800px){.games-grid{grid-template-columns:repeat(3,1fr)}.hero{padding:10vh 6vw}}
129
161
  @media(max-width:520px){.games-grid{grid-template-columns:repeat(2,1fr)}}
130
162
  .game-tile{aspect-ratio:1;border-radius:26px;overflow:hidden;position:relative;cursor:pointer;
131
163
  border:1px solid var(--glass-border);
@@ -153,7 +185,7 @@ button{font-family:inherit;cursor:pointer;border:none;background:none;color:inhe
153
185
  .game-tile .feat-tag{position:absolute;top:10px;left:10px;background:var(--accent);color:#000;
154
186
  font-size:9px;font-weight:800;letter-spacing:.12em;padding:4px 9px;border-radius:8px}
155
187
 
156
- /* movies grid: no names */
188
+ /* movies */
157
189
  .movies-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:26px}
158
190
  .movie-tile{border-radius:20px;overflow:hidden;cursor:pointer;position:relative;aspect-ratio:2/3;
159
191
  border:1px solid var(--glass-border);
@@ -169,24 +201,25 @@ button{font-family:inherit;cursor:pointer;border:none;background:none;color:inhe
169
201
  filter:drop-shadow(0 0 14px var(--accent));transition:transform .3s}
170
202
  .movie-tile:hover .tri{transform:scale(1.2)}
171
203
 
172
- /* ---------- play modal ---------- */
204
+ /* play modal */
173
205
  #playModal{position:fixed;inset:0;z-index:50;display:none;align-items:center;justify-content:center;
174
206
  background:rgba(0,0,0,.82);backdrop-filter:blur(18px);-webkit-backdrop-filter:blur(18px)}
175
207
  #playModal.open{display:flex;animation:fadeIn .3s ease}
176
- @keyframes fadeIn{from{opacity:0}to{opacity:1}}
177
208
  .play-shell{width:min(1100px,94vw);height:min(86vh,90vh);border-radius:26px;overflow:hidden;
178
- display:flex;flex-direction:column;border:1px solid var(--glass-border);
179
- background:#0a0a0a;box-shadow:0 40px 120px rgba(0,0,0,.8),0 0 60px color-mix(in srgb,var(--accent) 10%,transparent);
209
+ display:flex;flex-direction:column;border:1px solid var(--glass-border);background:#0a0a0a;
210
+ box-shadow:0 40px 120px rgba(0,0,0,.8),0 0 60px color-mix(in srgb,var(--accent) 10%,transparent);
180
211
  animation:popIn .45s cubic-bezier(.34,1.56,.64,1)}
181
212
  @keyframes popIn{from{opacity:0;transform:scale(.9)}to{opacity:1;transform:none}}
182
213
  .play-bar{display:flex;align-items:center;gap:14px;padding:12px 18px;
183
214
  background:rgba(255,255,255,.04);border-bottom:1px solid var(--glass-border)}
184
- .play-bar .ttl{font-weight:700;font-size:15px;letter-spacing:.02em;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
215
+ .play-bar .ttl{font-weight:700;font-size:15px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
185
216
  .play-bar .live{width:8px;height:8px;border-radius:50%;background:var(--accent);animation:blink 1.4s infinite;flex:none}
186
217
  .play-bar button{padding:8px 18px;border-radius:12px;font-size:13px;font-weight:700;
187
218
  background:rgba(255,255,255,.08);border:1px solid var(--glass-border);transition:all .25s}
188
219
  .play-bar button:hover{background:var(--accent);color:#000}
189
220
  #playFrame{flex:1;width:100%;border:none;background:#000}
221
+ .play-shell:fullscreen{width:100vw;height:100vh;border-radius:0}
222
+ .play-shell:fullscreen .play-bar{padding:14px 22px}
190
223
  .play-loading{position:absolute;inset:44px 0 0;display:flex;flex-direction:column;gap:18px;
191
224
  align-items:center;justify-content:center;background:#0a0a0a;transition:opacity .4s;z-index:2}
192
225
  .play-loading.gone{opacity:0;pointer-events:none}
@@ -194,9 +227,10 @@ button{font-family:inherit;cursor:pointer;border:none;background:none;color:inhe
194
227
  border-top-color:var(--accent);animation:spin 1s linear infinite}
195
228
  @keyframes spin{to{transform:rotate(360deg)}}
196
229
 
197
- /* ---------- bottom nav ---------- */
230
+ /* dock */
198
231
  #dock{position:fixed;left:50%;bottom:22px;transform:translateX(-50%);z-index:20;display:flex;gap:6px;
199
232
  padding:8px;border-radius:24px;animation:riseIn 1s .5s cubic-bezier(.22,1,.36,1) both}
233
+ @keyframes riseIn{from{opacity:0;transform:translate(-50%,40px)}to{opacity:1;transform:translate(-50%,0)}}
200
234
  .dock-btn{padding:12px 22px;border-radius:17px;font-size:14px;font-weight:600;color:#aaa;
201
235
  display:flex;align-items:center;gap:8px;transition:all .3s cubic-bezier(.22,1,.36,1);position:relative}
202
236
  .dock-btn:hover{color:#fff;transform:translateY(-3px)}
@@ -204,7 +238,7 @@ button{font-family:inherit;cursor:pointer;border:none;background:none;color:inhe
204
238
  box-shadow:0 6px 22px color-mix(in srgb,var(--accent) 40%,transparent)}
205
239
  .dock-btn .dicon{font-size:16px}
206
240
 
207
- /* ---------- settings ---------- */
241
+ /* settings */
208
242
  #settingsModal{position:fixed;inset:0;z-index:60;display:none;align-items:center;justify-content:center;
209
243
  background:rgba(0,0,0,.7);backdrop-filter:blur(14px)}
210
244
  #settingsModal.open{display:flex;animation:fadeIn .25s ease}
@@ -226,10 +260,31 @@ input[type=text],input[type=range]{accent-color:var(--accent)}
226
260
  padding:10px 14px;border-radius:12px;font-size:14px;width:170px;outline:none;transition:border .25s}
227
261
  .set-input:focus{border-color:var(--accent)}
228
262
 
263
+ /* ---------- TOUR ---------- */
264
+ #tour{position:fixed;inset:0;z-index:80;display:none}
265
+ #tour.open{display:block}
266
+ #tour .dim{position:absolute;inset:0;background:rgba(0,0,0,.72);backdrop-filter:blur(6px);animation:fadeIn .3s}
267
+ .tour-card{position:absolute;left:50%;bottom:110px;transform:translateX(-50%);width:min(440px,90vw);
268
+ padding:26px 28px;animation:popIn .45s cubic-bezier(.34,1.56,.64,1)}
269
+ .tour-card .step-n{font-size:10px;letter-spacing:.3em;color:var(--accent);font-weight:800;text-transform:uppercase}
270
+ .tour-card h4{font-size:20px;font-weight:800;margin:8px 0 8px}
271
+ .tour-card p{font-size:14px;color:#b5b5b5;line-height:1.6}
272
+ .tour-card .tour-nav{display:flex;gap:10px;margin-top:20px;align-items:center}
273
+ .tour-card .tour-nav .prog{flex:1;display:flex;gap:6px}
274
+ .tour-card .tour-nav .prog i{flex:1;height:3px;border-radius:3px;background:rgba(255,255,255,.14);transition:background .3s}
275
+ .tour-card .tour-nav .prog i.on{background:var(--accent);box-shadow:0 0 8px var(--accent)}
276
+ .tour-btn{padding:10px 20px;border-radius:13px;font-size:13px;font-weight:700;
277
+ background:rgba(255,255,255,.08);border:1px solid var(--glass-border);transition:all .25s}
278
+ .tour-btn.next{background:linear-gradient(135deg,var(--accent),color-mix(in srgb,var(--accent) 75%,#000));color:#0a0a0a;border:none}
279
+ .tour-btn:hover{transform:translateY(-2px)}
280
+ .tour-spot{position:absolute;z-index:81;border-radius:28px;pointer-events:none;
281
+ box-shadow:0 0 0 3px var(--accent),0 0 44px 6px color-mix(in srgb,var(--accent) 45%,transparent);
282
+ animation:spotPulse 1.6s ease-in-out infinite;transition:all .5s cubic-bezier(.22,1,.36,1)}
283
+ @keyframes spotPulse{0%,100%{opacity:.85}50%{opacity:1}}
284
+
229
285
  /* ---------- AI ---------- */
230
- #ai{padding:0}
231
286
  .ai-wrap{flex:1;display:flex;flex-direction:column;max-width:900px;width:100%;margin:0 auto;padding:30px 4vw 120px}
232
- .ai-head{display:flex;align-items:center;gap:16px;margin-bottom:20px;animation:riseIn .6s both}
287
+ .ai-head{display:flex;align-items:center;gap:16px;margin-bottom:20px;animation:slideLeft .6s both}
233
288
  .ai-orb{width:52px;height:52px;border-radius:50%;flex:none;position:relative;
234
289
  background:radial-gradient(circle at 32% 30%,color-mix(in srgb,var(--accent) 80%,#fff),var(--accent) 45%,#3a1500);
235
290
  animation:orbFloat 4s ease-in-out infinite;box-shadow:0 0 34px color-mix(in srgb,var(--accent) 45%,transparent)}
@@ -249,7 +304,6 @@ input[type=text],input[type=range]{accent-color:var(--accent)}
249
304
  .msg.bot{align-self:flex-start;background:var(--glass);backdrop-filter:blur(20px);
250
305
  border:1px solid var(--glass-border);border-bottom-left-radius:6px}
251
306
  .msg.bot .who{font-size:10px;letter-spacing:.2em;text-transform:uppercase;color:var(--accent);font-weight:800;margin-bottom:6px}
252
- /* code widget */
253
307
  .code-widget{margin:12px 0;border-radius:16px;overflow:hidden;border:1px solid var(--glass-border);
254
308
  background:#0b0b0f;box-shadow:0 8px 24px rgba(0,0,0,.4)}
255
309
  .code-head{display:flex;align-items:center;gap:10px;padding:9px 14px;background:rgba(255,255,255,.05);
@@ -271,7 +325,8 @@ input[type=text],input[type=range]{accent-color:var(--accent)}
271
325
  .typing i:nth-child(2){animation-delay:.18s}.typing i:nth-child(3){animation-delay:.36s}
272
326
  @keyframes tp{0%,60%,100%{transform:translateY(0);opacity:.4}30%{transform:translateY(-7px);opacity:1}}
273
327
  #aiInputBar{display:flex;gap:12px;margin-top:18px;padding:10px 10px 10px 22px;align-items:center;
274
- animation:riseIn .8s .2s both}
328
+ animation:riseInDock .8s .2s both}
329
+ @keyframes riseInDock{from{opacity:0;transform:translateY(24px)}to{opacity:1;transform:none}}
275
330
  #aiInput{flex:1;background:none;border:none;outline:none;color:#fff;font-size:15px;font-family:inherit}
276
331
  #aiInput::placeholder{color:#666}
277
332
  #sendBtn{width:48px;height:48px;border-radius:16px;flex:none;display:flex;align-items:center;justify-content:center;
@@ -283,14 +338,14 @@ input[type=text],input[type=range]{accent-color:var(--accent)}
283
338
  color:#aaa;background:rgba(255,255,255,.04);transition:all .25s}
284
339
  .chip:hover{color:#fff;border-color:var(--accent);transform:translateY(-2px)}
285
340
 
286
- /* toast */
287
341
  #toast{position:fixed;left:50%;bottom:96px;transform:translate(-50%,20px);z-index:99;opacity:0;
288
- background:rgba(15,15,15,.9);backdrop-filter:blur(14px);border:1px solid var(--glass-border);
342
+ background:rgba(15,15,10,.92);backdrop-filter:blur(14px);border:1px solid var(--glass-border);
289
343
  padding:11px 22px;border-radius:14px;font-size:13px;font-weight:600;pointer-events:none;
290
344
  transition:all .35s cubic-bezier(.22,1,.36,1)}
291
345
  #toast.on{opacity:1;transform:translate(-50%,0)}
292
346
  </style>
293
347
  <base target="_blank">
348
+ <base target="_blank">
294
349
  </head>
295
350
  <body>
296
351
 
@@ -300,17 +355,20 @@ input[type=text],input[type=range]{accent-color:var(--accent)}
300
355
 
301
356
  <!-- ============ HOME ============ -->
302
357
  <div class="screen active" id="home">
303
- <div class="home-left">
304
- <div class="os-badge"><span class="dot"></span>IGNITE OS · v2.0</div>
305
- <h1 id="bigTitle">IGNITE<br><span>OS</span></h1>
358
+ <div class="hero" id="hero">
359
+ <div class="os-badge"><span class="dot"></span>IGNITE OS · v2.1</div>
360
+ <h1 id="bigTitle">
361
+ <span class="row" data-txt="IGNITE"></span>
362
+ <span class="row os" data-txt="OS"></span>
363
+ </h1>
306
364
  <div id="rotator"></div>
307
365
 
308
- <div class="widget-row">
309
- <div class="widget glass">
366
+ <div class="widget-row" id="widgetRow">
367
+ <div class="widget glass" style="--fd:0s">
310
368
  <div class="wicon" id="wIcon">⛅</div>
311
369
  <div><div class="wlabel">Weather</div><div class="wvalue" id="wVal">--°</div></div>
312
370
  </div>
313
- <div class="widget glass">
371
+ <div class="widget glass" style="--fd:1.4s">
314
372
  <svg class="ring" width="52" height="52" viewBox="0 0 52 52">
315
373
  <circle class="bg" cx="26" cy="26" r="21" fill="none" stroke-width="5"/>
316
374
  <circle class="fg" id="battRing" cx="26" cy="26" r="21" fill="none" stroke-width="5"
@@ -318,7 +376,7 @@ input[type=text],input[type=range]{accent-color:var(--accent)}
318
376
  </svg>
319
377
  <div><div class="wlabel">Battery</div><div class="wvalue" id="battVal">--%</div></div>
320
378
  </div>
321
- <div class="widget glass">
379
+ <div class="widget glass" style="--fd:2.8s">
322
380
  <div class="wicon">🕒</div>
323
381
  <div><div class="wlabel">Time</div><div class="wvalue" id="clockVal">--:--</div></div>
324
382
  </div>
@@ -329,17 +387,19 @@ input[type=text],input[type=range]{accent-color:var(--accent)}
329
387
  <button class="nav-btn glass" onclick="go('movies')">Movies <span class="arr">→</span></button>
330
388
  <button class="nav-btn glass" onclick="go('ai')">Ask IGNITE AI <span class="arr">→</span></button>
331
389
  </div>
390
+
391
+ <div class="scroll-cue"><span>Scroll</span><div class="chev"></div></div>
332
392
  </div>
333
393
 
334
394
  <!-- library marquee -->
335
- <div class="library">
336
- <div class="lib-head">Game Library · hover to pause · click to play</div>
395
+ <div class="library" id="librarySec">
396
+ <div class="lib-title">Game <em>Library</em> <span class="n">20 TITLES</span></div>
397
+ <div class="lib-sub">Auto-scrolling · hover to pause · click any icon to play instantly</div>
337
398
  <div class="rail-window"><div class="rail-track" id="railTrack"></div></div>
338
399
  <div class="rail-window"><div class="rail-track rev" id="railTrack2"></div></div>
339
400
  </div>
340
401
  </div>
341
402
 
342
-
343
403
  <!-- ============ GAMES HUB ============ -->
344
404
  <div class="screen" id="games">
345
405
  <div class="hub">
@@ -373,7 +433,7 @@ input[type=text],input[type=range]{accent-color:var(--accent)}
373
433
  </div>
374
434
  </div>
375
435
  <div id="chatLog"></div>
376
- <div class="chips" id="chips">
436
+ <div class="chips">
377
437
  <button class="chip" onclick="quickAsk('Write a Python script that sorts a list and explain it')">💻 Give me code</button>
378
438
  <button class="chip" onclick="quickAsk('Explain quantum computing simply')">🧠 Teach me something</button>
379
439
  <button class="chip" onclick="quickAsk('Help me build a game in JavaScript')">🎮 Build a game</button>
@@ -385,13 +445,20 @@ input[type=text],input[type=range]{accent-color:var(--accent)}
385
445
  </div>
386
446
  </div>
387
447
 
448
+ <!-- ============ PROXY ============ -->
449
+ <div class="screen" id="proxy">
450
+ <iframe id="proxyFrame" src="about:blank"
451
+ sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"></iframe>
452
+ </div>
453
+
388
454
  <!-- ============ DOCK ============ -->
389
455
  <nav id="dock" class="glass">
390
456
  <button class="dock-btn on" data-s="home" onclick="go('home')"><span class="dicon">⌂</span>Home</button>
391
457
  <button class="dock-btn" data-s="games" onclick="go('games')"><span class="dicon">🎮</span>Games</button>
392
458
  <button class="dock-btn" data-s="movies" onclick="go('movies')"><span class="dicon">🎬</span>Movies</button>
393
459
  <button class="dock-btn" data-s="ai" onclick="go('ai')"><span class="dicon">✦</span>AI</button>
394
- <button class="dock-btn" onclick="openSettings()"><span class="dicon">⚙</span></button>
460
+ <button class="dock-btn" data-s="proxy" onclick="go('proxy')"><span class="dicon">🧭</span>Proxy</button>
461
+ <button class="dock-btn" id="settingsBtn" onclick="openSettings()"><span class="dicon">⚙</span></button>
395
462
  </nav>
396
463
 
397
464
  <!-- ============ PLAY MODAL ============ -->
@@ -400,11 +467,12 @@ input[type=text],input[type=range]{accent-color:var(--accent)}
400
467
  <div class="play-bar">
401
468
  <span class="live"></span>
402
469
  <span class="ttl" id="playTitle">Now Playing</span>
470
+ <button onclick="toggleFullScreen()" title="Fullscreen">⛶ Fullscreen</button>
403
471
  <button onclick="closePlay()">✕ Close</button>
404
472
  </div>
405
473
  <div style="position:relative;flex:1;display:flex">
406
474
  <div class="play-loading" id="playLoading"><div class="spinner"></div><div style="color:#888;font-size:13px">Loading…</div></div>
407
- <iframe id="playFrame" allowfullscreen sandbox="allow-scripts allow-same-origin allow-forms allow-popups-to-escape-sandbox"></iframe>
475
+ <iframe id="playFrame" allowfullscreen sandbox="allow-scripts allow-same-origin allow-forms allow-fullscreen allow-popups-to-escape-sandbox"></iframe>
408
476
  </div>
409
477
  </div>
410
478
  </div>
@@ -413,7 +481,7 @@ input[type=text],input[type=range]{accent-color:var(--accent)}
413
481
  <div id="settingsModal" onclick="if(event.target===this)closeSettings()">
414
482
  <div class="settings-card glass">
415
483
  <h3>Settings</h3>
416
- <div class="sub">Customize IGNITE OS — accent color, weather & particles.</div>
484
+ <div class="sub">Customize IGNITE OS — accent color, weather, particles & AI.</div>
417
485
 
418
486
  <div class="set-row">
419
487
  <div><div class="lbl">Accent Color</div><div class="hint">The tiny bit of orange — make it yours.</div></div>
@@ -431,10 +499,15 @@ input[type=text],input[type=range]{accent-color:var(--accent)}
431
499
  </div>
432
500
 
433
501
  <div class="set-row">
434
- <div><div class="lbl">AI Model</div><div class="hint">Switch the brain behind IGNITE AI.</div></div>
502
+ <div><div class="lbl">AI Model</div><div class="hint">Which OpenAI model IGNITE AI uses.</div></div>
435
503
  <input class="set-input" id="modelInput" type="text" placeholder="model name">
436
504
  </div>
437
505
 
506
+ <div class="set-row">
507
+ <div><div class="lbl">Beginner Tour</div><div class="hint">Replay the welcome walkthrough.</div></div>
508
+ <button class="tour-btn" onclick="closeSettings();startTour()">▶ Replay</button>
509
+ </div>
510
+
438
511
  <div style="display:flex;gap:12px;margin-top:26px">
439
512
  <button class="nav-btn primary glass" style="flex:1;justify-content:center" onclick="saveSettings()">Save</button>
440
513
  <button class="nav-btn glass" style="flex:1;justify-content:center" onclick="closeSettings()">Cancel</button>
@@ -442,12 +515,27 @@ input[type=text],input[type=range]{accent-color:var(--accent)}
442
515
  </div>
443
516
  </div>
444
517
 
518
+ <!-- ============ TOUR ============ -->
519
+ <div id="tour">
520
+ <div class="dim"></div>
521
+ <div class="tour-spot" id="tourSpot"></div>
522
+ <div class="tour-card glass">
523
+ <div class="step-n" id="tourStepN">Step 1 of 5</div>
524
+ <h4 id="tourTitle"></h4>
525
+ <p id="tourText"></p>
526
+ <div class="tour-nav">
527
+ <div class="prog" id="tourProg"></div>
528
+ <button class="tour-btn" onclick="endTour()">Skip</button>
529
+ <button class="tour-btn next" id="tourNext" onclick="tourNext()">Next →</button>
530
+ </div>
531
+ </div>
532
+ </div>
533
+
445
534
  <div id="toast"></div>
446
535
 
447
536
  <script>
448
537
  /* ================= DATA (links encoded — not exposed) ================= */
449
- const GAMES = [{id:'duck-math',name:'DUCK MATH',color:'#ffb26b',featured:true,u:'aHR0cHM6Ly91bnBrZy5jb20vaWduaXRlLWdvb2JlckAxLjEuNC9pbmRleC5odG1sIy9jbGFzcy9kcmlmdC1raW5n',cover:'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRJFXWvY3bIWxbR2SZEnbZaVFRYqFzZJZlOo4SYkWOexg&s'},
450
- {id:'cookie-clicker',name:'Cookie Clicker',color:'#22d3ee',featured:false,u:'aHR0cHM6Ly91bnBrZy5jb20vZWRjdWJlLWNvb2tpZS1jbGlja2VyQDUuMC4wL2luZGV4Lmh0bWw=',cover:'https://i.imgur.com/3ZQ3Z3Z.png'},
538
+ const GAMES = [{id:'cookie-clicker',name:'Cookie Clicker',color:'#22d3ee',featured:false,u:'aHR0cHM6Ly91bnBrZy5jb20vZWRjdWJlLWNvb2tpZS1jbGlja2VyQDUuMC4wL2luZGV4Lmh0bWw=',cover:'https://i.imgur.com/3ZQ3Z3Z.png'},
451
539
  {id:'recoil',name:'Recoil',color:'#ff6b1a',featured:false,u:'aHR0cHM6Ly9nZW5lcmljLWFscGFjYS0zMjUzMjEuZnJhbWVyLmFwcC8=',cover:'https://fancade.com/images/5E9F40DA7F9C9FA8.jpg'},
452
540
  {id:'retro-ball',name:'Retro Ball',color:'#a855f7',featured:false,u:'aHR0cHM6Ly9vYmplY3RzdG9yYWdlLnVzLWFzaGJ1cm4tMS5vcmFjbGVjbG91ZC5jb20vbi9pZHpneWZpN2F3d3IvYi9idWNrZXQtMjAyMzEwMDEtMTUxOC1yYjEuMC9vL2luZGV4Lmh0bWw=',cover:'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTfQEUbXopS95V30bru2UdgdystMD8ElzCDFzH6k6_pUQ&s'},
453
541
  {id:'drift-boss',name:'Drift Boss',color:'#3b82f6',featured:false,u:'aHR0cHM6Ly93d3cubWF0aHBsYXlncm91bmQuY29tL2RyaWZ0LWJvc3MtdjMvaW5kZXguaHRtbA==',cover:'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTMW_L-ZhbnvWj1lwNrWeAGDaK0rG0lCw52zSIsTfTmWNZ9oOVhXvQ_oJRA&s=10'},
@@ -466,16 +554,18 @@ const GAMES = [{id:'duck-math',name:'DUCK MATH',color:'#ffb26b',featured:true,u:
466
554
  {id:'bitlife',name:'Bitlife',color:'#34d399',featured:false,u:'aHR0cHM6Ly91bnBrZy5jb20vZWRjdWJlLWJpdGxpZmVANS4wLjAvaW5kZXguaHRtbA==',cover:'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRZdo5cBqkv1uAcfCofpC-WAViocRNLlysohwHMV2Lqzw&s=10'},
467
555
  {id:'tag',name:'Tag',color:'#facc15',featured:false,u:'aHR0cHM6Ly91bnBrZy5jb20vZWRjdWJlLXRhZ0AxLjAuMC9pbmRleC5odG1s',cover:'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSFwGQ9vG6ZifxDpWHtuu8oSzYqll79xZ9rW_KwPwzCdA&s'},
468
556
  {id:'block-blast',name:'Block Blast',color:'#a855f7',featured:false,u:'aHR0cHM6Ly91bnBrZy5jb20vZWRjdWJlLWJsb2NrLWJsYXN0QDUuMC4wL2luZGV4Lmh0bWw=',cover:'https://imgs.crazygames.com/block-puzzle-master_1x1/20250924025352/block-puzzle-master_1x1-cover?format=auto&quality=70&metadata=none'},
469
- {id:'cut-the-rope',name:'Cut the Rope',color:'#34d399',featured:false,u:'aHR0cHM6Ly91bnBrZy5jb20vZWRjdWJlLWN1dC10aGUtcm9wZUA1LjAuMC9pbmRleC5odG1s',cover:'https://play-lh.googleusercontent.com/2bTmHHM7DBMVe7fLh16Em-J-tVBMFGL2HJCxPAMNF7gAYIfU6EZoYG1__lQ0cvFMEyp3GU9CGijZzEB1qJHF'}];
557
+ {id:'cut-the-rope',name:'Cut the Rope',color:'#34d399',featured:false,u:'aHR0cHM6Ly91bnBrZy5jb20vZWRjdWJlLWN1dC10aGUtcm9wZUA1LjAuMC9pbmRleC5odG1s',cover:'https://play-lh.googleusercontent.com/2bTmHHM7DBMVe7fLh16Em-J-tVBMFGL2HJCxPAMNF7gAYIfU6EZoYG1__lQ0cvFMEyp3GU9CGijZzEB1qJHF'},
558
+ {id:'duck-math',name:'Duck Math',color:'#34d399',featured:true,u:'aHR0cHM6Ly91bnBrZy5jb20vaWduaXRlLWdvb2JlckAxLjEuNC9pbmRleC5odG1sIy9jbGFzcy9kcmlmdC1raW5n',cover:'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRJFXWvY3bIWxbR2SZEnbZaVFRYqFzZJZlOo4SYkWOexg&s'}];
470
559
  const MOVIES = [{name:'Spiderman: Far From Home',u:'aHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL2ZpbGUvZC8xdDYyWVk1VjJXRnFQQTgwMWNERUN3dzFFUVlrNGZNbmsvcHJldmlldw==',poster:'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSjdXbHSbJUSaJdgj45vDisJUiPj1qljxkOPbfojU3M1w&s=10'},
471
560
  {name:'Spiderman: Homecoming',u:'aHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL2ZpbGUvZC8xaFVJeDA1RzNOX3VjZzhTd2RaSFJ0XzVNZHFMUVp0YWkvcHJldmlldw==',poster:'https://resizing.flixster.com/-XZAfHZM39UwaGJIFWKAE8fS0ak=/v3/t/assets/p12798844_p_v8_ao.jpg'},
472
561
  {name:'F1',u:'aHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL2ZpbGUvZC8xa196UkR0NFJuXzlGb3RZSlVmcmhKWjBaREtmd3FRazEvcHJldmlldw==',poster:'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSr2SgmDLkFhLGuF1CnP_xJv1stiKvK027DkN6m6wdqdg&s=10'},
473
562
  {name:'Obsession',u:'aHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL2ZpbGUvZC8xRkVOMTZMd001WnpUQlNiQnRrWTk1THJJemxzSFhGeDcvcHJldmlldw==',poster:'https://images.contentstack.io/v3/assets/blt13adb7e2033fcee5/blt5c8bcbfee5dade5c/6a2b47a8c284bac540ef3353/Obsession_keyart_poster_2000x3000.jpg?width=2560'}];
474
563
  const dec = s => atob(s);
564
+ const PROXY_URL = dec('aHR0cHM6Ly9tb2JpbGUtc2VydmljZS0zODYzMTEuZnJhbWVyLmFwcC8=');
475
565
  const AI_KEY = "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";
476
566
 
477
567
  /* ================= SETTINGS ================= */
478
- const DEFAULTS = {accent:"#ff6b1a", city:"", particles:140, model:"gpt-4o-mini"};
568
+ const DEFAULTS = {accent:"#ff6b1a", city:"", particles:140, model:"gpt-4o"};
479
569
  let S = {...DEFAULTS, ...JSON.parse(localStorage.getItem("igniteos")||"{}")};
480
570
  const saveS = () => localStorage.setItem("igniteos", JSON.stringify(S));
481
571
  const applyAccent = () => document.documentElement.style.setProperty("--accent", S.accent);
@@ -485,10 +575,15 @@ function go(id){
485
575
  document.querySelectorAll(".screen").forEach(s=>s.classList.remove("active"));
486
576
  document.getElementById(id).classList.add("active");
487
577
  document.querySelectorAll(".dock-btn[data-s]").forEach(b=>b.classList.toggle("on", b.dataset.s===id));
578
+ if(id==="proxy"){
579
+ const pf = document.getElementById("proxyFrame");
580
+ if(!pf.src || pf.src==="about:blank") pf.src = PROXY_URL;
581
+ }
488
582
  if(id==="home") startParticles(); else stopParticles();
583
+ window.scrollTo(0,0); document.getElementById(id==='home'?'home':id).scrollTop = 0;
489
584
  }
490
585
 
491
- /* ================= PARTICLES (falling snow) ================= */
586
+ /* ================= PARTICLES ================= */
492
587
  const cv = document.getElementById("particles"), ctx = cv.getContext("2d");
493
588
  let flakes = [], raf = null;
494
589
  function sizeCanvas(){ cv.width = innerWidth; cv.height = innerHeight; }
@@ -520,9 +615,19 @@ function hexA(h, a){
520
615
  function startParticles(){ if(!raf){ makeFlakes(); drawFlakes(); } }
521
616
  function stopParticles(){ cancelAnimationFrame(raf); raf = null; ctx.clearRect(0,0,cv.width,cv.height); }
522
617
 
523
- /* ================= ROTATING DESCRIPTION ================= */
618
+ /* ================= TITLE letters ================= */
619
+ document.querySelectorAll("#bigTitle .row").forEach((row, ri)=>{
620
+ [...row.dataset.txt].forEach((ch, i)=>{
621
+ const s = document.createElement("span");
622
+ s.className = "ch"; s.textContent = ch;
623
+ s.style.setProperty("--cd", (0.15 + ri*0.28 + i*0.055) + "s");
624
+ row.appendChild(s);
625
+ });
626
+ });
627
+
628
+ /* ================= ROTATOR ================= */
524
629
  const LINES = [
525
- "21 hand-picked games. Zero loading pain.",
630
+ "20 hand-picked games. Zero loading pain.",
526
631
  "A private movie theater, right in your browser.",
527
632
  "An AI that codes, explains and builds with you.",
528
633
  "Liquid glass. Falling light. Pure black.",
@@ -544,11 +649,23 @@ setInterval(()=>{
544
649
  lines[li].classList.add("on");
545
650
  }, 3800);
546
651
 
652
+ /* ================= HERO mouse parallax ================= */
653
+ const hero = document.getElementById("hero");
654
+ hero.addEventListener("mousemove", e=>{
655
+ const rx = (e.clientX/innerWidth - .5), ry = (e.clientY/innerHeight - .5);
656
+ hero.querySelectorAll(".os-badge,#bigTitle,.widget-row,.home-nav").forEach((el,i)=>{
657
+ const d = (i+1)*4;
658
+ el.style.transform = `translate(${-rx*d}px, ${-ry*d}px)`;
659
+ });
660
+ });
661
+ hero.addEventListener("mouseleave", ()=>{
662
+ hero.querySelectorAll(".os-badge,#bigTitle,.widget-row,.home-nav").forEach(el=>el.style.transform="");
663
+ });
664
+
547
665
  /* ================= CLOCK ================= */
548
666
  setInterval(()=>{
549
- const d = new Date();
550
667
  document.getElementById("clockVal").textContent =
551
- d.toLocaleTimeString([], {hour:"2-digit", minute:"2-digit"});
668
+ new Date().toLocaleTimeString([], {hour:"2-digit", minute:"2-digit"});
552
669
  }, 1000);
553
670
 
554
671
  /* ================= BATTERY ================= */
@@ -564,7 +681,7 @@ setInterval(()=>{
564
681
  }catch(e){ document.getElementById("battVal").textContent = "N/A"; }
565
682
  })();
566
683
 
567
- /* ================= WEATHER (Open-Meteo, no key needed) ================= */
684
+ /* ================= WEATHER ================= */
568
685
  const WMO = {0:"☀️",1:"🌤️",2:"⛅",3:"☁️",45:"🌫️",48:"🌫️",51:"🌦️",53:"🌦️",55:"🌧️",56:"🌧️",57:"🌧️",
569
686
  61:"🌧️",63:"🌧️",65:"🌧️",66:"🌧️",67:"🌧️",71:"🌨️",73:"🌨️",75:"🌨️",77:"🌨️",80:"🌦️",81:"🌧️",82:"⛈️",
570
687
  85:"🌨️",86:"🌨️",95:"⛈️",96:"⛈️",99:"⛈️"};
@@ -587,7 +704,7 @@ async function loadWeather(){
587
704
  }catch(e){ val.textContent = "Set city ⚙"; ic.textContent = "🌍"; }
588
705
  }
589
706
 
590
- /* ================= BUILD GAMES ================= */
707
+ /* ================= BUILD GAMES GRID ================= */
591
708
  const gamesGrid = document.getElementById("gamesGrid");
592
709
  GAMES.forEach((g,i)=>{
593
710
  const t = document.createElement("div");
@@ -602,11 +719,12 @@ GAMES.forEach((g,i)=>{
602
719
  });
603
720
  document.getElementById("gameCount").textContent = GAMES.length + " titles · click to play";
604
721
 
605
- /* library marquee — two counter-scrolling rows, duplicated for seamless loop */
722
+ /* library marquee rows */
606
723
  function buildRail(el, list){
607
- [...list, ...list].forEach(g=>{
724
+ [...list, ...list].forEach((g,i)=>{
608
725
  const d = document.createElement("div");
609
726
  d.className = "rail-game"; d.style.setProperty("--gc", g.color);
727
+ d.style.setProperty("--fd", ((i%10)*0.35)+"s");
610
728
  d.innerHTML = `<img src="${g.cover}" alt="" loading="lazy" onerror="this.style.display='none'">`;
611
729
  d.title = g.name;
612
730
  d.onclick = ()=>openPlay(g.name, dec(g.u));
@@ -627,7 +745,7 @@ MOVIES.forEach((m,i)=>{
627
745
  });
628
746
  document.getElementById("movieCount").textContent = MOVIES.length + " now streaming";
629
747
 
630
- /* ================= PLAY MODAL (no new tabs, no exposed links) ================= */
748
+ /* ================= PLAY MODAL ================= */
631
749
  const pm = document.getElementById("playModal"), pf = document.getElementById("playFrame"),
632
750
  pl = document.getElementById("playLoading");
633
751
  function openPlay(title, url){
@@ -636,16 +754,25 @@ function openPlay(title, url){
636
754
  pm.classList.add("open");
637
755
  pf.src = url;
638
756
  pf.onload = ()=>setTimeout(()=>pl.classList.add("gone"), 400);
639
- document.body.style.overflow = "hidden";
640
757
  }
641
758
  function closePlay(){
759
+ if(document.fullscreenElement) document.exitFullscreen().catch(()=>{});
642
760
  pm.classList.remove("open");
643
761
  pf.src = "about:blank";
644
- document.body.style.overflow = "";
645
762
  }
646
- addEventListener("keydown", e=>{ if(e.key==="Escape"){ closePlay(); closeSettings(); } });
763
+ function toggleFullScreen(){
764
+ const shell = pm.querySelector(".play-shell");
765
+ if(document.fullscreenElement){ document.exitFullscreen().catch(()=>{}); return; }
766
+ if(shell.requestFullscreen) shell.requestFullscreen().catch(()=>toast("Fullscreen blocked by browser"));
767
+ else toast("Fullscreen not supported");
768
+ }
769
+ document.addEventListener("fullscreenchange", ()=>{
770
+ const btn = document.querySelector("#playModal .play-bar button[onclick='toggleFullScreen()']");
771
+ if(btn) btn.innerHTML = document.fullscreenElement ? "🗗 Exit Full" : "⛶ Fullscreen";
772
+ });
773
+ addEventListener("keydown", e=>{ if(e.key==="Escape"){ closePlay(); closeSettings(); endTour(); } });
647
774
 
648
- /* ================= SETTINGS UI ================= */
775
+ /* ================= SETTINGS ================= */
649
776
  const COLORS = ["#ff6b1a","#ff3b30","#ff9500","#ffd60a","#34c759","#00c7be","#32ade6","#7d7aff","#bf5af2","#ff375f"];
650
777
  const sw = document.getElementById("swatches");
651
778
  COLORS.forEach(c=>{
@@ -674,17 +801,55 @@ function toast(t){
674
801
  clearTimeout(el._t); el._t = setTimeout(()=>el.classList.remove("on"), 2200);
675
802
  }
676
803
 
804
+ /* ================= TOUR ================= */
805
+ const TOUR_STEPS = [
806
+ {t:"#bigTitle", title:"Welcome to IGNITE OS ✦", text:"This is your command center. Everything runs right here in the browser — snow falls, glass glows, and your whole library lives one click away."},
807
+ {t:"#widgetRow", title:"Live widgets", text:"Real-time weather, your battery ring, and the clock — all in liquid glass. Set your city later in Settings (the ⚙ button)."},
808
+ {t:"#librarySec", title:"The Game Library", text:"All 20 games scroll past automatically — two rows, opposite directions. Hover to pause, click any icon and it starts instantly. No links, no new tabs."},
809
+ {t:"#dock", title:"The Dock", text:"Jump between Home, Games Hub, Movie Theater and IGNITE AI anytime. The ✦ button opens your AI assistant."},
810
+ {t:"#settingsBtn", title:"Make it yours", text:"Tap the gear to change the accent color, weather city, snow density, and the AI model. That's it — you're ready. Have fun!"}
811
+ ];
812
+ let tIdx = 0;
813
+ const tourEl = document.getElementById("tour"), spot = document.getElementById("tourSpot");
814
+ function startTour(){
815
+ if(!document.getElementById("home").classList.contains("active")) go("home");
816
+ document.getElementById("home").scrollTop = 0;
817
+ tourEl.classList.add("open"); tIdx = 0; showStep();
818
+ }
819
+ function showStep(){
820
+ const s = TOUR_STEPS[tIdx];
821
+ document.getElementById("tourStepN").textContent = `Step ${tIdx+1} of ${TOUR_STEPS.length}`;
822
+ document.getElementById("tourTitle").textContent = s.title;
823
+ document.getElementById("tourText").textContent = s.text;
824
+ document.getElementById("tourNext").textContent = tIdx===TOUR_STEPS.length-1 ? "Finish ✦" : "Next →";
825
+ const prog = document.getElementById("tourProg");
826
+ prog.innerHTML = TOUR_STEPS.map((_,i)=>`<i class="${i<=tIdx?'on':''}"></i>`).join("");
827
+ const el = document.querySelector(s.t);
828
+ el.scrollIntoView({block:"center", behavior:"smooth"});
829
+ setTimeout(()=>{
830
+ const r = el.getBoundingClientRect();
831
+ spot.style.cssText = `left:${r.left-14}px;top:${r.top-14}px;width:${r.width+28}px;height:${r.height+28}px;display:block`;
832
+ }, 350);
833
+ }
834
+ function tourNext(){
835
+ if(tIdx === TOUR_STEPS.length-1){ endTour(); return; }
836
+ tIdx++; showStep();
837
+ }
838
+ function endTour(){
839
+ tourEl.classList.remove("open");
840
+ spot.style.display = "none";
841
+ localStorage.setItem("igniteos_toured", "1");
842
+ }
843
+
677
844
  /* ================= AI ================= */
678
845
  const chatLog = document.getElementById("chatLog");
679
846
  const history = [];
680
847
  function addMsg(text, who){
681
848
  const d = document.createElement("div");
682
849
  d.className = "msg " + who;
683
- if(who==="bot"){
684
- d.innerHTML = '<div class="who">IGNITE AI</div>' + renderBot(text);
685
- } else d.textContent = text;
850
+ if(who==="bot") d.innerHTML = '<div class="who">IGNITE AI</div>' + renderBot(text);
851
+ else d.textContent = text;
686
852
  chatLog.appendChild(d); chatLog.scrollTop = chatLog.scrollHeight;
687
- return d;
688
853
  }
689
854
  function renderBot(text){
690
855
  const parts = text.split(/```(\w*)\n?([\s\S]*?)(?:```|$)/g);
@@ -716,6 +881,26 @@ chatLog.addEventListener("click", e=>{
716
881
  }
717
882
  });
718
883
  function quickAsk(q){ document.getElementById("aiInput").value = q; sendMsg(); }
884
+
885
+ async function askOpenAI(model, q){
886
+ const res = await fetch("https://api.openai.com/v1/chat/completions", {
887
+ method:"POST",
888
+ headers:{ "Content-Type":"application/json", "Authorization":"Bearer " + AI_KEY },
889
+ body: JSON.stringify({
890
+ model,
891
+ messages: [
892
+ {role:"system", content:"You are IGNITE AI, a super-smart, friendly assistant inside IGNITE OS. You are excellent at coding, explanations, math, creative writing, game ideas and problem solving. Always format code in triple backticks with the language name. Be concise but complete."},
893
+ ...history.slice(-10),
894
+ {role:"user", content:q}
895
+ ]
896
+ })
897
+ });
898
+ const data = await res.json().catch(()=>({}));
899
+ if(!res.ok) throw {status:res.status, msg:data.error?.message || ("HTTP " + res.status)};
900
+ if(!data.choices?.[0]?.message?.content) throw {status:res.status, msg:"Unexpected response shape"};
901
+ return data.choices[0].message.content;
902
+ }
903
+
719
904
  async function sendMsg(){
720
905
  const inp = document.getElementById("aiInput"), q = inp.value.trim();
721
906
  if(!q) return;
@@ -724,27 +909,26 @@ async function sendMsg(){
724
909
  tp.className = "msg bot typing"; tp.innerHTML = "<i></i><i></i><i></i>";
725
910
  chatLog.appendChild(tp); chatLog.scrollTop = chatLog.scrollHeight;
726
911
  try{
727
- const res = await fetch("https://api.openai.com/v1/chat/completions", {
728
- method:"POST",
729
- headers:{ "Content-Type":"application/json", "Authorization":"Bearer " + AI_KEY },
730
- body: JSON.stringify({
731
- model: S.model,
732
- messages: [
733
- {role:"system", content:"You are IGNITE AI, a super-smart, friendly assistant inside IGNITE OS. You are excellent at coding, explanations, math, creative writing, game ideas and problem solving. Always format code in triple backticks with the language name. Be concise but complete."},
734
- ...history.slice(-10),
735
- {role:"user", content:q}
736
- ]
737
- })
738
- });
739
- const data = await res.json();
912
+ let reply, lastErr;
913
+ const tries = [S.model, "gpt-4o", "gpt-4o-mini", "gpt-3.5-turbo"].filter((v,i,a)=>a.indexOf(v)===i);
914
+ for(const m of tries){
915
+ try{ reply = await askOpenAI(m, q); if(m!==S.model) toast(`Auto-switched to ${m}`); break; }
916
+ catch(e){
917
+ lastErr = e;
918
+ const notModelIssue = e.status===401 || e.status===403 || e.status===429;
919
+ if(notModelIssue || tries.indexOf(m)===tries.length-1) throw e;
920
+ }
921
+ }
740
922
  tp.remove();
741
- if(!res.ok || !data.choices) throw new Error(data.error?.message || "API error");
742
- const reply = data.choices[0].message.content;
743
923
  history.push({role:"user",content:q},{role:"assistant",content:reply});
744
924
  addMsg(reply, "bot");
745
925
  }catch(err){
746
926
  tp.remove();
747
- addMsg("⚠️ Couldn't reach the AI right now. Check the API key / model in Settings, or try again. (" + esc(err.message||"") + ")", "bot");
927
+ let hint = "";
928
+ if(err.status===401) hint = "The API key was rejected (401). Double-check it in the code.";
929
+ else if(err.status===429) hint = "Rate limit hit (429) — wait a moment and retry.";
930
+ else if(err.status===404) hint = "Model not found (404) — set a valid model in Settings.";
931
+ addMsg(`⚠️ AI error: ${esc(err.msg||err.message||"unknown")}\n${hint}`, "bot");
748
932
  }
749
933
  }
750
934
  document.getElementById("aiInput").addEventListener("keydown", e=>{ if(e.key==="Enter") sendMsg(); });
@@ -752,6 +936,7 @@ document.getElementById("aiInput").addEventListener("keydown", e=>{ if(e.key==="
752
936
  /* ================= BOOT ================= */
753
937
  applyAccent(); startParticles(); loadWeather();
754
938
  addMsg("Hey! I'm IGNITE AI ✦ I'm great at a lot of things — coding, explaining, brainstorming, math, you name it. Ask me anything, and if you want code just say so — I'll wrap it in a neat copyable block.", "bot");
939
+ if(!localStorage.getItem("igniteos_toured")) setTimeout(startTour, 900);
755
940
  </script>
756
941
  </body>
757
942
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "class-studys",
3
- "version": "7.0.1",
3
+ "version": "7.0.2",
4
4
  "description": "IGNITE OS — Advanced proxy-enabled gaming platform with comprehensive filter bypass system",
5
5
  "main": "index.html",
6
6
  "files": [