MoneyPack commited on
Commit
efee818
Β·
verified Β·
1 Parent(s): e39c166

MoneyPackCleaner ELITE v3 - Maximum animations, alive UI, particles, glow, waves

Browse files
Files changed (1) hide show
  1. moneypack_elite.py +1196 -0
moneypack_elite.py ADDED
@@ -0,0 +1,1196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ MoneyPackCleaner ELITE v3.0
5
+ The most animated, alive, eye-catching cleaner UI ever built.
6
+ Every element breathes, pulses, glows, and pops.
7
+ Created by MoneyPack
8
+ """
9
+
10
+ APP_VERSION = "3.0.0"
11
+
12
+ import os, sys, time, math, shutil, hashlib, platform, json, random, subprocess, urllib.request
13
+ from pathlib import Path
14
+ from datetime import datetime
15
+ from collections import defaultdict
16
+ from typing import List
17
+
18
+ from PyQt6.QtWidgets import (
19
+ QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
20
+ QLabel, QPushButton, QStackedWidget, QTreeWidget, QTreeWidgetItem,
21
+ QTableWidget, QTableWidgetItem, QHeaderView, QAbstractItemView,
22
+ QTextEdit, QLineEdit, QFileDialog, QMessageBox, QFrame,
23
+ QSpinBox, QSystemTrayIcon, QMenu, QCheckBox, QGraphicsDropShadowEffect,
24
+ QProgressBar, QGroupBox
25
+ )
26
+ from PyQt6.QtCore import (
27
+ Qt, QThread, pyqtSignal, QTimer, QSize, QPropertyAnimation,
28
+ QPoint, QRect, QEasingCurve, QPointF, pyqtProperty, QRectF,
29
+ QParallelAnimationGroup, QSequentialAnimationGroup
30
+ )
31
+ from PyQt6.QtGui import (
32
+ QFont, QColor, QAction, QIcon, QPainter, QPen, QBrush,
33
+ QLinearGradient, QRadialGradient, QPixmap, QPainterPath,
34
+ QConicalGradient, QMouseEvent, QFontMetrics
35
+ )
36
+
37
+
38
+ # ═══════════════════════════════════════════════════════════════
39
+ # PALETTE
40
+ # ═══════════════════════════════════════════════════════════════
41
+
42
+ class C:
43
+ BG = "#06060e"
44
+ BG2 = "#0c0c18"
45
+ PANEL = "#101025"
46
+ CARD = "#161635"
47
+ HOVER = "#1e1e45"
48
+ GOLD = "#d4af37"
49
+ GOLD2 = "#ffd700"
50
+ PINK = "#ff1a5c"
51
+ PINK2 = "#ff4d88"
52
+ CYAN = "#00e5ff"
53
+ CYAN2 = "#00ffff"
54
+ PURPLE = "#a855f7"
55
+ BLUE = "#3b82f6"
56
+ WHITE = "#f8f8ff"
57
+ TEXT = "#c4c4e0"
58
+ DIM = "#5c5c80"
59
+ MUTED = "#2e2e50"
60
+ GREEN = "#00e676"
61
+ YELLOW = "#ffc107"
62
+ RED = "#ff1744"
63
+ GLASS = "#ffffff06"
64
+ BORDER = "#ffffff10"
65
+
66
+
67
+ def format_size(b):
68
+ if b <= 0: return "0 B"
69
+ for u in ['B','KB','MB','GB','TB']:
70
+ if b < 1024: return f"{b:.1f} {u}"
71
+ b /= 1024
72
+ return f"{b:.1f} PB"
73
+
74
+
75
+ # ═══════════════════════════════════════════════════════════════
76
+ # ENGINE (condensed)
77
+ # ═══════════════════════════════════════════════════════════════
78
+
79
+ class E:
80
+ SKIP = {'$Recycle.Bin','System Volume Information','Windows','WinSxS','node_modules','.git','__pycache__','venv','.venv','Recovery','proc','sys','dev'}
81
+ CATS = {'Video':{'.mp4','.avi','.mkv','.mov','.wmv','.webm'},'Audio':{'.mp3','.wav','.flac','.aac','.ogg'},'Image':{'.jpg','.jpeg','.png','.gif','.bmp','.webp','.psd'},'Archive':{'.zip','.rar','.7z','.tar','.gz','.iso'},'Document':{'.pdf','.doc','.docx','.xls','.xlsx','.ppt','.pptx','.txt'},'Installer':{'.exe','.msi','.dmg','.deb'}}
82
+
83
+ @classmethod
84
+ def cat(cls, ext):
85
+ for c, es in cls.CATS.items():
86
+ if ext.lower() in es: return c
87
+ return "Other"
88
+
89
+ @classmethod
90
+ def disks(cls):
91
+ ds = []
92
+ if sys.platform == 'win32':
93
+ try:
94
+ import ctypes
95
+ bm = ctypes.windll.kernel32.GetLogicalDrives()
96
+ for l in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
97
+ if bm & 1:
98
+ try:
99
+ u = shutil.disk_usage(f"{l}:\\")
100
+ if u.total > 0: ds.append({'d':f"{l}:",'t':u.total,'u':u.used,'f':u.free,'p':round(u.used/u.total*100,1)})
101
+ except: pass
102
+ bm >>= 1
103
+ except: pass
104
+ else:
105
+ seen = set()
106
+ for m in ['/',str(Path.home())]:
107
+ try:
108
+ u = shutil.disk_usage(m)
109
+ if u.total not in seen:
110
+ seen.add(u.total)
111
+ ds.append({'d':m,'t':u.total,'u':u.used,'f':u.free,'p':round(u.used/u.total*100,1)})
112
+ except: pass
113
+ return ds
114
+
115
+ @classmethod
116
+ def large(cls, root, thresh=50*1024*1024, mx=300):
117
+ r = []
118
+ for dp,dns,fns in os.walk(root):
119
+ dns[:] = [d for d in dns if d not in cls.SKIP and not d.startswith('.')]
120
+ for f in fns:
121
+ try:
122
+ fp = os.path.join(dp,f)
123
+ sz = os.path.getsize(fp)
124
+ if sz >= thresh:
125
+ ext = os.path.splitext(f)[1].lower()
126
+ r.append({'p':fp,'s':sz,'n':f,'c':cls.cat(ext)})
127
+ if len(r) >= mx: return sorted(r, key=lambda x:x['s'], reverse=True)
128
+ except: pass
129
+ return sorted(r, key=lambda x:x['s'], reverse=True)
130
+
131
+ @classmethod
132
+ def temp_size(cls):
133
+ paths = [os.environ.get('TEMP','')] if sys.platform=='win32' else ['/tmp',str(Path.home()/'.cache')]
134
+ t, c = 0, 0
135
+ for d in paths:
136
+ if not d or not os.path.exists(d): continue
137
+ try:
138
+ for rr,_,fs in os.walk(d):
139
+ for f in fs:
140
+ try: t += os.path.getsize(os.path.join(rr,f)); c += 1
141
+ except: pass
142
+ except: pass
143
+ return t, c
144
+
145
+ @classmethod
146
+ def clean(cls):
147
+ r = {'del':0,'freed':0}
148
+ paths = [os.environ.get('TEMP','')] if sys.platform=='win32' else ['/tmp',str(Path.home()/'.cache')]
149
+ for d in paths:
150
+ if not d or not os.path.exists(d): continue
151
+ try:
152
+ for rr,_,fs in os.walk(d):
153
+ for f in fs:
154
+ fp = os.path.join(rr,f)
155
+ try:
156
+ sz = os.path.getsize(fp); os.remove(fp)
157
+ r['del'] += 1; r['freed'] += sz
158
+ except: pass
159
+ except: pass
160
+ return r
161
+
162
+ @classmethod
163
+ def delete(cls, paths):
164
+ r = {'del':0,'freed':0}
165
+ for p in paths:
166
+ try:
167
+ sz = os.path.getsize(p) if os.path.isfile(p) else 0
168
+ if os.path.isfile(p): os.remove(p)
169
+ elif os.path.isdir(p): shutil.rmtree(p)
170
+ r['del'] += 1; r['freed'] += sz
171
+ except: pass
172
+ return r
173
+
174
+
175
+ class Worker(QThread):
176
+ done = pyqtSignal(object)
177
+ def __init__(self, fn, *a, **kw):
178
+ super().__init__()
179
+ self.fn, self.a, self.kw = fn, a, kw
180
+ def run(self):
181
+ self.done.emit(self.fn(*self.a, **self.kw))
182
+
183
+
184
+ # ═══════════════════════════════════════════════════════════════
185
+ # ANIMATED WIDGETS
186
+ # ═══════════════════════════════════════════════════════════════
187
+
188
+ class GlowingBorder(QWidget):
189
+ """Widget with animated glowing border that cycles colors."""
190
+ def __init__(self, child_widget, colors=None):
191
+ super().__init__()
192
+ self._colors = colors or [C.PINK, C.CYAN, C.PURPLE, C.GOLD]
193
+ self._phase = 0.0
194
+ self._timer = QTimer(self)
195
+ self._timer.timeout.connect(self._tick)
196
+ self._timer.start(30)
197
+
198
+ layout = QVBoxLayout(self)
199
+ layout.setContentsMargins(3, 3, 3, 3)
200
+ layout.addWidget(child_widget)
201
+
202
+ def _tick(self):
203
+ self._phase += 0.02
204
+ if self._phase > len(self._colors): self._phase = 0
205
+ self.update()
206
+
207
+ def paintEvent(self, e):
208
+ p = QPainter(self)
209
+ p.setRenderHint(QPainter.RenderHint.Antialiasing)
210
+
211
+ # Animated gradient border
212
+ grad = QConicalGradient(self.width()/2, self.height()/2, self._phase * 60)
213
+ for i, c in enumerate(self._colors):
214
+ grad.setColorAt(i / len(self._colors), QColor(c))
215
+ grad.setColorAt(1.0, QColor(self._colors[0]))
216
+
217
+ pen = QPen(QBrush(grad), 2)
218
+ p.setPen(pen)
219
+ p.setBrush(Qt.BrushStyle.NoBrush)
220
+ p.drawRoundedRect(1, 1, self.width()-2, self.height()-2, 14, 14)
221
+ p.end()
222
+
223
+
224
+ class PulsingDot(QWidget):
225
+ """Animated pulsing status dot."""
226
+ def __init__(self, color=C.GREEN, size=12):
227
+ super().__init__()
228
+ self.setFixedSize(size*3, size*3)
229
+ self._color = QColor(color)
230
+ self._size = size
231
+ self._pulse = 0.0
232
+ self._dir = 1
233
+ t = QTimer(self)
234
+ t.timeout.connect(self._tick)
235
+ t.start(30)
236
+
237
+ def _tick(self):
238
+ self._pulse += 0.04 * self._dir
239
+ if self._pulse >= 1: self._dir = -1
240
+ elif self._pulse <= 0: self._dir = 1
241
+ self.update()
242
+
243
+ def paintEvent(self, e):
244
+ p = QPainter(self)
245
+ p.setRenderHint(QPainter.RenderHint.Antialiasing)
246
+ cx, cy = self.width()/2, self.height()/2
247
+
248
+ # Outer pulse
249
+ c = QColor(self._color)
250
+ c.setAlpha(int(80 * (1 - self._pulse)))
251
+ p.setBrush(c)
252
+ p.setPen(Qt.PenStyle.NoPen)
253
+ pr = self._size/2 + self._pulse * self._size * 0.8
254
+ p.drawEllipse(QPointF(cx, cy), pr, pr)
255
+
256
+ # Core
257
+ p.setBrush(self._color)
258
+ p.drawEllipse(QPointF(cx, cy), self._size/2, self._size/2)
259
+ p.end()
260
+
261
+
262
+ class AnimatedRing(QWidget):
263
+ """Big animated ring with glow, gradient, and smooth animation."""
264
+ def __init__(self, size=200):
265
+ super().__init__()
266
+ self.setFixedSize(size, size)
267
+ self._size = size
268
+ self._value = 0.0
269
+ self._target = 0.0
270
+ self._glow = 0.0
271
+ self._glow_dir = 1
272
+ self._rotation = 0.0
273
+
274
+ t = QTimer(self)
275
+ t.timeout.connect(self._tick)
276
+ t.start(16)
277
+
278
+ def set_value(self, v):
279
+ self._target = min(max(v, 0), 100)
280
+
281
+ def _tick(self):
282
+ # Smooth value
283
+ self._value += (self._target - self._value) * 0.06
284
+ # Glow pulse
285
+ self._glow += 0.03 * self._glow_dir
286
+ if self._glow >= 1: self._glow_dir = -1
287
+ elif self._glow <= 0: self._glow_dir = 1
288
+ # Slow rotation for shimmer
289
+ self._rotation += 0.3
290
+ self.update()
291
+
292
+ def paintEvent(self, e):
293
+ p = QPainter(self)
294
+ p.setRenderHint(QPainter.RenderHint.Antialiasing)
295
+ s = self._size
296
+ cx, cy = s/2, s/2
297
+ thick = 14
298
+ r = (s - thick*2 - 10) / 2
299
+ rect = QRectF(cx-r, cy-r, r*2, r*2)
300
+
301
+ # Background ring with subtle glow
302
+ bg_color = QColor(C.MUTED)
303
+ bg_color.setAlpha(60)
304
+ p.setPen(QPen(bg_color, thick, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap))
305
+ p.drawArc(rect, 0, 360*16)
306
+
307
+ # Glow behind arc
308
+ if self._value > 0:
309
+ glow_color = QColor(C.PINK)
310
+ glow_color.setAlpha(int(30 + 25 * self._glow))
311
+ p.setPen(QPen(glow_color, thick + 12, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap))
312
+ span = int(self._value / 100 * 360 * 16)
313
+ p.drawArc(rect, 90*16, -span)
314
+
315
+ # Main arc - gradient
316
+ if self._value > 0:
317
+ grad = QConicalGradient(cx, cy, 90 + self._rotation)
318
+ grad.setColorAt(0, QColor(C.PINK))
319
+ grad.setColorAt(0.3, QColor(C.PURPLE))
320
+ grad.setColorAt(0.6, QColor(C.CYAN))
321
+ grad.setColorAt(1, QColor(C.PINK))
322
+ p.setPen(QPen(QBrush(grad), thick, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap))
323
+ span = int(self._value / 100 * 360 * 16)
324
+ p.drawArc(rect, 90*16, -span)
325
+
326
+ # Center text
327
+ p.setPen(QColor(C.WHITE))
328
+ p.setFont(QFont("Segoe UI", int(s*0.14), QFont.Weight.Bold))
329
+ p.drawText(rect, Qt.AlignmentFlag.AlignCenter, f"{self._value:.0f}%")
330
+
331
+ # Sub text
332
+ p.setPen(QColor(C.DIM))
333
+ p.setFont(QFont("Segoe UI", int(s*0.05)))
334
+ sub_rect = QRectF(0, cy + s*0.12, s, s*0.1)
335
+ p.drawText(sub_rect, Qt.AlignmentFlag.AlignCenter, "DISK USED")
336
+
337
+ p.end()
338
+
339
+
340
+ class WaveProgress(QWidget):
341
+ """Animated wave progress bar with liquid effect."""
342
+ def __init__(self, height=30):
343
+ super().__init__()
344
+ self.setFixedHeight(height)
345
+ self._value = 0.0
346
+ self._target = 0.0
347
+ self._wave_phase = 0.0
348
+ self._color1 = C.PINK
349
+ self._color2 = C.PURPLE
350
+
351
+ t = QTimer(self)
352
+ t.timeout.connect(self._tick)
353
+ t.start(30)
354
+
355
+ def set_value(self, v, c1=None, c2=None):
356
+ self._target = min(max(v, 0), 100)
357
+ if c1: self._color1 = c1
358
+ if c2: self._color2 = c2
359
+
360
+ def _tick(self):
361
+ self._value += (self._target - self._value) * 0.08
362
+ self._wave_phase += 0.1
363
+ self.update()
364
+
365
+ def paintEvent(self, e):
366
+ p = QPainter(self)
367
+ p.setRenderHint(QPainter.RenderHint.Antialiasing)
368
+ w, h = self.width(), self.height()
369
+
370
+ # Background
371
+ p.setBrush(QColor(C.MUTED))
372
+ p.setPen(Qt.PenStyle.NoPen)
373
+ p.drawRoundedRect(0, 0, w, h, h/2, h/2)
374
+
375
+ # Fill with wave
376
+ fill_w = int(w * self._value / 100)
377
+ if fill_w > 0:
378
+ # Gradient fill
379
+ grad = QLinearGradient(0, 0, fill_w, 0)
380
+ grad.setColorAt(0, QColor(self._color1))
381
+ grad.setColorAt(1, QColor(self._color2))
382
+
383
+ # Create wave path
384
+ path = QPainterPath()
385
+ path.moveTo(0, h)
386
+ path.lineTo(0, 0)
387
+
388
+ # Wave on top edge
389
+ for x in range(fill_w):
390
+ wave_y = math.sin(x * 0.05 + self._wave_phase) * 2
391
+ path.lineTo(x, h/2 + wave_y - h*0.3)
392
+
393
+ path.lineTo(fill_w, h)
394
+ path.closeSubpath()
395
+
396
+ # Clip to rounded rect
397
+ clip_path = QPainterPath()
398
+ clip_path.addRoundedRect(QRectF(0, 0, w, h), h/2, h/2)
399
+ p.setClipPath(clip_path)
400
+
401
+ p.setBrush(QBrush(grad))
402
+ p.drawPath(path)
403
+
404
+ # Second wave layer (lighter)
405
+ path2 = QPainterPath()
406
+ path2.moveTo(0, h)
407
+ path2.lineTo(0, h*0.6)
408
+ for x in range(fill_w):
409
+ wave_y = math.sin(x * 0.04 + self._wave_phase * 1.3 + 1) * 3
410
+ path2.lineTo(x, h/2 + wave_y)
411
+ path2.lineTo(fill_w, h)
412
+ path2.closeSubpath()
413
+
414
+ c = QColor(self._color2)
415
+ c.setAlpha(100)
416
+ p.setBrush(c)
417
+ p.drawPath(path2)
418
+
419
+ p.setClipping(False)
420
+
421
+ # Text
422
+ p.setPen(QColor(C.WHITE if self._value > 30 else C.TEXT))
423
+ p.setFont(QFont("Segoe UI", 9, QFont.Weight.Bold))
424
+ p.drawText(QRect(0, 0, w, h), Qt.AlignmentFlag.AlignCenter, f"{self._value:.0f}%")
425
+ p.end()
426
+
427
+
428
+ class NeonButton(QPushButton):
429
+ """Button with neon glow animation on hover."""
430
+ def __init__(self, text, color=C.PINK, parent=None):
431
+ super().__init__(text, parent)
432
+ self._color = color
433
+ self._glow = 0.0
434
+ self._hover = False
435
+ self.setCursor(Qt.CursorShape.PointingHandCursor)
436
+ self.setFixedHeight(42)
437
+ self.setFont(QFont("Segoe UI", 11, QFont.Weight.Bold))
438
+
439
+ t = QTimer(self)
440
+ t.timeout.connect(self._tick)
441
+ t.start(20)
442
+
443
+ def _tick(self):
444
+ target = 1.0 if self._hover else 0.0
445
+ self._glow += (target - self._glow) * 0.15
446
+ self.update()
447
+
448
+ def enterEvent(self, e): self._hover = True
449
+ def leaveEvent(self, e): self._hover = False
450
+
451
+ def paintEvent(self, e):
452
+ p = QPainter(self)
453
+ p.setRenderHint(QPainter.RenderHint.Antialiasing)
454
+ w, h = self.width(), self.height()
455
+
456
+ # Glow shadow
457
+ if self._glow > 0.01:
458
+ glow_c = QColor(self._color)
459
+ glow_c.setAlpha(int(60 * self._glow))
460
+ p.setBrush(glow_c)
461
+ p.setPen(Qt.PenStyle.NoPen)
462
+ p.drawRoundedRect(QRectF(-4, -2, w+8, h+6), 14, 14)
463
+
464
+ # Background
465
+ grad = QLinearGradient(0, 0, w, 0)
466
+ c1 = QColor(self._color)
467
+ c2 = QColor(self._color).lighter(130)
468
+ if self._glow > 0.5:
469
+ c1 = c1.lighter(int(110 + 20*self._glow))
470
+ grad.setColorAt(0, c1)
471
+ grad.setColorAt(1, c2)
472
+
473
+ p.setBrush(QBrush(grad))
474
+ p.setPen(Qt.PenStyle.NoPen)
475
+ p.drawRoundedRect(QRectF(0, 0, w, h), 10, 10)
476
+
477
+ # Shimmer line
478
+ if self._glow > 0.3:
479
+ shimmer_x = (self._glow * 2 % 1) * w
480
+ shimmer_grad = QLinearGradient(shimmer_x - 30, 0, shimmer_x + 30, 0)
481
+ shimmer_grad.setColorAt(0, QColor(255, 255, 255, 0))
482
+ shimmer_grad.setColorAt(0.5, QColor(255, 255, 255, int(40 * self._glow)))
483
+ shimmer_grad.setColorAt(1, QColor(255, 255, 255, 0))
484
+ p.setBrush(QBrush(shimmer_grad))
485
+ clip = QPainterPath()
486
+ clip.addRoundedRect(QRectF(0, 0, w, h), 10, 10)
487
+ p.setClipPath(clip)
488
+ p.drawRect(0, 0, w, h)
489
+ p.setClipping(False)
490
+
491
+ # Text
492
+ p.setPen(QColor(C.WHITE))
493
+ p.setFont(self.font())
494
+ p.drawText(QRect(0, 0, w, h), Qt.AlignmentFlag.AlignCenter, self.text())
495
+ p.end()
496
+
497
+
498
+ class FloatingParticles(QWidget):
499
+ """Background particles that float and connect with lines."""
500
+ def __init__(self, parent=None):
501
+ super().__init__(parent)
502
+ self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents)
503
+ self._particles = []
504
+ for _ in range(40):
505
+ self._particles.append({
506
+ 'x': random.random(), 'y': random.random(),
507
+ 'vx': (random.random()-0.5)*0.0008, 'vy': (random.random()-0.5)*0.0008,
508
+ 'size': random.uniform(1.5, 4),
509
+ 'alpha': random.randint(15, 50),
510
+ 'color': random.choice([C.PINK, C.CYAN, C.GOLD, C.PURPLE]),
511
+ })
512
+ t = QTimer(self)
513
+ t.timeout.connect(self._tick)
514
+ t.start(33)
515
+
516
+ def _tick(self):
517
+ for p in self._particles:
518
+ p['x'] += p['vx']; p['y'] += p['vy']
519
+ if p['x'] < 0 or p['x'] > 1: p['vx'] *= -1
520
+ if p['y'] < 0 or p['y'] > 1: p['vy'] *= -1
521
+ self.update()
522
+
523
+ def paintEvent(self, e):
524
+ painter = QPainter(self)
525
+ painter.setRenderHint(QPainter.RenderHint.Antialiasing)
526
+ w, h = self.width(), self.height()
527
+
528
+ # Draw connection lines between nearby particles
529
+ for i, p1 in enumerate(self._particles):
530
+ for p2 in self._particles[i+1:]:
531
+ dx = (p1['x'] - p2['x']) * w
532
+ dy = (p1['y'] - p2['y']) * h
533
+ dist = math.sqrt(dx*dx + dy*dy)
534
+ if dist < 120:
535
+ alpha = int(20 * (1 - dist/120))
536
+ c = QColor(C.CYAN)
537
+ c.setAlpha(alpha)
538
+ painter.setPen(QPen(c, 0.5))
539
+ painter.drawLine(QPointF(p1['x']*w, p1['y']*h), QPointF(p2['x']*w, p2['y']*h))
540
+
541
+ # Draw particles
542
+ for p in self._particles:
543
+ c = QColor(p['color'])
544
+ c.setAlpha(p['alpha'])
545
+ painter.setBrush(c)
546
+ painter.setPen(Qt.PenStyle.NoPen)
547
+ painter.drawEllipse(QPointF(p['x']*w, p['y']*h), p['size'], p['size'])
548
+
549
+ painter.end()
550
+
551
+
552
+ class AnimatedCard(QFrame):
553
+ """Card with hover lift animation and border glow."""
554
+ def __init__(self):
555
+ super().__init__()
556
+ self._hover = False
557
+ self._lift = 0.0
558
+ self.setStyleSheet(f"background-color: {C.CARD}; border: 1px solid {C.BORDER}; border-radius: 14px;")
559
+
560
+ shadow = QGraphicsDropShadowEffect()
561
+ shadow.setBlurRadius(15)
562
+ shadow.setColor(QColor(0, 0, 0, 60))
563
+ shadow.setOffset(0, 4)
564
+ self.setGraphicsEffect(shadow)
565
+
566
+ t = QTimer(self)
567
+ t.timeout.connect(self._tick)
568
+ t.start(20)
569
+
570
+ def _tick(self):
571
+ target = 1.0 if self._hover else 0.0
572
+ old = self._lift
573
+ self._lift += (target - self._lift) * 0.15
574
+ if abs(old - self._lift) > 0.01:
575
+ effect = self.graphicsEffect()
576
+ if effect:
577
+ effect.setBlurRadius(15 + self._lift * 20)
578
+ effect.setOffset(0, 4 - self._lift * 3)
579
+ effect.setColor(QColor(int(255*0.1*self._lift), 0, int(100*self._lift), int(60 + 40*self._lift)))
580
+
581
+ def enterEvent(self, e): self._hover = True
582
+ def leaveEvent(self, e): self._hover = False
583
+
584
+
585
+ class GlowText(QLabel):
586
+ """Label with animated neon text glow."""
587
+ def __init__(self, text, color=C.GOLD, size=24):
588
+ super().__init__(text)
589
+ self._color = color
590
+ self._glow = 0.0
591
+ self._dir = 1
592
+ self.setFont(QFont("Segoe UI", size, QFont.Weight.Bold))
593
+ self.setStyleSheet(f"color: {color}; background: transparent; border: none;")
594
+
595
+ t = QTimer(self)
596
+ t.timeout.connect(self._tick)
597
+ t.start(40)
598
+
599
+ def _tick(self):
600
+ self._glow += 0.03 * self._dir
601
+ if self._glow >= 1: self._dir = -1
602
+ elif self._glow <= 0: self._dir = 1
603
+
604
+ # Update text shadow via stylesheet
605
+ blur = int(8 + 12 * self._glow)
606
+ self.setStyleSheet(f"color: {self._color}; background: transparent; border: none; text-shadow: 0 0 {blur}px {self._color};")
607
+
608
+
609
+ class LiveStatCard(AnimatedCard):
610
+ """Stat card with animated counting value and glow icon."""
611
+ def __init__(self, icon, label, color):
612
+ super().__init__()
613
+ self.setFixedHeight(90)
614
+ self.setMinimumWidth(170)
615
+ self._color = color
616
+ self._display_value = 0.0
617
+ self._target_value = 0.0
618
+ self._text_value = "---"
619
+
620
+ layout = QHBoxLayout(self)
621
+ layout.setContentsMargins(16, 12, 16, 12)
622
+
623
+ # Pulsing icon
624
+ icon_w = QWidget()
625
+ icon_w.setFixedSize(44, 44)
626
+ icon_w.setStyleSheet(f"background: qradialgradient(cx:0.5,cy:0.5,radius:0.5, stop:0 {color}40, stop:1 {color}10); border-radius: 22px; border: none;")
627
+ il = QVBoxLayout(icon_w)
628
+ il.setContentsMargins(0, 0, 0, 0)
629
+ ic = QLabel(icon)
630
+ ic.setAlignment(Qt.AlignmentFlag.AlignCenter)
631
+ ic.setStyleSheet(f"font-size: 20px; border: none; background: transparent;")
632
+ il.addWidget(ic)
633
+ layout.addWidget(icon_w)
634
+
635
+ # Text
636
+ tl = QVBoxLayout()
637
+ tl.setSpacing(2)
638
+ self._val_label = QLabel("---")
639
+ self._val_label.setFont(QFont("Segoe UI", 18, QFont.Weight.Bold))
640
+ self._val_label.setStyleSheet(f"color: {color}; border: none; background: transparent;")
641
+ tl.addWidget(self._val_label)
642
+
643
+ ll = QLabel(label)
644
+ ll.setStyleSheet(f"color: {C.DIM}; font-size: 10px; border: none; background: transparent;")
645
+ tl.addWidget(ll)
646
+ layout.addLayout(tl)
647
+ layout.addStretch()
648
+
649
+ def set_value(self, text):
650
+ self._val_label.setText(text)
651
+
652
+
653
+ # ═══════════════════════════════════════════════════════════════
654
+ # TITLE BAR
655
+ # ═══════════════════════════════════════════════════════════════
656
+
657
+ class TitleBar(QWidget):
658
+ def __init__(self, win):
659
+ super().__init__()
660
+ self._win = win
661
+ self._drag = None
662
+ self.setFixedHeight(44)
663
+ self.setStyleSheet(f"background: {C.BG}; border-bottom: 1px solid {C.MUTED};")
664
+
665
+ layout = QHBoxLayout(self)
666
+ layout.setContentsMargins(14, 0, 8, 0)
667
+
668
+ # Animated dot
669
+ self._dot = PulsingDot(C.PINK, 6)
670
+ layout.addWidget(self._dot)
671
+
672
+ title = QLabel("MoneyPackCleaner")
673
+ title.setFont(QFont("Segoe UI", 11, QFont.Weight.Bold))
674
+ title.setStyleSheet(f"color: {C.GOLD}; border: none; background: transparent;")
675
+ layout.addWidget(title)
676
+
677
+ tag = QLabel("ELITE")
678
+ tag.setStyleSheet(f"color: {C.PINK}; font-size: 8px; font-weight: bold; letter-spacing: 2px; border: none; background: transparent; padding-top: 2px;")
679
+ layout.addWidget(tag)
680
+
681
+ layout.addStretch()
682
+
683
+ for txt, color, act in [("\u2014", C.DIM, win.showMinimized), ("\u25a1", C.DIM, self._max), ("\u2715", C.RED, win.close)]:
684
+ b = QPushButton(txt)
685
+ b.setFixedSize(36, 30)
686
+ b.setStyleSheet(f"QPushButton {{ background: transparent; color: {color}; border: none; font-size: 13px; border-radius: 5px; }} QPushButton:hover {{ background: {C.HOVER}; color: {C.WHITE}; }}")
687
+ b.clicked.connect(act)
688
+ layout.addWidget(b)
689
+
690
+ def _max(self):
691
+ if self._win.isMaximized(): self._win.showNormal()
692
+ else: self._win.showMaximized()
693
+
694
+ def mousePressEvent(self, e):
695
+ if e.button() == Qt.MouseButton.LeftButton:
696
+ self._drag = e.globalPosition().toPoint() - self._win.frameGeometry().topLeft()
697
+ def mouseMoveEvent(self, e):
698
+ if self._drag and e.buttons() == Qt.MouseButton.LeftButton:
699
+ self._win.move(e.globalPosition().toPoint() - self._drag)
700
+ def mouseReleaseEvent(self, e): self._drag = None
701
+ def mouseDoubleClickEvent(self, e): self._max()
702
+
703
+
704
+ # ═══════════════════════════════════════════════════════════════
705
+ # SIDEBAR
706
+ # ═══════════════════════════════════════════════════════════════
707
+
708
+ class NavButton(QPushButton):
709
+ def __init__(self, icon, label, idx):
710
+ super().__init__()
711
+ self.idx = idx
712
+ self._icon = icon
713
+ self._label = label
714
+ self._active = False
715
+ self._hover = False
716
+ self._glow = 0.0
717
+ self.setFixedHeight(50)
718
+ self.setCursor(Qt.CursorShape.PointingHandCursor)
719
+ self.setStyleSheet("border: none; background: transparent;")
720
+ t = QTimer(self)
721
+ t.timeout.connect(self._tick)
722
+ t.start(20)
723
+
724
+ def set_active(self, a): self._active = a; self.update()
725
+ def enterEvent(self, e): self._hover = True
726
+ def leaveEvent(self, e): self._hover = False
727
+
728
+ def _tick(self):
729
+ target = 1.0 if (self._active or self._hover) else 0.0
730
+ self._glow += (target - self._glow) * 0.12
731
+ self.update()
732
+
733
+ def paintEvent(self, e):
734
+ p = QPainter(self)
735
+ p.setRenderHint(QPainter.RenderHint.Antialiasing)
736
+ w, h = self.width(), self.height()
737
+
738
+ # Background glow
739
+ if self._glow > 0.01:
740
+ c = QColor(C.PINK if self._active else C.CYAN)
741
+ c.setAlpha(int(25 * self._glow))
742
+ p.setBrush(c)
743
+ p.setPen(Qt.PenStyle.NoPen)
744
+ p.drawRoundedRect(QRectF(4, 2, w-8, h-4), 10, 10)
745
+
746
+ # Active bar
747
+ if self._active:
748
+ grad = QLinearGradient(0, 0, 0, h)
749
+ grad.setColorAt(0, QColor(C.PINK))
750
+ grad.setColorAt(1, QColor(C.PURPLE))
751
+ p.setBrush(QBrush(grad))
752
+ p.setPen(Qt.PenStyle.NoPen)
753
+ p.drawRoundedRect(QRectF(0, 8, 3, h-16), 2, 2)
754
+
755
+ # Icon
756
+ p.setPen(QColor(C.PINK if self._active else (C.TEXT if self._hover else C.DIM)))
757
+ p.setFont(QFont("Segoe UI Emoji", 16))
758
+ p.drawText(QRect(18, 0, 30, h), Qt.AlignmentFlag.AlignVCenter, self._icon)
759
+
760
+ # Label
761
+ p.setPen(QColor(C.WHITE if self._active else (C.TEXT if self._hover else C.DIM)))
762
+ p.setFont(QFont("Segoe UI", 11, QFont.Weight.Bold if self._active else QFont.Weight.Normal))
763
+ p.drawText(QRect(54, 0, w-60, h), Qt.AlignmentFlag.AlignVCenter, self._label)
764
+ p.end()
765
+
766
+
767
+ class Sidebar(QWidget):
768
+ changed = pyqtSignal(int)
769
+ def __init__(self):
770
+ super().__init__()
771
+ self.setFixedWidth(200)
772
+ self.setStyleSheet(f"background: {C.BG2}; border-right: 1px solid {C.MUTED};")
773
+ layout = QVBoxLayout(self)
774
+ layout.setContentsMargins(0, 12, 0, 12)
775
+ layout.setSpacing(0)
776
+
777
+ # Brand
778
+ brand = QHBoxLayout()
779
+ brand.setContentsMargins(16, 8, 0, 16)
780
+ dot = QLabel()
781
+ dot.setFixedSize(28, 28)
782
+ dot.setStyleSheet(f"background: qlineargradient(x1:0,y1:0,x2:1,y2:1, stop:0 {C.PINK}, stop:1 {C.GOLD}); border-radius: 14px; border: none;")
783
+ brand.addWidget(dot)
784
+ bl = QVBoxLayout()
785
+ bl.setSpacing(0)
786
+ bn = QLabel("MoneyPack")
787
+ bn.setFont(QFont("Segoe UI", 11, QFont.Weight.Bold))
788
+ bn.setStyleSheet(f"color: {C.GOLD}; border: none; background: transparent;")
789
+ bl.addWidget(bn)
790
+ bs = QLabel("ELITE")
791
+ bs.setStyleSheet(f"color: {C.PINK}; font-size: 8px; letter-spacing: 3px; border: none; background: transparent;")
792
+ bl.addWidget(bs)
793
+ brand.addLayout(bl)
794
+ brand.addStretch()
795
+ layout.addLayout(brand)
796
+
797
+ layout.addSpacing(10)
798
+
799
+ self._btns = []
800
+ for i, (icon, label) in enumerate([("🏠","Dashboard"),("πŸ“","Large Files"),("🧹","Clean"),("πŸ“Š","Log")]):
801
+ btn = NavButton(icon, label, i)
802
+ btn.clicked.connect(lambda _, idx=i: self._click(idx))
803
+ layout.addWidget(btn)
804
+ self._btns.append(btn)
805
+
806
+ layout.addStretch()
807
+ v = QLabel(f"v{APP_VERSION}")
808
+ v.setAlignment(Qt.AlignmentFlag.AlignCenter)
809
+ v.setStyleSheet(f"color: {C.MUTED}; font-size: 9px; border: none; background: transparent;")
810
+ layout.addWidget(v)
811
+
812
+ self._btns[0].set_active(True)
813
+
814
+ def _click(self, idx):
815
+ for b in self._btns: b.set_active(b.idx == idx)
816
+ self.changed.emit(idx)
817
+
818
+
819
+ # ═══════════════════════════════════════════════════════════════
820
+ # PAGES
821
+ # ═══════════════════════════════════════════════════════════════
822
+
823
+ class DashPage(QWidget):
824
+ log = pyqtSignal(str)
825
+ def __init__(self):
826
+ super().__init__()
827
+ self.setStyleSheet("background: transparent; border: none;")
828
+ layout = QVBoxLayout(self)
829
+ layout.setContentsMargins(28, 28, 28, 28)
830
+ layout.setSpacing(20)
831
+
832
+ # Header
833
+ h = QHBoxLayout()
834
+ self._title = GlowText("Dashboard", C.GOLD, 22)
835
+ h.addWidget(self._title)
836
+ h.addStretch()
837
+
838
+ qb = NeonButton(" Quick Clean ", C.PINK)
839
+ qb.clicked.connect(self._quick)
840
+ h.addWidget(qb)
841
+ layout.addLayout(h)
842
+
843
+ # Stats
844
+ stats = QHBoxLayout()
845
+ stats.setSpacing(14)
846
+ self.c_total = LiveStatCard("πŸ’Ύ","Total Storage", C.CYAN)
847
+ self.c_used = LiveStatCard("πŸ“Š","Used", C.PINK)
848
+ self.c_free = LiveStatCard("✨","Free", C.GREEN)
849
+ self.c_temp = LiveStatCard("πŸ—‘","Cleanable", C.YELLOW)
850
+ stats.addWidget(self.c_total)
851
+ stats.addWidget(self.c_used)
852
+ stats.addWidget(self.c_free)
853
+ stats.addWidget(self.c_temp)
854
+ layout.addLayout(stats)
855
+
856
+ # Ring + drives
857
+ mid = QHBoxLayout()
858
+ mid.setSpacing(20)
859
+
860
+ ring_card = AnimatedCard()
861
+ rl = QVBoxLayout(ring_card)
862
+ rl.setAlignment(Qt.AlignmentFlag.AlignCenter)
863
+ self.ring = AnimatedRing(200)
864
+ rl.addWidget(self.ring, alignment=Qt.AlignmentFlag.AlignCenter)
865
+ mid.addWidget(ring_card)
866
+
867
+ # Drive bars
868
+ drive_card = AnimatedCard()
869
+ dl = QVBoxLayout(drive_card)
870
+ dl.setContentsMargins(16, 16, 16, 16)
871
+ dl.setSpacing(12)
872
+ dtitle = QLabel("Drives")
873
+ dtitle.setStyleSheet(f"color: {C.DIM}; font-size: 11px; font-weight: bold; border: none; background: transparent;")
874
+ dl.addWidget(dtitle)
875
+ self._drive_bars = []
876
+ for _ in range(4):
877
+ row = QHBoxLayout()
878
+ lbl = QLabel("---")
879
+ lbl.setFixedWidth(50)
880
+ lbl.setStyleSheet(f"color: {C.TEXT}; border: none; background: transparent; font-size: 11px;")
881
+ row.addWidget(lbl)
882
+ bar = WaveProgress(24)
883
+ row.addWidget(bar)
884
+ dl.addLayout(row)
885
+ self._drive_bars.append((lbl, bar))
886
+ dl.addStretch()
887
+ mid.addWidget(drive_card)
888
+
889
+ layout.addLayout(mid)
890
+ layout.addStretch()
891
+ self.refresh()
892
+
893
+ def refresh(self):
894
+ disks = E.disks()
895
+ total = sum(d['t'] for d in disks)
896
+ used = sum(d['u'] for d in disks)
897
+ free = sum(d['f'] for d in disks)
898
+
899
+ self.c_total.set_value(format_size(total))
900
+ self.c_used.set_value(format_size(used))
901
+ self.c_free.set_value(format_size(free))
902
+
903
+ tsz, _ = E.temp_size()
904
+ self.c_temp.set_value(format_size(tsz))
905
+
906
+ pct = round(used/total*100, 1) if total else 0
907
+ self.ring.set_value(pct)
908
+
909
+ for i, d in enumerate(disks[:4]):
910
+ lbl, bar = self._drive_bars[i]
911
+ lbl.setText(d['d'])
912
+ c1 = C.GREEN if d['p'] < 60 else (C.YELLOW if d['p'] < 85 else C.RED)
913
+ c2 = C.CYAN if d['p'] < 60 else (C.GOLD if d['p'] < 85 else C.PINK)
914
+ bar.set_value(d['p'], c1, c2)
915
+
916
+ def _quick(self):
917
+ r = E.clean()
918
+ self.log.emit(f"Quick clean: {r['del']} files, {format_size(r['freed'])} freed")
919
+ self.refresh()
920
+
921
+
922
+ class FilesPage(QWidget):
923
+ log = pyqtSignal(str)
924
+ def __init__(self):
925
+ super().__init__()
926
+ self.setStyleSheet("background: transparent; border: none;")
927
+ layout = QVBoxLayout(self)
928
+ layout.setContentsMargins(28, 28, 28, 28)
929
+ layout.setSpacing(12)
930
+
931
+ layout.addWidget(GlowText("Large Files", C.GOLD, 20))
932
+
933
+ ctrl = QHBoxLayout()
934
+ self.path = QLineEdit(str(Path.home()))
935
+ self.path.setStyleSheet(f"background: {C.CARD}; border: 1px solid {C.MUTED}; border-radius: 10px; padding: 10px 14px; color: {C.TEXT}; font-size: 12px;")
936
+ ctrl.addWidget(self.path)
937
+
938
+ bb = NeonButton("Browse", C.PURPLE)
939
+ bb.setFixedWidth(100)
940
+ bb.clicked.connect(lambda: self.path.setText(QFileDialog.getExistingDirectory(self) or self.path.text()))
941
+ ctrl.addWidget(bb)
942
+
943
+ sb = NeonButton("Scan", C.CYAN)
944
+ sb.setFixedWidth(100)
945
+ sb.clicked.connect(self._scan)
946
+ ctrl.addWidget(sb)
947
+ layout.addLayout(ctrl)
948
+
949
+ self.tree = QTreeWidget()
950
+ self.tree.setHeaderLabels(["File","Size","Type","Path"])
951
+ self.tree.header().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
952
+ self.tree.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
953
+ self.tree.setStyleSheet(f"""
954
+ QTreeWidget {{ background: {C.PANEL}; border: 1px solid {C.MUTED}; border-radius: 10px; color: {C.TEXT}; alternate-background-color: {C.BG2}; }}
955
+ QTreeWidget::item {{ padding: 4px; }}
956
+ QTreeWidget::item:selected {{ background: {C.HOVER}; }}
957
+ QTreeWidget::item:hover {{ background: {C.CARD}; }}
958
+ QHeaderView::section {{ background: {C.BG2}; color: {C.DIM}; border: none; padding: 8px; font-weight: bold; font-size: 10px; }}
959
+ """)
960
+ self.tree.setAlternatingRowColors(True)
961
+ layout.addWidget(self.tree)
962
+
963
+ bottom = QHBoxLayout()
964
+ self.status = QLabel("Ready")
965
+ self.status.setStyleSheet(f"color: {C.DIM}; border: none; background: transparent;")
966
+ bottom.addWidget(self.status)
967
+ bottom.addStretch()
968
+ db = NeonButton("Delete Selected", C.RED)
969
+ db.setFixedWidth(160)
970
+ db.clicked.connect(self._del)
971
+ bottom.addWidget(db)
972
+ layout.addLayout(bottom)
973
+ self._w = None
974
+
975
+ def _scan(self):
976
+ self.tree.clear()
977
+ self.status.setText("Scanning...")
978
+ self._w = Worker(E.large, self.path.text())
979
+ self._w.done.connect(self._done)
980
+ self._w.start()
981
+
982
+ def _done(self, results):
983
+ colors = {'Video':C.RED,'Audio':C.YELLOW,'Image':C.GREEN,'Archive':C.PURPLE,'Installer':C.PINK,'Document':C.CYAN}
984
+ total = sum(r['s'] for r in results)
985
+ for r in results:
986
+ item = QTreeWidgetItem()
987
+ item.setText(0, r['n'])
988
+ item.setText(1, format_size(r['s']))
989
+ item.setText(2, r['c'])
990
+ item.setText(3, r['p'])
991
+ item.setData(0, Qt.ItemDataRole.UserRole, r['p'])
992
+ item.setForeground(2, QColor(colors.get(r['c'], C.DIM)))
993
+ self.tree.addTopLevelItem(item)
994
+ self.status.setText(f"{len(results)} files ({format_size(total)})")
995
+ self.log.emit(f"Found {len(results)} large files ({format_size(total)})")
996
+
997
+ def _del(self):
998
+ items = self.tree.selectedItems()
999
+ if not items: return
1000
+ paths = [i.data(0, Qt.ItemDataRole.UserRole) for i in items]
1001
+ if QMessageBox.question(self,"Delete",f"Delete {len(paths)} files?") == QMessageBox.StandardButton.Yes:
1002
+ r = E.delete(paths)
1003
+ self.log.emit(f"Deleted {r['del']}, freed {format_size(r['freed'])}")
1004
+ self._scan()
1005
+
1006
+
1007
+ class CleanPage(QWidget):
1008
+ log = pyqtSignal(str)
1009
+ def __init__(self):
1010
+ super().__init__()
1011
+ self.setStyleSheet("background: transparent; border: none;")
1012
+ layout = QVBoxLayout(self)
1013
+ layout.setContentsMargins(28, 28, 28, 28)
1014
+ layout.setSpacing(16)
1015
+
1016
+ layout.addWidget(GlowText("System Cleaner", C.GOLD, 20))
1017
+
1018
+ self.info = QLabel("Analyzing...")
1019
+ self.info.setStyleSheet(f"color: {C.TEXT}; border: none; background: transparent; font-size: 13px;")
1020
+ layout.addWidget(self.info)
1021
+
1022
+ card = AnimatedCard()
1023
+ cl = QVBoxLayout(card)
1024
+ cl.setContentsMargins(20, 20, 20, 20)
1025
+ cl.setSpacing(14)
1026
+ for text in ["Temporary Files","Recycle Bin / Trash","Browser Cache","Thumbnail Cache"]:
1027
+ cb = QCheckBox(text)
1028
+ cb.setChecked(True)
1029
+ cb.setStyleSheet(f"QCheckBox {{ color: {C.TEXT}; spacing: 10px; border: none; background: transparent; font-size: 12px; }} QCheckBox::indicator {{ width: 22px; height: 22px; border: 2px solid {C.MUTED}; border-radius: 6px; background: {C.PANEL}; }} QCheckBox::indicator:checked {{ background: qlineargradient(x1:0,y1:0,x2:1,y2:1, stop:0 {C.PINK}, stop:1 {C.PURPLE}); border-color: {C.PINK}; }}")
1030
+ cl.addWidget(cb)
1031
+ layout.addWidget(card)
1032
+
1033
+ # Big clean button
1034
+ clean_btn = NeonButton(" CLEAN NOW ", C.PINK)
1035
+ clean_btn.setFixedHeight(52)
1036
+ clean_btn.setFont(QFont("Segoe UI", 14, QFont.Weight.Bold))
1037
+ clean_btn.clicked.connect(self._clean)
1038
+ layout.addWidget(clean_btn)
1039
+
1040
+ self.result = QLabel("")
1041
+ self.result.setStyleSheet(f"color: {C.GREEN}; border: none; background: transparent; font-size: 14px; font-weight: bold;")
1042
+ layout.addWidget(self.result)
1043
+ layout.addStretch()
1044
+ QTimer.singleShot(500, self._analyze)
1045
+
1046
+ def _analyze(self):
1047
+ sz, cnt = E.temp_size()
1048
+ self.info.setText(f"Found {cnt:,} cleanable files ({format_size(sz)})")
1049
+
1050
+ def _clean(self):
1051
+ if QMessageBox.question(self,"Clean","Clean all selected?") == QMessageBox.StandardButton.Yes:
1052
+ r = E.clean()
1053
+ self.result.setText(f"Cleaned {r['del']:,} files | Freed {format_size(r['freed'])}")
1054
+ self.log.emit(f"Cleaned {r['del']} files, freed {format_size(r['freed'])}")
1055
+ QTimer.singleShot(1000, self._analyze)
1056
+
1057
+
1058
+ class LogPage(QWidget):
1059
+ def __init__(self):
1060
+ super().__init__()
1061
+ self.setStyleSheet("background: transparent; border: none;")
1062
+ layout = QVBoxLayout(self)
1063
+ layout.setContentsMargins(28, 28, 28, 28)
1064
+ layout.addWidget(GlowText("Activity Log", C.GOLD, 20))
1065
+ self.log = QTextEdit()
1066
+ self.log.setReadOnly(True)
1067
+ self.log.setFont(QFont("JetBrains Mono", 10))
1068
+ self.log.setStyleSheet(f"background: {C.PANEL}; border: 1px solid {C.MUTED}; border-radius: 10px; color: {C.TEXT}; padding: 10px;")
1069
+ layout.addWidget(self.log)
1070
+ self.add("MoneyPackCleaner ELITE started")
1071
+
1072
+ def add(self, msg):
1073
+ ts = datetime.now().strftime("%H:%M:%S")
1074
+ self.log.append(f"<span style='color:{C.DIM}'>[{ts}]</span> <span style='color:{C.CYAN}'>&gt;</span> <span style='color:{C.TEXT}'>{msg}</span>")
1075
+
1076
+
1077
+ # ═══════════════════════════════════════════════════════════════
1078
+ # MAIN WINDOW
1079
+ # ═══════════════════════════════════════════════════════════════
1080
+
1081
+ class MoneyPackElite(QMainWindow):
1082
+ def __init__(self):
1083
+ super().__init__()
1084
+ self.setWindowTitle("MoneyPackCleaner ELITE")
1085
+ self.setMinimumSize(1100, 700)
1086
+ self.resize(1300, 800)
1087
+ self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
1088
+ self.setStyleSheet(f"QMainWindow {{ background: {C.BG}; }}")
1089
+
1090
+ container = QWidget()
1091
+ self.setCentralWidget(container)
1092
+ ml = QVBoxLayout(container)
1093
+ ml.setContentsMargins(0, 0, 0, 0)
1094
+ ml.setSpacing(0)
1095
+
1096
+ # Titlebar
1097
+ ml.addWidget(TitleBar(self))
1098
+
1099
+ # Body
1100
+ body = QHBoxLayout()
1101
+ body.setContentsMargins(0, 0, 0, 0)
1102
+ body.setSpacing(0)
1103
+
1104
+ # Sidebar
1105
+ self.sidebar = Sidebar()
1106
+ self.sidebar.changed.connect(self._nav)
1107
+ body.addWidget(self.sidebar)
1108
+
1109
+ # Content
1110
+ content = QWidget()
1111
+ content.setStyleSheet(f"background: {C.BG};")
1112
+ cl = QVBoxLayout(content)
1113
+ cl.setContentsMargins(0, 0, 0, 0)
1114
+
1115
+ # Particles
1116
+ self.particles = FloatingParticles(content)
1117
+
1118
+ # Stack
1119
+ self.stack = QStackedWidget()
1120
+ self.stack.setStyleSheet("background: transparent;")
1121
+
1122
+ self.dash = DashPage()
1123
+ self.files = FilesPage()
1124
+ self.clean = CleanPage()
1125
+ self.logp = LogPage()
1126
+
1127
+ self.stack.addWidget(self.dash)
1128
+ self.stack.addWidget(self.files)
1129
+ self.stack.addWidget(self.clean)
1130
+ self.stack.addWidget(self.logp)
1131
+
1132
+ cl.addWidget(self.stack)
1133
+ body.addWidget(content)
1134
+ ml.addLayout(body)
1135
+
1136
+ # Connect logs
1137
+ for pg in [self.dash, self.files, self.clean]:
1138
+ pg.log.connect(self.logp.add)
1139
+
1140
+ # Tray
1141
+ self._tray_setup()
1142
+
1143
+ def _nav(self, idx):
1144
+ self.stack.setCurrentIndex(idx)
1145
+
1146
+ def _tray_setup(self):
1147
+ if not QSystemTrayIcon.isSystemTrayAvailable(): return
1148
+ pix = QPixmap(32,32)
1149
+ pix.fill(QColor(0,0,0,0))
1150
+ pa = QPainter(pix)
1151
+ pa.setRenderHint(QPainter.RenderHint.Antialiasing)
1152
+ grad = QLinearGradient(0,0,32,32)
1153
+ grad.setColorAt(0, QColor(C.PINK))
1154
+ grad.setColorAt(1, QColor(C.GOLD))
1155
+ pa.setBrush(QBrush(grad))
1156
+ pa.setPen(Qt.PenStyle.NoPen)
1157
+ pa.drawEllipse(2,2,28,28)
1158
+ pa.setPen(QColor(C.WHITE))
1159
+ pa.setFont(QFont("Segoe UI",13,QFont.Weight.Bold))
1160
+ pa.drawText(QRect(0,0,32,32), Qt.AlignmentFlag.AlignCenter, "M")
1161
+ pa.end()
1162
+ self._tray = QSystemTrayIcon(QIcon(pix), self)
1163
+ self._tray.setToolTip("MoneyPackCleaner ELITE")
1164
+ menu = QMenu()
1165
+ menu.addAction("Show", self.show)
1166
+ menu.addAction("Quick Clean", self.dash._quick)
1167
+ menu.addSeparator()
1168
+ menu.addAction("Exit", QApplication.quit)
1169
+ self._tray.setContextMenu(menu)
1170
+ self._tray.show()
1171
+
1172
+ def resizeEvent(self, e):
1173
+ super().resizeEvent(e)
1174
+ if hasattr(self, 'particles'):
1175
+ self.particles.resize(self.width()-200, self.height()-44)
1176
+ self.particles.move(200, 44)
1177
+
1178
+ def closeEvent(self, e):
1179
+ if hasattr(self, '_tray') and self._tray.isVisible():
1180
+ self.hide(); e.ignore()
1181
+ else: e.accept()
1182
+
1183
+
1184
+ # ═══════════════════════════════════════════════════════════════
1185
+ # ENTRY
1186
+ # ═══════════════════════════════════════════════════════════════
1187
+
1188
+ def main():
1189
+ app = QApplication(sys.argv)
1190
+ app.setFont(QFont("Segoe UI", 10))
1191
+ w = MoneyPackElite()
1192
+ w.show()
1193
+ sys.exit(app.exec())
1194
+
1195
+ if __name__ == "__main__":
1196
+ main()