APRK01 commited on
Commit
5fb7488
·
1 Parent(s): c35213b

Premium Redesign: System Modules and Surgical Layout

Browse files
WSW/src/app/cracks/page.tsx CHANGED
@@ -38,37 +38,59 @@ export default async function Cracks() {
38
  <div className="flex flex-col md:flex-row gap-8 flex-1 min-h-0">
39
 
40
  {/* Module Sidebar */}
41
- <nav className="w-full md:w-48 lg:w-64 flex-shrink-0 border-b md:border-b-0 md:border-r border-zinc-900 pr-0 md:pr-8 pb-8 md:pb-0 flex flex-row md:flex-col gap-4 overflow-x-auto md:overflow-visible">
42
- <Link href="/" className="font-mono text-xs tracking-[0.2em] text-zinc-600 hover:text-white transition-colors uppercase whitespace-nowrap hidden md:block mb-8">
43
- Back Base
44
- </Link>
45
- <Link href="/" className="font-mono text-xs tracking-[0.2em] text-zinc-600 hover:text-white transition-colors uppercase whitespace-nowrap block md:hidden">
46
- [ BACK ]
47
- </Link>
48
-
49
- {[
50
- { id: 'sources', label: 'Sources', active: false },
51
- { id: 'cracks', label: 'Cracks', active: true },
52
- { id: 'scripts', label: 'Scripts', active: false },
53
- { id: 'tools', label: 'Tools', active: false },
54
- ].map((mod) => (
55
- <Link
56
- key={mod.id}
57
- href={`/${mod.id}`}
58
- className={`font-mono text-xs tracking-widest uppercase py-2 flex items-center gap-3 whitespace-nowrap group ${
59
- mod.active ? 'text-white' : 'text-zinc-500 hover:text-zinc-300'
60
- }`}
61
- >
62
- <span className={`w-1.5 h-1.5 rounded-full transition-all ${
63
- mod.active ? 'bg-white shadow-[0_0_10px_rgba(255,255,255,0.8)]' : 'bg-transparent border border-zinc-700 group-hover:border-zinc-500'
64
- }`} />
65
- {mod.label}
66
  </Link>
67
- ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
- <div className="mt-0 md:mt-12 flex items-center md:items-start ml-4 md:ml-0">
70
- <Link href="/vip" className="font-mono text-[10px] tracking-[0.2em] px-4 py-2 border border-zinc-800 text-zinc-400 hover:text-black hover:bg-white transition-all uppercase whitespace-nowrap">
71
- Unlock VIP
 
 
 
 
72
  </Link>
73
  </div>
74
  </nav>
@@ -79,13 +101,13 @@ export default async function Cracks() {
79
  {/* Top Edge Decoration Removed */}
80
 
81
  {/* Ledger Table Header (Desktop) */}
82
- <div className="hidden md:grid grid-cols-12 gap-4 px-8 py-4 border-b border-zinc-800 font-mono text-[9px] tracking-[0.2em] text-zinc-500 uppercase bg-zinc-900/20">
83
- <div className="col-span-2">Date</div>
84
- <div className="col-span-1">ID</div>
85
- <div className="col-span-4">Identifier</div>
86
- <div className="col-span-2">Availability</div>
87
- <div className="col-span-2">Status</div>
88
- <div className="col-span-1 text-right">Action</div>
89
  </div>
90
 
91
  {/* Ledger Content */}
 
38
  <div className="flex flex-col md:flex-row gap-8 flex-1 min-h-0">
39
 
40
  {/* Module Sidebar */}
41
+ <nav className="w-full md:w-56 lg:w-72 flex-shrink-0 border-b md:border-b-0 md:border-r border-white/10 pr-0 md:pr-10 pb-8 md:pb-0 flex flex-col gap-0 overflow-hidden">
42
+
43
+ <div className="mb-10 hidden md:block">
44
+ <Link href="/" className="font-mono text-[9px] tracking-[0.4em] text-zinc-500 hover:text-white transition-all uppercase flex items-center gap-2 group">
45
+ <span className="w-4 h-[1px] bg-zinc-800 group-hover:w-8 group-hover:bg-white transition-all" />
46
+ RETURN TO BASE
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  </Link>
48
+ </div>
49
+
50
+ <div className="mb-6 flex flex-col gap-1">
51
+ <span className="font-mono text-[10px] tracking-[0.3em] text-zinc-600 uppercase">SYSTEM</span>
52
+ <span className="font-black text-xs tracking-tighter uppercase text-white">MODULES</span>
53
+ </div>
54
+
55
+ <div className="flex flex-row md:flex-col gap-2 md:gap-0 overflow-x-auto md:overflow-visible no-scrollbar">
56
+ {[
57
+ { id: 'sources', label: 'Sources', active: false, index: '01' },
58
+ { id: 'cracks', label: 'Cracks', active: true, index: '02' },
59
+ { id: 'scripts', label: 'Scripts', active: false, index: '03' },
60
+ { id: 'tools', label: 'Tools', active: false, index: '04' },
61
+ ].map((mod) => (
62
+ <Link
63
+ key={mod.id}
64
+ href={`/${mod.id}`}
65
+ className={`group relative flex items-center justify-between py-3 md:py-4 border-b border-white/5 transition-all ${
66
+ mod.active ? 'px-4 bg-white text-black' : 'px-0 text-zinc-500 hover:text-white hover:pl-4'
67
+ }`}
68
+ >
69
+ <div className="flex items-center gap-4">
70
+ <span className={`font-mono text-[9px] ${mod.active ? 'text-black/50' : 'text-zinc-800 group-hover:text-zinc-400'}`}>
71
+ {mod.index}
72
+ </span>
73
+ <span className="font-black text-[11px] tracking-widest uppercase italic">
74
+ {mod.label}
75
+ </span>
76
+ </div>
77
+ {mod.active && (
78
+ <div className="w-1.5 h-1.5 bg-black rounded-full shadow-[0_0_8px_rgba(0,0,0,0.5)]" />
79
+ )}
80
+ {!mod.active && (
81
+ <span className="opacity-0 group-hover:opacity-100 transition-opacity font-mono text-[10px]">→</span>
82
+ )}
83
+ </Link>
84
+ ))}
85
+ </div>
86
 
87
+ <div className="mt-auto pt-12 hidden md:block">
88
+ <Link href="/vip" className="relative group block overflow-hidden">
89
+ <div className="absolute inset-0 bg-white translate-y-[101%] group-hover:translate-y-0 transition-transform duration-300" />
90
+ <div className="border border-white/20 px-6 py-4 flex flex-col gap-1 relative transition-colors group-hover:text-black">
91
+ <span className="font-mono text-[8px] tracking-[.3em] text-zinc-500 group-hover:text-black/50 uppercase transition-colors">Access Level</span>
92
+ <span className="font-black text-xs tracking-[.2em] uppercase">Request VIP</span>
93
+ </div>
94
  </Link>
95
  </div>
96
  </nav>
 
101
  {/* Top Edge Decoration Removed */}
102
 
103
  {/* Ledger Table Header (Desktop) */}
104
+ <div className="hidden md:grid grid-cols-12 gap-4 px-8 py-5 border-b border-zinc-900 font-mono text-[8px] tracking-[0.3em] text-zinc-600 uppercase bg-black/40">
105
+ <div className="col-span-2">TIMESTAMP</div>
106
+ <div className="col-span-1">REF</div>
107
+ <div className="col-span-4">IDENTIFIER</div>
108
+ <div className="col-span-2">LATENCY / AVAIL</div>
109
+ <div className="col-span-2">ENCRYPTION / STATUS</div>
110
+ <div className="col-span-1 text-right">ACTION</div>
111
  </div>
112
 
113
  {/* Ledger Content */}
WSW/src/app/globals.css CHANGED
@@ -38,3 +38,11 @@ body {
38
  animation: noise 8s steps(10) infinite;
39
  z-index: 50;
40
  }
 
 
 
 
 
 
 
 
 
38
  animation: noise 8s steps(10) infinite;
39
  z-index: 50;
40
  }
41
+
42
+ .no-scrollbar::-webkit-scrollbar {
43
+ display: none;
44
+ }
45
+ .no-scrollbar {
46
+ -ms-overflow-style: none;
47
+ scrollbar-width: none;
48
+ }
WSW/src/app/page.tsx CHANGED
@@ -30,38 +30,60 @@ export default function Home() {
30
  </div>
31
 
32
  {/* Right Side: Navigation Grid (4 columns) */}
33
- <div className="lg:col-span-4 bg-black flex flex-col lg:h-full lg:overflow-y-auto">
34
 
35
- <div className="p-4 sm:p-6 md:p-8 border-b border-zinc-800 shrink-0 bg-black sticky top-0 z-10 hidden lg:block">
36
- <h2 className="font-mono text-[9px] sm:text-[10px] tracking-[0.3em] text-zinc-500 uppercase">
37
- System Modules
38
- </h2>
 
39
  </div>
40
 
41
- <div className="flex-1 flex flex-col justify-center lg:justify-start">
42
- <Link href="/sources" className="flex-1 sm:min-h-[80px] p-4 sm:p-6 md:p-8 border-b border-zinc-800 flex items-center justify-between group hover:bg-white hover:text-black transition-colors duration-300">
43
- <span className="font-mono text-[10px] sm:text-xs md:text-sm tracking-widest uppercase">Sources</span>
44
- <span className="font-sans text-zinc-500 group-hover:text-black transition-colors">↗</span>
45
- </Link>
46
-
47
- <Link href="/cracks" className="flex-1 sm:min-h-[80px] p-4 sm:p-6 md:p-8 border-b border-zinc-800 flex items-center justify-between group hover:bg-white hover:text-black transition-colors duration-300">
48
- <span className="font-mono text-[10px] sm:text-xs md:text-sm tracking-widest uppercase">Cracks</span>
49
- <span className="font-sans text-zinc-500 group-hover:text-black transition-colors">↗</span>
50
- </Link>
51
-
52
- <Link href="/scripts" className="flex-1 sm:min-h-[80px] p-4 sm:p-6 md:p-8 border-b border-zinc-800 flex items-center justify-between group hover:bg-white hover:text-black transition-colors duration-300">
53
- <span className="font-mono text-[10px] sm:text-xs md:text-sm tracking-widest uppercase">Scripts</span>
54
- <span className="font-sans text-zinc-500 group-hover:text-black transition-colors">↗</span>
55
- </Link>
56
-
57
- <Link href="/tools" className="flex-1 sm:min-h-[80px] p-4 sm:p-6 md:p-8 lg:border-b border-zinc-800 flex items-center justify-between group hover:bg-white hover:text-black transition-colors duration-300">
58
- <span className="font-mono text-[10px] sm:text-xs md:text-sm tracking-widest uppercase">Tools</span>
59
- <span className="font-sans text-zinc-500 group-hover:text-black transition-colors">↗</span>
60
- </Link>
 
 
 
 
 
 
 
 
61
 
62
- <Link href="/vip" className="flex-1 sm:min-h-[80px] p-4 sm:p-6 md:p-8 bg-zinc-900 flex items-center justify-between group hover:bg-white hover:text-black transition-colors duration-300">
63
- <span className="font-mono text-[10px] sm:text-xs md:text-sm tracking-widest uppercase text-white group-hover:text-black">VIP Access</span>
64
- <span className="font-sans text-white group-hover:text-black">↗</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  </Link>
66
  </div>
67
 
 
30
  </div>
31
 
32
  {/* Right Side: Navigation Grid (4 columns) */}
33
+ <div className="lg:col-span-4 bg-black flex flex-col lg:h-full lg:overflow-y-auto border-t lg:border-t-0 border-zinc-900">
34
 
35
+ <div className="p-8 border-b border-zinc-900 shrink-0 bg-black sticky top-0 z-10">
36
+ <div className="flex flex-col gap-1">
37
+ <span className="font-mono text-[10px] tracking-[0.3em] text-zinc-600 uppercase">SYSTEM</span>
38
+ <span className="font-black text-xs tracking-tighter uppercase text-white">MODULES</span>
39
+ </div>
40
  </div>
41
 
42
+ <div className="flex-1 flex flex-col">
43
+ {[
44
+ { id: 'sources', label: 'Sources', index: '01', desc: 'Resource Directory' },
45
+ { id: 'cracks', label: 'Cracks', index: '02', desc: 'Bypass Protocols' },
46
+ { id: 'scripts', label: 'Scripts', index: '03', desc: 'Automated Logic' },
47
+ { id: 'tools', label: 'Tools', index: '04', desc: 'Utility Suite' },
48
+ ].map((mod) => (
49
+ <Link
50
+ key={mod.id}
51
+ href={`/${mod.id}`}
52
+ className="flex-1 min-h-[100px] p-8 border-b border-zinc-900 flex flex-col justify-center gap-2 group hover:bg-white transition-all duration-500"
53
+ >
54
+ <div className="flex items-center justify-between">
55
+ <div className="flex items-center gap-4">
56
+ <span className="font-mono text-[10px] text-zinc-800 group-hover:text-black/30 transition-colors">
57
+ {mod.index}
58
+ </span>
59
+ <span className="font-black text-xl tracking-tighter uppercase italic group-hover:text-black transition-colors">
60
+ {mod.label}
61
+ </span>
62
+ </div>
63
+ <span className="font-mono text-zinc-800 group-hover:text-black transition-colors">→</span>
64
+ </div>
65
+ <p className="font-mono text-[9px] tracking-widest text-zinc-600 uppercase group-hover:text-black/60 transition-colors">
66
+ {mod.desc}
67
+ </p>
68
+ </Link>
69
+ ))}
70
 
71
+ <Link href="/vip" className="flex-1 min-h-[120px] p-8 bg-zinc-950 flex flex-col justify-center gap-2 group hover:bg-white transition-all duration-500 relative overflow-hidden">
72
+ <div className="absolute top-0 left-0 w-2 h-full bg-white -translate-x-full group-hover:translate-x-0 transition-transform duration-500" />
73
+ <div className="flex items-center justify-between">
74
+ <div className="flex items-center gap-4">
75
+ <span className="font-mono text-[10px] text-zinc-800 group-hover:text-black/30 transition-colors">
76
+ EXP
77
+ </span>
78
+ <span className="font-black text-xl tracking-tighter uppercase text-white group-hover:text-black transition-colors italic">
79
+ VIP Access
80
+ </span>
81
+ </div>
82
+ <div className="w-2 h-2 bg-white rounded-full group-hover:bg-black transition-colors shadow-[0_0_10px_rgba(255,255,255,0.5)] group-hover:shadow-none animate-pulse" />
83
+ </div>
84
+ <p className="font-mono text-[9px] tracking-widest text-zinc-500 uppercase group-hover:text-black/60 transition-colors">
85
+ Clearance Elevation Required
86
+ </p>
87
  </Link>
88
  </div>
89
 
WSW/src/app/scripts/page.tsx CHANGED
@@ -38,37 +38,59 @@ export default async function Scripts() {
38
  <div className="flex flex-col md:flex-row gap-8 flex-1 min-h-0">
39
 
40
  {/* Module Sidebar */}
41
- <nav className="w-full md:w-48 lg:w-64 flex-shrink-0 border-b md:border-b-0 md:border-r border-zinc-900 pr-0 md:pr-8 pb-8 md:pb-0 flex flex-row md:flex-col gap-4 overflow-x-auto md:overflow-visible">
42
- <Link href="/" className="font-mono text-xs tracking-[0.2em] text-zinc-600 hover:text-white transition-colors uppercase whitespace-nowrap hidden md:block mb-8">
43
- Back Base
44
- </Link>
45
- <Link href="/" className="font-mono text-xs tracking-[0.2em] text-zinc-600 hover:text-white transition-colors uppercase whitespace-nowrap block md:hidden">
46
- [ BACK ]
47
- </Link>
48
-
49
- {[
50
- { id: 'sources', label: 'Sources', active: false },
51
- { id: 'cracks', label: 'Cracks', active: false },
52
- { id: 'scripts', label: 'Scripts', active: true },
53
- { id: 'tools', label: 'Tools', active: false },
54
- ].map((mod) => (
55
- <Link
56
- key={mod.id}
57
- href={`/${mod.id}`}
58
- className={`font-mono text-xs tracking-widest uppercase py-2 flex items-center gap-3 whitespace-nowrap group ${
59
- mod.active ? 'text-white' : 'text-zinc-500 hover:text-zinc-300'
60
- }`}
61
- >
62
- <span className={`w-1.5 h-1.5 rounded-full transition-all ${
63
- mod.active ? 'bg-white shadow-[0_0_10px_rgba(255,255,255,0.8)]' : 'bg-transparent border border-zinc-700 group-hover:border-zinc-500'
64
- }`} />
65
- {mod.label}
66
  </Link>
67
- ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
- <div className="mt-0 md:mt-12 flex items-center md:items-start ml-4 md:ml-0">
70
- <Link href="/vip" className="font-mono text-[10px] tracking-[0.2em] px-4 py-2 border border-zinc-800 text-zinc-400 hover:text-black hover:bg-white transition-all uppercase whitespace-nowrap">
71
- Unlock VIP
 
 
 
 
72
  </Link>
73
  </div>
74
  </nav>
@@ -79,13 +101,13 @@ export default async function Scripts() {
79
  {/* Top Edge Decoration Removed */}
80
 
81
  {/* Ledger Table Header (Desktop) */}
82
- <div className="hidden md:grid grid-cols-12 gap-4 px-8 py-4 border-b border-zinc-800 font-mono text-[9px] tracking-[0.2em] text-zinc-500 uppercase bg-zinc-900/20">
83
- <div className="col-span-2">Date</div>
84
- <div className="col-span-1">ID</div>
85
- <div className="col-span-4">Identifier</div>
86
- <div className="col-span-2">Availability</div>
87
- <div className="col-span-2">Status</div>
88
- <div className="col-span-1 text-right">Action</div>
89
  </div>
90
 
91
  {/* Ledger Content */}
 
38
  <div className="flex flex-col md:flex-row gap-8 flex-1 min-h-0">
39
 
40
  {/* Module Sidebar */}
41
+ <nav className="w-full md:w-56 lg:w-72 flex-shrink-0 border-b md:border-b-0 md:border-r border-white/10 pr-0 md:pr-10 pb-8 md:pb-0 flex flex-col gap-0 overflow-hidden">
42
+
43
+ <div className="mb-10 hidden md:block">
44
+ <Link href="/" className="font-mono text-[9px] tracking-[0.4em] text-zinc-500 hover:text-white transition-all uppercase flex items-center gap-2 group">
45
+ <span className="w-4 h-[1px] bg-zinc-800 group-hover:w-8 group-hover:bg-white transition-all" />
46
+ RETURN TO BASE
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  </Link>
48
+ </div>
49
+
50
+ <div className="mb-6 flex flex-col gap-1">
51
+ <span className="font-mono text-[10px] tracking-[0.3em] text-zinc-600 uppercase">SYSTEM</span>
52
+ <span className="font-black text-xs tracking-tighter uppercase text-white">MODULES</span>
53
+ </div>
54
+
55
+ <div className="flex flex-row md:flex-col gap-2 md:gap-0 overflow-x-auto md:overflow-visible no-scrollbar">
56
+ {[
57
+ { id: 'sources', label: 'Sources', active: false, index: '01' },
58
+ { id: 'cracks', label: 'Cracks', active: false, index: '02' },
59
+ { id: 'scripts', label: 'Scripts', active: true, index: '03' },
60
+ { id: 'tools', label: 'Tools', active: false, index: '04' },
61
+ ].map((mod) => (
62
+ <Link
63
+ key={mod.id}
64
+ href={`/${mod.id}`}
65
+ className={`group relative flex items-center justify-between py-3 md:py-4 border-b border-white/5 transition-all ${
66
+ mod.active ? 'px-4 bg-white text-black' : 'px-0 text-zinc-500 hover:text-white hover:pl-4'
67
+ }`}
68
+ >
69
+ <div className="flex items-center gap-4">
70
+ <span className={`font-mono text-[9px] ${mod.active ? 'text-black/50' : 'text-zinc-800 group-hover:text-zinc-400'}`}>
71
+ {mod.index}
72
+ </span>
73
+ <span className="font-black text-[11px] tracking-widest uppercase italic">
74
+ {mod.label}
75
+ </span>
76
+ </div>
77
+ {mod.active && (
78
+ <div className="w-1.5 h-1.5 bg-black rounded-full shadow-[0_0_8px_rgba(0,0,0,0.5)]" />
79
+ )}
80
+ {!mod.active && (
81
+ <span className="opacity-0 group-hover:opacity-100 transition-opacity font-mono text-[10px]">→</span>
82
+ )}
83
+ </Link>
84
+ ))}
85
+ </div>
86
 
87
+ <div className="mt-auto pt-12 hidden md:block">
88
+ <Link href="/vip" className="relative group block overflow-hidden">
89
+ <div className="absolute inset-0 bg-white translate-y-[101%] group-hover:translate-y-0 transition-transform duration-300" />
90
+ <div className="border border-white/20 px-6 py-4 flex flex-col gap-1 relative transition-colors group-hover:text-black">
91
+ <span className="font-mono text-[8px] tracking-[.3em] text-zinc-500 group-hover:text-black/50 uppercase transition-colors">Access Level</span>
92
+ <span className="font-black text-xs tracking-[.2em] uppercase">Request VIP</span>
93
+ </div>
94
  </Link>
95
  </div>
96
  </nav>
 
101
  {/* Top Edge Decoration Removed */}
102
 
103
  {/* Ledger Table Header (Desktop) */}
104
+ <div className="hidden md:grid grid-cols-12 gap-4 px-8 py-5 border-b border-zinc-900 font-mono text-[8px] tracking-[0.3em] text-zinc-600 uppercase bg-black/40">
105
+ <div className="col-span-2">TIMESTAMP</div>
106
+ <div className="col-span-1">REF</div>
107
+ <div className="col-span-4">IDENTIFIER</div>
108
+ <div className="col-span-2">LATENCY / AVAIL</div>
109
+ <div className="col-span-2">ENCRYPTION / STATUS</div>
110
+ <div className="col-span-1 text-right">ACTION</div>
111
  </div>
112
 
113
  {/* Ledger Content */}
WSW/src/app/sources/page.tsx CHANGED
@@ -38,37 +38,59 @@ export default async function Sources() {
38
  <div className="flex flex-col md:flex-row gap-8 flex-1 min-h-0">
39
 
40
  {/* Module Sidebar */}
41
- <nav className="w-full md:w-48 lg:w-64 flex-shrink-0 border-b md:border-b-0 md:border-r border-zinc-900 pr-0 md:pr-8 pb-8 md:pb-0 flex flex-row md:flex-col gap-4 overflow-x-auto md:overflow-visible">
42
- <Link href="/" className="font-mono text-xs tracking-[0.2em] text-zinc-600 hover:text-white transition-colors uppercase whitespace-nowrap hidden md:block mb-8">
43
- Back Base
44
- </Link>
45
- <Link href="/" className="font-mono text-xs tracking-[0.2em] text-zinc-600 hover:text-white transition-colors uppercase whitespace-nowrap block md:hidden">
46
- [ BACK ]
47
- </Link>
48
-
49
- {[
50
- { id: 'sources', label: 'Sources', active: true },
51
- { id: 'cracks', label: 'Cracks', active: false },
52
- { id: 'scripts', label: 'Scripts', active: false },
53
- { id: 'tools', label: 'Tools', active: false },
54
- ].map((mod) => (
55
- <Link
56
- key={mod.id}
57
- href={`/${mod.id}`}
58
- className={`font-mono text-xs tracking-widest uppercase py-2 flex items-center gap-3 whitespace-nowrap group ${
59
- mod.active ? 'text-white' : 'text-zinc-500 hover:text-zinc-300'
60
- }`}
61
- >
62
- <span className={`w-1.5 h-1.5 rounded-full transition-all ${
63
- mod.active ? 'bg-white shadow-[0_0_10px_rgba(255,255,255,0.8)]' : 'bg-transparent border border-zinc-700 group-hover:border-zinc-500'
64
- }`} />
65
- {mod.label}
66
  </Link>
67
- ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
- <div className="mt-0 md:mt-12 flex items-center md:items-start ml-4 md:ml-0">
70
- <Link href="/vip" className="font-mono text-[10px] tracking-[0.2em] px-4 py-2 border border-zinc-800 text-zinc-400 hover:text-black hover:bg-white transition-all uppercase whitespace-nowrap">
71
- Unlock VIP
 
 
 
 
72
  </Link>
73
  </div>
74
  </nav>
@@ -79,13 +101,13 @@ export default async function Sources() {
79
  {/* Top Edge Decoration Removed */}
80
 
81
  {/* Ledger Table Header (Desktop) */}
82
- <div className="hidden md:grid grid-cols-12 gap-4 px-8 py-4 border-b border-zinc-800 font-mono text-[9px] tracking-[0.2em] text-zinc-500 uppercase bg-zinc-900/20">
83
- <div className="col-span-2">Date</div>
84
- <div className="col-span-1">ID</div>
85
- <div className="col-span-4">Identifier</div>
86
- <div className="col-span-2">Availability</div>
87
- <div className="col-span-2">Status</div>
88
- <div className="col-span-1 text-right">Action</div>
89
  </div>
90
 
91
  {/* Ledger Content */}
 
38
  <div className="flex flex-col md:flex-row gap-8 flex-1 min-h-0">
39
 
40
  {/* Module Sidebar */}
41
+ <nav className="w-full md:w-56 lg:w-72 flex-shrink-0 border-b md:border-b-0 md:border-r border-white/10 pr-0 md:pr-10 pb-8 md:pb-0 flex flex-col gap-0 overflow-hidden">
42
+
43
+ <div className="mb-10 hidden md:block">
44
+ <Link href="/" className="font-mono text-[9px] tracking-[0.4em] text-zinc-500 hover:text-white transition-all uppercase flex items-center gap-2 group">
45
+ <span className="w-4 h-[1px] bg-zinc-800 group-hover:w-8 group-hover:bg-white transition-all" />
46
+ RETURN TO BASE
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  </Link>
48
+ </div>
49
+
50
+ <div className="mb-6 flex flex-col gap-1">
51
+ <span className="font-mono text-[10px] tracking-[0.3em] text-zinc-600 uppercase">SYSTEM</span>
52
+ <span className="font-black text-xs tracking-tighter uppercase text-white">MODULES</span>
53
+ </div>
54
+
55
+ <div className="flex flex-row md:flex-col gap-2 md:gap-0 overflow-x-auto md:overflow-visible no-scrollbar">
56
+ {[
57
+ { id: 'sources', label: 'Sources', active: true, index: '01' },
58
+ { id: 'cracks', label: 'Cracks', active: false, index: '02' },
59
+ { id: 'scripts', label: 'Scripts', active: false, index: '03' },
60
+ { id: 'tools', label: 'Tools', active: false, index: '04' },
61
+ ].map((mod) => (
62
+ <Link
63
+ key={mod.id}
64
+ href={`/${mod.id}`}
65
+ className={`group relative flex items-center justify-between py-3 md:py-4 border-b border-white/5 transition-all ${
66
+ mod.active ? 'px-4 bg-white text-black' : 'px-0 text-zinc-500 hover:text-white hover:pl-4'
67
+ }`}
68
+ >
69
+ <div className="flex items-center gap-4">
70
+ <span className={`font-mono text-[9px] ${mod.active ? 'text-black/50' : 'text-zinc-800 group-hover:text-zinc-400'}`}>
71
+ {mod.index}
72
+ </span>
73
+ <span className="font-black text-[11px] tracking-widest uppercase italic">
74
+ {mod.label}
75
+ </span>
76
+ </div>
77
+ {mod.active && (
78
+ <div className="w-1.5 h-1.5 bg-black rounded-full shadow-[0_0_8px_rgba(0,0,0,0.5)]" />
79
+ )}
80
+ {!mod.active && (
81
+ <span className="opacity-0 group-hover:opacity-100 transition-opacity font-mono text-[10px]">→</span>
82
+ )}
83
+ </Link>
84
+ ))}
85
+ </div>
86
 
87
+ <div className="mt-auto pt-12 hidden md:block">
88
+ <Link href="/vip" className="relative group block overflow-hidden">
89
+ <div className="absolute inset-0 bg-white translate-y-[101%] group-hover:translate-y-0 transition-transform duration-300" />
90
+ <div className="border border-white/20 px-6 py-4 flex flex-col gap-1 relative transition-colors group-hover:text-black">
91
+ <span className="font-mono text-[8px] tracking-[.3em] text-zinc-500 group-hover:text-black/50 uppercase transition-colors">Access Level</span>
92
+ <span className="font-black text-xs tracking-[.2em] uppercase">Request VIP</span>
93
+ </div>
94
  </Link>
95
  </div>
96
  </nav>
 
101
  {/* Top Edge Decoration Removed */}
102
 
103
  {/* Ledger Table Header (Desktop) */}
104
+ <div className="hidden md:grid grid-cols-12 gap-4 px-8 py-5 border-b border-zinc-900 font-mono text-[8px] tracking-[0.3em] text-zinc-600 uppercase bg-black/40">
105
+ <div className="col-span-2">TIMESTAMP</div>
106
+ <div className="col-span-1">REF</div>
107
+ <div className="col-span-4">IDENTIFIER</div>
108
+ <div className="col-span-2">LATENCY / AVAIL</div>
109
+ <div className="col-span-2">ENCRYPTION / STATUS</div>
110
+ <div className="col-span-1 text-right">ACTION</div>
111
  </div>
112
 
113
  {/* Ledger Content */}
WSW/src/app/tools/page.tsx CHANGED
@@ -38,37 +38,59 @@ export default async function Tools() {
38
  <div className="flex flex-col md:flex-row gap-8 flex-1 min-h-0">
39
 
40
  {/* Module Sidebar */}
41
- <nav className="w-full md:w-48 lg:w-64 flex-shrink-0 border-b md:border-b-0 md:border-r border-zinc-900 pr-0 md:pr-8 pb-8 md:pb-0 flex flex-row md:flex-col gap-4 overflow-x-auto md:overflow-visible">
42
- <Link href="/" className="font-mono text-xs tracking-[0.2em] text-zinc-600 hover:text-white transition-colors uppercase whitespace-nowrap hidden md:block mb-8">
43
- Back Base
44
- </Link>
45
- <Link href="/" className="font-mono text-xs tracking-[0.2em] text-zinc-600 hover:text-white transition-colors uppercase whitespace-nowrap block md:hidden">
46
- [ BACK ]
47
- </Link>
48
-
49
- {[
50
- { id: 'sources', label: 'Sources', active: false },
51
- { id: 'cracks', label: 'Cracks', active: false },
52
- { id: 'scripts', label: 'Scripts', active: false },
53
- { id: 'tools', label: 'Tools', active: true },
54
- ].map((mod) => (
55
- <Link
56
- key={mod.id}
57
- href={`/${mod.id}`}
58
- className={`font-mono text-xs tracking-widest uppercase py-2 flex items-center gap-3 whitespace-nowrap group ${
59
- mod.active ? 'text-white' : 'text-zinc-500 hover:text-zinc-300'
60
- }`}
61
- >
62
- <span className={`w-1.5 h-1.5 rounded-full transition-all ${
63
- mod.active ? 'bg-white shadow-[0_0_10px_rgba(255,255,255,0.8)]' : 'bg-transparent border border-zinc-700 group-hover:border-zinc-500'
64
- }`} />
65
- {mod.label}
66
  </Link>
67
- ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
- <div className="mt-0 md:mt-12 flex items-center md:items-start ml-4 md:ml-0">
70
- <Link href="/vip" className="font-mono text-[10px] tracking-[0.2em] px-4 py-2 border border-zinc-800 text-zinc-400 hover:text-black hover:bg-white transition-all uppercase whitespace-nowrap">
71
- Unlock VIP
 
 
 
 
72
  </Link>
73
  </div>
74
  </nav>
@@ -79,13 +101,13 @@ export default async function Tools() {
79
  {/* Top Edge Decoration Removed */}
80
 
81
  {/* Ledger Table Header (Desktop) */}
82
- <div className="hidden md:grid grid-cols-12 gap-4 px-8 py-4 border-b border-zinc-800 font-mono text-[9px] tracking-[0.2em] text-zinc-500 uppercase bg-zinc-900/20">
83
- <div className="col-span-2">Date</div>
84
- <div className="col-span-1">ID</div>
85
- <div className="col-span-4">Identifier</div>
86
- <div className="col-span-2">Availability</div>
87
- <div className="col-span-2">Status</div>
88
- <div className="col-span-1 text-right">Action</div>
89
  </div>
90
 
91
  {/* Ledger Content */}
 
38
  <div className="flex flex-col md:flex-row gap-8 flex-1 min-h-0">
39
 
40
  {/* Module Sidebar */}
41
+ <nav className="w-full md:w-56 lg:w-72 flex-shrink-0 border-b md:border-b-0 md:border-r border-white/10 pr-0 md:pr-10 pb-8 md:pb-0 flex flex-col gap-0 overflow-hidden">
42
+
43
+ <div className="mb-10 hidden md:block">
44
+ <Link href="/" className="font-mono text-[9px] tracking-[0.4em] text-zinc-500 hover:text-white transition-all uppercase flex items-center gap-2 group">
45
+ <span className="w-4 h-[1px] bg-zinc-800 group-hover:w-8 group-hover:bg-white transition-all" />
46
+ RETURN TO BASE
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  </Link>
48
+ </div>
49
+
50
+ <div className="mb-6 flex flex-col gap-1">
51
+ <span className="font-mono text-[10px] tracking-[0.3em] text-zinc-600 uppercase">SYSTEM</span>
52
+ <span className="font-black text-xs tracking-tighter uppercase text-white">MODULES</span>
53
+ </div>
54
+
55
+ <div className="flex flex-row md:flex-col gap-2 md:gap-0 overflow-x-auto md:overflow-visible no-scrollbar">
56
+ {[
57
+ { id: 'sources', label: 'Sources', active: false, index: '01' },
58
+ { id: 'cracks', label: 'Cracks', active: false, index: '02' },
59
+ { id: 'scripts', label: 'Scripts', active: false, index: '03' },
60
+ { id: 'tools', label: 'Tools', active: true, index: '04' },
61
+ ].map((mod) => (
62
+ <Link
63
+ key={mod.id}
64
+ href={`/${mod.id}`}
65
+ className={`group relative flex items-center justify-between py-3 md:py-4 border-b border-white/5 transition-all ${
66
+ mod.active ? 'px-4 bg-white text-black' : 'px-0 text-zinc-500 hover:text-white hover:pl-4'
67
+ }`}
68
+ >
69
+ <div className="flex items-center gap-4">
70
+ <span className={`font-mono text-[9px] ${mod.active ? 'text-black/50' : 'text-zinc-800 group-hover:text-zinc-400'}`}>
71
+ {mod.index}
72
+ </span>
73
+ <span className="font-black text-[11px] tracking-widest uppercase italic">
74
+ {mod.label}
75
+ </span>
76
+ </div>
77
+ {mod.active && (
78
+ <div className="w-1.5 h-1.5 bg-black rounded-full shadow-[0_0_8px_rgba(0,0,0,0.5)]" />
79
+ )}
80
+ {!mod.active && (
81
+ <span className="opacity-0 group-hover:opacity-100 transition-opacity font-mono text-[10px]">→</span>
82
+ )}
83
+ </Link>
84
+ ))}
85
+ </div>
86
 
87
+ <div className="mt-auto pt-12 hidden md:block">
88
+ <Link href="/vip" className="relative group block overflow-hidden">
89
+ <div className="absolute inset-0 bg-white translate-y-[101%] group-hover:translate-y-0 transition-transform duration-300" />
90
+ <div className="border border-white/20 px-6 py-4 flex flex-col gap-1 relative transition-colors group-hover:text-black">
91
+ <span className="font-mono text-[8px] tracking-[.3em] text-zinc-500 group-hover:text-black/50 uppercase transition-colors">Access Level</span>
92
+ <span className="font-black text-xs tracking-[.2em] uppercase">Request VIP</span>
93
+ </div>
94
  </Link>
95
  </div>
96
  </nav>
 
101
  {/* Top Edge Decoration Removed */}
102
 
103
  {/* Ledger Table Header (Desktop) */}
104
+ <div className="hidden md:grid grid-cols-12 gap-4 px-8 py-5 border-b border-zinc-900 font-mono text-[8px] tracking-[0.3em] text-zinc-600 uppercase bg-black/40">
105
+ <div className="col-span-2">TIMESTAMP</div>
106
+ <div className="col-span-1">REF</div>
107
+ <div className="col-span-4">IDENTIFIER</div>
108
+ <div className="col-span-2">LATENCY / AVAIL</div>
109
+ <div className="col-span-2">ENCRYPTION / STATUS</div>
110
+ <div className="col-span-1 text-right">ACTION</div>
111
  </div>
112
 
113
  {/* Ledger Content */}
WSW/src/app/vip/page.tsx CHANGED
@@ -90,13 +90,27 @@ export default async function VIPPage() {
90
  <p className="font-mono text-[10px] text-zinc-400 tracking-widest leading-relaxed mb-6">
91
  Your security clearance is elevated. You now have unrestricted access to all highly classified directory index protocols.
92
  </p>
93
- <div className="grid grid-cols-2 gap-4">
94
- <Link href="/sources" className="p-4 border border-zinc-700 hover:border-white transition-colors text-center font-mono text-[10px] tracking-widest text-white uppercase bg-zinc-900 group">
95
- Access <span className="group-hover:text-zinc-400">Sources</span>
96
- </Link>
97
- <Link href="/cracks" className="p-4 border border-zinc-700 hover:border-white transition-colors text-center font-mono text-[10px] tracking-widest text-white uppercase bg-zinc-900 group">
98
- Access <span className="group-hover:text-zinc-400">Cracks</span>
99
- </Link>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  </div>
101
  </div>
102
  ) : (
 
90
  <p className="font-mono text-[10px] text-zinc-400 tracking-widest leading-relaxed mb-6">
91
  Your security clearance is elevated. You now have unrestricted access to all highly classified directory index protocols.
92
  </p>
93
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
94
+ {[
95
+ { id: 'sources', label: 'Sources', index: '01' },
96
+ { id: 'cracks', label: 'Cracks', index: '02' },
97
+ { id: 'scripts', label: 'Scripts', index: '03' },
98
+ { id: 'tools', label: 'Tools', index: '04' },
99
+ ].map((mod) => (
100
+ <Link
101
+ key={mod.id}
102
+ href={`/${mod.id}`}
103
+ className="p-6 border border-zinc-800 hover:border-white transition-all bg-zinc-900/50 group flex flex-col gap-2"
104
+ >
105
+ <div className="flex justify-between items-center">
106
+ <span className="font-mono text-[9px] text-zinc-600 group-hover:text-zinc-400 transition-colors uppercase tracking-widest">{mod.index}</span>
107
+ <span className="text-zinc-700 group-hover:text-white transition-colors">→</span>
108
+ </div>
109
+ <span className="font-black text-sm tracking-widest text-white uppercase italic group-hover:pl-2 transition-all">
110
+ {mod.label}
111
+ </span>
112
+ </Link>
113
+ ))}
114
  </div>
115
  </div>
116
  ) : (
WSW/src/components/AnimatedRow.tsx CHANGED
@@ -22,38 +22,48 @@ export default function AnimatedRow({
22
  delay: index * 0.05,
23
  ease: [0.22, 1, 0.36, 1],
24
  }}
25
- className="grid grid-cols-1 md:grid-cols-12 gap-2 md:gap-4 px-6 md:px-8 py-4 sm:py-6 border-b border-zinc-800/50 hover:bg-zinc-900/40 hover:shadow-[inset_0_0_15px_rgba(255,255,255,0.02)] transition-all duration-300 font-mono text-[10px] tracking-widest group items-center relative overflow-hidden"
26
  >
27
- <div className="col-span-1 border-b border-zinc-800/30 pb-2 mb-2 md:border-none md:pb-0 md:mb-0 md:col-span-3 flex md:contents justify-between text-zinc-500">
28
- <div className="md:col-span-2 group-hover:text-zinc-400 transition-colors">
29
  {new Date(drop.published_at).toLocaleDateString('en-GB', {
30
  day: '2-digit',
31
  month: '2-digit',
32
  year: 'numeric',
33
  })}
34
  </div>
35
- <div className="md:col-span-1 text-zinc-600 group-hover:text-zinc-500 transition-colors">
36
- {prefix}-{drop.id.toString().padStart(3, '0')}
37
  </div>
38
  </div>
39
 
40
- <div className="col-span-1 md:col-span-4 text-white font-bold truncate tracking-[0.2em] group-hover:drop-shadow-[0_0_8px_rgba(255,255,255,0.3)] transition-all duration-300">
41
- {drop.title.toUpperCase()}
42
  </div>
43
 
44
- <div className="col-span-1 md:col-span-5 flex md:contents justify-between items-center mt-2 md:mt-0 text-[10px]">
45
- <div className="md:col-span-2 text-zinc-400 truncate pr-2 group-hover:text-zinc-300 transition-colors">
46
- {drop.file_url ? 'AVAILABLE' : 'N/A'}
 
 
 
 
 
 
 
 
 
 
47
  </div>
48
 
49
  <div
50
  className={`md:col-span-2 ${
51
  drop.status === 'checked'
52
- ? 'text-white group-hover:drop-shadow-[0_0_5px_rgba(255,255,255,0.5)]'
53
- : 'text-zinc-400 animate-pulse group-hover:text-zinc-300'
54
  } transition-all duration-300`}
55
  >
56
- [{drop.status === 'checked' ? 'VERIFIED' : 'NOT VERIFIED'}]
57
  </div>
58
 
59
  <div className="md:col-span-1 text-right">
@@ -63,13 +73,13 @@ export default function AnimatedRow({
63
  href={drop.file_url}
64
  target="_blank"
65
  rel="noopener noreferrer"
66
- className="text-white hover:drop-shadow-[0_0_8px_rgba(255,255,255,0.8)] transition-all border-b border-transparent hover:border-white pb-0.5"
67
  >
68
- DOWNLOAD
69
  </a>
70
  ) : (
71
- <span className="text-zinc-600 cursor-not-allowed">
72
- [ VIP ONLY ]
73
  </span>
74
  )
75
  )}
 
22
  delay: index * 0.05,
23
  ease: [0.22, 1, 0.36, 1],
24
  }}
25
+ className="grid grid-cols-1 md:grid-cols-12 gap-2 md:gap-4 px-6 md:px-8 py-5 border-b border-zinc-900/50 hover:bg-white/[0.02] transition-all duration-300 font-mono text-[10px] tracking-widest group items-center relative overflow-hidden"
26
  >
27
+ <div className="col-span-1 md:col-span-3 flex md:contents justify-between text-zinc-600">
28
+ <div className="md:col-span-2 group-hover:text-zinc-400 transition-colors font-mono tabular-nums">
29
  {new Date(drop.published_at).toLocaleDateString('en-GB', {
30
  day: '2-digit',
31
  month: '2-digit',
32
  year: 'numeric',
33
  })}
34
  </div>
35
+ <div className="md:col-span-1 text-zinc-700 group-hover:text-zinc-500 transition-colors">
36
+ {prefix}{drop.id.toString().padStart(3, '0')}
37
  </div>
38
  </div>
39
 
40
+ <div className="col-span-1 md:col-span-4 text-white font-black tracking-tighter uppercase text-xs italic group-hover:pl-2 transition-all duration-300">
41
+ {drop.title}
42
  </div>
43
 
44
+ <div className="col-span-1 md:col-span-5 flex md:contents justify-between items-center text-[9px] tracking-[0.2em]">
45
+ <div className="md:col-span-2 text-zinc-500 group-hover:text-zinc-300 transition-colors flex items-center gap-2">
46
+ {drop.file_url ? (
47
+ <>
48
+ <span className="w-1 h-1 bg-green-500 rounded-full shadow-[0_0_5px_rgba(34,197,94,0.5)]" />
49
+ AVAILABLE
50
+ </>
51
+ ) : (
52
+ <>
53
+ <span className="w-1 h-1 bg-zinc-800 rounded-full" />
54
+ N/A
55
+ </>
56
+ )}
57
  </div>
58
 
59
  <div
60
  className={`md:col-span-2 ${
61
  drop.status === 'checked'
62
+ ? 'text-white'
63
+ : 'text-zinc-600 animate-pulse'
64
  } transition-all duration-300`}
65
  >
66
+ {drop.status === 'checked' ? '• SECURE' : ' UNVERIFIED'}
67
  </div>
68
 
69
  <div className="md:col-span-1 text-right">
 
73
  href={drop.file_url}
74
  target="_blank"
75
  rel="noopener noreferrer"
76
+ className="bg-white text-black px-3 py-1.5 font-black text-[9px] tracking-widest hover:bg-zinc-200 transition-colors"
77
  >
78
+ FETCH
79
  </a>
80
  ) : (
81
+ <span className="text-zinc-800 border border-zinc-900 px-3 py-1.5 cursor-not-allowed">
82
+ LOCKED
83
  </span>
84
  )
85
  )}
package-lock.json CHANGED
@@ -9,6 +9,7 @@
9
  "version": "1.0.0",
10
  "dependencies": {
11
  "@octokit/rest": "^22.0.1",
 
12
  "better-sqlite3": "^11.7.0",
13
  "discord.js": "^14.16.0",
14
  "dotenv": "^16.4.0",
@@ -334,6 +335,86 @@
334
  "npm": ">=7.0.0"
335
  }
336
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
  "node_modules/@types/node": {
338
  "version": "25.3.3",
339
  "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz",
@@ -343,6 +424,12 @@
343
  "undici-types": "~7.18.0"
344
  }
345
  },
 
 
 
 
 
 
346
  "node_modules/@types/ws": {
347
  "version": "8.18.1",
348
  "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
@@ -588,6 +675,15 @@
588
  "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
589
  "license": "MIT"
590
  },
 
 
 
 
 
 
 
 
 
591
  "node_modules/ieee754": {
592
  "version": "1.2.1",
593
  "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
 
9
  "version": "1.0.0",
10
  "dependencies": {
11
  "@octokit/rest": "^22.0.1",
12
+ "@supabase/supabase-js": "^2.99.3",
13
  "better-sqlite3": "^11.7.0",
14
  "discord.js": "^14.16.0",
15
  "dotenv": "^16.4.0",
 
335
  "npm": ">=7.0.0"
336
  }
337
  },
338
+ "node_modules/@supabase/auth-js": {
339
+ "version": "2.99.3",
340
+ "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.99.3.tgz",
341
+ "integrity": "sha512-vMEVLA1kGGYd/kdsJSwtjiFUZM1nGfrz2DWmgMBZtocV48qL+L2+4QpIkueXyBEumMQZFEyhz57i/5zGHjvdBw==",
342
+ "license": "MIT",
343
+ "dependencies": {
344
+ "tslib": "2.8.1"
345
+ },
346
+ "engines": {
347
+ "node": ">=20.0.0"
348
+ }
349
+ },
350
+ "node_modules/@supabase/functions-js": {
351
+ "version": "2.99.3",
352
+ "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.99.3.tgz",
353
+ "integrity": "sha512-6tk2zrcBkzKaaBXPOG5nshn30uJNFGOH9LxOnE8i850eQmsX+jVm7vql9kTPyvUzEHwU4zdjSOkXS9M+9ukMVA==",
354
+ "license": "MIT",
355
+ "dependencies": {
356
+ "tslib": "2.8.1"
357
+ },
358
+ "engines": {
359
+ "node": ">=20.0.0"
360
+ }
361
+ },
362
+ "node_modules/@supabase/postgrest-js": {
363
+ "version": "2.99.3",
364
+ "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.99.3.tgz",
365
+ "integrity": "sha512-8HxEf+zNycj7Z8+ONhhlu+7J7Ha+L6weyCtdEeK2mN5OWJbh6n4LPU4iuJ5UlCvvNnbSXMoutY7piITEEAgl2g==",
366
+ "license": "MIT",
367
+ "dependencies": {
368
+ "tslib": "2.8.1"
369
+ },
370
+ "engines": {
371
+ "node": ">=20.0.0"
372
+ }
373
+ },
374
+ "node_modules/@supabase/realtime-js": {
375
+ "version": "2.99.3",
376
+ "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.99.3.tgz",
377
+ "integrity": "sha512-c1azgZ2nZPczbY5k5u5iFrk1InpxN81IvNE+UBAkjrBz3yc5ALLJNkeTQwbJZT4PZBuYXEzqYGLMuh9fdTtTMg==",
378
+ "license": "MIT",
379
+ "dependencies": {
380
+ "@types/phoenix": "^1.6.6",
381
+ "@types/ws": "^8.18.1",
382
+ "tslib": "2.8.1",
383
+ "ws": "^8.18.2"
384
+ },
385
+ "engines": {
386
+ "node": ">=20.0.0"
387
+ }
388
+ },
389
+ "node_modules/@supabase/storage-js": {
390
+ "version": "2.99.3",
391
+ "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.99.3.tgz",
392
+ "integrity": "sha512-lOfIm4hInNcd8x0i1LWphnLKxec42wwbjs+vhaVAvR801Vda0UAMbTooUY6gfqgQb8v29GofqKuQMMTAsl6w/w==",
393
+ "license": "MIT",
394
+ "dependencies": {
395
+ "iceberg-js": "^0.8.1",
396
+ "tslib": "2.8.1"
397
+ },
398
+ "engines": {
399
+ "node": ">=20.0.0"
400
+ }
401
+ },
402
+ "node_modules/@supabase/supabase-js": {
403
+ "version": "2.99.3",
404
+ "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.99.3.tgz",
405
+ "integrity": "sha512-GuPbzoEaI51AkLw9VGhLNvnzw4PHbS3p8j2/JlvLeZNQMKwZw4aEYQIDBRtFwL5Nv7/275n9m4DHtakY8nCvgg==",
406
+ "license": "MIT",
407
+ "dependencies": {
408
+ "@supabase/auth-js": "2.99.3",
409
+ "@supabase/functions-js": "2.99.3",
410
+ "@supabase/postgrest-js": "2.99.3",
411
+ "@supabase/realtime-js": "2.99.3",
412
+ "@supabase/storage-js": "2.99.3"
413
+ },
414
+ "engines": {
415
+ "node": ">=20.0.0"
416
+ }
417
+ },
418
  "node_modules/@types/node": {
419
  "version": "25.3.3",
420
  "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz",
 
424
  "undici-types": "~7.18.0"
425
  }
426
  },
427
+ "node_modules/@types/phoenix": {
428
+ "version": "1.6.7",
429
+ "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.7.tgz",
430
+ "integrity": "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==",
431
+ "license": "MIT"
432
+ },
433
  "node_modules/@types/ws": {
434
  "version": "8.18.1",
435
  "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
 
675
  "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
676
  "license": "MIT"
677
  },
678
+ "node_modules/iceberg-js": {
679
+ "version": "0.8.1",
680
+ "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz",
681
+ "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==",
682
+ "license": "MIT",
683
+ "engines": {
684
+ "node": ">=20.0.0"
685
+ }
686
+ },
687
  "node_modules/ieee754": {
688
  "version": "1.2.1",
689
  "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
package.json CHANGED
@@ -8,6 +8,7 @@
8
  },
9
  "dependencies": {
10
  "@octokit/rest": "^22.0.1",
 
11
  "better-sqlite3": "^11.7.0",
12
  "discord.js": "^14.16.0",
13
  "dotenv": "^16.4.0",
 
8
  },
9
  "dependencies": {
10
  "@octokit/rest": "^22.0.1",
11
+ "@supabase/supabase-js": "^2.99.3",
12
  "better-sqlite3": "^11.7.0",
13
  "discord.js": "^14.16.0",
14
  "dotenv": "^16.4.0",
src/commands/deleteDrop.js CHANGED
@@ -15,14 +15,14 @@ module.exports = {
15
  const id = parseInt(args[0]);
16
  if (isNaN(id)) return message.reply({ content: '❌ Invalid Drop ID. Must be a number.' });
17
 
18
- const drop = stmts.getWebDrop.get(id);
19
  if (!drop) {
20
  return message.reply({ content: `❌ No drop found in database with ID: **${id}**` });
21
  }
22
 
23
  try {
24
- // 1. Delete locally from SQLite
25
- stmts.deleteWebDrop.run(id);
26
 
27
  // 2. Send DELETE request to Website Backend API
28
  const WEBSITE_API = process.env.WEBSITE_API_URL || 'http://localhost:3000/api/drops';
 
15
  const id = parseInt(args[0]);
16
  if (isNaN(id)) return message.reply({ content: '❌ Invalid Drop ID. Must be a number.' });
17
 
18
+ const drop = await stmts.getWebDrop(id);
19
  if (!drop) {
20
  return message.reply({ content: `❌ No drop found in database with ID: **${id}**` });
21
  }
22
 
23
  try {
24
+ // 1. Delete from Supabase
25
+ await stmts.deleteWebDrop(id);
26
 
27
  // 2. Send DELETE request to Website Backend API
28
  const WEBSITE_API = process.env.WEBSITE_API_URL || 'http://localhost:3000/api/drops';
src/commands/editDrop.js CHANGED
@@ -20,7 +20,7 @@ module.exports = {
20
 
21
  if (isNaN(id)) return message.reply({ content: '❌ Invalid Drop ID. Must be a number.' });
22
 
23
- const drop = stmts.getWebDrop.get(id);
24
  if (!drop) {
25
  return message.reply({ content: `❌ No drop found in database with ID: **${id}**` });
26
  }
@@ -31,9 +31,9 @@ module.exports = {
31
  }
32
 
33
  try {
34
- // 1. Update SQLite locally
35
  const { db } = require('../database');
36
- db.prepare(`UPDATE web_drops SET ${property} = ? WHERE id = ?`).run(newValue, id);
37
 
38
  // 2. Send update request to Website Backend API
39
  const WEBSITE_API = process.env.WEBSITE_API_URL || 'http://localhost:3000/api/drops';
 
20
 
21
  if (isNaN(id)) return message.reply({ content: '❌ Invalid Drop ID. Must be a number.' });
22
 
23
+ const drop = await stmts.getWebDrop(id);
24
  if (!drop) {
25
  return message.reply({ content: `❌ No drop found in database with ID: **${id}**` });
26
  }
 
31
  }
32
 
33
  try {
34
+ // 1. Update Supabase
35
  const { db } = require('../database');
36
+ await db.from('web_drops').update({ [property]: newValue }).eq('id', id);
37
 
38
  // 2. Send update request to Website Backend API
39
  const WEBSITE_API = process.env.WEBSITE_API_URL || 'http://localhost:3000/api/drops';
src/commands/ticketStats.js CHANGED
@@ -5,7 +5,7 @@ const { stmts } = require('../database');
5
  module.exports = {
6
  name: 'ticket stats',
7
  async execute(client, message) {
8
- const stats = stmts.ticketStats.get();
9
 
10
  const embed = createEmbed({
11
  title: '🎫 Ticket Statistics',
 
5
  module.exports = {
6
  name: 'ticket stats',
7
  async execute(client, message) {
8
+ const stats = await stmts.ticketStats();
9
 
10
  const embed = createEmbed({
11
  title: '🎫 Ticket Statistics',
src/database.js CHANGED
@@ -1,119 +1,179 @@
1
- const path = require('path');
2
- const Database = require('better-sqlite3');
3
- const fs = require('fs');
4
 
5
- const DATA_DIR = path.join(__dirname, '..', 'data');
6
- if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true });
 
 
7
 
8
- const db = new Database(path.join(DATA_DIR, 'wsb.db'));
9
-
10
- // Enable WAL mode for better concurrency
11
- db.pragma('journal_mode = WAL');
12
-
13
- // ── Schema ────────────────────────────────────────────────────
14
- db.exec(`
15
- CREATE TABLE IF NOT EXISTS tickets (
16
- id INTEGER PRIMARY KEY AUTOINCREMENT,
17
- user_id TEXT NOT NULL,
18
- username TEXT NOT NULL,
19
- channel_id TEXT,
20
- status TEXT DEFAULT 'open',
21
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
22
- closed_at DATETIME
23
- );
24
-
25
- CREATE TABLE IF NOT EXISTS verification_log (
26
- id INTEGER PRIMARY KEY AUTOINCREMENT,
27
- user_id TEXT NOT NULL,
28
- username TEXT NOT NULL,
29
- action TEXT NOT NULL,
30
- timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
31
- );
32
-
33
- CREATE TABLE IF NOT EXISTS bot_state (
34
- key TEXT PRIMARY KEY,
35
- value TEXT NOT NULL
36
- );
37
-
38
- CREATE TABLE IF NOT EXISTS whitelist (
39
- user_id TEXT PRIMARY KEY,
40
- max_drops INTEGER DEFAULT 3,
41
- added_by TEXT NOT NULL,
42
- added_at DATETIME DEFAULT CURRENT_TIMESTAMP
43
- );
44
-
45
- CREATE TABLE IF NOT EXISTS drop_log (
46
- id INTEGER PRIMARY KEY AUTOINCREMENT,
47
- user_id TEXT NOT NULL,
48
- title TEXT NOT NULL,
49
- channel_id TEXT NOT NULL,
50
- dropped_at DATETIME DEFAULT CURRENT_TIMESTAMP
51
- );
52
-
53
- CREATE TABLE IF NOT EXISTS web_drops (
54
- id INTEGER PRIMARY KEY AUTOINCREMENT,
55
- user_id TEXT NOT NULL,
56
- category TEXT NOT NULL DEFAULT 'sources',
57
- title TEXT NOT NULL,
58
- description TEXT,
59
- status TEXT,
60
- is_external INTEGER DEFAULT 0,
61
- asset_id TEXT,
62
- file_url TEXT,
63
- image_url TEXT,
64
- published_at DATETIME DEFAULT CURRENT_TIMESTAMP
65
- );
66
-
67
- CREATE TABLE IF NOT EXISTS vip_users (
68
- discord_id TEXT PRIMARY KEY,
69
- purchased_at DATETIME DEFAULT CURRENT_TIMESTAMP,
70
- expires_at DATETIME
71
- );
72
- `);
73
-
74
- // ── Prepared Statements ───────────────────────────────────────
75
  const stmts = {
76
  // Tickets
77
- createTicket: db.prepare('INSERT INTO tickets (user_id, username, channel_id) VALUES (?, ?, ?)'),
78
- closeTicket: db.prepare('UPDATE tickets SET status = ?, closed_at = CURRENT_TIMESTAMP WHERE channel_id = ?'),
79
- getTicket: db.prepare('SELECT * FROM tickets WHERE channel_id = ?'),
80
- getOpenTickets: db.prepare('SELECT * FROM tickets WHERE status = ?'),
81
- getUserTicket: db.prepare('SELECT * FROM tickets WHERE user_id = ? AND status = ?'),
82
- ticketStats: db.prepare(`
83
- SELECT
84
- COUNT(*) as total,
85
- SUM(CASE WHEN status = 'open' THEN 1 ELSE 0 END) as open_count,
86
- SUM(CASE WHEN status = 'closed' THEN 1 ELSE 0 END) as closed_count,
87
- SUM(CASE WHEN status = 'deleted' THEN 1 ELSE 0 END) as deleted_count
88
- FROM tickets
89
- `),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
  // Verification log
92
- logVerification: db.prepare('INSERT INTO verification_log (user_id, username, action) VALUES (?, ?, ?)'),
 
 
 
 
93
 
94
  // Bot state (key-value)
95
- setState: db.prepare('INSERT OR REPLACE INTO bot_state (key, value) VALUES (?, ?)'),
96
- getState: db.prepare('SELECT value FROM bot_state WHERE key = ?'),
97
- delState: db.prepare('DELETE FROM bot_state WHERE key = ?'),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
 
99
  // Whitelist
100
- addWhitelist: db.prepare('INSERT OR REPLACE INTO whitelist (user_id, max_drops, added_by) VALUES (?, ?, ?)'),
101
- removeWhitelist: db.prepare('DELETE FROM whitelist WHERE user_id = ?'),
102
- getWhitelist: db.prepare('SELECT * FROM whitelist WHERE user_id = ?'),
103
- getAllWhitelist: db.prepare('SELECT * FROM whitelist'),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
 
105
  // Drops array
106
- logDrop: db.prepare('INSERT INTO drop_log (user_id, title, channel_id) VALUES (?, ?, ?)'),
107
- getDropCount24h: db.prepare(`SELECT COUNT(*) as count FROM drop_log WHERE user_id = ? AND dropped_at > datetime('now', '-24 hours')`),
108
- getLastDrop: db.prepare(`SELECT dropped_at FROM drop_log WHERE user_id = ? ORDER BY dropped_at DESC LIMIT 1`),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
 
110
- addWebDrop: db.prepare(`
111
- INSERT INTO web_drops (user_id, category, title, description, status, is_external, asset_id, file_url, image_url)
112
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
113
- `),
114
- getWebDrop: db.prepare('SELECT * FROM web_drops WHERE id = ?'),
115
- deleteWebDrop: db.prepare('DELETE FROM web_drops WHERE id = ?'),
116
- getAllWebDrops: db.prepare('SELECT * FROM web_drops ORDER BY published_at DESC LIMIT 50'),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  };
118
 
119
- module.exports = { db, stmts };
 
1
+ const { createClient } = require('@supabase/supabase-js');
 
 
2
 
3
+ const supabase = createClient(
4
+ process.env.SUPABASE_URL,
5
+ process.env.SUPABASE_SERVICE_ROLE_KEY
6
+ );
7
 
8
+ // ── Prepared Statements (Wrappers for Supabase) ─────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  const stmts = {
10
  // Tickets
11
+ createTicket: async (userId, username, channelId) => {
12
+ return await supabase
13
+ .from('tickets')
14
+ .insert({ user_id: userId, username, channel_id: channelId });
15
+ },
16
+ closeTicket: async (status, channelId) => {
17
+ return await supabase
18
+ .from('tickets')
19
+ .update({ status, closed_at: new Date().toISOString() })
20
+ .eq('channel_id', channelId);
21
+ },
22
+ getTicket: async (channelId) => {
23
+ const { data } = await supabase
24
+ .from('tickets')
25
+ .select('*')
26
+ .eq('channel_id', channelId)
27
+ .single();
28
+ return data;
29
+ },
30
+ getOpenTickets: async (status) => {
31
+ const { data } = await supabase
32
+ .from('tickets')
33
+ .select('*')
34
+ .eq('status', status);
35
+ return data || [];
36
+ },
37
+ getUserTicket: async (userId, status) => {
38
+ const { data } = await supabase
39
+ .from('tickets')
40
+ .select('*')
41
+ .eq('user_id', userId)
42
+ .eq('status', status)
43
+ .single();
44
+ return data;
45
+ },
46
+ ticketStats: async () => {
47
+ const { data, error } = await supabase.rpc('get_ticket_stats');
48
+ if (data) return data;
49
+
50
+ // Manual fallback if RPC not setup
51
+ const { count: total } = await supabase.from('tickets').select('*', { count: 'exact', head: true });
52
+ const { count: open } = await supabase.from('tickets').select('*', { count: 'exact', head: true }).eq('status', 'open');
53
+ const { count: closed } = await supabase.from('tickets').select('*', { count: 'exact', head: true }).eq('status', 'closed');
54
+ const { count: deleted } = await supabase.from('tickets').select('*', { count: 'exact', head: true }).eq('status', 'deleted');
55
+
56
+ return { total, open_count: open, closed_count: closed, deleted_count: deleted };
57
+ },
58
 
59
  // Verification log
60
+ logVerification: async (userId, username, action) => {
61
+ return await supabase
62
+ .from('verification_log')
63
+ .insert({ user_id: userId, username, action });
64
+ },
65
 
66
  // Bot state (key-value)
67
+ setState: async (key, value) => {
68
+ return await supabase
69
+ .from('bot_state')
70
+ .upsert({ key, value });
71
+ },
72
+ getState: async (key) => {
73
+ const { data } = await supabase
74
+ .from('bot_state')
75
+ .select('value')
76
+ .eq('key', key)
77
+ .single();
78
+ return data ? data.value : null;
79
+ },
80
+ delState: async (key) => {
81
+ return await supabase
82
+ .from('bot_state')
83
+ .delete()
84
+ .eq('key', key);
85
+ },
86
 
87
  // Whitelist
88
+ addWhitelist: async (userId, maxDrops, addedBy) => {
89
+ return await supabase
90
+ .from('whitelist')
91
+ .upsert({ user_id: userId, max_drops: maxDrops, added_by: addedBy });
92
+ },
93
+ removeWhitelist: async (userId) => {
94
+ return await supabase
95
+ .from('whitelist')
96
+ .delete()
97
+ .eq('user_id', userId);
98
+ },
99
+ getWhitelist: async (userId) => {
100
+ const { data } = await supabase
101
+ .from('whitelist')
102
+ .select('*')
103
+ .eq('user_id', userId)
104
+ .single();
105
+ return data;
106
+ },
107
+ getAllWhitelist: async () => {
108
+ const { data } = await supabase
109
+ .from('whitelist')
110
+ .select('*');
111
+ return data || [];
112
+ },
113
 
114
  // Drops array
115
+ logDrop: async (userId, title, channelId) => {
116
+ return await supabase
117
+ .from('drop_log')
118
+ .insert({ user_id: userId, title, channel_id: channelId });
119
+ },
120
+ getDropCount24h: async (userId) => {
121
+ const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
122
+ const { count } = await supabase
123
+ .from('drop_log')
124
+ .select('*', { count: 'exact', head: true })
125
+ .eq('user_id', userId)
126
+ .gt('dropped_at', twentyFourHoursAgo);
127
+ return { count: count || 0 };
128
+ },
129
+ getLastDrop: async (userId) => {
130
+ const { data } = await supabase
131
+ .from('drop_log')
132
+ .select('dropped_at')
133
+ .eq('user_id', userId)
134
+ .order('dropped_at', { ascending: false })
135
+ .limit(1)
136
+ .single();
137
+ return data;
138
+ },
139
 
140
+ addWebDrop: async (userId, category, title, description, status, isExternal, assetId, fileUrl, imageUrl) => {
141
+ return await supabase
142
+ .from('web_drops')
143
+ .insert({
144
+ user_id: userId,
145
+ category,
146
+ title,
147
+ description,
148
+ status,
149
+ is_external: isExternal,
150
+ asset_id: assetId,
151
+ file_url: fileUrl,
152
+ image_url: imageUrl
153
+ });
154
+ },
155
+ getWebDrop: async (id) => {
156
+ const { data } = await supabase
157
+ .from('web_drops')
158
+ .select('*')
159
+ .eq('id', id)
160
+ .single();
161
+ return data;
162
+ },
163
+ deleteWebDrop: async (id) => {
164
+ return await supabase
165
+ .from('web_drops')
166
+ .delete()
167
+ .eq('id', id);
168
+ },
169
+ getAllWebDrops: async () => {
170
+ const { data } = await supabase
171
+ .from('web_drops')
172
+ .select('*')
173
+ .order('published_at', { ascending: false })
174
+ .limit(50);
175
+ return data || [];
176
+ },
177
  };
178
 
179
+ module.exports = { db: supabase, stmts };
src/events/messageCreate.js CHANGED
@@ -77,7 +77,7 @@ module.exports = {
77
  // ── Whitelisted user: only allow "drop" ──
78
  if (userId !== OWNER_ID) {
79
  if (content === 'drop' || content === 'linkdrop') {
80
- const check = canDrop(userId);
81
  if (!check.allowed) {
82
  if (check.reason === 'not_whitelisted') {
83
  return; // Silently ignore non-whitelisted users
@@ -128,7 +128,7 @@ module.exports = {
128
  if (!targetId || !/^\d{17,20}$/.test(targetId)) {
129
  return message.reply({ embeds: [errorEmbed('Invalid ID', 'Usage: `whitelist <user_id> [limit]`\nExample: `whitelist 123456789 5`')] });
130
  }
131
- stmts.addWhitelist.run(targetId, limit, userId);
132
  const user = await client.users.fetch(targetId).catch(() => null);
133
  const name = user ? `**${user.tag}**` : `ID \`${targetId}\``;
134
  return message.reply({ embeds: [successEmbed('✅ Whitelisted', `${name} can now use \`drop\` (**${limit}** per day)`)] });
@@ -137,7 +137,7 @@ module.exports = {
137
  if (content.startsWith('unwhitelist ')) {
138
  const targetId = content.split(' ')[1]?.trim();
139
  if (!targetId) return message.reply({ embeds: [errorEmbed('Invalid ID', 'Usage: `unwhitelist <user_id>`')] });
140
- stmts.removeWhitelist.run(targetId);
141
  return message.reply({ embeds: [successEmbed('✅ Removed', `User \`${targetId}\` removed from whitelist`)] });
142
  }
143
 
@@ -171,7 +171,7 @@ module.exports = {
171
  }
172
 
173
  if (content === 'whitelist') {
174
- const all = stmts.getAllWhitelist.all();
175
  if (all.length === 0) {
176
  return message.reply({ embeds: [infoEmbed('Whitelist', 'No users whitelisted.\n\nUse `whitelist <user_id>` to add.')] });
177
  }
@@ -179,7 +179,7 @@ module.exports = {
179
  for (const w of all) {
180
  const user = await client.users.fetch(w.user_id).catch(() => null);
181
  const name = user ? user.tag : 'Unknown';
182
- const drops = stmts.getDropCount24h.get(w.user_id);
183
  lines.push(`• **${name}** (\`${w.user_id}\`) — ${drops.count}/${w.max_drops} drops today`);
184
  }
185
  return message.reply({ embeds: [infoEmbed('📋 Whitelist', lines.join('\n'))] });
 
77
  // ── Whitelisted user: only allow "drop" ──
78
  if (userId !== OWNER_ID) {
79
  if (content === 'drop' || content === 'linkdrop') {
80
+ const check = await canDrop(userId);
81
  if (!check.allowed) {
82
  if (check.reason === 'not_whitelisted') {
83
  return; // Silently ignore non-whitelisted users
 
128
  if (!targetId || !/^\d{17,20}$/.test(targetId)) {
129
  return message.reply({ embeds: [errorEmbed('Invalid ID', 'Usage: `whitelist <user_id> [limit]`\nExample: `whitelist 123456789 5`')] });
130
  }
131
+ await stmts.addWhitelist(targetId, limit, userId);
132
  const user = await client.users.fetch(targetId).catch(() => null);
133
  const name = user ? `**${user.tag}**` : `ID \`${targetId}\``;
134
  return message.reply({ embeds: [successEmbed('✅ Whitelisted', `${name} can now use \`drop\` (**${limit}** per day)`)] });
 
137
  if (content.startsWith('unwhitelist ')) {
138
  const targetId = content.split(' ')[1]?.trim();
139
  if (!targetId) return message.reply({ embeds: [errorEmbed('Invalid ID', 'Usage: `unwhitelist <user_id>`')] });
140
+ await stmts.removeWhitelist(targetId);
141
  return message.reply({ embeds: [successEmbed('✅ Removed', `User \`${targetId}\` removed from whitelist`)] });
142
  }
143
 
 
171
  }
172
 
173
  if (content === 'whitelist') {
174
+ const all = await stmts.getAllWhitelist();
175
  if (all.length === 0) {
176
  return message.reply({ embeds: [infoEmbed('Whitelist', 'No users whitelisted.\n\nUse `whitelist <user_id>` to add.')] });
177
  }
 
179
  for (const w of all) {
180
  const user = await client.users.fetch(w.user_id).catch(() => null);
181
  const name = user ? user.tag : 'Unknown';
182
+ const drops = await stmts.getDropCount24h(w.user_id);
183
  lines.push(`• **${name}** (\`${w.user_id}\`) — ${drops.count}/${w.max_drops} drops today`);
184
  }
185
  return message.reply({ embeds: [infoEmbed('📋 Whitelist', lines.join('\n'))] });
src/systems/drops.js CHANGED
@@ -22,17 +22,17 @@ const STEPS = ['category', 'title', 'file', 'warnings', 'status', 'about', 'imag
22
  * Check if a user can drop (owner = unlimited, whitelisted = custom limit).
23
  * Returns { allowed: boolean, remaining?: number, resetIn?: string, limit?: number }
24
  */
25
- function canDrop(userId) {
26
  if (userId === OWNER_ID) return { allowed: true, remaining: Infinity };
27
 
28
- const wl = stmts.getWhitelist.get(userId);
29
  if (!wl) return { allowed: false, reason: 'not_whitelisted' };
30
 
31
  const limit = wl.max_drops;
32
- const { count } = stmts.getDropCount24h.get(userId);
33
  if (count >= limit) {
34
- const last = stmts.getLastDrop.get(userId);
35
- const resetTime = new Date(last.dropped_at + 'Z');
36
  resetTime.setHours(resetTime.getHours() + 24);
37
  const diff = resetTime - new Date();
38
  const hours = Math.floor(diff / 3600000);
@@ -444,8 +444,8 @@ async function handleDropButton(interaction) {
444
  }).catch(() => {});
445
  } catch(e) {}
446
 
447
- // 4. Save to SQLite Web Tracking
448
- stmts.addWebDrop.run(
449
  userId,
450
  session.category,
451
  payload.title,
@@ -458,7 +458,7 @@ async function handleDropButton(interaction) {
458
  );
459
 
460
  // Log drop for Discord rate limits
461
- stmts.logDrop.run(userId, `[${session.category.toUpperCase()}] ${session.title}`, 'website');
462
 
463
  activeSessions.delete(userId);
464
 
@@ -575,4 +575,5 @@ module.exports = {
575
  handleDownloadButton,
576
  buildDropEmbed,
577
  canDrop,
 
578
  };
 
22
  * Check if a user can drop (owner = unlimited, whitelisted = custom limit).
23
  * Returns { allowed: boolean, remaining?: number, resetIn?: string, limit?: number }
24
  */
25
+ async function canDrop(userId) {
26
  if (userId === OWNER_ID) return { allowed: true, remaining: Infinity };
27
 
28
+ const wl = await stmts.getWhitelist(userId);
29
  if (!wl) return { allowed: false, reason: 'not_whitelisted' };
30
 
31
  const limit = wl.max_drops;
32
+ const { count } = await stmts.getDropCount24h(userId);
33
  if (count >= limit) {
34
+ const last = await stmts.getLastDrop(userId);
35
+ const resetTime = new Date(last.dropped_at);
36
  resetTime.setHours(resetTime.getHours() + 24);
37
  const diff = resetTime - new Date();
38
  const hours = Math.floor(diff / 3600000);
 
444
  }).catch(() => {});
445
  } catch(e) {}
446
 
447
+ // 4. Save to Supabase Web Tracking
448
+ await stmts.addWebDrop(
449
  userId,
450
  session.category,
451
  payload.title,
 
458
  );
459
 
460
  // Log drop for Discord rate limits
461
+ await stmts.logDrop(userId, `[${session.category.toUpperCase()}] ${session.title}`, 'website');
462
 
463
  activeSessions.delete(userId);
464
 
 
575
  handleDownloadButton,
576
  buildDropEmbed,
577
  canDrop,
578
+ activeSessions,
579
  };
src/systems/logger.js CHANGED
@@ -7,10 +7,10 @@ const { Colors } = require('../config');
7
  */
8
  async function log(client, { title, description, fields = [], color = Colors.MUTED }) {
9
  try {
10
- const row = stmts.getState.get('channel_staff-logs');
11
  if (!row) return;
12
 
13
- const channelId = row.value;
14
  const channel = await client.channels.fetch(channelId).catch(() => null);
15
  if (!channel) return;
16
 
@@ -23,7 +23,7 @@ async function log(client, { title, description, fields = [], color = Colors.MUT
23
 
24
  // Convenience wrappers
25
  async function logVerification(client, user, action) {
26
- stmts.logVerification.run(user.id, user.tag, action);
27
  await log(client, {
28
  title: '🔐 Verification',
29
  description: `**${user.tag}** (${user.id})`,
 
7
  */
8
  async function log(client, { title, description, fields = [], color = Colors.MUTED }) {
9
  try {
10
+ const row = await stmts.getState('channel_staff-logs');
11
  if (!row) return;
12
 
13
+ const channelId = row;
14
  const channel = await client.channels.fetch(channelId).catch(() => null);
15
  if (!channel) return;
16
 
 
23
 
24
  // Convenience wrappers
25
  async function logVerification(client, user, action) {
26
+ await stmts.logVerification(user.id, user.tag, action);
27
  await log(client, {
28
  title: '🔐 Verification',
29
  description: `**${user.tag}** (${user.id})`,
src/systems/massdrop.js CHANGED
@@ -328,9 +328,10 @@ async function handleMassDropMessage(message) {
328
  }).catch(() => {});
329
  } catch(e) {}
330
 
331
- // 4. Save to SQLite Web Tracking
332
- stmts.addWebDrop.run(
333
  userId,
 
334
  payload.title,
335
  payload.description,
336
  payload.status,
@@ -341,7 +342,7 @@ async function handleMassDropMessage(message) {
341
  );
342
 
343
  successCount++;
344
- stmts.logDrop.run(userId, fileConf.title, 'website-mass');
345
 
346
  } catch (pushErr) {
347
  console.error(`[Mass Drop Deploy Error] ${fileConf.title}:`, pushErr);
 
328
  }).catch(() => {});
329
  } catch(e) {}
330
 
331
+ // 4. Save to Supabase Web Tracking
332
+ await stmts.addWebDrop(
333
  userId,
334
+ 'sources', // Defaulting to sources for mass drop for now, or use from config
335
  payload.title,
336
  payload.description,
337
  payload.status,
 
342
  );
343
 
344
  successCount++;
345
+ await stmts.logDrop(userId, fileConf.title, 'website-mass');
346
 
347
  } catch (pushErr) {
348
  console.error(`[Mass Drop Deploy Error] ${fileConf.title}:`, pushErr);
src/systems/tickets.js CHANGED
@@ -14,10 +14,10 @@ const { logTicket } = require('./logger');
14
  * Send the ticket embed with a "Create a Ticket" button to 🎫・open-ticket channel.
15
  */
16
  async function sendTicketEmbed(client) {
17
- const row = stmts.getState.get('channel_🎫・open-ticket');
18
  if (!row) throw new Error('Ticket channel not found in bot state.');
19
 
20
- const channel = await client.channels.fetch(row.value);
21
  if (!channel) throw new Error('Could not fetch ticket channel.');
22
 
23
  const embed = createEmbed({
@@ -46,8 +46,8 @@ async function sendTicketEmbed(client) {
46
 
47
  const msg = await channel.send({ embeds: [embed], components: [actionRow] });
48
 
49
- stmts.setState.run('ticket_message_id', msg.id);
50
- stmts.setState.run('ticket_channel_id', channel.id);
51
 
52
  return msg;
53
  }
@@ -57,7 +57,7 @@ async function sendTicketEmbed(client) {
57
  */
58
  async function createTicket(guild, user, client) {
59
  // Check if user already has an open ticket
60
- const existing = stmts.getUserTicket.get(user.id, 'open');
61
  if (existing) return null;
62
 
63
  const staffRole = guild.roles.cache.find(r => r.name === '@@ Staff');
@@ -85,7 +85,7 @@ async function createTicket(guild, user, client) {
85
  });
86
 
87
  // Save to database
88
- stmts.createTicket.run(user.id, user.tag, ticketChannel.id);
89
 
90
  // Send welcome embed with action buttons
91
  const embed = createEmbed({
@@ -172,7 +172,7 @@ async function handleTicketButton(interaction, client) {
172
 
173
  if (!['ticket_close', 'ticket_delete', 'ticket_transcript'].includes(customId)) return false;
174
 
175
- const ticket = stmts.getTicket.get(channel.id);
176
  if (!ticket) {
177
  await interaction.reply({ content: '❌ This is not a ticket channel.', ephemeral: true });
178
  return true;
@@ -198,13 +198,13 @@ async function handleTicketButton(interaction, client) {
198
  }
199
 
200
  if (customId === 'ticket_close') {
201
- stmts.closeTicket.run('closed', channel.id);
202
 
203
  // Save transcript to ticket-logs
204
  const transcript = await generateTranscript(channel);
205
- const logsRow = stmts.getState.get('channel_📂・ticket-logs');
206
  if (logsRow) {
207
- const logsChannel = await client.channels.fetch(logsRow.value).catch(() => null);
208
  if (logsChannel) {
209
  const embed = createEmbed({
210
  title: '📂 Ticket Closed',
@@ -233,7 +233,7 @@ async function handleTicketButton(interaction, client) {
233
  }
234
 
235
  if (customId === 'ticket_delete') {
236
- stmts.closeTicket.run('deleted', channel.id);
237
  await logTicket(client, { user: { tag: ticket.username, id: ticket.user_id }, action: 'deleted', channelName: channel.name });
238
  await interaction.reply({ content: '🗑️ Deleting ticket...' });
239
  setTimeout(() => channel.delete().catch(() => { }), 1000);
 
14
  * Send the ticket embed with a "Create a Ticket" button to 🎫・open-ticket channel.
15
  */
16
  async function sendTicketEmbed(client) {
17
+ const row = await stmts.getState('channel_🎫・open-ticket');
18
  if (!row) throw new Error('Ticket channel not found in bot state.');
19
 
20
+ const channel = await client.channels.fetch(row);
21
  if (!channel) throw new Error('Could not fetch ticket channel.');
22
 
23
  const embed = createEmbed({
 
46
 
47
  const msg = await channel.send({ embeds: [embed], components: [actionRow] });
48
 
49
+ await stmts.setState('ticket_message_id', msg.id);
50
+ await stmts.setState('ticket_channel_id', channel.id);
51
 
52
  return msg;
53
  }
 
57
  */
58
  async function createTicket(guild, user, client) {
59
  // Check if user already has an open ticket
60
+ const existing = await stmts.getUserTicket(user.id, 'open');
61
  if (existing) return null;
62
 
63
  const staffRole = guild.roles.cache.find(r => r.name === '@@ Staff');
 
85
  });
86
 
87
  // Save to database
88
+ await stmts.createTicket(user.id, user.tag, ticketChannel.id);
89
 
90
  // Send welcome embed with action buttons
91
  const embed = createEmbed({
 
172
 
173
  if (!['ticket_close', 'ticket_delete', 'ticket_transcript'].includes(customId)) return false;
174
 
175
+ const ticket = await stmts.getTicket(channel.id);
176
  if (!ticket) {
177
  await interaction.reply({ content: '❌ This is not a ticket channel.', ephemeral: true });
178
  return true;
 
198
  }
199
 
200
  if (customId === 'ticket_close') {
201
+ await stmts.closeTicket('closed', channel.id);
202
 
203
  // Save transcript to ticket-logs
204
  const transcript = await generateTranscript(channel);
205
+ const logsRow = await stmts.getState('channel_📂・ticket-logs');
206
  if (logsRow) {
207
+ const logsChannel = await client.channels.fetch(logsRow).catch(() => null);
208
  if (logsChannel) {
209
  const embed = createEmbed({
210
  title: '📂 Ticket Closed',
 
233
  }
234
 
235
  if (customId === 'ticket_delete') {
236
+ await stmts.closeTicket('deleted', channel.id);
237
  await logTicket(client, { user: { tag: ticket.username, id: ticket.user_id }, action: 'deleted', channelName: channel.name });
238
  await interaction.reply({ content: '🗑️ Deleting ticket...' });
239
  setTimeout(() => channel.delete().catch(() => { }), 1000);
src/systems/verification.js CHANGED
@@ -7,7 +7,7 @@ const { logVerification } = require('./logger');
7
  * Send the verification embed to the ✅・verify channel.
8
  */
9
  async function sendVerificationEmbed(client) {
10
- const row = stmts.getState.get('channel_✅・verify');
11
  if (!row) throw new Error('Verify channel not found in bot state.');
12
 
13
  const channel = await client.channels.fetch(row.value);
@@ -32,8 +32,8 @@ async function sendVerificationEmbed(client) {
32
  await msg.react('✅');
33
 
34
  // Store the message ID so we can listen for reactions
35
- stmts.setState.run('verify_message_id', msg.id);
36
- stmts.setState.run('verify_channel_id', channel.id);
37
 
38
  return msg;
39
  }
@@ -49,7 +49,8 @@ async function handleVerifyReaction(reaction, user, client) {
49
  const isBotMessage = reaction.message.author?.id === client.user.id;
50
 
51
  // Also check stored message ID as fallback
52
- const verifyMsgId = stmts.getState.get('verify_message_id')?.value;
 
53
  const isStoredMsg = verifyMsgId && reaction.message.id === verifyMsgId;
54
 
55
  if (!isVerifyChannel && !isStoredMsg) return false;
@@ -76,7 +77,8 @@ async function handleVerifyReactionRemove(reaction, user, client) {
76
  const isVerifyChannel = channel.name === '✅・verify';
77
  const isBotMessage = reaction.message.author?.id === client.user.id;
78
 
79
- const verifyMsgId = stmts.getState.get('verify_message_id')?.value;
 
80
  const isStoredMsg = verifyMsgId && reaction.message.id === verifyMsgId;
81
 
82
  if (!isVerifyChannel && !isStoredMsg) return false;
 
7
  * Send the verification embed to the ✅・verify channel.
8
  */
9
  async function sendVerificationEmbed(client) {
10
+ const row = await stmts.getState('channel_✅・verify');
11
  if (!row) throw new Error('Verify channel not found in bot state.');
12
 
13
  const channel = await client.channels.fetch(row.value);
 
32
  await msg.react('✅');
33
 
34
  // Store the message ID so we can listen for reactions
35
+ await stmts.setState('verify_message_id', msg.id);
36
+ await stmts.setState('verify_channel_id', channel.id);
37
 
38
  return msg;
39
  }
 
49
  const isBotMessage = reaction.message.author?.id === client.user.id;
50
 
51
  // Also check stored message ID as fallback
52
+ const stateVal = await stmts.getState('verify_message_id');
53
+ const verifyMsgId = stateVal;
54
  const isStoredMsg = verifyMsgId && reaction.message.id === verifyMsgId;
55
 
56
  if (!isVerifyChannel && !isStoredMsg) return false;
 
77
  const isVerifyChannel = channel.name === '✅・verify';
78
  const isBotMessage = reaction.message.author?.id === client.user.id;
79
 
80
+ const stateVal = await stmts.getState('verify_message_id');
81
+ const verifyMsgId = stateVal;
82
  const isStoredMsg = verifyMsgId && reaction.message.id === verifyMsgId;
83
 
84
  if (!isVerifyChannel && !isStoredMsg) return false;