mlv2boii 1.0.6 → 1.0.7

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 CHANGED
@@ -1,39 +1,47 @@
1
1
  <!DOCTYPE html>
2
- <html>
2
+ <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
6
6
  <title>mlv2</title>
7
- <meta name="description" content="mlv2 — a small, fast library of games." />
7
+ <meta name="description" content="mlv2: a small, fast library of games." />
8
8
  <meta name="color-scheme" content="dark" />
9
- <meta name="theme-color" content="#09090b" />
10
- <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='8' fill='%2309090b'/%3E%3Ccircle cx='16' cy='16' r='6' fill='%23e8142f'/%3E%3C/svg%3E" />
9
+ <meta name="theme-color" content="#000000" />
10
+ <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='7' fill='%23000'/%3E%3Ccircle cx='16' cy='16' r='6' fill='%23e8142f'/%3E%3C/svg%3E" />
11
11
 
12
- <!-- Fonts -->
12
+ <!-- Fonts: SF Pro on Apple devices (system stack), Inter everywhere else -->
13
13
  <link rel="preconnect" href="https://fonts.googleapis.com" />
14
14
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
15
- <link href="https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600;700&family=Manrope:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
15
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
16
16
 
17
17
  <!-- Tailwind CSS v4 (Play CDN, mirrored on jsDelivr) -->
18
18
  <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
19
19
  <style type="text/tailwindcss">
20
+ /*
21
+ Design language
22
+ Mood: quiet, precise, confident. Airy. Ancestor: Vercel/Resend black, Apple HIG habits.
23
+ Color: void black canvas, Apple grays for text, ONE red for tint (selection, primary action, focus).
24
+ Type: one family. 17/19/21 for reading, 13 for labels, 44-72 for the title.
25
+ Shape: 10px controls, 12px groups, 28px sheets (concentric: 12 + 16 padding), pills for chips,
26
+ 22.37% on game icons (the iOS app icon ratio).
27
+ Motion: 150-300ms ease-out on interaction, one entrance, all of it off under reduced-motion.
28
+ */
20
29
  @theme {
21
- --font-display: "Sora", ui-sans-serif, system-ui, sans-serif;
22
- --font-sans: "Manrope", ui-sans-serif, system-ui, sans-serif;
23
- --font-mono: "JetBrains Mono", ui-monospace, "SFMono-Regular", monospace;
24
-
25
- --color-ink-950: #09090b;
26
- --color-ink-900: #0e0e11;
27
- --color-ink-850: #131316;
28
- --color-ink-800: #18181c;
29
- --color-ink-700: #202024;
30
- --color-ink-600: #2a2a30;
31
- --color-ink-500: #3f3f47;
32
- --color-ink-400: #6e6e78;
33
- --color-ink-300: #98979f;
34
- --color-ink-200: #c2c1c7;
35
- --color-ink-100: #e2e1e6;
36
- --color-ink-50: #f6f5f7;
30
+ --font-sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Inter", ui-sans-serif, system-ui, sans-serif;
31
+ --font-mono: ui-monospace, "SF Mono", Menlo, monospace;
32
+
33
+ --color-ink-950: #000000;
34
+ --color-ink-900: #0a0a0b;
35
+ --color-ink-850: #101012;
36
+ --color-ink-800: #161618;
37
+ --color-ink-700: #232326;
38
+ --color-ink-600: #2c2c30;
39
+ --color-ink-500: #48484c;
40
+ --color-ink-400: #76767b;
41
+ --color-ink-300: #a1a1a6;
42
+ --color-ink-200: #c7c7cc;
43
+ --color-ink-100: #e5e5ea;
44
+ --color-ink-50: #f5f5f7;
37
45
 
38
46
  --color-red-700: #8f0e20;
39
47
  --color-red-600: #c11228;
@@ -41,47 +49,45 @@
41
49
  --color-red-400: #ff4d61;
42
50
  --color-red-300: #ff8b96;
43
51
  }
52
+
53
+ @layer components {
54
+ .btn { @apply inline-flex h-11 items-center justify-center rounded-[10px] px-4 text-[15px] font-medium transition-[background-color,filter,transform] duration-200 active:scale-[0.98] disabled:opacity-50 sm:h-9 sm:text-[14px]; }
55
+ .btn-primary { @apply bg-red-500 text-white hover:brightness-110; }
56
+ .btn-ghost { @apply bg-white/10 text-ink-50 hover:bg-white/15; }
57
+ .icon-btn { @apply grid h-11 w-11 shrink-0 place-items-center rounded-full bg-white/10 text-ink-200 transition-colors duration-200 hover:bg-white/15 hover:text-white sm:h-9 sm:w-9; }
58
+ .field { @apply h-11 w-full rounded-[10px] bg-white/[0.07] px-3 text-[16px] text-ink-50 ring-1 ring-inset ring-white/10 transition-shadow duration-200 placeholder:text-ink-400 focus:ring-2 focus:ring-red-500 sm:h-9 sm:text-[14px]; }
59
+ .chip { @apply h-10 shrink-0 whitespace-nowrap rounded-full px-4 text-[14px] font-medium transition-colors duration-200 sm:h-9; }
60
+ }
44
61
  </style>
45
62
 
46
63
  <style>
47
- /* ---------------------------------------------------------------
48
- Base layer — kept outside the Tailwind @theme block so it works
49
- regardless of Tailwind's preflight timing.
50
- ----------------------------------------------------------------*/
51
- html, body { background: var(--color-ink-950); }
64
+ html, body { background: #000; }
52
65
  body { -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; }
53
- ::selection { background: rgba(232,20,47,0.35); color: #fff; }
54
-
55
- /* thin, quiet scrollbars */
56
- * { scrollbar-width: thin; scrollbar-color: #2a2a30 transparent; }
57
- *::-webkit-scrollbar { width: 8px; height: 8px; }
58
- *::-webkit-scrollbar-track { background: transparent; }
59
- *::-webkit-scrollbar-thumb { background: #26262b; border-radius: 999px; }
60
- *::-webkit-scrollbar-thumb:hover { background: #3a3a42; }
66
+ ::selection { background: rgba(232,20,47,0.4); color: #fff; }
67
+ input { caret-color: var(--color-red-400); }
61
68
 
62
- /* film-grain texture, very subtle, for depth without gradients */
63
- .grain { position: fixed; inset: 0; pointer-events: none; z-index: 0; opacity: 0.035; mix-blend-mode: overlay;
64
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); }
69
+ /* keyboard focus: one red ring everywhere */
70
+ :focus-visible { outline: 2px solid var(--color-red-400); outline-offset: 2px; }
71
+ input:focus-visible { outline: none; }
72
+ .tile:focus-visible { outline: none; }
73
+ .tile:focus-visible .tile-icon { outline: 2px solid var(--color-red-400); outline-offset: 3px; }
65
74
 
66
- .spotlight { position: fixed; top: -20%; left: 50%; width: 1100px; height: 600px; transform: translateX(-50%);
67
- background: radial-gradient(closest-side, rgba(232,20,47,0.16), transparent 70%); pointer-events: none; z-index: 0; }
75
+ /* safe areas (viewport-fit=cover) */
76
+ .page-x { padding-left: max(1.25rem, env(safe-area-inset-left)); padding-right: max(1.25rem, env(safe-area-inset-right)); }
68
77
 
69
- /* skeleton shimmer */
70
- @keyframes shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } }
71
- .skel { background: linear-gradient(100deg, #18181c 30%, #202024 45%, #18181c 60%); background-size: 200% 100%; animation: shimmer 1.6s ease-in-out infinite; }
78
+ /* game icons: 10% edge so dark art never melts into the canvas */
79
+ .tile-img { outline: 1px solid rgba(255,255,255,0.1); outline-offset: -1px; }
72
80
 
73
- [data-fx] { will-change: transform, opacity; }
81
+ .no-scrollbar { scrollbar-width: none; }
82
+ .no-scrollbar::-webkit-scrollbar { display: none; }
83
+ * { scrollbar-width: thin; scrollbar-color: #2c2c30 transparent; }
84
+ *::-webkit-scrollbar { width: 8px; height: 8px; }
85
+ *::-webkit-scrollbar-thumb { background: #2c2c30; border-radius: 999px; }
74
86
 
75
- .hero-orb { animation: heroFloat 7s ease-in-out infinite; }
76
- .hero-line { animation: heroLine 2.8s ease-in-out infinite; }
77
- @keyframes heroFloat {
78
- 0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
79
- 50% { transform: translate3d(0, 12px, 0) scale(1.04); }
80
- }
81
- @keyframes heroLine {
82
- 0%, 100% { opacity: .35; transform: scaleX(.7); }
83
- 50% { opacity: 1; transform: scaleX(1); }
84
- }
87
+ /* the single page-load moment: title and subline rise in */
88
+ @keyframes rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
89
+ .rise { animation: rise 0.7s cubic-bezier(0.25, 0.46, 0.45, 0.94) both; animation-delay: var(--d, 0s); }
90
+ @media (prefers-reduced-motion: reduce) { .rise { animation: none; } }
85
91
 
86
92
  /* Blank Overlay Lock Screen */
87
93
  #blank-overlay {
@@ -100,107 +106,86 @@
100
106
  </style>
101
107
  </head>
102
108
 
103
- <body class="bg-ink-950 text-ink-100 font-sans antialiased min-h-screen">
109
+ <body class="min-h-screen bg-ink-950 font-sans text-ink-50 antialiased">
104
110
 
105
111
  <!-- Blank Overlay Screen -->
106
112
  <div id="blank-overlay"></div>
107
113
 
108
- <div class="grain"></div>
109
- <div class="spotlight"></div>
110
-
111
114
  <!-- ================= HEADER ================= -->
112
- <header class="sticky top-0 z-40 border-b border-ink-800/80 bg-ink-950/75 backdrop-blur-md">
113
- <div class="mx-auto max-w-6xl px-4 sm:px-6 h-14 flex items-center gap-3">
114
- <a href="#" class="flex items-center gap-2 shrink-0 group">
115
- <span class="relative h-2 w-2">
116
- <span class="absolute inset-0 rounded-full bg-red-500"></span>
117
- <span class="absolute inset-0 animate-ping rounded-full bg-red-500 opacity-60"></span>
118
- </span>
119
- <span class="font-display font-semibold tracking-tight text-[15px] leading-none">mlv2</span>
115
+ <header class="sticky top-0 z-40 border-b border-white/[0.08] bg-black/70 pt-[env(safe-area-inset-top)] backdrop-blur-xl backdrop-saturate-150">
116
+ <div class="page-x mx-auto flex h-14 max-w-5xl items-center gap-4">
117
+ <a href="#" class="flex shrink-0 items-center gap-2" aria-label="mlv2 home">
118
+ <span class="h-2.5 w-2.5 rounded-full bg-red-500"></span>
119
+ <span class="text-[17px] font-semibold tracking-[-0.02em]">mlv2</span>
120
120
  </a>
121
121
 
122
- <div class="flex-1 max-w-xs sm:max-w-sm relative">
123
- <svg class="pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-ink-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
124
- <input id="search" type="text" placeholder="Search games…" autocomplete="off"
125
- class="w-full rounded-lg border border-ink-700 bg-ink-900/70 py-1.5 pl-8 pr-3 text-[13px] placeholder:text-ink-400 outline-none transition-colors focus:border-red-500/60 focus:bg-ink-900" />
122
+ <div class="ml-auto flex min-w-0 flex-1 items-center justify-end gap-4">
123
+ <span id="count-badge" class="hidden shrink-0 text-[13px] tabular-nums text-ink-400 sm:block"><span id="count-num">0</span>&nbsp;games</span>
124
+ <div class="relative w-full max-w-[16rem]">
125
+ <svg class="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-ink-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
126
+ <input id="search" type="text" placeholder="Search games" aria-label="Search games" autocomplete="off" enterkeyhint="search" class="field pl-9" />
127
+ </div>
126
128
  </div>
127
-
128
- <span id="count-badge" class="hidden sm:flex items-center gap-1.5 shrink-0 text-[11px] font-mono uppercase tracking-wider text-ink-400">
129
- <span id="count-num">0</span>&nbsp;games
130
- </span>
131
- </div>
132
-
133
- <div class="border-t border-ink-800/60">
134
- <div id="category-bar" class="mx-auto flex max-w-5xl items-center justify-center gap-1.5 overflow-x-auto px-4 py-2.5 sm:px-6"></div>
135
129
  </div>
136
130
  </header>
137
131
 
138
132
  <!-- ================= MAIN ================= -->
139
- <main class="relative z-10 mx-auto max-w-5xl px-4 sm:px-6 py-8 sm:py-12">
140
-
141
- <!-- ================= HERO ================= -->
142
- <section class="relative mx-auto mb-10 max-w-2xl overflow-hidden rounded-[2rem] border border-ink-700/70 bg-ink-900/65 px-5 py-10 text-center shadow-[0_24px_80px_-40px_rgba(232,20,47,0.5)] sm:px-10 sm:py-14">
143
- <div class="hero-orb pointer-events-none absolute -right-20 -top-24 h-64 w-64 rounded-full bg-red-500/10 blur-3xl"></div>
144
- <div class="hero-orb pointer-events-none absolute -bottom-28 -left-20 h-56 w-56 rounded-full bg-red-700/10 blur-3xl" style="animation-delay: -3.5s"></div>
145
- <div class="relative">
146
- <div class="mx-auto mb-5 flex w-fit items-center gap-2 rounded-full border border-red-500/25 bg-red-500/[0.07] px-3 py-1 text-[10px] font-mono uppercase tracking-[0.22em] text-red-300">
147
- <span class="h-1.5 w-1.5 rounded-full bg-red-400 shadow-[0_0_12px_rgba(255,77,97,0.9)]"></span>
148
- Play something
149
- </div>
150
- <h1 class="font-display text-4xl font-semibold tracking-[-0.05em] text-ink-50 sm:text-6xl">school sucks, <br/>games r <span class="text-red-400">better</span></h1>
151
- <p class="mx-auto mt-4 max-w-md text-sm leading-6 text-ink-300 sm:text-[15px]">we're so back 🥹</p>
152
- <div class="hero-line mx-auto mt-7 h-px w-24 bg-gradient-to-r from-transparent via-red-400 to-transparent"></div>
153
- </div>
133
+ <main class="page-x mx-auto max-w-5xl pb-24">
134
+
135
+ <!-- ================= TITLE ================= -->
136
+ <section class="pb-10 pt-14 sm:pb-14 sm:pt-24">
137
+ <h1 class="rise text-balance text-[2.75rem] font-semibold leading-[1.04] tracking-[-0.04em] text-ink-50 sm:text-[4.5rem]">school sucks,<br/>games r better</h1>
138
+ <p class="rise mt-4 text-[19px] text-ink-300 sm:text-[21px]" style="--d: 0.08s">we're so back</p>
154
139
  </section>
155
140
 
141
+ <!-- categories -->
142
+ <div id="category-bar" role="group" aria-label="Categories" class="no-scrollbar -mx-5 mb-8 flex items-center gap-2 overflow-x-auto px-5 sm:mx-0 sm:mb-10 sm:px-0"></div>
143
+
156
144
  <!-- setup banner, shown only while CONFIG still has placeholder values -->
157
- <div id="setup-banner" class="hidden mb-8 rounded-xl border border-red-500/25 bg-red-500/[0.06] px-4 py-3.5">
158
- <p class="text-[13px] font-medium text-red-300">Not connected yet</p>
159
- <p class="mt-1 text-[13px] leading-relaxed text-ink-300">Open this file and fill in the <code class="font-mono text-ink-100">CONFIG</code> object near the bottom with your Appwrite endpoint, project, database, table and bucket IDs.</p>
145
+ <div id="setup-banner" class="mb-8 hidden rounded-xl bg-ink-900 p-4 ring-1 ring-white/10">
146
+ <p class="text-[15px] font-semibold">Not connected yet</p>
147
+ <p class="mt-1 text-[14px] leading-relaxed text-ink-300">Open this file and fill in the <code class="font-mono text-ink-50">CONFIG</code> object near the bottom with your Appwrite endpoint, project, database, table and bucket IDs.</p>
160
148
  </div>
161
149
 
162
150
  <!-- skeleton loading state -->
163
- <div id="skeleton-grid" class="grid grid-cols-2 gap-3.5 sm:grid-cols-3 sm:gap-4 md:grid-cols-4 lg:grid-cols-5">
164
- </div>
151
+ <div id="skeleton-grid" class="grid grid-cols-3 gap-x-4 gap-y-7 sm:grid-cols-4 sm:gap-x-5 sm:gap-y-8 md:grid-cols-5 lg:grid-cols-6"></div>
165
152
 
166
153
  <!-- empty state -->
167
154
  <div id="empty-state" class="hidden flex-col items-center justify-center py-24 text-center">
168
- <div class="h-10 w-10 rounded-full border border-ink-700 flex items-center justify-center mb-4">
169
- <svg class="h-4 w-4 text-ink-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M6 12h12M12 6v12"/></svg>
170
- </div>
171
- <p id="empty-title" class="font-display text-[15px] font-medium text-ink-100">No games yet</p>
172
- <p id="empty-sub" class="mt-1.5 text-[13px] text-ink-400 max-w-xs">Games you add in Appwrite with a valid link will show up here automatically.</p>
155
+ <p id="empty-title" class="text-[19px] font-semibold">No games yet</p>
156
+ <p id="empty-sub" class="mt-2 max-w-xs text-pretty text-[15px] text-ink-400">Games you add in Appwrite with a valid link will show up here automatically.</p>
173
157
  </div>
174
158
 
175
159
  <!-- game grid -->
176
- <div id="grid" class="hidden grid grid-cols-2 gap-3.5 sm:grid-cols-3 sm:gap-4 md:grid-cols-4 lg:grid-cols-5"></div>
160
+ <div id="grid" class="hidden grid grid-cols-3 gap-x-4 gap-y-7 sm:grid-cols-4 sm:gap-x-5 sm:gap-y-8 md:grid-cols-5 lg:grid-cols-6"></div>
177
161
  </main>
178
162
 
179
163
  <!-- ================= FOOTER ================= -->
180
- <footer class="relative z-10 border-t border-ink-800/70">
181
- <div class="mx-auto max-w-6xl px-4 sm:px-6 py-6 flex items-center justify-between text-[11px] text-ink-500">
182
- <p class="font-mono">mlv2 © <span id="year"></span></p>
183
- <button id="admin-trigger" class="font-mono uppercase tracking-wider text-ink-500 hover:text-ink-200 transition-colors">Admin</button>
164
+ <footer class="border-t border-white/[0.08]">
165
+ <div class="page-x mx-auto flex max-w-5xl items-center justify-between py-4 text-[13px] text-ink-400">
166
+ <p>mlv2 © <span id="year"></span></p>
167
+ <button id="admin-trigger" class="-mr-3 rounded-lg px-3 py-2.5 transition-colors duration-200 hover:text-ink-50">Admin</button>
184
168
  </div>
185
169
  </footer>
186
170
 
187
171
  <!-- ================= PLAYER MODAL ================= -->
188
172
  <div id="player-modal" class="fixed inset-0 z-50 hidden">
189
- <div class="absolute inset-0 bg-ink-950/92 backdrop-blur-sm" data-player-close></div>
190
- <div id="player-panel" class="relative h-full w-full flex flex-col opacity-0">
191
- <div class="flex items-center justify-between gap-3 px-3 sm:px-4 h-14 border-b border-ink-800 bg-ink-950">
192
- <div class="flex items-center gap-2.5 min-w-0">
193
- <img id="player-icon" class="h-7 w-7 rounded-md object-cover border border-ink-700" alt="" />
194
- <p id="player-title" class="truncate font-display text-[14px] font-medium"></p>
195
- </div>
196
- <div class="flex items-center gap-1.5 shrink-0">
197
- <a id="player-newtab" target="_blank" rel="noopener" title="Open in new tab"
198
- class="h-8 w-8 grid place-items-center rounded-lg border border-ink-700 text-ink-300 hover:text-ink-50 hover:border-ink-500 transition-colors">
199
- <svg class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><path d="M15 3h6v6"/><path d="M10 14 21 3"/></svg>
200
- </a>
201
- <button id="player-close" title="Close" class="h-8 w-8 grid place-items-center rounded-lg border border-ink-700 text-ink-300 hover:text-red-400 hover:border-red-500/50 transition-colors">
202
- <svg class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6 6 18M6 6l12 12"/></svg>
203
- </button>
173
+ <div class="absolute inset-0 bg-black" data-player-close></div>
174
+ <div id="player-panel" class="relative flex h-full w-full flex-col opacity-0">
175
+ <div class="border-b border-white/[0.08] bg-black pt-[env(safe-area-inset-top)]">
176
+ <div class="flex h-14 items-center justify-between gap-3 px-3 sm:px-4">
177
+ <div class="flex min-w-0 items-center gap-3">
178
+ <img id="player-icon" class="h-8 w-8 rounded-[22.37%] object-cover" alt="" />
179
+ <p id="player-title" class="truncate text-[15px] font-semibold"></p>
180
+ </div>
181
+ <div class="flex shrink-0 items-center gap-2">
182
+ <a id="player-newtab" target="_blank" rel="noopener" title="Open in new tab" aria-label="Open in new tab" class="icon-btn">
183
+ <svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><path d="M15 3h6v6"/><path d="M10 14 21 3"/></svg>
184
+ </a>
185
+ <button id="player-close" title="Close" aria-label="Close" class="icon-btn">
186
+ <svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6 6 18M6 6l12 12"/></svg>
187
+ </button>
188
+ </div>
204
189
  </div>
205
190
  </div>
206
191
  <div class="relative flex-1 bg-black">
@@ -211,40 +196,40 @@
211
196
 
212
197
  <!-- ================= ADMIN: PIN GATE ================= -->
213
198
  <div id="admin-login-modal" class="fixed inset-0 z-50 hidden">
214
- <div class="absolute inset-0 bg-ink-950/85 backdrop-blur-sm" data-admin-login-close></div>
199
+ <div class="absolute inset-0 bg-black/80 backdrop-blur-xl" data-admin-login-close></div>
215
200
  <div class="absolute inset-0 flex items-center justify-center p-4">
216
- <form id="admin-login-form" class="w-full max-w-[320px] rounded-2xl border border-ink-700 bg-ink-900 p-5 opacity-0" data-fx>
217
- <p class="font-display text-[15px] font-medium">Enter PIN</p>
218
- <p class="mt-1 text-[12.5px] text-ink-400">This only unlocks the admin UI on this device — real write access is controlled by your Appwrite permissions.</p>
201
+ <form id="admin-login-form" class="w-full max-w-[340px] rounded-[28px] bg-ink-900 p-6 opacity-0 ring-1 ring-white/10" data-fx>
202
+ <p class="text-[19px] font-semibold">Enter PIN</p>
203
+ <p class="mt-1.5 text-pretty text-[14px] text-ink-400">This unlocks the admin screen on this device. Write access is controlled by your Appwrite permissions.</p>
219
204
 
220
- <div id="pin-boxes" class="mt-4 grid grid-cols-6 gap-2">
205
+ <div id="pin-boxes" class="mt-5 grid grid-cols-6 gap-2">
221
206
  <!-- 6 single-digit inputs, filled in by JS -->
222
207
  </div>
223
208
  <input id="admin-pin" type="hidden" />
224
209
 
225
- <p id="admin-login-error" class="hidden mt-3 text-[12.5px] text-red-400"></p>
210
+ <p id="admin-login-error" class="mt-3 hidden text-[14px] text-red-400" role="alert"></p>
226
211
 
227
- <button type="submit" id="admin-login-submit" class="mt-4 w-full rounded-lg bg-red-500 hover:bg-red-400 transition-colors text-ink-950 font-medium text-[13px] py-2">Unlock</button>
228
- <button type="button" data-admin-login-close class="mt-2 w-full rounded-lg border border-ink-700 hover:border-ink-500 transition-colors text-[13px] py-2 text-ink-300">Cancel</button>
212
+ <button type="submit" id="admin-login-submit" class="btn btn-primary mt-5 w-full">Unlock</button>
213
+ <button type="button" data-admin-login-close class="btn btn-ghost mt-2 w-full">Cancel</button>
229
214
  </form>
230
215
  </div>
231
216
  </div>
232
217
 
233
218
  <!-- ================= ADMIN: PANEL ================= -->
234
219
  <div id="admin-panel" class="fixed inset-0 z-50 hidden">
235
- <div class="absolute inset-0 bg-ink-950/90 backdrop-blur-sm" data-admin-panel-close></div>
236
- <div class="absolute inset-0 flex items-start sm:items-center justify-center p-0 sm:p-4">
237
- <div id="admin-panel-card" class="relative w-full sm:max-w-2xl h-full sm:h-[85vh] sm:rounded-2xl border-0 sm:border border-ink-700 bg-ink-900 flex flex-col opacity-0" data-fx>
220
+ <div class="absolute inset-0 bg-black/80 backdrop-blur-xl" data-admin-panel-close></div>
221
+ <div class="absolute inset-0 flex items-start justify-center p-0 sm:items-center sm:p-4">
222
+ <div id="admin-panel-card" class="relative flex h-full w-full flex-col bg-ink-900 opacity-0 sm:h-[85vh] sm:max-w-2xl sm:rounded-[32px] sm:ring-1 sm:ring-white/10" data-fx>
238
223
 
239
- <div class="flex items-center justify-between px-5 h-14 border-b border-ink-800 shrink-0">
224
+ <div class="flex h-14 shrink-0 items-center justify-between border-b border-white/[0.08] px-5 pt-[env(safe-area-inset-top)] box-content">
240
225
  <div>
241
- <p class="font-display text-[14px] font-medium leading-none">Admin panel</p>
242
- <p class="mt-1 text-[11px] font-mono text-ink-400">Unlocked with PIN</p>
226
+ <p class="text-[17px] font-semibold leading-none">Admin</p>
227
+ <p class="mt-1 text-[13px] text-ink-400">Unlocked with PIN</p>
243
228
  </div>
244
- <div class="flex items-center gap-1.5">
245
- <button id="admin-logout" class="text-[11.5px] font-mono uppercase tracking-wider text-ink-400 hover:text-ink-100 transition-colors px-2 py-1">Lock</button>
246
- <button data-admin-panel-close class="h-8 w-8 grid place-items-center rounded-lg border border-ink-700 text-ink-300 hover:text-red-400 hover:border-red-500/50 transition-colors">
247
- <svg class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6 6 18M6 6l12 12"/></svg>
229
+ <div class="flex items-center gap-2">
230
+ <button id="admin-logout" class="btn btn-ghost">Lock</button>
231
+ <button data-admin-panel-close aria-label="Close" class="icon-btn">
232
+ <svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6 6 18M6 6l12 12"/></svg>
248
233
  </button>
249
234
  </div>
250
235
  </div>
@@ -252,36 +237,38 @@
252
237
  <div class="flex-1 overflow-y-auto px-5 py-5">
253
238
 
254
239
  <!-- add game form -->
255
- <form id="add-game-form" class="rounded-xl border border-ink-700 p-4">
256
- <p class="text-[11px] font-mono uppercase tracking-wider text-ink-400">Add game</p>
257
- <div class="mt-3 grid grid-cols-1 sm:grid-cols-2 gap-3">
240
+ <form id="add-game-form" class="rounded-xl p-4 ring-1 ring-white/10">
241
+ <p class="text-[15px] font-semibold">Add game</p>
242
+ <div class="mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2">
258
243
  <div>
259
- <label class="text-[11px] text-ink-400">Name</label>
260
- <input id="new-name" required class="mt-1 w-full rounded-lg border border-ink-700 bg-ink-850 px-3 py-2 text-[13px] outline-none focus:border-red-500/60" />
244
+ <label for="new-name" class="text-[13px] text-ink-300">Name</label>
245
+ <input id="new-name" required class="field mt-1.5" />
261
246
  </div>
262
247
  <div>
263
- <label class="text-[11px] text-ink-400">Category (optional)</label>
264
- <input id="new-category" class="mt-1 w-full rounded-lg border border-ink-700 bg-ink-850 px-3 py-2 text-[13px] outline-none focus:border-red-500/60" />
248
+ <label for="new-category" class="text-[13px] text-ink-300">Category (optional)</label>
249
+ <input id="new-category" class="field mt-1.5" />
265
250
  </div>
266
251
  <div class="sm:col-span-2">
267
- <label class="text-[11px] text-ink-400">Game URL</label>
268
- <input id="new-url" required type="url" placeholder="https://…" class="mt-1 w-full rounded-lg border border-ink-700 bg-ink-850 px-3 py-2 text-[13px] outline-none focus:border-red-500/60" />
252
+ <label for="new-url" class="text-[13px] text-ink-300">Game URL</label>
253
+ <input id="new-url" required type="url" placeholder="https://" class="field mt-1.5" />
269
254
  </div>
270
255
  <div>
271
- <label class="text-[11px] text-ink-400">Icon upload</label>
272
- <input id="new-icon-file" type="file" accept="image/*" class="mt-1 w-full text-[12px] text-ink-400 file:mr-2 file:rounded-md file:border-0 file:bg-ink-700 file:px-2.5 file:py-1.5 file:text-[11.5px] file:text-ink-100" />
256
+ <label for="new-icon-file" class="text-[13px] text-ink-300">Icon upload</label>
257
+ <input id="new-icon-file" type="file" accept="image/*" class="mt-2 w-full text-[13px] text-ink-400 file:mr-3 file:rounded-full file:border-0 file:bg-white/10 file:px-3.5 file:py-1.5 file:text-[13px] file:font-medium file:text-ink-50" />
273
258
  </div>
274
259
  <div>
275
- <label class="text-[11px] text-ink-400">or external icon URL</label>
276
- <input id="new-icon-url" type="url" placeholder="https://…" class="mt-1 w-full rounded-lg border border-ink-700 bg-ink-850 px-3 py-2 text-[13px] outline-none focus:border-red-500/60" />
260
+ <label for="new-icon-url" class="text-[13px] text-ink-300">Or external icon URL</label>
261
+ <input id="new-icon-url" type="url" placeholder="https://" class="field mt-1.5" />
277
262
  </div>
278
263
  </div>
279
- <button type="submit" id="add-game-submit" class="mt-3.5 rounded-lg bg-red-500 hover:bg-red-400 transition-colors text-ink-950 font-medium text-[13px] px-4 py-2">Add game</button>
280
- <span id="add-game-status" class="ml-3 text-[12px] text-ink-400"></span>
264
+ <div class="mt-5 flex items-center gap-3">
265
+ <button type="submit" id="add-game-submit" class="btn btn-primary">Add game</button>
266
+ <span id="add-game-status" class="text-[13px] text-ink-400"></span>
267
+ </div>
281
268
  </form>
282
269
 
283
270
  <!-- existing games -->
284
- <p class="mt-6 mb-2 text-[11px] font-mono uppercase tracking-wider text-ink-400">Existing games</p>
271
+ <p class="mb-3 mt-8 text-[15px] font-semibold">Games</p>
285
272
  <div id="admin-list" class="space-y-2"></div>
286
273
  </div>
287
274
  </div>
@@ -289,7 +276,7 @@
289
276
  </div>
290
277
 
291
278
  <!-- ================= TOASTS ================= -->
292
- <div id="toast-root" class="fixed bottom-4 right-4 z-[60] flex flex-col gap-2 items-end"></div>
279
+ <div id="toast-root" class="fixed bottom-4 right-4 z-[60] flex flex-col items-end gap-2 pb-[env(safe-area-inset-bottom)]"></div>
293
280
 
294
281
  <!-- Konami Code Script with 1-Hour Expiration -->
295
282
  <script>
@@ -361,7 +348,9 @@ const isConfigured = () =>
361
348
  !CONFIG.projectId.includes("YOUR_") &&
362
349
  !CONFIG.databaseId.includes("YOUR_");
363
350
 
351
+ const reduceMotion = matchMedia("(prefers-reduced-motion: reduce)").matches;
364
352
  function fx(targets, keyframes, options = {}) {
353
+ if (reduceMotion) options = { ...options, duration: 0, delay: 0 };
365
354
  return animate(targets, keyframes, options);
366
355
  }
367
356
 
@@ -386,8 +375,8 @@ function iconSrcFor(game) {
386
375
  function monogramSrc(name) {
387
376
  const initials = (name || "?").trim().slice(0, 2).toUpperCase();
388
377
  const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
389
- <rect width="100" height="100" fill="#18181c"/>
390
- <text x="50" y="58" font-family="Sora, sans-serif" font-size="34" font-weight="600" fill="#e8142f" text-anchor="middle">${initials}</text>
378
+ <rect width="100" height="100" fill="#161618"/>
379
+ <text x="50" y="58" font-family="-apple-system, Inter, sans-serif" font-size="34" font-weight="600" fill="#e8142f" text-anchor="middle">${initials}</text>
391
380
  </svg>`;
392
381
  return "data:image/svg+xml;utf8," + encodeURIComponent(svg);
393
382
  }
@@ -399,20 +388,20 @@ function escapeHtml(s) {
399
388
  function toast(message, tone = "default") {
400
389
  const root = el("toast-root");
401
390
  const node = document.createElement("div");
402
- const toneClasses = tone === "error" ? "border-red-500/40 text-red-300" : "border-ink-700 text-ink-100";
403
- node.className = `rounded-lg border ${toneClasses} bg-ink-900/95 backdrop-blur px-3.5 py-2.5 text-[12.5px] shadow-lg shadow-black/40 opacity-0`;
391
+ const toneClasses = tone === "error" ? "text-red-300" : "text-ink-50";
392
+ node.className = `rounded-full ${toneClasses} bg-ink-800/90 backdrop-blur-xl px-4 py-2.5 text-[14px] ring-1 ring-white/10 opacity-0`;
404
393
  node.textContent = message;
405
394
  root.appendChild(node);
406
- fx(node, { opacity: [0, 1], y: [8, 0] }, { duration: 0.25, easing: "ease-out" });
395
+ fx(node, { opacity: [0, 1], y: [8, 0] }, { duration: 0.25, ease: "easeOut" });
407
396
  setTimeout(() => {
408
- fx(node, { opacity: [1, 0], y: [0, 6] }, { duration: 0.2, easing: "ease-in" }).finished.then(() => node.remove());
397
+ fx(node, { opacity: [1, 0], y: [0, 6] }, { duration: 0.2, ease: "easeIn" }).finished.then(() => node.remove());
409
398
  }, 2800);
410
399
  }
411
400
 
412
401
  /* -------------------------- render: skeleton -------------------------- */
413
402
  function renderSkeleton() {
414
- el("skeleton-grid").innerHTML = Array.from({ length: 10 })
415
- .map(() => `<div class="skel motion-reduce:animate-none aspect-square rounded-2xl border border-ink-800"></div>`)
403
+ el("skeleton-grid").innerHTML = Array.from({ length: 12 })
404
+ .map(() => `<div class="animate-pulse motion-reduce:animate-none"><div class="aspect-square rounded-[22.37%] bg-ink-800"></div><div class="mx-auto mt-3 h-3 w-2/3 rounded-full bg-ink-800"></div></div>`)
416
405
  .join("");
417
406
  }
418
407
  renderSkeleton();
@@ -432,8 +421,8 @@ function renderCategoryBar() {
432
421
  bar.innerHTML = cats
433
422
  .map((c) => {
434
423
  const active = c === activeCategory;
435
- return `<button data-cat="${escapeHtml(c)}" class="shrink-0 rounded-full border px-3 py-1 text-[12px] font-medium transition-colors ${
436
- active ? "border-red-500/60 bg-red-500/10 text-red-300" : "border-ink-700 text-ink-300 hover:border-ink-500 hover:text-ink-100"
424
+ return `<button data-cat="${escapeHtml(c)}" aria-pressed="${active}" class="chip ${
425
+ active ? "bg-red-500 text-white" : "bg-white/[0.07] text-ink-300 hover:bg-white/[0.12] hover:text-ink-50"
437
426
  }">${escapeHtml(c)}</button>`;
438
427
  })
439
428
  .join("");
@@ -446,6 +435,7 @@ function renderCategoryBar() {
446
435
  );
447
436
  }
448
437
 
438
+ let gridShown = false;
449
439
  function renderGrid() {
450
440
  const grid = el("grid");
451
441
  const games = currentFiltered();
@@ -473,13 +463,12 @@ function renderGrid() {
473
463
  grid.innerHTML = games
474
464
  .map((g) => {
475
465
  const src = iconSrcFor(g) || monogramSrc(g.name);
476
- return `<button data-fx data-id="${g.$id}" class="group relative aspect-square overflow-hidden rounded-2xl border border-ink-700/70 bg-ink-900 text-left opacity-0 shadow-[0_12px_28px_-18px_rgba(0,0,0,0.9)] transition-[border-color,box-shadow,transform] duration-300 hover:-translate-y-1 hover:border-red-400/65 hover:shadow-[0_0_0_1px_rgba(232,20,47,0.35),0_18px_32px_-14px_rgba(232,20,47,0.55)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-500/60">
477
- <img src="${src}" alt="${escapeHtml(g.name)}" loading="lazy" class="h-full w-full object-cover transition-transform duration-700 ease-out group-hover:scale-[1.09]" onerror="this.onerror=null;this.src='${monogramSrc(g.name)}'" />
478
- <div class="absolute inset-0 bg-gradient-to-t from-ink-950/95 via-ink-950/10 to-transparent opacity-90 transition-opacity duration-300 group-hover:opacity-100"></div>
479
- <div class="absolute inset-x-0 bottom-0 p-3">
480
- <p class="truncate text-[12.5px] font-medium text-ink-50 leading-tight">${escapeHtml(g.name)}</p>
481
- ${g.category ? `<p class="truncate mt-0.5 text-[9.5px] font-mono uppercase tracking-wide text-ink-400">${escapeHtml(g.category)}</p>` : ""}
482
- </div>
466
+ return `<button data-fx data-id="${g.$id}" class="tile group block w-full text-left opacity-0">
467
+ <span class="tile-icon block aspect-square overflow-hidden rounded-[22.37%] bg-ink-800 transition-transform duration-300 ease-out group-hover:scale-[1.04] group-active:scale-[0.97]">
468
+ <img src="${src}" alt="" loading="lazy" class="tile-img h-full w-full object-cover" onerror="this.onerror=null;this.src='${monogramSrc(g.name)}'" />
469
+ </span>
470
+ <span class="mt-2.5 block truncate text-center text-[13px] font-medium leading-tight text-ink-50">${escapeHtml(g.name)}</span>
471
+ ${g.category ? `<span class="mt-0.5 block truncate text-center text-[12px] text-ink-400">${escapeHtml(g.category)}</span>` : ""}
483
472
  </button>`;
484
473
  })
485
474
  .join("");
@@ -488,7 +477,10 @@ function renderGrid() {
488
477
  card.addEventListener("click", () => openPlayer(games.find((g) => g.$id === card.dataset.id)))
489
478
  );
490
479
 
491
- fx("#grid [data-fx]", { opacity: [0, 1], y: [10, 0] }, { duration: 0.35, delay: stagger(0.035), easing: "ease-out" });
480
+ // full entrance once; later filter/search changes only fade
481
+ fx("#grid [data-fx]", { opacity: [0, 1], y: gridShown ? 0 : [8, 0] },
482
+ { duration: gridShown ? 0.18 : 0.5, delay: gridShown ? 0 : stagger(0.03), ease: "easeOut" });
483
+ gridShown = true;
492
484
  }
493
485
 
494
486
  el("count-badge") && (el("count-num").textContent = "0");
@@ -547,10 +539,10 @@ function openPlayer(game) {
547
539
  const modal = el("player-modal");
548
540
  modal.classList.remove("hidden");
549
541
  document.documentElement.style.overflow = "hidden";
550
- fx("#player-panel", { opacity: [0, 1], scale: [0.98, 1] }, { duration: 0.28, easing: "ease-out" });
542
+ fx("#player-panel", { opacity: [0, 1], scale: [0.98, 1] }, { duration: 0.28, ease: "easeOut" });
551
543
  }
552
544
  function closePlayer() {
553
- fx("#player-panel", { opacity: [1, 0], scale: [1, 0.98] }, { duration: 0.18, easing: "ease-in" }).finished.then(() => {
545
+ fx("#player-panel", { opacity: [1, 0], scale: [1, 0.98] }, { duration: 0.18, ease: "easeIn" }).finished.then(() => {
554
546
  el("player-modal").classList.add("hidden");
555
547
  el("player-frame").src = "";
556
548
  document.documentElement.style.overflow = "";
@@ -569,7 +561,7 @@ document.addEventListener("keydown", (e) => {
569
561
  /* -------------------------------- admin -------------------------------- */
570
562
  function openAdminLogin() {
571
563
  el("admin-login-modal").classList.remove("hidden");
572
- fx("#admin-login-form", { opacity: [0, 1], y: [8, 0] }, { duration: 0.25, easing: "ease-out" });
564
+ fx("#admin-login-form", { opacity: [0, 1], y: [8, 0] }, { duration: 0.25, ease: "easeOut" });
573
565
  resetPinBoxes();
574
566
  pinInputs[0]?.focus();
575
567
  }
@@ -579,7 +571,7 @@ function closeAdminLogin() {
579
571
  }
580
572
  function openAdminPanel() {
581
573
  el("admin-panel").classList.remove("hidden");
582
- fx("#admin-panel-card", { opacity: [0, 1], y: [12, 0] }, { duration: 0.28, easing: "ease-out" });
574
+ fx("#admin-panel-card", { opacity: [0, 1], y: [12, 0] }, { duration: 0.28, ease: "easeOut" });
583
575
  renderAdminList();
584
576
  }
585
577
  function closeAdminPanel() {
@@ -601,8 +593,8 @@ const pinBoxesEl = el("pin-boxes");
601
593
  pinBoxesEl.innerHTML = Array.from({ length: 6 })
602
594
  .map(
603
595
  (_, i) =>
604
- `<input data-pin-idx="${i}" type="password" inputmode="numeric" pattern="[0-9]*" maxlength="1"
605
- class="h-12 w-full rounded-lg border border-ink-700 bg-ink-850 text-center text-[18px] font-mono outline-none focus:border-red-500/60" />`
596
+ `<input data-pin-idx="${i}" type="password" inputmode="numeric" pattern="[0-9]*" maxlength="1" aria-label="Digit ${i + 1}"
597
+ class="h-12 w-full rounded-[10px] bg-white/[0.07] text-center text-[20px] tabular-nums outline-none ring-1 ring-inset ring-white/10 transition-shadow focus:ring-2 focus:ring-red-500" />`
606
598
  )
607
599
  .join("");
608
600
  const pinInputs = Array.from(pinBoxesEl.querySelectorAll("[data-pin-idx]"));
@@ -657,7 +649,7 @@ el("admin-logout").addEventListener("click", () => {
657
649
 
658
650
  async function renderAdminList() {
659
651
  const list = el("admin-list");
660
- list.innerHTML = `<p class="text-[12.5px] text-ink-400">Loading…</p>`;
652
+ list.innerHTML = `<p class="text-[14px] text-ink-400">Loading…</p>`;
661
653
  try {
662
654
  const res = await tablesDB.listRows({
663
655
  databaseId: CONFIG.databaseId,
@@ -665,24 +657,24 @@ async function renderAdminList() {
665
657
  queries: [Query.limit(200), Query.orderAsc("order")],
666
658
  });
667
659
  if (res.rows.length === 0) {
668
- list.innerHTML = `<p class="text-[12.5px] text-ink-400">No games added yet.</p>`;
660
+ list.innerHTML = `<p class="text-[14px] text-ink-400">No games added yet.</p>`;
669
661
  return;
670
662
  }
671
663
  list.innerHTML = res.rows
672
664
  .map(
673
665
  (g) => `
674
- <div class="flex items-center gap-3 rounded-xl border border-ink-700 p-2.5" data-row="${g.$id}">
675
- <img src="${iconSrcFor(g) || monogramSrc(g.name)}" class="h-9 w-9 rounded-lg object-cover shrink-0 border border-ink-700" onerror="this.onerror=null;this.src='${monogramSrc(g.name)}'" />
666
+ <div class="flex items-center gap-3 rounded-xl p-3 ring-1 ring-white/10" data-row="${g.$id}">
667
+ <img src="${iconSrcFor(g) || monogramSrc(g.name)}" alt="" class="tile-img h-10 w-10 shrink-0 rounded-[22.37%] object-cover" onerror="this.onerror=null;this.src='${monogramSrc(g.name)}'" />
676
668
  <div class="min-w-0 flex-1">
677
- <input data-field="name" data-id="${g.$id}" value="${escapeHtml(g.name)}" class="w-full bg-transparent text-[13px] font-medium outline-none border-b border-transparent focus:border-red-500/50" />
678
- <input data-field="url" data-id="${g.$id}" value="${escapeHtml(g.url)}" class="mt-0.5 w-full bg-transparent text-[11.5px] text-ink-400 outline-none border-b border-transparent focus:border-red-500/50" />
669
+ <input data-field="name" data-id="${g.$id}" value="${escapeHtml(g.name)}" aria-label="Name" class="w-full border-b border-transparent bg-transparent text-[15px] font-medium outline-none focus:border-red-500" />
670
+ <input data-field="url" data-id="${g.$id}" value="${escapeHtml(g.url)}" aria-label="URL" class="mt-0.5 w-full border-b border-transparent bg-transparent text-[13px] text-ink-400 outline-none focus:border-red-500" />
679
671
  </div>
680
- <input data-field="category" data-id="${g.$id}" value="${escapeHtml(g.category || "")}" placeholder="category" class="hidden sm:block w-24 bg-ink-850 border border-ink-700 rounded-md px-2 py-1 text-[11.5px] outline-none focus:border-red-500/50" />
681
- <label class="hidden sm:flex items-center gap-1 text-[11px] text-ink-400 shrink-0">
682
- <input type="checkbox" data-field="enabled" data-id="${g.$id}" ${g.enabled === false ? "" : "checked"} class="accent-red-500" /> on
672
+ <input data-field="category" data-id="${g.$id}" value="${escapeHtml(g.category || "")}" placeholder="Category" aria-label="Category" class="field hidden !w-28 sm:block" />
673
+ <label class="hidden shrink-0 items-center gap-2 text-[13px] text-ink-300 sm:flex">
674
+ <input type="checkbox" data-field="enabled" data-id="${g.$id}" ${g.enabled === false ? "" : "checked"} class="h-4 w-4 accent-red-500" /> On
683
675
  </label>
684
- <button data-delete="${g.$id}" title="Delete" class="h-8 w-8 shrink-0 grid place-items-center rounded-lg border border-ink-700 text-ink-400 hover:text-red-400 hover:border-red-500/50 transition-colors">
685
- <svg class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0-1 14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2L4 6"/></svg>
676
+ <button data-delete="${g.$id}" title="Delete" aria-label="Delete" class="icon-btn hover:!text-red-400">
677
+ <svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0-1 14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2L4 6"/></svg>
686
678
  </button>
687
679
  </div>`
688
680
  )
@@ -718,7 +710,7 @@ async function renderAdminList() {
718
710
  })
719
711
  );
720
712
  } catch (err) {
721
- list.innerHTML = `<p class="text-[12.5px] text-red-400">${escapeHtml(err?.message || "Failed to load games.")}</p>`;
713
+ list.innerHTML = `<p class="text-[14px] text-red-400">${escapeHtml(err?.message || "Failed to load games.")}</p>`;
722
714
  }
723
715
  }
724
716
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mlv2boii",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -0,0 +1,10 @@
1
+ const CONFIG = {
2
+ endpoint: "https://nyc.cloud.appwrite.io/v1",
3
+ projectId: "6a9b8a1b001b9c61537f",
4
+ databaseId: "6a9b8b23003de0ec5cf7",
5
+ gamesTableId: "6a9b8bb3000013a3fd80",
6
+ iconsBucketId: "6a9b8b36002565ac5987",
7
+ };
8
+
9
+ const ADMIN_PIN = "051511";
10
+ const ADMIN_SESSION_KEY = "mlv2_admin_unlocked";
package/holdw.py DELETED
@@ -1,21 +0,0 @@
1
- import keyboard
2
- import time
3
-
4
-
5
-
6
- def start():
7
- keyboard_pressing = True
8
- while keyboard_pressing:
9
- keyboard.press("W")
10
- time.sleep(0.1)
11
-
12
- if keyboard.is_pressed("C"):
13
- keyboard_pressing = False
14
- while not (keyboard.is_pressed("X")):
15
- time.sleep(0.1)
16
- keyboard_pressing = True; start()
17
-
18
- start()
19
-
20
-
21
-