Upload 2 files
Browse files- CybberTicket.jsx +1407 -0
- CyberTicket.jsx +646 -0
CybberTicket.jsx
ADDED
|
@@ -0,0 +1,1407 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useState, useEffect, useRef } from "react";
|
| 2 |
+
|
| 3 |
+
/* ============================================================
|
| 4 |
+
TICKETPUNCH UI — Complete Cyberpunk Design System
|
| 5 |
+
Font: IBM Plex Mono | Palette: Dark / White / Gold / Red / Peach
|
| 6 |
+
============================================================ */
|
| 7 |
+
|
| 8 |
+
const FontInjector = () => (
|
| 9 |
+
<style>{`
|
| 10 |
+
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap');
|
| 11 |
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
| 12 |
+
:root {
|
| 13 |
+
/* ── DARK ── */
|
| 14 |
+
--d-900: #060606;
|
| 15 |
+
--d-800: #0A0A0A;
|
| 16 |
+
--d-700: #0f0f0f;
|
| 17 |
+
--d-600: #1e1e1e;
|
| 18 |
+
--d-500: #2a2a2a;
|
| 19 |
+
|
| 20 |
+
/* ── WHITE ── */
|
| 21 |
+
--w-100: #FFFFFF;
|
| 22 |
+
--w-200: #EFEFEF;
|
| 23 |
+
--w-300: #CDCDCD;
|
| 24 |
+
--w-400: #AAAAAA;
|
| 25 |
+
--w-500: #888888;
|
| 26 |
+
|
| 27 |
+
/* ── GOLD ── */
|
| 28 |
+
--g-100: #FFF4CC;
|
| 29 |
+
--g-300: #FFD700;
|
| 30 |
+
--g-500: #C8960C;
|
| 31 |
+
--g-700: #7A5800;
|
| 32 |
+
--g-900: #3D2C00;
|
| 33 |
+
|
| 34 |
+
/* ── RED ── */
|
| 35 |
+
--r-100: #FFE5E5;
|
| 36 |
+
--r-300: #FF6666;
|
| 37 |
+
--r-500: #FF3333;
|
| 38 |
+
--r-700: #CC0000;
|
| 39 |
+
--r-900: #550000;
|
| 40 |
+
|
| 41 |
+
/* ── PEACH ── */
|
| 42 |
+
--p-100: #FFF0EB;
|
| 43 |
+
--p-300: #FFBFA8;
|
| 44 |
+
--p-500: #FF8C69;
|
| 45 |
+
--p-700: #C45A35;
|
| 46 |
+
--p-900: #5C2010;
|
| 47 |
+
|
| 48 |
+
/* ── SEMANTIC ALIASES ── */
|
| 49 |
+
--bg: var(--d-900);
|
| 50 |
+
--surface: var(--d-800);
|
| 51 |
+
--header: var(--d-700);
|
| 52 |
+
--border-1: var(--d-600);
|
| 53 |
+
--border-2: var(--d-500);
|
| 54 |
+
--text-primary: var(--w-100);
|
| 55 |
+
--text-6: #383838;
|
| 56 |
+
|
| 57 |
+
--font: 'IBM Plex Mono', monospace;
|
| 58 |
+
--tooth: 9px;
|
| 59 |
+
}
|
| 60 |
+
body { background: var(--bg); color: var(--text-primary); font-family: var(--font); }
|
| 61 |
+
::-webkit-scrollbar { width: 4px; }
|
| 62 |
+
::-webkit-scrollbar-track { background: var(--bg); }
|
| 63 |
+
::-webkit-scrollbar-thumb { background: var(--border-2); }
|
| 64 |
+
|
| 65 |
+
@keyframes pulse-dot { 0%,100%{opacity:1} 50%{opacity:0.2} }
|
| 66 |
+
@keyframes scroll-up { 0%{transform:translateY(0)} 100%{transform:translateY(-50%)} }
|
| 67 |
+
@keyframes spin-slow { from{transform:rotate(0deg)} to{transform:rotate(360deg)} }
|
| 68 |
+
@keyframes progress-fill { from{width:0} to{width:var(--fill)} }
|
| 69 |
+
@keyframes toast-in { from{transform:translateX(120%);opacity:0} to{transform:translateX(0);opacity:1} }
|
| 70 |
+
|
| 71 |
+
.pulse-anim { animation: pulse-dot 1400ms ease-in-out infinite; }
|
| 72 |
+
.scroll-anim { animation: scroll-up 12s linear infinite; }
|
| 73 |
+
.spin-slow { animation: spin-slow 8s linear infinite; }
|
| 74 |
+
.toast-anim { animation: toast-in 0.3s ease forwards; }
|
| 75 |
+
|
| 76 |
+
.punched-frame { position: relative; }
|
| 77 |
+
.punched-frame::before,
|
| 78 |
+
.punched-frame::after {
|
| 79 |
+
content: '';
|
| 80 |
+
position: absolute;
|
| 81 |
+
left: 0; right: 0;
|
| 82 |
+
height: var(--tooth);
|
| 83 |
+
z-index: 20;
|
| 84 |
+
pointer-events: none;
|
| 85 |
+
background: repeating-linear-gradient(
|
| 86 |
+
90deg,
|
| 87 |
+
transparent 0px, transparent 8px,
|
| 88 |
+
var(--bg) 8px, var(--bg) 18px
|
| 89 |
+
);
|
| 90 |
+
}
|
| 91 |
+
.punched-frame::before { top: 0; }
|
| 92 |
+
.punched-frame::after { bottom: 0; }
|
| 93 |
+
`}</style>
|
| 94 |
+
);
|
| 95 |
+
|
| 96 |
+
const GrainOverlay = ({ strength = 0.18 }) => {
|
| 97 |
+
const canvasRef = useRef(null);
|
| 98 |
+
useEffect(() => {
|
| 99 |
+
const canvas = canvasRef.current;
|
| 100 |
+
if (!canvas) return;
|
| 101 |
+
const ctx = canvas.getContext("2d");
|
| 102 |
+
const W = 256, H = 256;
|
| 103 |
+
canvas.width = W; canvas.height = H;
|
| 104 |
+
const imageData = ctx.createImageData(W, H);
|
| 105 |
+
const data = imageData.data;
|
| 106 |
+
for (let i = 0; i < data.length; i += 4) {
|
| 107 |
+
const v = (Math.random() * 255) | 0;
|
| 108 |
+
data[i] = data[i + 1] = data[i + 2] = v;
|
| 109 |
+
data[i + 3] = (strength * 255) | 0;
|
| 110 |
+
}
|
| 111 |
+
ctx.putImageData(imageData, 0, 0);
|
| 112 |
+
}, [strength]);
|
| 113 |
+
return (
|
| 114 |
+
<canvas ref={canvasRef} style={{
|
| 115 |
+
position: "absolute", inset: 0, width: "100%", height: "100%",
|
| 116 |
+
pointerEvents: "none", mixBlendMode: "overlay", opacity: 0.9, zIndex: 5
|
| 117 |
+
}} />
|
| 118 |
+
);
|
| 119 |
+
};
|
| 120 |
+
|
| 121 |
+
const GeometricOverlay = () => (
|
| 122 |
+
<svg style={{ position: "absolute", inset: 0, width: "100%", height: "100%", pointerEvents: "none", zIndex: 4, opacity: 0.07 }} preserveAspectRatio="xMidYMid slice">
|
| 123 |
+
<defs>
|
| 124 |
+
<pattern id="lattice" width="32" height="32" patternUnits="userSpaceOnUse">
|
| 125 |
+
<path d="M 32 0 L 0 0 0 32" fill="none" stroke="white" strokeWidth="0.5" />
|
| 126 |
+
<circle cx="0" cy="0" r="1" fill="white" opacity="0.5" />
|
| 127 |
+
</pattern>
|
| 128 |
+
</defs>
|
| 129 |
+
<rect width="100%" height="100%" fill="url(#lattice)" />
|
| 130 |
+
<circle cx="50%" cy="50%" r="80" fill="none" stroke="white" strokeWidth="0.5" />
|
| 131 |
+
<circle cx="50%" cy="50%" r="140" fill="none" stroke="white" strokeWidth="0.3" />
|
| 132 |
+
<line x1="50%" y1="0" x2="50%" y2="100%" stroke="white" strokeWidth="0.3" />
|
| 133 |
+
<line x1="0" y1="50%" x2="100%" y2="50%" stroke="white" strokeWidth="0.3" />
|
| 134 |
+
<polygon points="50%,10% 90%,90% 10%,90%" fill="none" stroke="white" strokeWidth="0.4" opacity="0.5" />
|
| 135 |
+
</svg>
|
| 136 |
+
);
|
| 137 |
+
|
| 138 |
+
const PunchHoles = () => (
|
| 139 |
+
<>
|
| 140 |
+
{[{ top: "3px", left: "4px" }, { top: "3px", right: "4px" }, { bottom: "3px", left: "4px" }, { bottom: "3px", right: "4px" }].map((pos, i) => (
|
| 141 |
+
<div key={i} style={{
|
| 142 |
+
position: "absolute", ...pos, width: 9, height: 9, borderRadius: "50%",
|
| 143 |
+
background: "radial-gradient(circle, var(--bg) 4px, transparent 4px)",
|
| 144 |
+
border: "1px solid var(--d-500)", zIndex: 25, pointerEvents: "none",
|
| 145 |
+
}} />
|
| 146 |
+
))}
|
| 147 |
+
</>
|
| 148 |
+
);
|
| 149 |
+
|
| 150 |
+
const Punched = ({ children, style = {}, className = "" }) => (
|
| 151 |
+
<div className={`punched-frame ${className}`} style={{ position: "relative", ...style }}>
|
| 152 |
+
<PunchHoles />
|
| 153 |
+
{children}
|
| 154 |
+
</div>
|
| 155 |
+
);
|
| 156 |
+
|
| 157 |
+
const TicketShell = ({ children, stubHeight = 60, style = {} }) => (
|
| 158 |
+
<Punched style={style}>
|
| 159 |
+
<div style={{
|
| 160 |
+
background: "var(--surface)", border: "1px solid var(--border-1)",
|
| 161 |
+
boxShadow: "0 40px 100px rgba(0,0,0,0.95)", position: "relative", overflow: "hidden"
|
| 162 |
+
}}>
|
| 163 |
+
<svg style={{ position: "absolute", inset: 0, width: "100%", height: "100%", pointerEvents: "none", zIndex: 6 }}>
|
| 164 |
+
<rect x="8" y="8" width="calc(100% - 16px)" height="calc(100% - 16px)"
|
| 165 |
+
fill="none" stroke="var(--d-500)" strokeWidth="1" strokeDasharray="4 3" />
|
| 166 |
+
</svg>
|
| 167 |
+
<GrainOverlay />
|
| 168 |
+
<GeometricOverlay />
|
| 169 |
+
{stubHeight > 0 && (
|
| 170 |
+
<div style={{
|
| 171 |
+
position: "absolute", bottom: stubHeight, left: 0, right: 0, zIndex: 8,
|
| 172 |
+
borderTop: "1px dashed var(--border-2)", display: "flex", alignItems: "center", justifyContent: "center"
|
| 173 |
+
}}>
|
| 174 |
+
<span style={{
|
| 175 |
+
background: "var(--surface)", padding: "0 8px", fontSize: 7, color: "var(--w-500)",
|
| 176 |
+
letterSpacing: "0.2em", fontFamily: "var(--font)"
|
| 177 |
+
}}>✂ TEAR</span>
|
| 178 |
+
</div>
|
| 179 |
+
)}
|
| 180 |
+
{children}
|
| 181 |
+
</div>
|
| 182 |
+
</Punched>
|
| 183 |
+
);
|
| 184 |
+
|
| 185 |
+
const Barcode = ({ value = "OMEGA-7749-XX", width = 120, height = 28 }) => {
|
| 186 |
+
const bars = [];
|
| 187 |
+
for (let i = 0; i < 48; i++)
|
| 188 |
+
bars.push({ x: i * (width / 48), w: Math.random() > 0.5 ? 1.5 : 0.8, o: 0.4 + Math.random() * 0.6 });
|
| 189 |
+
return (
|
| 190 |
+
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 2 }}>
|
| 191 |
+
<svg width={width} height={height} style={{ display: "block" }}>
|
| 192 |
+
{bars.map((b, i) => <rect key={i} x={b.x} y={0} width={b.w} height={height} fill="var(--w-300)" opacity={b.o} />)}
|
| 193 |
+
</svg>
|
| 194 |
+
<span style={{ fontSize: 6, letterSpacing: "0.28em", color: "var(--w-500)", fontFamily: "var(--font)", textTransform: "uppercase" }}>{value}</span>
|
| 195 |
+
</div>
|
| 196 |
+
);
|
| 197 |
+
};
|
| 198 |
+
|
| 199 |
+
const StatusPill = ({ status = "active", label, pulse = true }) => {
|
| 200 |
+
const colors = {
|
| 201 |
+
active: "var(--g-300)",
|
| 202 |
+
inactive: "var(--w-500)",
|
| 203 |
+
warning: "var(--p-500)",
|
| 204 |
+
danger: "var(--r-500)",
|
| 205 |
+
pending: "var(--g-100)",
|
| 206 |
+
};
|
| 207 |
+
const color = colors[status] || colors.active;
|
| 208 |
+
return (
|
| 209 |
+
<div style={{
|
| 210 |
+
display: "inline-flex", alignItems: "center", gap: 6, padding: "3px 8px",
|
| 211 |
+
border: `1px solid ${color}33`, background: `${color}0d`, borderRadius: 2
|
| 212 |
+
}}>
|
| 213 |
+
<div className={pulse ? "pulse-anim" : ""} style={{
|
| 214 |
+
width: 5, height: 5, borderRadius: "50%",
|
| 215 |
+
background: color, boxShadow: `0 0 6px ${color}`
|
| 216 |
+
}} />
|
| 217 |
+
<span style={{ fontSize: 8, letterSpacing: "0.28em", color, fontFamily: "var(--font)", textTransform: "uppercase" }}>
|
| 218 |
+
{label || status}
|
| 219 |
+
</span>
|
| 220 |
+
</div>
|
| 221 |
+
);
|
| 222 |
+
};
|
| 223 |
+
|
| 224 |
+
const TerminalMask = ({ lines = [], size = 120 }) => {
|
| 225 |
+
const defaultLines = [
|
| 226 |
+
"CLASSIFIED // OMEGA PROTOCOL", "ACCESS GRANTED — NEXUS-7", "THREAT LEVEL: CRITICAL",
|
| 227 |
+
"SYNC: 99.7% COMPLETE", "FIREWALL: ACTIVE", "ENCRYPTION: AES-512",
|
| 228 |
+
"NODE: 192.168.0.77", "UPTIME: 847:22:11", "AGENT: SPECTRE-9", "STATUS: OPERATIONAL", ...lines
|
| 229 |
+
];
|
| 230 |
+
const doubled = [...defaultLines, ...defaultLines];
|
| 231 |
+
return (
|
| 232 |
+
<div style={{
|
| 233 |
+
width: size, height: size, borderRadius: "50%", overflow: "hidden", position: "relative",
|
| 234 |
+
border: "1px solid var(--d-500)", background: "var(--bg)",
|
| 235 |
+
boxShadow: "inset 0 0 20px rgba(0,0,0,0.8), 0 0 20px rgba(0,0,0,0.6)"
|
| 236 |
+
}}>
|
| 237 |
+
<div className="scroll-anim" style={{ position: "absolute", width: "100%", paddingTop: 8 }}>
|
| 238 |
+
{doubled.map((line, i) => (
|
| 239 |
+
<div key={i} style={{
|
| 240 |
+
padding: "1.5px 8px", fontSize: 6.5, letterSpacing: "0.18em",
|
| 241 |
+
color: i % 3 === 0 ? "var(--g-300)" : "var(--w-500)",
|
| 242 |
+
fontFamily: "var(--font)", textTransform: "uppercase", whiteSpace: "nowrap", overflow: "hidden"
|
| 243 |
+
}}>{line}</div>
|
| 244 |
+
))}
|
| 245 |
+
</div>
|
| 246 |
+
<div style={{
|
| 247 |
+
position: "absolute", inset: 0, borderRadius: "50%",
|
| 248 |
+
background: "radial-gradient(circle, transparent 40%, var(--bg) 80%)", pointerEvents: "none"
|
| 249 |
+
}} />
|
| 250 |
+
</div>
|
| 251 |
+
);
|
| 252 |
+
};
|
| 253 |
+
|
| 254 |
+
const Divider = ({ label }) => (
|
| 255 |
+
<div style={{ display: "flex", alignItems: "center", gap: 10, padding: "4px 0" }}>
|
| 256 |
+
<div style={{ flex: 1, height: 1, background: "var(--border-1)" }} />
|
| 257 |
+
{label
|
| 258 |
+
? <span style={{ fontSize: 7, letterSpacing: "0.28em", color: "var(--w-500)", textTransform: "uppercase", fontFamily: "var(--font)" }}>{label}</span>
|
| 259 |
+
: <svg width="8" height="8"><polygon points="4,0 8,4 4,8 0,4" fill="var(--d-500)" /></svg>
|
| 260 |
+
}
|
| 261 |
+
<div style={{ flex: 1, height: 1, background: "var(--border-1)" }} />
|
| 262 |
+
</div>
|
| 263 |
+
);
|
| 264 |
+
|
| 265 |
+
const SectionLabel = ({ children, sub }) => (
|
| 266 |
+
<div style={{ marginBottom: 20 }}>
|
| 267 |
+
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
| 268 |
+
<div style={{ width: 2, height: 14, background: "var(--g-300)" }} />
|
| 269 |
+
<span style={{
|
| 270 |
+
fontSize: 9.5, fontWeight: 700, letterSpacing: "0.34em", textTransform: "uppercase",
|
| 271 |
+
fontFamily: "var(--font)", color: "var(--w-100)"
|
| 272 |
+
}}>{children}</span>
|
| 273 |
+
</div>
|
| 274 |
+
{sub && <div style={{
|
| 275 |
+
marginTop: 4, marginLeft: 10, fontSize: 7, letterSpacing: "0.2em",
|
| 276 |
+
color: "var(--g-700)", textTransform: "uppercase", fontFamily: "var(--font)"
|
| 277 |
+
}}>{sub}</div>}
|
| 278 |
+
</div>
|
| 279 |
+
);
|
| 280 |
+
|
| 281 |
+
const Micro = ({ children, color = "var(--text-6)" }) => (
|
| 282 |
+
<span style={{ fontSize: 7, letterSpacing: "0.28em", color, fontFamily: "var(--font)", textTransform: "uppercase" }}>{children}</span>
|
| 283 |
+
);
|
| 284 |
+
|
| 285 |
+
// ── 1. HEADER ──────────────────────────────────────────────────
|
| 286 |
+
const Header = ({ activeTab, setActiveTab }) => {
|
| 287 |
+
const navItems = ["DASHBOARD", "INTEL", "ASSETS", "COMMS", "SYSTEM"];
|
| 288 |
+
return (
|
| 289 |
+
<Punched style={{ zIndex: 100 }}>
|
| 290 |
+
<div style={{
|
| 291 |
+
position: "relative", background: "var(--header)", borderBottom: "1px solid var(--border-1)",
|
| 292 |
+
height: 52, display: "flex", alignItems: "center", padding: "0 24px", gap: 24, overflow: "hidden"
|
| 293 |
+
}}>
|
| 294 |
+
<GrainOverlay strength={0.1} />
|
| 295 |
+
<div style={{ display: "flex", alignItems: "center", gap: 10, zIndex: 7 }}>
|
| 296 |
+
<svg width="18" height="18" viewBox="0 0 18 18">
|
| 297 |
+
<polygon points="9,1 17,5 17,13 9,17 1,13 1,5" fill="none" stroke="var(--g-300)" strokeWidth="1.5" />
|
| 298 |
+
<polygon points="9,5 13,7 13,11 9,13 5,11 5,7" fill="var(--g-500)" opacity="0.3" />
|
| 299 |
+
<circle cx="9" cy="9" r="2" fill="var(--g-300)" />
|
| 300 |
+
</svg>
|
| 301 |
+
<div>
|
| 302 |
+
<div style={{
|
| 303 |
+
fontSize: 11, fontWeight: 700, letterSpacing: "0.22em", textTransform: "uppercase",
|
| 304 |
+
fontFamily: "var(--font)", color: "var(--w-100)"
|
| 305 |
+
}}>NEXUS</div>
|
| 306 |
+
<div style={{ fontSize: 6.5, letterSpacing: "0.34em", color: "var(--g-500)", textTransform: "uppercase", fontFamily: "var(--font)" }}>OMEGA PROTOCOL</div>
|
| 307 |
+
</div>
|
| 308 |
+
</div>
|
| 309 |
+
<div style={{ width: 1, height: 28, background: "var(--border-1)", zIndex: 7 }} />
|
| 310 |
+
<div style={{ display: "flex", gap: 2, zIndex: 7 }}>
|
| 311 |
+
{navItems.map(item => (
|
| 312 |
+
<button key={item} onClick={() => setActiveTab(item)} style={{
|
| 313 |
+
background: activeTab === item ? "rgba(255,215,0,0.06)" : "transparent",
|
| 314 |
+
border: activeTab === item ? "1px solid var(--g-700)" : "1px solid transparent",
|
| 315 |
+
color: activeTab === item ? "var(--g-300)" : "var(--w-500)",
|
| 316 |
+
padding: "5px 12px", cursor: "pointer", fontFamily: "var(--font)",
|
| 317 |
+
fontSize: 8, letterSpacing: "0.22em", textTransform: "uppercase", transition: "all 0.2s"
|
| 318 |
+
}}>
|
| 319 |
+
{item}
|
| 320 |
+
{activeTab === item && <div style={{ width: "100%", height: 1, background: "var(--g-300)", marginTop: 2 }} />}
|
| 321 |
+
</button>
|
| 322 |
+
))}
|
| 323 |
+
</div>
|
| 324 |
+
<div style={{ flex: 1 }} />
|
| 325 |
+
<div style={{ display: "flex", alignItems: "center", gap: 14, zIndex: 7 }}>
|
| 326 |
+
<StatusPill status="active" label="SYS ONLINE" />
|
| 327 |
+
<div style={{ textAlign: "right" }}>
|
| 328 |
+
<div style={{ fontSize: 8, letterSpacing: "0.2em", color: "var(--w-300)", textTransform: "uppercase", fontFamily: "var(--font)" }}>AGT-09</div>
|
| 329 |
+
<div style={{ fontSize: 6.5, letterSpacing: "0.18em", color: "var(--g-500)", textTransform: "uppercase", fontFamily: "var(--font)" }}>CLEARANCE: OMEGA</div>
|
| 330 |
+
</div>
|
| 331 |
+
<div style={{
|
| 332 |
+
width: 28, height: 28, borderRadius: "50%", border: "1px solid var(--g-700)",
|
| 333 |
+
background: "var(--surface)", display: "flex", alignItems: "center", justifyContent: "center",
|
| 334 |
+
fontSize: 9, color: "var(--g-300)", fontFamily: "var(--font)"
|
| 335 |
+
}}>Ω</div>
|
| 336 |
+
</div>
|
| 337 |
+
</div>
|
| 338 |
+
</Punched>
|
| 339 |
+
);
|
| 340 |
+
};
|
| 341 |
+
|
| 342 |
+
// ── 2. BUTTON ──────────────────────────────────────────────────
|
| 343 |
+
const Button = ({ variant = "primary", children, icon, size = "md", disabled, onClick }) => {
|
| 344 |
+
const sizes = { sm: "4px 10px", md: "7px 16px", lg: "10px 22px" };
|
| 345 |
+
const fontSizes = { sm: 7.5, md: 8.5, lg: 9.5 };
|
| 346 |
+
const bases = {
|
| 347 |
+
primary: { bg: "var(--g-300)", color: "var(--d-900)", border: "1px solid var(--g-300)" },
|
| 348 |
+
secondary: { bg: "rgba(255,215,0,0.06)", color: "var(--g-300)", border: "1px solid var(--g-700)" },
|
| 349 |
+
outline: { bg: "transparent", color: "var(--w-100)", border: "1px solid var(--w-300)" },
|
| 350 |
+
ghost: { bg: "transparent", color: "var(--w-500)", border: "1px solid transparent" },
|
| 351 |
+
danger: { bg: "rgba(255,51,51,0.10)", color: "var(--r-500)", border: "1px solid var(--r-700)" },
|
| 352 |
+
success: { bg: "rgba(255,215,0,0.08)", color: "var(--g-300)", border: "1px solid var(--g-700)" },
|
| 353 |
+
};
|
| 354 |
+
const s = bases[variant] || bases.primary;
|
| 355 |
+
return (
|
| 356 |
+
<button onClick={onClick} disabled={disabled} style={{
|
| 357 |
+
background: s.bg, color: s.color, border: s.border,
|
| 358 |
+
padding: sizes[size], fontFamily: "var(--font)",
|
| 359 |
+
fontSize: fontSizes[size], letterSpacing: "0.22em", textTransform: "uppercase",
|
| 360 |
+
cursor: disabled ? "not-allowed" : "pointer", opacity: disabled ? 0.4 : 1,
|
| 361 |
+
display: "inline-flex", alignItems: "center", gap: 6,
|
| 362 |
+
position: "relative", overflow: "hidden", transition: "all 0.2s", outline: "none",
|
| 363 |
+
}}>
|
| 364 |
+
{["top:2px;left:2px", "top:2px;right:2px", "bottom:2px;left:2px", "bottom:2px;right:2px"].map((pos, i) => {
|
| 365 |
+
const st = pos.split(";").reduce((a, p) => { const [k, v] = p.split(":"); a[k] = v; return a; }, {});
|
| 366 |
+
return <div key={i} style={{ position: "absolute", ...st, width: 2, height: 2, background: s.color, opacity: 0.5 }} />;
|
| 367 |
+
})}
|
| 368 |
+
{icon && <span style={{ fontSize: fontSizes[size] * 1.1 }}>{icon}</span>}
|
| 369 |
+
{children}
|
| 370 |
+
</button>
|
| 371 |
+
);
|
| 372 |
+
};
|
| 373 |
+
|
| 374 |
+
// ── 3. INPUT ───────────────────────────────────────────────────
|
| 375 |
+
const Input = ({ placeholder, label, type = "text", value, onChange, prefix, suffix, hint }) => (
|
| 376 |
+
<div style={{ display: "flex", flexDirection: "column", gap: 5 }}>
|
| 377 |
+
{label && <label style={{ fontSize: 7.5, letterSpacing: "0.28em", color: "var(--g-500)", textTransform: "uppercase", fontFamily: "var(--font)" }}>{label}</label>}
|
| 378 |
+
<div style={{
|
| 379 |
+
position: "relative", background: "var(--surface)", border: "1px solid var(--border-1)",
|
| 380 |
+
display: "flex", alignItems: "center", overflow: "hidden"
|
| 381 |
+
}}>
|
| 382 |
+
{prefix && <span style={{ padding: "0 8px", fontSize: 8, color: "var(--g-500)", fontFamily: "var(--font)", borderRight: "1px solid var(--border-1)" }}>{prefix}</span>}
|
| 383 |
+
<input type={type} placeholder={placeholder} value={value} onChange={onChange} style={{
|
| 384 |
+
flex: 1, background: "transparent", border: "none", outline: "none",
|
| 385 |
+
color: "var(--w-100)", fontFamily: "var(--font)", fontSize: 9, letterSpacing: "0.14em", padding: "9px 12px",
|
| 386 |
+
}} />
|
| 387 |
+
{suffix && <span style={{ padding: "0 8px", fontSize: 8, color: "var(--g-500)", fontFamily: "var(--font)", borderLeft: "1px solid var(--border-1)" }}>{suffix}</span>}
|
| 388 |
+
<div style={{ position: "absolute", inset: 2, border: "1px dashed var(--border-1)", pointerEvents: "none" }} />
|
| 389 |
+
</div>
|
| 390 |
+
{hint && <div style={{ fontSize: 7, letterSpacing: "0.18em", color: "var(--g-700)", fontFamily: "var(--font)", textTransform: "uppercase" }}>{hint}</div>}
|
| 391 |
+
</div>
|
| 392 |
+
);
|
| 393 |
+
|
| 394 |
+
// ── 4. CARD ────────────────────────────────────────────────────
|
| 395 |
+
const Card = ({ children, title, sub, tag, style = {} }) => (
|
| 396 |
+
<Punched style={style}>
|
| 397 |
+
<div style={{ position: "relative", background: "var(--surface)", border: "1px solid var(--border-1)", overflow: "hidden" }}>
|
| 398 |
+
<GrainOverlay strength={0.12} />
|
| 399 |
+
{["top:0;left:0", "top:0;right:0", "bottom:0;left:0", "bottom:0;right:0"].map((pos, i) => {
|
| 400 |
+
const st = pos.split(";").reduce((a, p) => { const [k, v] = p.split(":"); a[k] = v; return a; }, {});
|
| 401 |
+
return (
|
| 402 |
+
<div key={i} style={{
|
| 403 |
+
position: "absolute", ...st, width: 12, height: 12, zIndex: 7,
|
| 404 |
+
borderTop: i < 2 ? "1px solid var(--g-700)" : "none",
|
| 405 |
+
borderBottom: i >= 2 ? "1px solid var(--g-700)" : "none",
|
| 406 |
+
borderLeft: (i === 0 || i === 2) ? "1px solid var(--g-700)" : "none",
|
| 407 |
+
borderRight: (i === 1 || i === 3) ? "1px solid var(--g-700)" : "none",
|
| 408 |
+
}} />
|
| 409 |
+
);
|
| 410 |
+
})}
|
| 411 |
+
{(title || sub || tag) && (
|
| 412 |
+
<div style={{ padding: "10px 14px 8px", borderBottom: "1px solid var(--border-1)", position: "relative", zIndex: 7 }}>
|
| 413 |
+
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start" }}>
|
| 414 |
+
<div>
|
| 415 |
+
{title && <div style={{
|
| 416 |
+
fontSize: 9.5, fontWeight: 600, letterSpacing: "0.22em", textTransform: "uppercase",
|
| 417 |
+
fontFamily: "var(--font)", color: "var(--w-100)"
|
| 418 |
+
}}>{title}</div>}
|
| 419 |
+
{sub && <div style={{
|
| 420 |
+
fontSize: 7, letterSpacing: "0.18em", color: "var(--g-500)", textTransform: "uppercase",
|
| 421 |
+
fontFamily: "var(--font)", marginTop: 3
|
| 422 |
+
}}>{sub}</div>}
|
| 423 |
+
</div>
|
| 424 |
+
{tag && <div style={{
|
| 425 |
+
fontSize: 7, padding: "2px 7px", border: "1px solid var(--g-700)", color: "var(--g-500)",
|
| 426 |
+
letterSpacing: "0.2em", textTransform: "uppercase", fontFamily: "var(--font)"
|
| 427 |
+
}}>{tag}</div>}
|
| 428 |
+
</div>
|
| 429 |
+
</div>
|
| 430 |
+
)}
|
| 431 |
+
<div style={{ padding: 14, position: "relative", zIndex: 7 }}>{children}</div>
|
| 432 |
+
</div>
|
| 433 |
+
</Punched>
|
| 434 |
+
);
|
| 435 |
+
|
| 436 |
+
// ── 5. BADGE ───────────────────────────────────────────────────
|
| 437 |
+
const Badge = ({ children, variant = "default" }) => {
|
| 438 |
+
const vars = {
|
| 439 |
+
default: { bg: "var(--surface)", color: "var(--w-400)", border: "1px solid var(--border-2)" },
|
| 440 |
+
active: { bg: "rgba(255,215,0,0.08)", color: "var(--g-300)", border: "1px solid var(--g-700)" },
|
| 441 |
+
danger: { bg: "rgba(255,51,51,0.08)", color: "var(--r-500)", border: "1px solid var(--r-700)" },
|
| 442 |
+
warning: { bg: "rgba(255,140,105,0.08)", color: "var(--p-500)", border: "1px solid var(--p-700)" },
|
| 443 |
+
omega: { bg: "rgba(255,244,204,0.05)", color: "var(--g-100)", border: "1px solid var(--g-700)" },
|
| 444 |
+
};
|
| 445 |
+
const v = vars[variant] || vars.default;
|
| 446 |
+
return (
|
| 447 |
+
<span style={{
|
| 448 |
+
...v, display: "inline-block", padding: "2px 7px", fontSize: 7,
|
| 449 |
+
letterSpacing: "0.22em", textTransform: "uppercase", fontFamily: "var(--font)"
|
| 450 |
+
}}>{children}</span>
|
| 451 |
+
);
|
| 452 |
+
};
|
| 453 |
+
|
| 454 |
+
// ── 6. PROGRESS BAR ────────────────────────────────────────────
|
| 455 |
+
const ProgressBar = ({ value = 65, label, color = "var(--g-300)" }) => (
|
| 456 |
+
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
|
| 457 |
+
{label && (
|
| 458 |
+
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
| 459 |
+
<Micro color="var(--w-500)">{label}</Micro>
|
| 460 |
+
<Micro color="var(--g-500)">{value}%</Micro>
|
| 461 |
+
</div>
|
| 462 |
+
)}
|
| 463 |
+
<div style={{ height: 3, background: "var(--border-1)", position: "relative", overflow: "hidden" }}>
|
| 464 |
+
<div style={{
|
| 465 |
+
"--fill": `${value}%`, width: `${value}%`, height: "100%", background: color,
|
| 466 |
+
animation: "progress-fill 1.5s ease forwards", boxShadow: `0 0 8px ${color}`
|
| 467 |
+
}} />
|
| 468 |
+
{[25, 50, 75].map(t => (
|
| 469 |
+
<div key={t} style={{ position: "absolute", top: 0, left: `${t}%`, width: 1, height: "100%", background: "var(--border-2)" }} />
|
| 470 |
+
))}
|
| 471 |
+
</div>
|
| 472 |
+
</div>
|
| 473 |
+
);
|
| 474 |
+
|
| 475 |
+
// ── 7. SPINNER ─────────────────────────────────────────────────
|
| 476 |
+
const Spinner = ({ size = 32 }) => (
|
| 477 |
+
<div style={{ position: "relative", width: size, height: size }}>
|
| 478 |
+
<div className="spin-slow" style={{
|
| 479 |
+
width: "100%", height: "100%",
|
| 480 |
+
border: "1px solid var(--border-2)", borderTop: "1px solid var(--g-300)", borderRadius: "50%"
|
| 481 |
+
}} />
|
| 482 |
+
<div style={{ position: "absolute", inset: 4, border: "1px dashed var(--g-700)", borderRadius: "50%" }} />
|
| 483 |
+
<div style={{
|
| 484 |
+
position: "absolute", inset: "50%", transform: "translate(-50%,-50%)",
|
| 485 |
+
width: 4, height: 4, background: "var(--g-300)", borderRadius: "50%"
|
| 486 |
+
}} />
|
| 487 |
+
</div>
|
| 488 |
+
);
|
| 489 |
+
|
| 490 |
+
// ── 8. TABS ────────────────────────────────────────────────────
|
| 491 |
+
const Tabs = ({ tabs, active, onSelect }) => (
|
| 492 |
+
<div style={{ borderBottom: "1px solid var(--border-1)", display: "flex", gap: 0 }}>
|
| 493 |
+
{tabs.map((tab, i) => (
|
| 494 |
+
<button key={i} onClick={() => onSelect(i)} style={{
|
| 495 |
+
background: active === i ? "rgba(255,215,0,0.04)" : "transparent",
|
| 496 |
+
border: "none", borderBottom: active === i ? "1px solid var(--g-300)" : "1px solid transparent",
|
| 497 |
+
color: active === i ? "var(--g-300)" : "var(--w-500)",
|
| 498 |
+
padding: "8px 16px", cursor: "pointer", fontFamily: "var(--font)",
|
| 499 |
+
fontSize: 8, letterSpacing: "0.22em", textTransform: "uppercase", position: "relative", marginBottom: -1,
|
| 500 |
+
}}>
|
| 501 |
+
{active === i && <div style={{ position: "absolute", top: 0, left: 0, right: 0, height: 1, background: "var(--g-700)" }} />}
|
| 502 |
+
{tab}
|
| 503 |
+
</button>
|
| 504 |
+
))}
|
| 505 |
+
</div>
|
| 506 |
+
);
|
| 507 |
+
|
| 508 |
+
// ── 9. ACCORDION ───────────────────────────────────────────────
|
| 509 |
+
const AccordionItem = ({ title, children }) => {
|
| 510 |
+
const [open, setOpen] = useState(false);
|
| 511 |
+
return (
|
| 512 |
+
<div style={{ borderBottom: "1px solid var(--border-1)" }}>
|
| 513 |
+
<button onClick={() => setOpen(!open)} style={{
|
| 514 |
+
width: "100%", background: "transparent", border: "none", padding: "10px 14px",
|
| 515 |
+
display: "flex", justifyContent: "space-between", alignItems: "center",
|
| 516 |
+
cursor: "pointer", color: "var(--w-300)", fontFamily: "var(--font)",
|
| 517 |
+
fontSize: 8.5, letterSpacing: "0.22em", textTransform: "uppercase",
|
| 518 |
+
}}>
|
| 519 |
+
<span>{title}</span>
|
| 520 |
+
<svg width="10" height="10" style={{ transform: open ? "rotate(180deg)" : "rotate(0deg)", transition: "transform 0.2s" }}>
|
| 521 |
+
<polygon points="5,7 0,0 10,0" fill="var(--g-500)" />
|
| 522 |
+
</svg>
|
| 523 |
+
</button>
|
| 524 |
+
{open && (
|
| 525 |
+
<div style={{
|
| 526 |
+
padding: "8px 14px 12px", color: "var(--w-500)", fontSize: 8.5, lineHeight: 1.7,
|
| 527 |
+
fontFamily: "var(--font)", letterSpacing: "0.1em", borderTop: "1px dashed var(--border-1)"
|
| 528 |
+
}}>
|
| 529 |
+
{children}
|
| 530 |
+
</div>
|
| 531 |
+
)}
|
| 532 |
+
</div>
|
| 533 |
+
);
|
| 534 |
+
};
|
| 535 |
+
|
| 536 |
+
// ── 10. TOOLTIP ────────────────────────────────────────────────
|
| 537 |
+
const Tooltip = ({ label, children }) => {
|
| 538 |
+
const [show, setShow] = useState(false);
|
| 539 |
+
return (
|
| 540 |
+
<div style={{ position: "relative", display: "inline-block" }}
|
| 541 |
+
onMouseEnter={() => setShow(true)} onMouseLeave={() => setShow(false)}>
|
| 542 |
+
{children}
|
| 543 |
+
{show && (
|
| 544 |
+
<div style={{
|
| 545 |
+
position: "absolute", bottom: "calc(100% + 6px)", left: "50%", transform: "translateX(-50%)",
|
| 546 |
+
background: "var(--header)", border: "1px solid var(--g-700)", padding: "5px 10px",
|
| 547 |
+
whiteSpace: "nowrap", zIndex: 200, pointerEvents: "none"
|
| 548 |
+
}}>
|
| 549 |
+
<span style={{
|
| 550 |
+
fontSize: 7.5, letterSpacing: "0.18em", color: "var(--g-300)",
|
| 551 |
+
fontFamily: "var(--font)", textTransform: "uppercase"
|
| 552 |
+
}}>{label}</span>
|
| 553 |
+
<div style={{
|
| 554 |
+
position: "absolute", bottom: -4, left: "50%", transform: "translateX(-50%)", width: 6, height: 4,
|
| 555 |
+
background: "var(--header)", borderRight: "1px solid var(--g-700)", borderBottom: "1px solid var(--g-700)",
|
| 556 |
+
transformOrigin: "center", rotate: "45deg"
|
| 557 |
+
}} />
|
| 558 |
+
</div>
|
| 559 |
+
)}
|
| 560 |
+
</div>
|
| 561 |
+
);
|
| 562 |
+
};
|
| 563 |
+
|
| 564 |
+
// ── 11. DATA TABLE ─────────────────────────────────────────────
|
| 565 |
+
const DataTable = ({ columns, rows }) => (
|
| 566 |
+
<Punched>
|
| 567 |
+
<div style={{ border: "1px solid var(--border-1)", overflow: "hidden", position: "relative" }}>
|
| 568 |
+
<GrainOverlay strength={0.08} />
|
| 569 |
+
<div style={{
|
| 570 |
+
display: "grid", gridTemplateColumns: `repeat(${columns.length}, 1fr)`,
|
| 571 |
+
borderBottom: "1px solid var(--border-2)", background: "var(--header)", position: "relative", zIndex: 7
|
| 572 |
+
}}>
|
| 573 |
+
{columns.map((col, i) => (
|
| 574 |
+
<div key={i} style={{
|
| 575 |
+
padding: "8px 12px", fontSize: 7.5, letterSpacing: "0.22em",
|
| 576 |
+
textTransform: "uppercase", color: "var(--g-500)", fontFamily: "var(--font)",
|
| 577 |
+
borderRight: i < columns.length - 1 ? "1px solid var(--border-1)" : "none"
|
| 578 |
+
}}>
|
| 579 |
+
{col}
|
| 580 |
+
</div>
|
| 581 |
+
))}
|
| 582 |
+
</div>
|
| 583 |
+
{rows.map((row, ri) => (
|
| 584 |
+
<div key={ri} style={{
|
| 585 |
+
display: "grid", gridTemplateColumns: `repeat(${columns.length}, 1fr)`,
|
| 586 |
+
borderBottom: ri < rows.length - 1 ? "1px solid var(--border-1)" : "none",
|
| 587 |
+
background: ri % 2 === 0 ? "transparent" : "rgba(255,215,0,0.01)", position: "relative", zIndex: 7
|
| 588 |
+
}}>
|
| 589 |
+
{row.map((cell, ci) => (
|
| 590 |
+
<div key={ci} style={{
|
| 591 |
+
padding: "7px 12px", fontSize: 8, letterSpacing: "0.12em",
|
| 592 |
+
color: ci === 0 ? "var(--w-200)" : "var(--w-500)", fontFamily: "var(--font)",
|
| 593 |
+
borderRight: ci < row.length - 1 ? "1px solid var(--border-1)" : "none"
|
| 594 |
+
}}>
|
| 595 |
+
{cell}
|
| 596 |
+
</div>
|
| 597 |
+
))}
|
| 598 |
+
</div>
|
| 599 |
+
))}
|
| 600 |
+
</div>
|
| 601 |
+
</Punched>
|
| 602 |
+
);
|
| 603 |
+
|
| 604 |
+
// ── 12. TOGGLE ─────────────────────────────────────────────────
|
| 605 |
+
const Toggle = ({ checked, onChange, label }) => (
|
| 606 |
+
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
|
| 607 |
+
<div onClick={onChange} style={{
|
| 608 |
+
cursor: "pointer", width: 40, height: 16, position: "relative",
|
| 609 |
+
background: checked ? "rgba(255,215,0,0.1)" : "var(--surface)",
|
| 610 |
+
border: `1px solid ${checked ? "var(--g-700)" : "var(--border-1)"}`
|
| 611 |
+
}}>
|
| 612 |
+
{[...Array(8)].map((_, i) => (
|
| 613 |
+
<div key={i} style={{
|
| 614 |
+
position: "absolute", top: 2, bottom: 2, left: 4 + i * 4, width: 1,
|
| 615 |
+
background: "var(--border-2)", opacity: checked ? 1 : 0.4
|
| 616 |
+
}} />
|
| 617 |
+
))}
|
| 618 |
+
<div style={{
|
| 619 |
+
position: "absolute", top: 1, width: 12, height: 12,
|
| 620 |
+
background: checked ? "var(--g-300)" : "var(--border-2)",
|
| 621 |
+
left: checked ? "calc(100% - 14px)" : 1,
|
| 622 |
+
transition: "all 0.2s", boxShadow: checked ? "0 0 8px var(--g-500)" : "none",
|
| 623 |
+
display: "flex", alignItems: "center", justifyContent: "center",
|
| 624 |
+
fontSize: 5, color: checked ? "var(--d-900)" : "transparent"
|
| 625 |
+
}}>■</div>
|
| 626 |
+
</div>
|
| 627 |
+
{label && <span style={{
|
| 628 |
+
fontSize: 8, letterSpacing: "0.18em", color: "var(--w-400)",
|
| 629 |
+
fontFamily: "var(--font)", textTransform: "uppercase"
|
| 630 |
+
}}>{label}</span>}
|
| 631 |
+
</div>
|
| 632 |
+
);
|
| 633 |
+
|
| 634 |
+
// ── 13. ALERT ──────────────────────────────────────────────────
|
| 635 |
+
const Alert = ({ type = "info", title, message, onClose }) => {
|
| 636 |
+
const types = {
|
| 637 |
+
info: { color: "var(--w-300)", border: "var(--border-2)", icon: "◈" },
|
| 638 |
+
success: { color: "var(--g-300)", border: "var(--g-700)", icon: "✓" },
|
| 639 |
+
warning: { color: "var(--p-500)", border: "var(--p-700)", icon: "⚠" },
|
| 640 |
+
danger: { color: "var(--r-500)", border: "var(--r-700)", icon: "✕" },
|
| 641 |
+
};
|
| 642 |
+
const t = types[type] || types.info;
|
| 643 |
+
return (
|
| 644 |
+
<Punched>
|
| 645 |
+
<div className="toast-anim" style={{
|
| 646 |
+
background: "var(--surface)", border: `1px solid ${t.border}`,
|
| 647 |
+
padding: "10px 14px", position: "relative", overflow: "hidden", display: "flex", gap: 10, alignItems: "flex-start"
|
| 648 |
+
}}>
|
| 649 |
+
<GrainOverlay strength={0.1} />
|
| 650 |
+
<span style={{ fontSize: 14, color: t.color, zIndex: 7 }}>{t.icon}</span>
|
| 651 |
+
<div style={{ flex: 1, zIndex: 7 }}>
|
| 652 |
+
{title && <div style={{
|
| 653 |
+
fontSize: 9, fontWeight: 600, letterSpacing: "0.2em", color: t.color,
|
| 654 |
+
textTransform: "uppercase", fontFamily: "var(--font)", marginBottom: 3
|
| 655 |
+
}}>{title}</div>}
|
| 656 |
+
<div style={{ fontSize: 8, letterSpacing: "0.14em", color: "var(--w-500)", fontFamily: "var(--font)" }}>{message}</div>
|
| 657 |
+
</div>
|
| 658 |
+
{onClose && <button onClick={onClose} style={{
|
| 659 |
+
background: "none", border: "none",
|
| 660 |
+
color: "var(--w-500)", cursor: "pointer", fontSize: 10, zIndex: 7
|
| 661 |
+
}}>×</button>}
|
| 662 |
+
</div>
|
| 663 |
+
</Punched>
|
| 664 |
+
);
|
| 665 |
+
};
|
| 666 |
+
|
| 667 |
+
// ── 14. BREADCRUMBS ────────────────────────────────────────────
|
| 668 |
+
const Breadcrumbs = ({ items }) => (
|
| 669 |
+
<div style={{ display: "flex", alignItems: "center", gap: 6 }}>
|
| 670 |
+
{items.map((item, i) => (
|
| 671 |
+
<div key={i} style={{ display: "flex", alignItems: "center", gap: 6 }}>
|
| 672 |
+
<span style={{
|
| 673 |
+
fontSize: 8, letterSpacing: "0.18em",
|
| 674 |
+
color: i === items.length - 1 ? "var(--g-300)" : "var(--w-500)",
|
| 675 |
+
textTransform: "uppercase", fontFamily: "var(--font)", cursor: i < items.length - 1 ? "pointer" : "default"
|
| 676 |
+
}}>
|
| 677 |
+
{item}
|
| 678 |
+
</span>
|
| 679 |
+
{i < items.length - 1 && (
|
| 680 |
+
<svg width="6" height="6"><polygon points="3,0 6,3 3,6 0,3" fill="var(--g-700)" /></svg>
|
| 681 |
+
)}
|
| 682 |
+
</div>
|
| 683 |
+
))}
|
| 684 |
+
</div>
|
| 685 |
+
);
|
| 686 |
+
|
| 687 |
+
// ── 15. STEPPER ────────────────────────────────────────────────
|
| 688 |
+
const Stepper = ({ steps, current }) => (
|
| 689 |
+
<div style={{ display: "flex", alignItems: "center" }}>
|
| 690 |
+
{steps.map((step, i) => (
|
| 691 |
+
<div key={i} style={{ display: "flex", alignItems: "center", flex: i < steps.length - 1 ? 1 : "none" }}>
|
| 692 |
+
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 4 }}>
|
| 693 |
+
<div style={{
|
| 694 |
+
width: 24, height: 24,
|
| 695 |
+
border: `1px solid ${i <= current ? "var(--g-300)" : "var(--border-2)"}`,
|
| 696 |
+
background: i < current ? "var(--g-300)" : i === current ? "rgba(255,215,0,0.1)" : "transparent",
|
| 697 |
+
display: "flex", alignItems: "center", justifyContent: "center",
|
| 698 |
+
fontSize: 7.5, color: i < current ? "var(--d-900)" : i === current ? "var(--g-300)" : "var(--w-500)",
|
| 699 |
+
fontFamily: "var(--font)", fontWeight: 700, letterSpacing: "0.1em"
|
| 700 |
+
}}>
|
| 701 |
+
{i < current ? "✓" : i + 1}
|
| 702 |
+
</div>
|
| 703 |
+
<span style={{
|
| 704 |
+
fontSize: 7, letterSpacing: "0.16em",
|
| 705 |
+
color: i <= current ? "var(--g-300)" : "var(--w-500)",
|
| 706 |
+
textTransform: "uppercase", fontFamily: "var(--font)", whiteSpace: "nowrap"
|
| 707 |
+
}}>{step}</span>
|
| 708 |
+
</div>
|
| 709 |
+
{i < steps.length - 1 && (
|
| 710 |
+
<div style={{
|
| 711 |
+
flex: 1, height: 1, background: i < current ? "var(--g-500)" : "var(--border-1)",
|
| 712 |
+
margin: "0 8px", marginBottom: 14
|
| 713 |
+
}} />
|
| 714 |
+
)}
|
| 715 |
+
</div>
|
| 716 |
+
))}
|
| 717 |
+
</div>
|
| 718 |
+
);
|
| 719 |
+
|
| 720 |
+
// ── 16. SELECT ─────────────────────────────────────────────────
|
| 721 |
+
const Select = ({ label, options, value, onChange }) => {
|
| 722 |
+
const [open, setOpen] = useState(false);
|
| 723 |
+
return (
|
| 724 |
+
<div style={{ position: "relative", display: "flex", flexDirection: "column", gap: 5 }}>
|
| 725 |
+
{label && <label style={{
|
| 726 |
+
fontSize: 7.5, letterSpacing: "0.28em", color: "var(--g-500)",
|
| 727 |
+
textTransform: "uppercase", fontFamily: "var(--font)"
|
| 728 |
+
}}>{label}</label>}
|
| 729 |
+
<div onClick={() => setOpen(!open)} style={{
|
| 730 |
+
background: "var(--surface)", border: "1px solid var(--border-1)",
|
| 731 |
+
padding: "9px 12px", cursor: "pointer", display: "flex", justifyContent: "space-between",
|
| 732 |
+
alignItems: "center", position: "relative"
|
| 733 |
+
}}>
|
| 734 |
+
<span style={{
|
| 735 |
+
fontSize: 9, letterSpacing: "0.14em", fontFamily: "var(--font)",
|
| 736 |
+
color: value ? "var(--w-100)" : "var(--w-500)"
|
| 737 |
+
}}>
|
| 738 |
+
{value || "— SELECT OPTION —"}
|
| 739 |
+
</span>
|
| 740 |
+
<svg width="8" height="8" style={{ transform: open ? "rotate(180deg)" : "none", transition: "transform 0.2s" }}>
|
| 741 |
+
<polygon points="4,6 0,0 8,0" fill="var(--g-500)" />
|
| 742 |
+
</svg>
|
| 743 |
+
<div style={{ position: "absolute", inset: 2, border: "1px dashed var(--border-1)", pointerEvents: "none" }} />
|
| 744 |
+
</div>
|
| 745 |
+
{open && (
|
| 746 |
+
<div style={{
|
| 747 |
+
position: "absolute", top: "calc(100% + 2px)", left: 0, right: 0, background: "var(--header)",
|
| 748 |
+
border: "1px solid var(--g-700)", zIndex: 100, overflow: "hidden"
|
| 749 |
+
}}>
|
| 750 |
+
{options.map((opt, i) => (
|
| 751 |
+
<div key={i} onClick={() => { onChange(opt); setOpen(false); }} style={{
|
| 752 |
+
padding: "8px 12px", cursor: "pointer", fontSize: 8.5, letterSpacing: "0.14em",
|
| 753 |
+
fontFamily: "var(--font)", color: "var(--w-400)", textTransform: "uppercase",
|
| 754 |
+
borderBottom: i < options.length - 1 ? "1px solid var(--border-1)" : "none", transition: "background 0.1s",
|
| 755 |
+
}}
|
| 756 |
+
onMouseEnter={e => e.currentTarget.style.background = "rgba(255,215,0,0.05)"}
|
| 757 |
+
onMouseLeave={e => e.currentTarget.style.background = "transparent"}>
|
| 758 |
+
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
| 759 |
+
<div style={{ width: 3, height: 3, background: "var(--g-500)" }} />{opt}
|
| 760 |
+
</div>
|
| 761 |
+
</div>
|
| 762 |
+
))}
|
| 763 |
+
</div>
|
| 764 |
+
)}
|
| 765 |
+
</div>
|
| 766 |
+
);
|
| 767 |
+
};
|
| 768 |
+
|
| 769 |
+
// ── 17. MODAL ──────────────────────────────────────────────────
|
| 770 |
+
const Modal = ({ open, onClose, title, children }) => {
|
| 771 |
+
if (!open) return null;
|
| 772 |
+
return (
|
| 773 |
+
<div style={{
|
| 774 |
+
position: "fixed", inset: 0, background: "rgba(0,0,0,0.85)", zIndex: 1000,
|
| 775 |
+
display: "flex", alignItems: "center", justifyContent: "center", backdropFilter: "blur(4px)"
|
| 776 |
+
}}>
|
| 777 |
+
<div style={{ position: "relative", maxWidth: 480, width: "90%", maxHeight: "80vh", overflow: "hidden" }}>
|
| 778 |
+
<TicketShell stubHeight={0}>
|
| 779 |
+
<div style={{ padding: "20px 20px 16px", position: "relative", zIndex: 8 }}>
|
| 780 |
+
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }}>
|
| 781 |
+
<div>
|
| 782 |
+
<div style={{
|
| 783 |
+
fontSize: 7, letterSpacing: "0.28em", color: "var(--g-700)",
|
| 784 |
+
fontFamily: "var(--font)", textTransform: "uppercase", marginBottom: 4
|
| 785 |
+
}}>// CLASSIFIED DIALOGUE</div>
|
| 786 |
+
<div style={{
|
| 787 |
+
fontSize: 11, fontWeight: 700, letterSpacing: "0.22em", textTransform: "uppercase",
|
| 788 |
+
fontFamily: "var(--font)", color: "var(--g-300)"
|
| 789 |
+
}}>{title}</div>
|
| 790 |
+
</div>
|
| 791 |
+
<button onClick={onClose} style={{
|
| 792 |
+
background: "transparent", border: "1px solid var(--g-700)",
|
| 793 |
+
color: "var(--g-500)", width: 24, height: 24, cursor: "pointer", fontFamily: "var(--font)", fontSize: 10,
|
| 794 |
+
display: "flex", alignItems: "center", justifyContent: "center"
|
| 795 |
+
}}>×</button>
|
| 796 |
+
</div>
|
| 797 |
+
<Divider />
|
| 798 |
+
<div style={{
|
| 799 |
+
marginTop: 14, color: "var(--w-400)", fontSize: 8.5, lineHeight: 1.7,
|
| 800 |
+
letterSpacing: "0.12em", fontFamily: "var(--font)"
|
| 801 |
+
}}>{children}</div>
|
| 802 |
+
<div style={{ marginTop: 18, display: "flex", gap: 8, justifyContent: "flex-end" }}>
|
| 803 |
+
<Button variant="ghost" onClick={onClose}>CANCEL</Button>
|
| 804 |
+
<Button variant="primary" onClick={onClose}>CONFIRM</Button>
|
| 805 |
+
</div>
|
| 806 |
+
</div>
|
| 807 |
+
</TicketShell>
|
| 808 |
+
</div>
|
| 809 |
+
</div>
|
| 810 |
+
);
|
| 811 |
+
};
|
| 812 |
+
|
| 813 |
+
// ── 18. AVATAR ─────────────────────────────────────────────────
|
| 814 |
+
const Avatar = ({ initials = "Ω", status }) => (
|
| 815 |
+
<div style={{ position: "relative", width: 40, height: 40 }}>
|
| 816 |
+
<div style={{
|
| 817 |
+
width: "100%", height: "100%", borderRadius: "50%", background: "var(--surface)",
|
| 818 |
+
border: "1px solid var(--g-700)", display: "flex", alignItems: "center", justifyContent: "center",
|
| 819 |
+
overflow: "hidden", position: "relative"
|
| 820 |
+
}}>
|
| 821 |
+
<GeometricOverlay />
|
| 822 |
+
<span style={{ fontSize: 13, color: "var(--g-300)", fontFamily: "var(--font)", zIndex: 7 }}>{initials}</span>
|
| 823 |
+
</div>
|
| 824 |
+
{status && (
|
| 825 |
+
<div className="pulse-anim" style={{
|
| 826 |
+
position: "absolute", bottom: 1, right: 1, width: 7, height: 7,
|
| 827 |
+
borderRadius: "50%", background: "var(--g-300)", border: "1px solid var(--bg)",
|
| 828 |
+
boxShadow: "0 0 6px var(--g-500)"
|
| 829 |
+
}} />
|
| 830 |
+
)}
|
| 831 |
+
</div>
|
| 832 |
+
);
|
| 833 |
+
|
| 834 |
+
// ── 19. FIELDSET ───────────────────────────────────────────────
|
| 835 |
+
const Fieldset = ({ legend, children }) => (
|
| 836 |
+
<div style={{ border: "1px solid var(--border-1)", padding: "14px 16px", position: "relative", marginTop: 8 }}>
|
| 837 |
+
<div style={{
|
| 838 |
+
position: "absolute", top: -7, left: 12, background: "var(--bg)", padding: "0 6px",
|
| 839 |
+
fontSize: 7, letterSpacing: "0.28em", color: "var(--g-500)", textTransform: "uppercase", fontFamily: "var(--font)"
|
| 840 |
+
}}>
|
| 841 |
+
{legend}
|
| 842 |
+
</div>
|
| 843 |
+
<div style={{ display: "flex", flexDirection: "column", gap: 14 }}>{children}</div>
|
| 844 |
+
</div>
|
| 845 |
+
);
|
| 846 |
+
|
| 847 |
+
// ── 20. STAT CARD ──────────────────────────────────────────────
|
| 848 |
+
const StatCard = ({ label, value, unit, change, trend }) => (
|
| 849 |
+
<Punched>
|
| 850 |
+
<div style={{
|
| 851 |
+
position: "relative", background: "var(--surface)", border: "1px solid var(--border-1)",
|
| 852 |
+
padding: "14px 16px", overflow: "hidden"
|
| 853 |
+
}}>
|
| 854 |
+
<GrainOverlay strength={0.1} />
|
| 855 |
+
<div style={{ position: "relative", zIndex: 7 }}>
|
| 856 |
+
<div style={{
|
| 857 |
+
fontSize: 7, letterSpacing: "0.28em", color: "var(--g-700)", textTransform: "uppercase",
|
| 858 |
+
fontFamily: "var(--font)", marginBottom: 8
|
| 859 |
+
}}>{label}</div>
|
| 860 |
+
<div style={{ display: "flex", alignItems: "baseline", gap: 4 }}>
|
| 861 |
+
<span style={{
|
| 862 |
+
fontSize: 26, fontWeight: 700, letterSpacing: "0.04em",
|
| 863 |
+
fontFamily: "var(--font)", lineHeight: 1, color: "var(--w-100)"
|
| 864 |
+
}}>{value}</span>
|
| 865 |
+
{unit && <span style={{ fontSize: 9, color: "var(--g-500)", fontFamily: "var(--font)", letterSpacing: "0.14em" }}>{unit}</span>}
|
| 866 |
+
</div>
|
| 867 |
+
{change !== undefined && (
|
| 868 |
+
<div style={{
|
| 869 |
+
marginTop: 6, fontSize: 7.5, letterSpacing: "0.18em", fontFamily: "var(--font)",
|
| 870 |
+
color: trend === "up" ? "var(--g-300)" : trend === "down" ? "var(--r-500)" : "var(--w-500)"
|
| 871 |
+
}}>
|
| 872 |
+
{trend === "up" ? "▲" : trend === "down" ? "▼" : "—"} {change}
|
| 873 |
+
</div>
|
| 874 |
+
)}
|
| 875 |
+
</div>
|
| 876 |
+
<div style={{
|
| 877 |
+
position: "absolute", bottom: -20, right: -20, width: 60, height: 60,
|
| 878 |
+
borderRadius: "50%", background: "rgba(255,215,0,0.03)", pointerEvents: "none"
|
| 879 |
+
}} />
|
| 880 |
+
</div>
|
| 881 |
+
</Punched>
|
| 882 |
+
);
|
| 883 |
+
|
| 884 |
+
// ── 21. SIDEBAR ────────────────────────────────────────────────
|
| 885 |
+
const Sidebar = ({ activeItem, setActiveItem }) => {
|
| 886 |
+
const items = [
|
| 887 |
+
{ icon: "◈", label: "DASHBOARD", sub: "OVERVIEW" },
|
| 888 |
+
{ icon: "◉", label: "INTEL", sub: "CLASSIFIED" },
|
| 889 |
+
{ icon: "▣", label: "ASSETS", sub: "INVENTORY" },
|
| 890 |
+
{ icon: "◆", label: "COMMS", sub: "ENCRYPTED" },
|
| 891 |
+
{ icon: "⬡", label: "ANALYTICS", sub: "METRICS" },
|
| 892 |
+
{ icon: "⊕", label: "SETTINGS", sub: "CONFIG" },
|
| 893 |
+
];
|
| 894 |
+
return (
|
| 895 |
+
<Punched style={{ width: 160 }}>
|
| 896 |
+
<div style={{
|
| 897 |
+
background: "var(--header)", borderRight: "1px solid var(--border-1)",
|
| 898 |
+
display: "flex", flexDirection: "column", position: "relative", overflow: "hidden", height: "100%"
|
| 899 |
+
}}>
|
| 900 |
+
<GrainOverlay strength={0.13} />
|
| 901 |
+
<div style={{
|
| 902 |
+
position: "absolute", right: 0, top: 0, bottom: 0, width: 8,
|
| 903 |
+
background: "repeating-linear-gradient(180deg, transparent 0px, transparent 8px, var(--bg) 8px, var(--bg) 18px)",
|
| 904 |
+
zIndex: 10
|
| 905 |
+
}} />
|
| 906 |
+
<div style={{ padding: "12px 0", flex: 1, position: "relative", zIndex: 7 }}>
|
| 907 |
+
{items.map((item, i) => (
|
| 908 |
+
<button key={i} onClick={() => setActiveItem(item.label)} style={{
|
| 909 |
+
width: "100%", background: activeItem === item.label ? "rgba(255,215,0,0.05)" : "transparent",
|
| 910 |
+
border: "none", borderLeft: activeItem === item.label ? "2px solid var(--g-300)" : "2px solid transparent",
|
| 911 |
+
borderRight: "none", padding: "10px 14px", cursor: "pointer", textAlign: "left",
|
| 912 |
+
borderBottom: i < items.length - 1 ? "1px solid var(--border-1)" : "none",
|
| 913 |
+
}}>
|
| 914 |
+
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
|
| 915 |
+
<span style={{ fontSize: 11, color: activeItem === item.label ? "var(--g-300)" : "var(--w-500)" }}>{item.icon}</span>
|
| 916 |
+
<div>
|
| 917 |
+
<div style={{
|
| 918 |
+
fontSize: 8, fontWeight: 600, letterSpacing: "0.18em", textTransform: "uppercase",
|
| 919 |
+
color: activeItem === item.label ? "var(--g-300)" : "var(--w-400)", fontFamily: "var(--font)"
|
| 920 |
+
}}>{item.label}</div>
|
| 921 |
+
<div style={{ fontSize: 6, letterSpacing: "0.2em", color: "var(--g-700)", textTransform: "uppercase", fontFamily: "var(--font)" }}>{item.sub}</div>
|
| 922 |
+
</div>
|
| 923 |
+
</div>
|
| 924 |
+
</button>
|
| 925 |
+
))}
|
| 926 |
+
</div>
|
| 927 |
+
<div style={{ padding: "12px 14px", borderTop: "1px solid var(--border-1)", position: "relative", zIndex: 7 }}>
|
| 928 |
+
<Barcode value="AGT-09-NEXUS" width={120} height={16} />
|
| 929 |
+
</div>
|
| 930 |
+
</div>
|
| 931 |
+
</Punched>
|
| 932 |
+
);
|
| 933 |
+
};
|
| 934 |
+
|
| 935 |
+
// ── 22. FOOTER ─────────────────────────────────────────────────
|
| 936 |
+
const Footer = () => (
|
| 937 |
+
<Punched style={{ marginTop: 8 }}>
|
| 938 |
+
<div style={{
|
| 939 |
+
position: "relative", background: "var(--header)", borderTop: "1px solid var(--border-1)",
|
| 940 |
+
padding: "12px 24px", display: "flex", justifyContent: "space-between", alignItems: "center", overflow: "hidden"
|
| 941 |
+
}}>
|
| 942 |
+
<GrainOverlay strength={0.1} />
|
| 943 |
+
<div style={{ position: "relative", zIndex: 7 }}>
|
| 944 |
+
<Micro color="var(--g-700)">© NEXUS PROTOCOL — OMEGA DIVISION — CLEARANCE REQUIRED</Micro>
|
| 945 |
+
</div>
|
| 946 |
+
<div style={{ position: "relative", zIndex: 7 }}>
|
| 947 |
+
<Barcode value="NEXUS-FOOTER-SYS" width={100} height={14} />
|
| 948 |
+
</div>
|
| 949 |
+
<div style={{ position: "relative", zIndex: 7, display: "flex", gap: 12 }}>
|
| 950 |
+
<Micro color="var(--g-500)">v4.7.2</Micro>
|
| 951 |
+
<Micro color="var(--g-700)">BUILD-20470312</Micro>
|
| 952 |
+
</div>
|
| 953 |
+
</div>
|
| 954 |
+
</Punched>
|
| 955 |
+
);
|
| 956 |
+
|
| 957 |
+
// ══════════════════════════════════════════════════════════════
|
| 958 |
+
// DEMO PAGE
|
| 959 |
+
// ══════════════════════════════════════════════════════════════
|
| 960 |
+
export default function TicketPunchUIDemo() {
|
| 961 |
+
const [activeNavTab, setActiveNavTab] = useState("DASHBOARD");
|
| 962 |
+
const [activeSideItem, setActiveSideItem] = useState("DASHBOARD");
|
| 963 |
+
const [activeTab, setActiveTab] = useState(0);
|
| 964 |
+
const [toggleA, setToggleA] = useState(true);
|
| 965 |
+
const [toggleB, setToggleB] = useState(false);
|
| 966 |
+
const [modalOpen, setModalOpen] = useState(false);
|
| 967 |
+
const [selectVal, setSelectVal] = useState("");
|
| 968 |
+
const [inputVal, setInputVal] = useState("");
|
| 969 |
+
|
| 970 |
+
return (
|
| 971 |
+
<div style={{
|
| 972 |
+
fontFamily: "var(--font)", background: "var(--bg)", minHeight: "100vh",
|
| 973 |
+
color: "var(--text-primary)", position: "relative"
|
| 974 |
+
}}>
|
| 975 |
+
<FontInjector />
|
| 976 |
+
|
| 977 |
+
{/* Ambient gold glow */}
|
| 978 |
+
<div style={{
|
| 979 |
+
position: "fixed", top: "30%", left: "50%", transform: "translate(-50%,-50%)",
|
| 980 |
+
width: 600, height: 600, borderRadius: "50%",
|
| 981 |
+
background: "radial-gradient(circle, rgba(255,215,0,0.025) 0%, transparent 70%)",
|
| 982 |
+
pointerEvents: "none", zIndex: 0
|
| 983 |
+
}} />
|
| 984 |
+
|
| 985 |
+
<div style={{ display: "flex", flexDirection: "column", height: "100vh", overflow: "hidden" }}>
|
| 986 |
+
<Header activeTab={activeNavTab} setActiveTab={setActiveNavTab} />
|
| 987 |
+
<div style={{ display: "flex", flex: 1, overflow: "hidden" }}>
|
| 988 |
+
<Sidebar activeItem={activeSideItem} setActiveItem={setActiveSideItem} />
|
| 989 |
+
|
| 990 |
+
<div style={{ flex: 1, overflow: "auto", padding: 24 }}>
|
| 991 |
+
|
| 992 |
+
{/* Page Header */}
|
| 993 |
+
<div style={{ marginBottom: 28, display: "flex", justifyContent: "space-between", alignItems: "flex-start" }}>
|
| 994 |
+
<div>
|
| 995 |
+
<Breadcrumbs items={["NEXUS", "DASHBOARD", "COMPONENT LAB"]} />
|
| 996 |
+
<h1 style={{
|
| 997 |
+
fontSize: 22, fontWeight: 700, letterSpacing: "0.18em", textTransform: "uppercase",
|
| 998 |
+
fontFamily: "var(--font)", marginTop: 8, lineHeight: 1, color: "var(--w-100)"
|
| 999 |
+
}}>TICKETPUNCH UI</h1>
|
| 1000 |
+
<div style={{
|
| 1001 |
+
fontSize: 8, letterSpacing: "0.28em", color: "var(--g-700)", textTransform: "uppercase",
|
| 1002 |
+
fontFamily: "var(--font)", marginTop: 6
|
| 1003 |
+
}}>
|
| 1004 |
+
DESIGN SYSTEM — OMEGA CLASSIFICATION — BUILD 2047.03.12
|
| 1005 |
+
</div>
|
| 1006 |
+
</div>
|
| 1007 |
+
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
|
| 1008 |
+
<StatusPill status="active" label="ALL SYSTEMS GO" />
|
| 1009 |
+
<Button variant="outline" size="sm">EXPORT</Button>
|
| 1010 |
+
</div>
|
| 1011 |
+
</div>
|
| 1012 |
+
|
| 1013 |
+
{/* STAT CARDS */}
|
| 1014 |
+
<SectionLabel sub="// REAL-TIME METRICS — CLASSIFIED">SYSTEM OVERVIEW</SectionLabel>
|
| 1015 |
+
<div style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 12, marginBottom: 28 }}>
|
| 1016 |
+
<StatCard label="ACTIVE NODES" value="847" change="+12 TODAY" trend="up" />
|
| 1017 |
+
<StatCard label="THREAT LEVEL" value="2.7" unit="%" change="-0.4 DELTA" trend="down" />
|
| 1018 |
+
<StatCard label="DATA STREAMS" value="1.4" unit="TB" change="+0.2 TB" trend="up" />
|
| 1019 |
+
<StatCard label="UPTIME" value="99.9" unit="%" change="NOMINAL" trend="neutral" />
|
| 1020 |
+
</div>
|
| 1021 |
+
|
| 1022 |
+
{/* MAIN TICKET CARD */}
|
| 1023 |
+
<SectionLabel sub="// TICKET SHELL — PRIMARY CONTAINER">CLASSIFIED INTEL BRIEF</SectionLabel>
|
| 1024 |
+
<div style={{ marginBottom: 28 }}>
|
| 1025 |
+
<TicketShell stubHeight={68}>
|
| 1026 |
+
<div style={{ padding: "28px 24px 80px", position: "relative", zIndex: 8 }}>
|
| 1027 |
+
<div style={{ display: "flex", gap: 20, alignItems: "flex-start" }}>
|
| 1028 |
+
<TerminalMask size={100} />
|
| 1029 |
+
<div style={{ flex: 1 }}>
|
| 1030 |
+
<div style={{
|
| 1031 |
+
fontSize: 7, letterSpacing: "0.34em", color: "var(--g-700)",
|
| 1032 |
+
textTransform: "uppercase", fontFamily: "var(--font)", marginBottom: 6
|
| 1033 |
+
}}>
|
| 1034 |
+
// OMEGA-NEXUS // DIRECTIVE 7749 // CLASSIFIED
|
| 1035 |
+
</div>
|
| 1036 |
+
<h2 style={{
|
| 1037 |
+
fontSize: 18, fontWeight: 700, letterSpacing: "0.18em", textTransform: "uppercase",
|
| 1038 |
+
fontFamily: "var(--font)", marginBottom: 10, lineHeight: 1.2, color: "var(--g-300)"
|
| 1039 |
+
}}>
|
| 1040 |
+
OPERATION<br />BLACKOUT
|
| 1041 |
+
</h2>
|
| 1042 |
+
<p style={{
|
| 1043 |
+
fontSize: 8.5, color: "var(--w-500)", lineHeight: 1.8,
|
| 1044 |
+
letterSpacing: "0.12em", fontFamily: "var(--font)", maxWidth: 420
|
| 1045 |
+
}}>
|
| 1046 |
+
AUTHORIZED PERSONNEL ONLY. MISSION PARAMETERS LOCKED PENDING CLEARANCE VERIFICATION.
|
| 1047 |
+
ALL COMMUNICATION ENCRYPTED VIA NEXUS-PROTOCOL AES-512.
|
| 1048 |
+
</p>
|
| 1049 |
+
<div style={{ marginTop: 14, display: "flex", gap: 8, flexWrap: "wrap" }}>
|
| 1050 |
+
<Badge variant="omega">OMEGA-7</Badge>
|
| 1051 |
+
<Badge variant="danger">TOP SECRET</Badge>
|
| 1052 |
+
<Badge variant="warning">ACTIVE</Badge>
|
| 1053 |
+
<Badge>NEXUS-PROTOCOL</Badge>
|
| 1054 |
+
</div>
|
| 1055 |
+
</div>
|
| 1056 |
+
<div style={{ display: "flex", flexDirection: "column", gap: 10, alignItems: "flex-end" }}>
|
| 1057 |
+
<Barcode value="OP-7749-BX-OMEGA" width={130} height={36} />
|
| 1058 |
+
<Micro color="var(--g-700)">ISSUED: 2047.03.12</Micro>
|
| 1059 |
+
</div>
|
| 1060 |
+
</div>
|
| 1061 |
+
</div>
|
| 1062 |
+
<div style={{
|
| 1063 |
+
position: "absolute", bottom: 0, left: 0, right: 0, height: 68, padding: "0 24px",
|
| 1064 |
+
display: "flex", alignItems: "center", justifyContent: "space-between", zIndex: 8
|
| 1065 |
+
}}>
|
| 1066 |
+
<Micro color="var(--g-700)">REF: NEXUS-OP-7749</Micro>
|
| 1067 |
+
<div style={{ display: "flex", gap: 16 }}>
|
| 1068 |
+
<StatusPill status="active" label="MISSION ACTIVE" />
|
| 1069 |
+
<StatusPill status="warning" label="HIGH RISK" />
|
| 1070 |
+
</div>
|
| 1071 |
+
</div>
|
| 1072 |
+
</TicketShell>
|
| 1073 |
+
</div>
|
| 1074 |
+
|
| 1075 |
+
{/* TABS + TABLE */}
|
| 1076 |
+
<SectionLabel sub="// DATA MANAGEMENT INTERFACE">INTEL DATABASE</SectionLabel>
|
| 1077 |
+
<div style={{ marginBottom: 28 }}>
|
| 1078 |
+
<Card>
|
| 1079 |
+
<Tabs tabs={["AGENTS", "MISSIONS", "ASSETS", "THREATS"]} active={activeTab} onSelect={setActiveTab} />
|
| 1080 |
+
<div style={{ marginTop: 16 }}>
|
| 1081 |
+
<DataTable
|
| 1082 |
+
columns={["AGENT ID", "STATUS", "CLEARANCE", "ASSIGNMENT", "LAST ACTIVE"]}
|
| 1083 |
+
rows={[
|
| 1084 |
+
["AGT-001 SPECTRE", <Badge variant="active">ACTIVE</Badge>, "OMEGA", "OP-7749", "0:04:22 AGO"],
|
| 1085 |
+
["AGT-002 PHANTOM", <Badge variant="warning">STANDBY</Badge>, "DELTA", "OP-7700", "2:14:07 AGO"],
|
| 1086 |
+
["AGT-003 WRAITH", <Badge variant="danger">COMPROMISED</Badge>, "GAMMA", "—", "6:55:13 AGO"],
|
| 1087 |
+
["AGT-004 NEXUS", <Badge>OFFLINE</Badge>, "ALPHA", "OP-7801", "1D 04H AGO"],
|
| 1088 |
+
]}
|
| 1089 |
+
/>
|
| 1090 |
+
</div>
|
| 1091 |
+
</Card>
|
| 1092 |
+
</div>
|
| 1093 |
+
|
| 1094 |
+
{/* BUTTONS */}
|
| 1095 |
+
<SectionLabel sub="// INTERACTION PRIMITIVES">BUTTON SYSTEM</SectionLabel>
|
| 1096 |
+
<div style={{ marginBottom: 28 }}>
|
| 1097 |
+
<Card title="BUTTON VARIANTS" sub="ALL STATES + SIZES">
|
| 1098 |
+
<div style={{ display: "flex", flexWrap: "wrap", gap: 8, marginBottom: 16 }}>
|
| 1099 |
+
<Button variant="primary">EXECUTE</Button>
|
| 1100 |
+
<Button variant="secondary">STANDBY</Button>
|
| 1101 |
+
<Button variant="outline">CONFIRM</Button>
|
| 1102 |
+
<Button variant="ghost">DISMISS</Button>
|
| 1103 |
+
<Button variant="danger">ABORT</Button>
|
| 1104 |
+
<Button variant="success">SECURE</Button>
|
| 1105 |
+
<Button variant="primary" disabled>LOCKED</Button>
|
| 1106 |
+
<Button variant="outline" size="sm">SMALL</Button>
|
| 1107 |
+
<Button variant="primary" size="lg">LAUNCH SEQUENCE</Button>
|
| 1108 |
+
<Tooltip label="OMEGA CLASSIFIED FUNCTION">
|
| 1109 |
+
<Button variant="secondary" icon="◈">WITH ICON</Button>
|
| 1110 |
+
</Tooltip>
|
| 1111 |
+
</div>
|
| 1112 |
+
<Divider label="ICON VARIANTS" />
|
| 1113 |
+
<div style={{ display: "flex", gap: 8, marginTop: 12 }}>
|
| 1114 |
+
{["◈", "◉", "▣", "◆", "⊕"].map((icon, i) => (
|
| 1115 |
+
<Button key={i} variant="secondary" size="sm" icon={icon}>{icon === "⊕" ? "ADD" : ""}</Button>
|
| 1116 |
+
))}
|
| 1117 |
+
</div>
|
| 1118 |
+
</Card>
|
| 1119 |
+
</div>
|
| 1120 |
+
|
| 1121 |
+
{/* FORM ELEMENTS */}
|
| 1122 |
+
<SectionLabel sub="// DATA ENTRY SYSTEM">FORM ELEMENTS</SectionLabel>
|
| 1123 |
+
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16, marginBottom: 28 }}>
|
| 1124 |
+
<Card title="TEXT INPUTS" sub="TERMINAL STYLE">
|
| 1125 |
+
<div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
|
| 1126 |
+
<Input label="AGENT ID" placeholder="ENTER AGENT IDENTIFIER"
|
| 1127 |
+
value={inputVal} onChange={e => setInputVal(e.target.value)} hint="FORMAT: AGT-XXX-CLEARANCE" />
|
| 1128 |
+
<Input label="AUTHORIZATION CODE" placeholder="••••••••" type="password" />
|
| 1129 |
+
<Input label="COORDINATES" placeholder="00.000000, 00.000000" prefix="GEO" suffix="WGS84" />
|
| 1130 |
+
<Fieldset legend="COMMS CHANNEL">
|
| 1131 |
+
<Input label="FREQUENCY" placeholder="XXX.XXXXX MHZ" prefix="RF" />
|
| 1132 |
+
<Toggle checked={toggleA} onChange={() => setToggleA(!toggleA)} label="ENCRYPTION ENABLED" />
|
| 1133 |
+
</Fieldset>
|
| 1134 |
+
</div>
|
| 1135 |
+
</Card>
|
| 1136 |
+
<Card title="SELECTORS + TOGGLES" sub="CONTROL ELEMENTS">
|
| 1137 |
+
<div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
|
| 1138 |
+
<Select label="CLEARANCE LEVEL" options={["ALPHA", "BETA", "GAMMA", "DELTA", "OMEGA"]}
|
| 1139 |
+
value={selectVal} onChange={setSelectVal} />
|
| 1140 |
+
<Select label="MISSION STATUS" options={["ACTIVE", "STANDBY", "COMPLETED", "ABORT", "CLASSIFIED"]}
|
| 1141 |
+
value="" onChange={() => { }} />
|
| 1142 |
+
<Divider label="TOGGLES" />
|
| 1143 |
+
<Toggle checked={toggleA} onChange={() => setToggleA(!toggleA)} label="ACTIVE SURVEILLANCE" />
|
| 1144 |
+
<Toggle checked={toggleB} onChange={() => setToggleB(!toggleB)} label="STEALTH MODE" />
|
| 1145 |
+
<Toggle checked={true} onChange={() => { }} label="ENCRYPTION LAYER" />
|
| 1146 |
+
<Divider />
|
| 1147 |
+
<div>
|
| 1148 |
+
<Micro color="var(--g-500)">PROGRESS INDICATORS</Micro>
|
| 1149 |
+
<div style={{ marginTop: 10, display: "flex", flexDirection: "column", gap: 10 }}>
|
| 1150 |
+
<ProgressBar value={87} label="UPLINK SYNC" color="var(--g-300)" />
|
| 1151 |
+
<ProgressBar value={44} label="THREAT SCAN" color="var(--p-500)" />
|
| 1152 |
+
<ProgressBar value={12} label="BREACH RISK" color="var(--r-500)" />
|
| 1153 |
+
</div>
|
| 1154 |
+
</div>
|
| 1155 |
+
</div>
|
| 1156 |
+
</Card>
|
| 1157 |
+
</div>
|
| 1158 |
+
|
| 1159 |
+
{/* STATUS + BADGES */}
|
| 1160 |
+
<SectionLabel sub="// CLASSIFICATION LABELS">STATUS SYSTEM</SectionLabel>
|
| 1161 |
+
<div style={{ marginBottom: 28 }}>
|
| 1162 |
+
<Card title="BADGES + STATUS PILLS" sub="ALL VARIANTS">
|
| 1163 |
+
<div style={{ display: "flex", flexWrap: "wrap", gap: 8, marginBottom: 16 }}>
|
| 1164 |
+
<StatusPill status="active" />
|
| 1165 |
+
<StatusPill status="inactive" label="OFFLINE" />
|
| 1166 |
+
<StatusPill status="warning" label="STANDBY" />
|
| 1167 |
+
<StatusPill status="danger" label="BREACH" />
|
| 1168 |
+
<StatusPill status="pending" label="SYNCING" />
|
| 1169 |
+
</div>
|
| 1170 |
+
<Divider label="BADGES" />
|
| 1171 |
+
<div style={{ display: "flex", flexWrap: "wrap", gap: 6, marginTop: 12 }}>
|
| 1172 |
+
<Badge variant="omega">OMEGA</Badge>
|
| 1173 |
+
<Badge variant="active">ACTIVE</Badge>
|
| 1174 |
+
<Badge variant="danger">COMPROMISED</Badge>
|
| 1175 |
+
<Badge variant="warning">ALERT</Badge>
|
| 1176 |
+
<Badge>DEFAULT</Badge>
|
| 1177 |
+
<Badge>CLASSIFIED</Badge>
|
| 1178 |
+
<Badge>NEXUS</Badge>
|
| 1179 |
+
</div>
|
| 1180 |
+
</Card>
|
| 1181 |
+
</div>
|
| 1182 |
+
|
| 1183 |
+
{/* ACCORDION + STEPPER */}
|
| 1184 |
+
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16, marginBottom: 28 }}>
|
| 1185 |
+
<div>
|
| 1186 |
+
<SectionLabel sub="// COLLAPSIBLE SECTIONS">ACCORDION</SectionLabel>
|
| 1187 |
+
<Card>
|
| 1188 |
+
<AccordionItem title="MISSION PARAMETERS">
|
| 1189 |
+
PRIMARY OBJECTIVE: INFILTRATE NEXUS SIGMA NODE. SECONDARY: EXTRACT DATA PACKAGE DELTA-7.
|
| 1190 |
+
ABORT CODE: 4419-OMEGA. DURATION: 72H MAX.
|
| 1191 |
+
</AccordionItem>
|
| 1192 |
+
<AccordionItem title="AGENT DOSSIER">
|
| 1193 |
+
AGENT: SPECTRE-9 // RANK: SENIOR OPERATIVE // CLEARANCE: OMEGA //
|
| 1194 |
+
STATUS: FIELD ACTIVE // DEPLOYMENTS: 47 COMPLETED.
|
| 1195 |
+
</AccordionItem>
|
| 1196 |
+
<AccordionItem title="CLASSIFIED INTEL">
|
| 1197 |
+
[REDACTED — CLEARANCE LEVEL OMEGA REQUIRED. CONTACT DIVISION COMMANDER FOR ACCESS CREDENTIALS.]
|
| 1198 |
+
</AccordionItem>
|
| 1199 |
+
</Card>
|
| 1200 |
+
</div>
|
| 1201 |
+
<div>
|
| 1202 |
+
<SectionLabel sub="// MISSION PROGRESS WIZARD">STEPPER</SectionLabel>
|
| 1203 |
+
<Card>
|
| 1204 |
+
<Stepper steps={["BRIEF", "DEPLOY", "ACTIVE", "EXTRACT", "DEBRIEF"]} current={2} />
|
| 1205 |
+
<div style={{ marginTop: 20 }}>
|
| 1206 |
+
<Divider label="CURRENT PHASE" />
|
| 1207 |
+
<div style={{ marginTop: 12, padding: "12px", border: "1px solid var(--g-900)", position: "relative" }}>
|
| 1208 |
+
<div style={{
|
| 1209 |
+
fontSize: 7, letterSpacing: "0.28em", color: "var(--g-300)",
|
| 1210 |
+
fontFamily: "var(--font)", textTransform: "uppercase", marginBottom: 6
|
| 1211 |
+
}}>▶ PHASE 3: ACTIVE DEPLOYMENT</div>
|
| 1212 |
+
<div style={{ fontSize: 8, color: "var(--w-500)", lineHeight: 1.7, letterSpacing: "0.12em", fontFamily: "var(--font)" }}>
|
| 1213 |
+
AGENT SPECTRE-9 IS CURRENTLY ACTIVE IN THE FIELD. ALL VITALS NOMINAL.
|
| 1214 |
+
COMMS ENCRYPTED. ETA TO OBJECTIVE: 04:22:11.
|
| 1215 |
+
</div>
|
| 1216 |
+
</div>
|
| 1217 |
+
</div>
|
| 1218 |
+
</Card>
|
| 1219 |
+
</div>
|
| 1220 |
+
</div>
|
| 1221 |
+
|
| 1222 |
+
{/* ALERTS + LOADER */}
|
| 1223 |
+
<SectionLabel sub="// SYSTEM NOTIFICATIONS">ALERTS + LOADERS</SectionLabel>
|
| 1224 |
+
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16, marginBottom: 28 }}>
|
| 1225 |
+
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
| 1226 |
+
<Alert type="success" title="OPERATION SECURED"
|
| 1227 |
+
message="NEXUS NODE ALPHA SECURED. ALL OBJECTIVES COMPLETE. EXTRACTION CONFIRMED." />
|
| 1228 |
+
<Alert type="warning" title="SIGNAL INTERFERENCE"
|
| 1229 |
+
message="COMMS DEGRADED ON CHANNEL 7. SWITCHING TO BACKUP FREQUENCY 449.7750 MHZ." />
|
| 1230 |
+
<Alert type="danger" title="BREACH DETECTED"
|
| 1231 |
+
message="UNAUTHORIZED ACCESS ATTEMPT — NODE SIGMA. LOCKDOWN INITIATED. STANDBY." onClose={() => { }} />
|
| 1232 |
+
<Alert type="info" title="ROUTINE SCAN COMPLETE"
|
| 1233 |
+
message="ALL SYSTEMS NOMINAL. NEXT SCHEDULED SWEEP IN 04:00:00." />
|
| 1234 |
+
</div>
|
| 1235 |
+
<Card title="SYSTEM STATES" sub="LOADING INDICATORS">
|
| 1236 |
+
<div style={{ display: "flex", gap: 20, alignItems: "center", justifyContent: "center", padding: 20 }}>
|
| 1237 |
+
{[["SMALL", 28], ["MEDIUM", 44], ["LARGE", 64]].map(([lbl, sz]) => (
|
| 1238 |
+
<div key={lbl} style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 8 }}>
|
| 1239 |
+
<Spinner size={sz} /><Micro color="var(--g-500)">{lbl}</Micro>
|
| 1240 |
+
</div>
|
| 1241 |
+
))}
|
| 1242 |
+
</div>
|
| 1243 |
+
<Divider label="AVATARS" />
|
| 1244 |
+
<div style={{ display: "flex", gap: 10, marginTop: 12, alignItems: "center" }}>
|
| 1245 |
+
<Avatar initials="Ω" status="active" />
|
| 1246 |
+
<Avatar initials="Σ" status="active" />
|
| 1247 |
+
<Avatar initials="Δ" />
|
| 1248 |
+
<Avatar initials="Γ" />
|
| 1249 |
+
<div style={{ flex: 1 }}>
|
| 1250 |
+
<div style={{ fontSize: 8.5, letterSpacing: "0.16em", fontFamily: "var(--font)", color: "var(--g-300)" }}>OMEGA TEAM</div>
|
| 1251 |
+
<div style={{
|
| 1252 |
+
fontSize: 7, letterSpacing: "0.18em", color: "var(--g-700)", fontFamily: "var(--font)",
|
| 1253 |
+
textTransform: "uppercase", marginTop: 2
|
| 1254 |
+
}}>4 AGENTS // 2 ACTIVE</div>
|
| 1255 |
+
</div>
|
| 1256 |
+
</div>
|
| 1257 |
+
</Card>
|
| 1258 |
+
</div>
|
| 1259 |
+
|
| 1260 |
+
{/* MODAL TRIGGER */}
|
| 1261 |
+
<SectionLabel sub="// OVERLAY SYSTEM">MODAL SYSTEM</SectionLabel>
|
| 1262 |
+
<div style={{ marginBottom: 28 }}>
|
| 1263 |
+
<Card title="MODAL TRIGGER" sub="TICKET-SHELL OVERLAY">
|
| 1264 |
+
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
|
| 1265 |
+
<Button variant="outline" onClick={() => setModalOpen(true)}>OPEN CLASSIFIED BRIEF</Button>
|
| 1266 |
+
<Tooltip label="OPENS A FULL TICKET-SHELL MODAL">
|
| 1267 |
+
<Button variant="ghost" icon="◈" />
|
| 1268 |
+
</Tooltip>
|
| 1269 |
+
</div>
|
| 1270 |
+
</Card>
|
| 1271 |
+
</div>
|
| 1272 |
+
|
| 1273 |
+
{/* TYPOGRAPHY */}
|
| 1274 |
+
<SectionLabel sub="// IBM PLEX MONO TYPE SYSTEM">TYPOGRAPHY</SectionLabel>
|
| 1275 |
+
<div style={{ marginBottom: 28 }}>
|
| 1276 |
+
<Card title="TYPE SCALE + WEIGHTS">
|
| 1277 |
+
<div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
|
| 1278 |
+
{[
|
| 1279 |
+
{ size: 22, weight: 700, text: "DISPLAY — OMEGA PROTOCOL", color: "var(--g-300)" },
|
| 1280 |
+
{ size: 16, weight: 700, text: "H1 — CLASSIFIED DIRECTIVE", color: "var(--w-100)" },
|
| 1281 |
+
{ size: 13, weight: 600, text: "H2 — NEXUS SYSTEM HEADER", color: "var(--w-200)" },
|
| 1282 |
+
{ size: 11, weight: 600, text: "H3 — OPERATION BRIEFING", color: "var(--w-300)" },
|
| 1283 |
+
{ size: 9.5, weight: 500, text: "H4 — SUBHEADING ALPHA", color: "var(--w-300)" },
|
| 1284 |
+
{ size: 8.5, weight: 400, text: "BODY — STANDARD INTEL REPORT BODY TEXT FOR EXTENDED BRIEFINGS", color: "var(--w-400)" },
|
| 1285 |
+
{ size: 7.5, weight: 400, text: "SMALL — SECONDARY SUPPORT LABELS AND DESCRIPTORS", color: "var(--w-500)" },
|
| 1286 |
+
{ size: 7, weight: 400, text: "MICRO — OMEGA-7749 // COPYRIGHT NEXUS DIVISION // CLEARANCE REQUIRED", color: "var(--g-700)" },
|
| 1287 |
+
].map((t, i) => (
|
| 1288 |
+
<div key={i} style={{
|
| 1289 |
+
display: "flex", alignItems: "baseline", gap: 16,
|
| 1290 |
+
borderBottom: i < 7 ? "1px solid var(--border-1)" : "none", paddingBottom: 6
|
| 1291 |
+
}}>
|
| 1292 |
+
<span style={{ fontSize: 7, color: "var(--g-700)", width: 28, fontFamily: "var(--font)", letterSpacing: "0.1em" }}>{t.size}PX</span>
|
| 1293 |
+
<span style={{
|
| 1294 |
+
fontSize: t.size, fontWeight: t.weight, letterSpacing: "0.14em",
|
| 1295 |
+
textTransform: "uppercase", fontFamily: "var(--font)", color: t.color
|
| 1296 |
+
}}>{t.text}</span>
|
| 1297 |
+
</div>
|
| 1298 |
+
))}
|
| 1299 |
+
</div>
|
| 1300 |
+
</Card>
|
| 1301 |
+
</div>
|
| 1302 |
+
|
| 1303 |
+
{/* COLOR PALETTE */}
|
| 1304 |
+
<SectionLabel sub="// DESIGN TOKENS — COLOR SYSTEM">COLOR PALETTE</SectionLabel>
|
| 1305 |
+
<div style={{ marginBottom: 28 }}>
|
| 1306 |
+
<Card title="TOKEN PALETTE" sub="TICKETPUNCH DESIGN LANGUAGE">
|
| 1307 |
+
<div style={{ display: "grid", gridTemplateColumns: "repeat(5, 1fr)", gap: 8 }}>
|
| 1308 |
+
{[
|
| 1309 |
+
{ scheme: "DARK", name: "D-900", val: "#060606" },
|
| 1310 |
+
{ scheme: "DARK", name: "D-800", val: "#0A0A0A" },
|
| 1311 |
+
{ scheme: "DARK", name: "D-700", val: "#0f0f0f" },
|
| 1312 |
+
{ scheme: "DARK", name: "D-600", val: "#1e1e1e" },
|
| 1313 |
+
{ scheme: "DARK", name: "D-500", val: "#2a2a2a" },
|
| 1314 |
+
{ scheme: "WHITE", name: "W-100", val: "#FFFFFF" },
|
| 1315 |
+
{ scheme: "WHITE", name: "W-200", val: "#EFEFEF" },
|
| 1316 |
+
{ scheme: "WHITE", name: "W-300", val: "#CDCDCD" },
|
| 1317 |
+
{ scheme: "WHITE", name: "W-400", val: "#AAAAAA" },
|
| 1318 |
+
{ scheme: "WHITE", name: "W-500", val: "#888888" },
|
| 1319 |
+
{ scheme: "GOLD", name: "G-100", val: "#FFF4CC" },
|
| 1320 |
+
{ scheme: "GOLD", name: "G-300", val: "#FFD700" },
|
| 1321 |
+
{ scheme: "GOLD", name: "G-500", val: "#C8960C" },
|
| 1322 |
+
{ scheme: "GOLD", name: "G-700", val: "#7A5800" },
|
| 1323 |
+
{ scheme: "GOLD", name: "G-900", val: "#3D2C00" },
|
| 1324 |
+
{ scheme: "RED", name: "R-100", val: "#FFE5E5" },
|
| 1325 |
+
{ scheme: "RED", name: "R-300", val: "#FF6666" },
|
| 1326 |
+
{ scheme: "RED", name: "R-500", val: "#FF3333" },
|
| 1327 |
+
{ scheme: "RED", name: "R-700", val: "#CC0000" },
|
| 1328 |
+
{ scheme: "RED", name: "R-900", val: "#550000" },
|
| 1329 |
+
{ scheme: "PEACH", name: "P-100", val: "#FFF0EB" },
|
| 1330 |
+
{ scheme: "PEACH", name: "P-300", val: "#FFBFA8" },
|
| 1331 |
+
{ scheme: "PEACH", name: "P-500", val: "#FF8C69" },
|
| 1332 |
+
{ scheme: "PEACH", name: "P-700", val: "#C45A35" },
|
| 1333 |
+
{ scheme: "PEACH", name: "P-900", val: "#5C2010" },
|
| 1334 |
+
].map((c, i) => (
|
| 1335 |
+
<div key={i} style={{ display: "flex", flexDirection: "column", gap: 4 }}>
|
| 1336 |
+
{i % 5 === 0 && (
|
| 1337 |
+
<div style={{
|
| 1338 |
+
fontSize: 6.5, letterSpacing: "0.22em", color: "var(--w-400)",
|
| 1339 |
+
fontFamily: "var(--font)", textTransform: "uppercase", marginBottom: 2, fontWeight: 600
|
| 1340 |
+
}}>{c.scheme}</div>
|
| 1341 |
+
)}
|
| 1342 |
+
{i % 5 !== 0 && <div style={{ height: 15 }} />}
|
| 1343 |
+
<div style={{
|
| 1344 |
+
height: 32, background: c.val, border: "1px solid var(--border-1)",
|
| 1345 |
+
boxShadow: ["GOLD", "RED", "PEACH"].includes(c.scheme) ? `0 0 10px ${c.val}55` : "none"
|
| 1346 |
+
}} />
|
| 1347 |
+
<div style={{
|
| 1348 |
+
fontSize: 6.5, letterSpacing: "0.18em", color: "var(--w-500)",
|
| 1349 |
+
fontFamily: "var(--font)", textTransform: "uppercase"
|
| 1350 |
+
}}>{c.name}</div>
|
| 1351 |
+
<div style={{ fontSize: 6, letterSpacing: "0.12em", color: "var(--g-700)", fontFamily: "var(--font)" }}>{c.val}</div>
|
| 1352 |
+
</div>
|
| 1353 |
+
))}
|
| 1354 |
+
</div>
|
| 1355 |
+
</Card>
|
| 1356 |
+
</div>
|
| 1357 |
+
|
| 1358 |
+
{/* PRIMITIVES */}
|
| 1359 |
+
<SectionLabel sub="// CORE VISUAL PRIMITIVES">DESIGN PRIMITIVES</SectionLabel>
|
| 1360 |
+
<div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 16, marginBottom: 28 }}>
|
| 1361 |
+
<Card title="GRAIN OVERLAY" sub="STATIC NOISE TEXTURE" style={{ minHeight: 120 }}>
|
| 1362 |
+
<div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
|
| 1363 |
+
{[0.08, 0.18, 0.28].map((s, i) => (
|
| 1364 |
+
<div key={i} style={{ position: "relative", height: 28, background: "var(--header)", border: "1px solid var(--border-1)" }}>
|
| 1365 |
+
<GrainOverlay strength={s} />
|
| 1366 |
+
<div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", zIndex: 7 }}>
|
| 1367 |
+
<Micro color="var(--g-500)">{(s * 100).toFixed(0)}% STRENGTH</Micro>
|
| 1368 |
+
</div>
|
| 1369 |
+
</div>
|
| 1370 |
+
))}
|
| 1371 |
+
</div>
|
| 1372 |
+
</Card>
|
| 1373 |
+
<Card title="BARCODES" sub="GENERATIVE PATTERN">
|
| 1374 |
+
<div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
|
| 1375 |
+
<Barcode value="OMEGA-7749-XX" width={160} height={32} />
|
| 1376 |
+
<Barcode value="NEXUS-ALPHA-01" width={160} height={20} />
|
| 1377 |
+
<Barcode value="CLASSIFIED" width={160} height={14} />
|
| 1378 |
+
</div>
|
| 1379 |
+
</Card>
|
| 1380 |
+
<Card title="TERMINAL MASK" sub="SCROLLING INTEL FEED">
|
| 1381 |
+
<div style={{ display: "flex", gap: 12, justifyContent: "center" }}>
|
| 1382 |
+
<TerminalMask size={90} /><TerminalMask size={70} />
|
| 1383 |
+
</div>
|
| 1384 |
+
</Card>
|
| 1385 |
+
</div>
|
| 1386 |
+
|
| 1387 |
+
<Footer />
|
| 1388 |
+
</div>
|
| 1389 |
+
</div>
|
| 1390 |
+
</div>
|
| 1391 |
+
|
| 1392 |
+
<Modal open={modalOpen} onClose={() => setModalOpen(false)} title="CLASSIFIED DIRECTIVE">
|
| 1393 |
+
<p style={{ marginBottom: 12 }}>
|
| 1394 |
+
THIS DOCUMENT CONTAINS INFORMATION OF THE HIGHEST CLASSIFICATION.
|
| 1395 |
+
UNAUTHORIZED DISCLOSURE IS A VIOLATION OF NEXUS PROTOCOL ARTICLE 7-OMEGA.
|
| 1396 |
+
</p>
|
| 1397 |
+
<p>
|
| 1398 |
+
OPERATION BLACKOUT IS AUTHORIZED BY DIVISION COMMANDER NEXUS-1.
|
| 1399 |
+
ALL AGENTS MUST CONFIRM RECEIPT VIA SECURE CHANNEL BEFORE DEPLOYMENT.
|
| 1400 |
+
</p>
|
| 1401 |
+
<div style={{ marginTop: 16 }}>
|
| 1402 |
+
<Barcode value="DIRECTIVE-4419-OMEGA" width={160} height={24} />
|
| 1403 |
+
</div>
|
| 1404 |
+
</Modal>
|
| 1405 |
+
</div>
|
| 1406 |
+
);
|
| 1407 |
+
}
|
CyberTicket.jsx
ADDED
|
@@ -0,0 +1,646 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useState, useEffect, useRef } from "react";
|
| 2 |
+
|
| 3 |
+
const FONT_URL = "https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap";
|
| 4 |
+
|
| 5 |
+
const TERMINAL_LINES = [
|
| 6 |
+
"INIT SEQUENCE 0x4F2A...",
|
| 7 |
+
"AUTH TOKEN: ██████████████",
|
| 8 |
+
"SCAN FREQ 847.221 MHz",
|
| 9 |
+
"SECTOR [CLASSIFIED]",
|
| 10 |
+
"NODE_ID NX-9912-DELTA",
|
| 11 |
+
"TIMESTAMP 2025.03.06",
|
| 12 |
+
"CLEARANCE LEVEL-OMEGA",
|
| 13 |
+
"STATUS AUTHORIZED",
|
| 14 |
+
"PAYLOAD ENCRYPTED",
|
| 15 |
+
"UPLINK ACTIVE",
|
| 16 |
+
"SIGNAL ████░░░░ 61%",
|
| 17 |
+
"VERIFY SHA256:c0d3f",
|
| 18 |
+
"MATRIX 7×7 LATTICE",
|
| 19 |
+
"HASH 0xDEADBEEF",
|
| 20 |
+
"PING 12.4ms OK",
|
| 21 |
+
"TRACE DISABLED",
|
| 22 |
+
"VAULT SEALED",
|
| 23 |
+
"PROTOCOL OMEGA-9",
|
| 24 |
+
"ENTROPY HIGH",
|
| 25 |
+
"FRAME #00441",
|
| 26 |
+
"BUFFER FLUSHED",
|
| 27 |
+
"CIPHER AES-256",
|
| 28 |
+
"RELAY PROXIED",
|
| 29 |
+
"ECHO SILENT",
|
| 30 |
+
"LOCK ENGAGED",
|
| 31 |
+
"GRID REF 44.0N 76.2W",
|
| 32 |
+
"KERNEL 3.14.159",
|
| 33 |
+
"DAEMON RUNNING",
|
| 34 |
+
"WATCHDOG ARMED",
|
| 35 |
+
"ROUTE OBFUSCATED",
|
| 36 |
+
];
|
| 37 |
+
|
| 38 |
+
// ── Noise / grain canvas ──────────────────────────────────────────────────────
|
| 39 |
+
function GrainOverlay({ strength = 0.18 }) {
|
| 40 |
+
const canvasRef = useRef();
|
| 41 |
+
const tick = useRef(0);
|
| 42 |
+
|
| 43 |
+
useEffect(() => {
|
| 44 |
+
const canvas = canvasRef.current;
|
| 45 |
+
if (!canvas) return;
|
| 46 |
+
const ctx = canvas.getContext("2d");
|
| 47 |
+
let raf;
|
| 48 |
+
|
| 49 |
+
const draw = () => {
|
| 50 |
+
tick.current++;
|
| 51 |
+
if (tick.current % 2 !== 0) { raf = requestAnimationFrame(draw); return; }
|
| 52 |
+
const { width, height } = canvas;
|
| 53 |
+
const img = ctx.createImageData(width, height);
|
| 54 |
+
const d = img.data;
|
| 55 |
+
for (let i = 0; i < d.length; i += 4) {
|
| 56 |
+
const px = i / 4;
|
| 57 |
+
const x = px % width;
|
| 58 |
+
const y = Math.floor(px / width);
|
| 59 |
+
const cx = x / width - 0.5, cy = y / height - 0.5;
|
| 60 |
+
const dist = Math.sqrt(cx * cx + cy * cy);
|
| 61 |
+
const vignette = Math.min(1, dist * 2.2);
|
| 62 |
+
const noise = (Math.random() - 0.5) * 255 * (strength + vignette * 0.18);
|
| 63 |
+
d[i] = d[i + 1] = d[i + 2] = 128 + noise;
|
| 64 |
+
d[i + 3] = Math.floor(18 + vignette * 30);
|
| 65 |
+
}
|
| 66 |
+
ctx.putImageData(img, 0, 0);
|
| 67 |
+
raf = requestAnimationFrame(draw);
|
| 68 |
+
};
|
| 69 |
+
|
| 70 |
+
const resize = () => {
|
| 71 |
+
canvas.width = canvas.offsetWidth;
|
| 72 |
+
canvas.height = canvas.offsetHeight;
|
| 73 |
+
};
|
| 74 |
+
resize();
|
| 75 |
+
draw();
|
| 76 |
+
window.addEventListener("resize", resize);
|
| 77 |
+
return () => { cancelAnimationFrame(raf); window.removeEventListener("resize", resize); };
|
| 78 |
+
}, [strength]);
|
| 79 |
+
|
| 80 |
+
return (
|
| 81 |
+
<canvas
|
| 82 |
+
ref={canvasRef}
|
| 83 |
+
style={{
|
| 84 |
+
position: "absolute", inset: 0, width: "100%", height: "100%",
|
| 85 |
+
pointerEvents: "none", mixBlendMode: "overlay", zIndex: 10,
|
| 86 |
+
}}
|
| 87 |
+
/>
|
| 88 |
+
);
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
// ── SVG geometric overlay ─────────────────────────────────────────────────────
|
| 92 |
+
function GeometricOverlay() {
|
| 93 |
+
return (
|
| 94 |
+
<svg
|
| 95 |
+
viewBox="0 0 480 860"
|
| 96 |
+
style={{
|
| 97 |
+
position: "absolute", inset: 0, width: "100%", height: "100%",
|
| 98 |
+
pointerEvents: "none", zIndex: 5, opacity: 0.08,
|
| 99 |
+
}}
|
| 100 |
+
preserveAspectRatio="none"
|
| 101 |
+
>
|
| 102 |
+
<polygon points="0,0 72,0 0,72" fill="none" stroke="white" strokeWidth="0.8" />
|
| 103 |
+
<polygon points="480,0 408,0 480,72" fill="none" stroke="white" strokeWidth="0.8" />
|
| 104 |
+
<polygon points="0,860 72,860 0,788" fill="none" stroke="white" strokeWidth="0.8" />
|
| 105 |
+
<polygon points="480,860 408,860 480,788" fill="none" stroke="white" strokeWidth="0.8" />
|
| 106 |
+
{[0.2, 0.4, 0.6, 0.8].map((t) => (
|
| 107 |
+
<line key={t} x1={480 * t} y1="0" x2={480 * t} y2="860" stroke="white" strokeWidth="0.4" />
|
| 108 |
+
))}
|
| 109 |
+
{[0.25, 0.5, 0.75].map((t) => (
|
| 110 |
+
<line key={t} x1="0" y1={860 * t} x2="480" y2={860 * t} stroke="white" strokeWidth="0.4" />
|
| 111 |
+
))}
|
| 112 |
+
<line x1="240" y1="430" x2="0" y2="0" stroke="white" strokeWidth="0.3" />
|
| 113 |
+
<line x1="240" y1="430" x2="480" y2="0" stroke="white" strokeWidth="0.3" />
|
| 114 |
+
<line x1="240" y1="430" x2="0" y2="860" stroke="white" strokeWidth="0.3" />
|
| 115 |
+
<line x1="240" y1="430" x2="480" y2="860" stroke="white" strokeWidth="0.3" />
|
| 116 |
+
<circle cx="240" cy="430" r="200" fill="none" stroke="white" strokeWidth="0.5" />
|
| 117 |
+
</svg>
|
| 118 |
+
);
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
// ── Terminal code cascade ─────────────────────────────────────────────────────
|
| 122 |
+
function TerminalMask() {
|
| 123 |
+
const [offset, setOffset] = useState(0);
|
| 124 |
+
const [glitch, setGlitch] = useState({ row: -1, px: 0 });
|
| 125 |
+
|
| 126 |
+
useEffect(() => {
|
| 127 |
+
const iv = setInterval(() => {
|
| 128 |
+
setOffset((o) => (o + 1) % TERMINAL_LINES.length);
|
| 129 |
+
}, 120);
|
| 130 |
+
const gv = setInterval(() => {
|
| 131 |
+
setGlitch({ row: Math.floor(Math.random() * 18), px: (Math.random() - 0.5) * 8 });
|
| 132 |
+
setTimeout(() => setGlitch({ row: -1, px: 0 }), 80);
|
| 133 |
+
}, 900);
|
| 134 |
+
return () => { clearInterval(iv); clearInterval(gv); };
|
| 135 |
+
}, []);
|
| 136 |
+
|
| 137 |
+
const visible = Array.from({ length: 20 }, (_, i) =>
|
| 138 |
+
TERMINAL_LINES[(offset + i) % TERMINAL_LINES.length]
|
| 139 |
+
);
|
| 140 |
+
|
| 141 |
+
return (
|
| 142 |
+
<div style={{
|
| 143 |
+
width: "100%", height: "100%",
|
| 144 |
+
background: "#000",
|
| 145 |
+
borderRadius: "50%",
|
| 146 |
+
overflow: "hidden",
|
| 147 |
+
display: "flex",
|
| 148 |
+
flexDirection: "column",
|
| 149 |
+
justifyContent: "center",
|
| 150 |
+
padding: "18px 14px",
|
| 151 |
+
gap: 0,
|
| 152 |
+
position: "relative",
|
| 153 |
+
}}>
|
| 154 |
+
<div style={{
|
| 155 |
+
position: "absolute", inset: 0, borderRadius: "50%",
|
| 156 |
+
background: "radial-gradient(ellipse at center, transparent 30%, rgba(0,0,0,0.72) 100%)",
|
| 157 |
+
zIndex: 2, pointerEvents: "none",
|
| 158 |
+
}} />
|
| 159 |
+
{visible.map((line, i) => (
|
| 160 |
+
<div key={i} style={{
|
| 161 |
+
fontFamily: "'IBM Plex Mono', monospace",
|
| 162 |
+
fontSize: "9.5px",
|
| 163 |
+
fontWeight: i === 0 ? 700 : 400,
|
| 164 |
+
letterSpacing: "0.08em",
|
| 165 |
+
color: i === 0 ? "#FFFFFF" : i < 3 ? "#D8D8D8" : "#888",
|
| 166 |
+
lineHeight: "1.55",
|
| 167 |
+
opacity: i === 0 ? 1 : Math.max(0.18, 1 - i * 0.045),
|
| 168 |
+
transform: glitch.row === i ? `translateX(${glitch.px}px)` : "none",
|
| 169 |
+
whiteSpace: "nowrap",
|
| 170 |
+
overflow: "hidden",
|
| 171 |
+
textOverflow: "ellipsis",
|
| 172 |
+
transition: "transform 0.04s",
|
| 173 |
+
position: "relative",
|
| 174 |
+
zIndex: 3,
|
| 175 |
+
}}>
|
| 176 |
+
{i === 0 ? "▶ " : " "}{line}
|
| 177 |
+
</div>
|
| 178 |
+
))}
|
| 179 |
+
</div>
|
| 180 |
+
);
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
// ── Barcode SVG ───────────────────────────────────────────────────────────────
|
| 184 |
+
function Barcode() {
|
| 185 |
+
const bars = Array.from({ length: 60 }, (_, i) => {
|
| 186 |
+
const w = [1, 1, 2, 1, 3, 1, 2, 1, 1, 2][i % 10];
|
| 187 |
+
return { w, gap: [2, 1, 2, 3, 1, 2, 1, 2, 3, 1][i % 10] };
|
| 188 |
+
});
|
| 189 |
+
return (
|
| 190 |
+
<svg width="160" height="36" viewBox="0 0 160 36">
|
| 191 |
+
{bars.reduce((acc, bar, i) => {
|
| 192 |
+
const x = acc.x;
|
| 193 |
+
acc.elements.push(
|
| 194 |
+
<rect key={i} x={x} y={2} width={bar.w} height={28} fill="white" />
|
| 195 |
+
);
|
| 196 |
+
acc.x += bar.w + bar.gap;
|
| 197 |
+
return acc;
|
| 198 |
+
}, { x: 2, elements: [] }).elements}
|
| 199 |
+
</svg>
|
| 200 |
+
);
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
// ── TICKET SHELL with authentic physical edges ────────────────────────────────
|
| 204 |
+
// Renders the outer ticket shape as a single SVG clip-path with:
|
| 205 |
+
// • Scalloped top & bottom edges (zigzag perforations)
|
| 206 |
+
// • Semicircle punch-outs on both sides at the tear line
|
| 207 |
+
// • Punched corner rounds
|
| 208 |
+
// • A perforated dashed-border inner stroke
|
| 209 |
+
function TicketShell({ children, stubHeight = 120 }) {
|
| 210 |
+
const W = 460; // SVG viewBox width
|
| 211 |
+
const H = 820; // SVG viewBox height
|
| 212 |
+
const R = 14; // corner radius
|
| 213 |
+
const PR = 9; // top/bottom diamond tooth size
|
| 214 |
+
const SR = 11; // side diamond notch size (tear notch)
|
| 215 |
+
const tearY = H - stubHeight; // Y where tear line sits
|
| 216 |
+
|
| 217 |
+
// Diamond zigzag teeth left→right: dir=-1 teeth UP, dir=+1 teeth DOWN
|
| 218 |
+
const zigzagLR = (x0, x1, y, dir) => {
|
| 219 |
+
const count = Math.floor((x1 - x0) / (PR * 2.2));
|
| 220 |
+
const step = (x1 - x0) / count;
|
| 221 |
+
let d = "";
|
| 222 |
+
for (let i = 0; i < count; i++) {
|
| 223 |
+
const mx = x0 + i * step + step / 2;
|
| 224 |
+
const ex = x0 + (i + 1) * step;
|
| 225 |
+
d += ` L ${mx} ${y + dir * PR} L ${ex} ${y}`;
|
| 226 |
+
}
|
| 227 |
+
return d;
|
| 228 |
+
};
|
| 229 |
+
|
| 230 |
+
// Diamond zigzag teeth right→left (bottom edge return)
|
| 231 |
+
const zigzagRL = (x0, x1, y, dir) => {
|
| 232 |
+
const count = Math.floor((x1 - x0) / (PR * 2.2));
|
| 233 |
+
const step = (x1 - x0) / count;
|
| 234 |
+
let d = "";
|
| 235 |
+
for (let i = count - 1; i >= 0; i--) {
|
| 236 |
+
const mx = x0 + i * step + step / 2;
|
| 237 |
+
const ex = x0 + i * step;
|
| 238 |
+
d += ` L ${mx} ${y + dir * PR} L ${ex} ${y}`;
|
| 239 |
+
}
|
| 240 |
+
return d;
|
| 241 |
+
};
|
| 242 |
+
|
| 243 |
+
const pad = 2;
|
| 244 |
+
const x0 = pad + R, x1 = W - pad - R;
|
| 245 |
+
const y0 = pad, y1 = H - pad;
|
| 246 |
+
|
| 247 |
+
// Right side semicircle at tearY (bites into card from right)
|
| 248 |
+
// Left side semicircle at tearY (bites into card from left)
|
| 249 |
+
const rightX = W - pad;
|
| 250 |
+
const leftX = pad;
|
| 251 |
+
|
| 252 |
+
// Build SVG path for clip
|
| 253 |
+
const path = [
|
| 254 |
+
`M ${x0} ${y0}`,
|
| 255 |
+
// top diamond zigzag edge →
|
| 256 |
+
zigzagLR(x0, x1, y0, -1),
|
| 257 |
+
// top-right corner
|
| 258 |
+
`L ${W - pad - R} ${y0}`,
|
| 259 |
+
`Q ${W - pad} ${y0} ${W - pad} ${y0 + R}`,
|
| 260 |
+
// right side down to tear notch
|
| 261 |
+
`L ${rightX} ${tearY - SR}`,
|
| 262 |
+
// semi-diamond bite: two lines forming a V-point inward
|
| 263 |
+
`L ${rightX - SR} ${tearY}`,
|
| 264 |
+
`L ${rightX} ${tearY + SR}`,
|
| 265 |
+
// right side continues down
|
| 266 |
+
`L ${rightX} ${y1 - R}`,
|
| 267 |
+
// bottom-right corner
|
| 268 |
+
`Q ${rightX} ${y1} ${x1} ${y1}`,
|
| 269 |
+
// bottom diamond zigzag edge ←
|
| 270 |
+
`L ${x1} ${y1}`,
|
| 271 |
+
zigzagRL(x0, x1, y1, 1),
|
| 272 |
+
// bottom-left corner
|
| 273 |
+
`L ${x0} ${y1}`,
|
| 274 |
+
`Q ${leftX} ${y1} ${leftX} ${y1 - R}`,
|
| 275 |
+
// left side up from bottom to tear notch
|
| 276 |
+
`L ${leftX} ${tearY + SR}`,
|
| 277 |
+
// semi-diamond bite: two lines forming a V-point inward from left
|
| 278 |
+
`L ${leftX + SR} ${tearY}`,
|
| 279 |
+
`L ${leftX} ${tearY - SR}`,
|
| 280 |
+
// left side up to top
|
| 281 |
+
`L ${leftX} ${y0 + R}`,
|
| 282 |
+
// top-left corner
|
| 283 |
+
`Q ${leftX} ${y0} ${x0} ${y0}`,
|
| 284 |
+
`Z`,
|
| 285 |
+
].join(" ");
|
| 286 |
+
|
| 287 |
+
const clipId = "ticket-clip";
|
| 288 |
+
const innerId = "ticket-inner";
|
| 289 |
+
|
| 290 |
+
return (
|
| 291 |
+
<div style={{ position: "relative", width: "100%", maxWidth: 460 }}>
|
| 292 |
+
{/* SVG defs for clip path */}
|
| 293 |
+
<svg width="0" height="0" style={{ position: "absolute" }}>
|
| 294 |
+
<defs>
|
| 295 |
+
<clipPath id={clipId} clipPathUnits="userSpaceOnUse">
|
| 296 |
+
<path d={path} />
|
| 297 |
+
</clipPath>
|
| 298 |
+
</defs>
|
| 299 |
+
</svg>
|
| 300 |
+
|
| 301 |
+
{/* Outer shadow/glow layer */}
|
| 302 |
+
<div style={{
|
| 303 |
+
position: "absolute",
|
| 304 |
+
inset: -2,
|
| 305 |
+
borderRadius: 18,
|
| 306 |
+
boxShadow: "0 40px 100px rgba(0,0,0,0.95), 0 0 0 1px #1a1a1a",
|
| 307 |
+
pointerEvents: "none",
|
| 308 |
+
zIndex: 0,
|
| 309 |
+
}} />
|
| 310 |
+
|
| 311 |
+
{/* The actual ticket content clipped to shape */}
|
| 312 |
+
<div
|
| 313 |
+
style={{
|
| 314 |
+
position: "relative",
|
| 315 |
+
width: "100%",
|
| 316 |
+
clipPath: `url(#${clipId})`,
|
| 317 |
+
// Fallback border for browsers without clipPath
|
| 318 |
+
}}
|
| 319 |
+
>
|
| 320 |
+
{/* SVG border drawn on top of content */}
|
| 321 |
+
<svg
|
| 322 |
+
viewBox={`0 0 ${W} ${H}`}
|
| 323 |
+
width="100%"
|
| 324 |
+
style={{
|
| 325 |
+
position: "absolute", inset: 0, width: "100%", height: "100%",
|
| 326 |
+
pointerEvents: "none", zIndex: 50,
|
| 327 |
+
}}
|
| 328 |
+
preserveAspectRatio="none"
|
| 329 |
+
>
|
| 330 |
+
{/* Outer border */}
|
| 331 |
+
<path
|
| 332 |
+
d={path}
|
| 333 |
+
fill="none"
|
| 334 |
+
stroke="#2e2e2e"
|
| 335 |
+
strokeWidth="1.2"
|
| 336 |
+
/>
|
| 337 |
+
{/* Inner inset border */}
|
| 338 |
+
<path
|
| 339 |
+
d={path}
|
| 340 |
+
fill="none"
|
| 341 |
+
stroke="#1e1e1e"
|
| 342 |
+
strokeWidth="3"
|
| 343 |
+
strokeDasharray="4 3"
|
| 344 |
+
opacity="0.5"
|
| 345 |
+
/>
|
| 346 |
+
|
| 347 |
+
{/* Tear-line dashed rule */}
|
| 348 |
+
<line
|
| 349 |
+
x1={pad + SR + 4} y1={tearY}
|
| 350 |
+
x2={W - pad - SR - 4} y2={tearY}
|
| 351 |
+
stroke="#2a2a2a"
|
| 352 |
+
strokeWidth="1"
|
| 353 |
+
strokeDasharray="5 4"
|
| 354 |
+
/>
|
| 355 |
+
{/* Tear scissors icon hint */}
|
| 356 |
+
<text x={W / 2 - 10} y={tearY - 5} fill="#333" fontSize="9" fontFamily="monospace" textAnchor="middle">✂ TEAR</text>
|
| 357 |
+
|
| 358 |
+
{/* Corner punch holes — diamond arrowhead shapes */}
|
| 359 |
+
{[
|
| 360 |
+
[pad + 28, pad + 28],
|
| 361 |
+
[W - pad - 28, pad + 28],
|
| 362 |
+
[pad + 28, H - pad - 28],
|
| 363 |
+
[W - pad - 28, H - pad - 28],
|
| 364 |
+
].map(([cx, cy], i) => {
|
| 365 |
+
const ro = 9;
|
| 366 |
+
const ri = 4.5;
|
| 367 |
+
return (
|
| 368 |
+
<g key={i}>
|
| 369 |
+
<polygon
|
| 370 |
+
points={`${cx},${cy - ro} ${cx + ro},${cy} ${cx},${cy + ro} ${cx - ro},${cy}`}
|
| 371 |
+
fill="#0A0A0A" stroke="#2e2e2e" strokeWidth="1"
|
| 372 |
+
/>
|
| 373 |
+
<polygon
|
| 374 |
+
points={`${cx},${cy - ri} ${cx + ri},${cy} ${cx},${cy + ri} ${cx - ri},${cy}`}
|
| 375 |
+
fill="none" stroke="#252525" strokeWidth="0.8"
|
| 376 |
+
/>
|
| 377 |
+
</g>
|
| 378 |
+
);
|
| 379 |
+
})}
|
| 380 |
+
|
| 381 |
+
{/* Perforation diamond dots along tear line */}
|
| 382 |
+
{Array.from({ length: 18 }, (_, i) => {
|
| 383 |
+
const spacing = (W - pad * 2 - SR * 2 - 20) / 17;
|
| 384 |
+
const x = pad + SR + 10 + i * spacing;
|
| 385 |
+
const r = 2.2;
|
| 386 |
+
return (
|
| 387 |
+
<polygon
|
| 388 |
+
key={i}
|
| 389 |
+
points={`${x},${tearY - r} ${x + r},${tearY} ${x},${tearY + r} ${x - r},${tearY}`}
|
| 390 |
+
fill="#252525"
|
| 391 |
+
/>
|
| 392 |
+
);
|
| 393 |
+
})}
|
| 394 |
+
</svg>
|
| 395 |
+
|
| 396 |
+
{children}
|
| 397 |
+
</div>
|
| 398 |
+
</div>
|
| 399 |
+
);
|
| 400 |
+
}
|
| 401 |
+
|
| 402 |
+
// ── Main component ────────────────────────────────────────────────────────────
|
| 403 |
+
export default function CyberTicket() {
|
| 404 |
+
const [pulse, setPulse] = useState(false);
|
| 405 |
+
|
| 406 |
+
useEffect(() => {
|
| 407 |
+
if (!document.getElementById("ibm-plex-mono")) {
|
| 408 |
+
const link = document.createElement("link");
|
| 409 |
+
link.id = "ibm-plex-mono";
|
| 410 |
+
link.rel = "stylesheet";
|
| 411 |
+
link.href = FONT_URL;
|
| 412 |
+
document.head.appendChild(link);
|
| 413 |
+
}
|
| 414 |
+
const iv = setInterval(() => setPulse((p) => !p), 1400);
|
| 415 |
+
return () => clearInterval(iv);
|
| 416 |
+
}, []);
|
| 417 |
+
|
| 418 |
+
const STUB_H = 130; // px — must roughly match SVG tearY portion
|
| 419 |
+
|
| 420 |
+
return (
|
| 421 |
+
<div
|
| 422 |
+
style={{
|
| 423 |
+
minHeight: "100vh",
|
| 424 |
+
background: "#060606",
|
| 425 |
+
display: "flex",
|
| 426 |
+
alignItems: "center",
|
| 427 |
+
justifyContent: "center",
|
| 428 |
+
padding: "60px 16px",
|
| 429 |
+
fontFamily: "'IBM Plex Mono', monospace",
|
| 430 |
+
position: "relative",
|
| 431 |
+
overflow: "hidden",
|
| 432 |
+
}}
|
| 433 |
+
>
|
| 434 |
+
{/* ambient radial glow */}
|
| 435 |
+
<div style={{
|
| 436 |
+
position: "absolute",
|
| 437 |
+
width: 600, height: 600,
|
| 438 |
+
background: "radial-gradient(circle, rgba(255,255,255,0.015) 0%, transparent 70%)",
|
| 439 |
+
left: "50%", top: "50%",
|
| 440 |
+
transform: "translate(-50%,-50%)",
|
| 441 |
+
pointerEvents: "none",
|
| 442 |
+
}} />
|
| 443 |
+
<GrainOverlay strength={0.13} />
|
| 444 |
+
|
| 445 |
+
<TicketShell stubHeight={130}>
|
| 446 |
+
{/* ── background fill */}
|
| 447 |
+
<div style={{
|
| 448 |
+
background: "#0A0A0A",
|
| 449 |
+
minHeight: 820,
|
| 450 |
+
position: "relative",
|
| 451 |
+
overflow: "hidden",
|
| 452 |
+
}}>
|
| 453 |
+
<GrainOverlay strength={0.2} />
|
| 454 |
+
<GeometricOverlay />
|
| 455 |
+
|
| 456 |
+
{/* 1 ── TOP HEADER BAR */}
|
| 457 |
+
<div style={{
|
| 458 |
+
background: "#0f0f0f",
|
| 459 |
+
borderBottom: "1px solid #1e1e1e",
|
| 460 |
+
padding: "16px 28px",
|
| 461 |
+
display: "flex",
|
| 462 |
+
alignItems: "center",
|
| 463 |
+
justifyContent: "space-between",
|
| 464 |
+
position: "relative",
|
| 465 |
+
zIndex: 20,
|
| 466 |
+
marginTop: 14, // clear top scallop
|
| 467 |
+
}}>
|
| 468 |
+
<div>
|
| 469 |
+
<div style={{
|
| 470 |
+
fontSize: 9, fontWeight: 700, letterSpacing: "0.32em",
|
| 471 |
+
color: "#FFF", textTransform: "uppercase",
|
| 472 |
+
}}>NEXUS PROTOCOL</div>
|
| 473 |
+
<div style={{ fontSize: 7.5, color: "#555", letterSpacing: "0.18em", marginTop: 1 }}>
|
| 474 |
+
CLASSIFIED ACCESS DOCUMENT
|
| 475 |
+
</div>
|
| 476 |
+
</div>
|
| 477 |
+
<div style={{ textAlign: "right" }}>
|
| 478 |
+
<div style={{ fontSize: 9, color: "#555", letterSpacing: "0.1em" }}>Ω-09</div>
|
| 479 |
+
<div style={{ fontSize: 7, color: "#333", marginTop: 1 }}>© 2025</div>
|
| 480 |
+
</div>
|
| 481 |
+
</div>
|
| 482 |
+
|
| 483 |
+
{/* 2 ── BRANDING BLOCK */}
|
| 484 |
+
<div style={{ padding: "22px 32px 14px", position: "relative", zIndex: 20 }}>
|
| 485 |
+
<div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 6 }}>
|
| 486 |
+
<svg width="28" height="28" viewBox="0 0 28 28" fill="none">
|
| 487 |
+
<circle cx="14" cy="14" r="13" stroke="white" strokeWidth="1" />
|
| 488 |
+
<path d="M4 14 Q7 8 10 14 Q13 20 16 14 Q19 8 22 14 Q24 18 24 14"
|
| 489 |
+
stroke="white" strokeWidth="1.2" fill="none" strokeLinecap="round" />
|
| 490 |
+
<circle cx="14" cy="14" r="2" fill="white" />
|
| 491 |
+
</svg>
|
| 492 |
+
<div>
|
| 493 |
+
<div style={{
|
| 494 |
+
fontSize: 22, fontWeight: 700, letterSpacing: "0.18em",
|
| 495 |
+
color: "#FFF", textTransform: "uppercase", lineHeight: 1,
|
| 496 |
+
}}>NEURAL</div>
|
| 497 |
+
<div style={{
|
| 498 |
+
fontSize: 22, fontWeight: 400, letterSpacing: "0.34em",
|
| 499 |
+
color: "#666", textTransform: "uppercase", lineHeight: 1, marginTop: 2,
|
| 500 |
+
}}>ACCESS</div>
|
| 501 |
+
</div>
|
| 502 |
+
</div>
|
| 503 |
+
<div style={{
|
| 504 |
+
fontSize: 7.5, color: "#383838", letterSpacing: "0.22em", textTransform: "uppercase",
|
| 505 |
+
}}>
|
| 506 |
+
HIGH-CLEARANCE ADMISSION — SERIES 9 — OMEGA TIER
|
| 507 |
+
</div>
|
| 508 |
+
</div>
|
| 509 |
+
|
| 510 |
+
{/* 3 ── TERMINAL CIRCLE */}
|
| 511 |
+
<div style={{
|
| 512 |
+
padding: "0 28px", position: "relative", zIndex: 20,
|
| 513 |
+
display: "flex", justifyContent: "center",
|
| 514 |
+
}}>
|
| 515 |
+
<div style={{
|
| 516 |
+
width: 250, height: 250,
|
| 517 |
+
borderRadius: "50%",
|
| 518 |
+
border: "1px solid #2a2a2a",
|
| 519 |
+
overflow: "hidden",
|
| 520 |
+
position: "relative",
|
| 521 |
+
boxShadow: "0 0 0 6px #0e0e0e, 0 0 0 7px #1e1e1e",
|
| 522 |
+
}}>
|
| 523 |
+
<TerminalMask />
|
| 524 |
+
<GrainOverlay strength={0.26} />
|
| 525 |
+
</div>
|
| 526 |
+
</div>
|
| 527 |
+
|
| 528 |
+
{/* scan status pill */}
|
| 529 |
+
<div style={{
|
| 530 |
+
display: "flex", justifyContent: "center", marginTop: 12,
|
| 531 |
+
position: "relative", zIndex: 20,
|
| 532 |
+
}}>
|
| 533 |
+
<div style={{
|
| 534 |
+
display: "flex", alignItems: "center", gap: 6,
|
| 535 |
+
border: "1px solid #222", borderRadius: 999,
|
| 536 |
+
padding: "4px 14px",
|
| 537 |
+
background: "#0d0d0d",
|
| 538 |
+
}}>
|
| 539 |
+
<div style={{
|
| 540 |
+
width: 5, height: 5, borderRadius: "50%",
|
| 541 |
+
background: pulse ? "#FFF" : "#333",
|
| 542 |
+
transition: "background 0.4s",
|
| 543 |
+
}} />
|
| 544 |
+
<span style={{ fontSize: 7.5, letterSpacing: "0.2em", color: "#666", textTransform: "uppercase" }}>
|
| 545 |
+
SIGNAL {pulse ? "ACTIVE" : "PINGING"}
|
| 546 |
+
</span>
|
| 547 |
+
</div>
|
| 548 |
+
</div>
|
| 549 |
+
|
| 550 |
+
{/* 4 ── INFO GRID */}
|
| 551 |
+
<div style={{
|
| 552 |
+
padding: "24px 32px 0",
|
| 553 |
+
position: "relative", zIndex: 20,
|
| 554 |
+
display: "grid", gridTemplateColumns: "1fr 1fr", gap: "16px 12px",
|
| 555 |
+
}}>
|
| 556 |
+
{[
|
| 557 |
+
{ label: "BEARER", value: "AGENT_NX-44" },
|
| 558 |
+
{ label: "ISSUED", value: "2025.03.06" },
|
| 559 |
+
{ label: "CLEARANCE", value: "OMEGA / Ω" },
|
| 560 |
+
{ label: "EXPIRES", value: "2025.12.31" },
|
| 561 |
+
{ label: "SECTOR", value: "DELTA-9" },
|
| 562 |
+
{ label: "NODE", value: "0xDEAD·BF9" },
|
| 563 |
+
].map(({ label, value }) => (
|
| 564 |
+
<div key={label}>
|
| 565 |
+
<div style={{
|
| 566 |
+
fontSize: 7, color: "#3a3a3a", letterSpacing: "0.28em",
|
| 567 |
+
textTransform: "uppercase", marginBottom: 3,
|
| 568 |
+
}}>{label}</div>
|
| 569 |
+
<div style={{
|
| 570 |
+
fontSize: 11, color: "#CDCDCD", letterSpacing: "0.1em",
|
| 571 |
+
fontWeight: 600,
|
| 572 |
+
}}>{value}</div>
|
| 573 |
+
</div>
|
| 574 |
+
))}
|
| 575 |
+
</div>
|
| 576 |
+
|
| 577 |
+
{/* subtle divider */}
|
| 578 |
+
<div style={{ padding: "22px 32px 0", position: "relative", zIndex: 20 }}>
|
| 579 |
+
<div style={{ display: "flex", gap: 6, alignItems: "center" }}>
|
| 580 |
+
{Array.from({ length: 3 }).map((_, i) => (
|
| 581 |
+
<div key={i} style={{ flex: 1, height: "1px", background: i === 1 ? "#222" : "#161616" }} />
|
| 582 |
+
))}
|
| 583 |
+
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" style={{ opacity: 0.3 }}>
|
| 584 |
+
<polygon points="5,0 10,10 0,10" stroke="white" strokeWidth="1" fill="none" />
|
| 585 |
+
</svg>
|
| 586 |
+
{Array.from({ length: 3 }).map((_, i) => (
|
| 587 |
+
<div key={i} style={{ flex: 1, height: "1px", background: i === 1 ? "#222" : "#161616" }} />
|
| 588 |
+
))}
|
| 589 |
+
</div>
|
| 590 |
+
</div>
|
| 591 |
+
|
| 592 |
+
{/* ── STUB SECTION (below tear line) ── */}
|
| 593 |
+
{/* Spacer to push stub below the scalloped tear cutout */}
|
| 594 |
+
<div style={{ height: 36 }} />
|
| 595 |
+
|
| 596 |
+
<div style={{
|
| 597 |
+
padding: "4px 32px 32px",
|
| 598 |
+
position: "relative", zIndex: 20,
|
| 599 |
+
display: "flex", alignItems: "center", justifyContent: "space-between",
|
| 600 |
+
}}>
|
| 601 |
+
{/* Barcode */}
|
| 602 |
+
<div>
|
| 603 |
+
<Barcode />
|
| 604 |
+
<div style={{ fontSize: 7, color: "#2e2e2e", letterSpacing: "0.16em", marginTop: 3 }}>
|
| 605 |
+
NX-9912-DELTA-44-0x2F
|
| 606 |
+
</div>
|
| 607 |
+
</div>
|
| 608 |
+
{/* Stamp area */}
|
| 609 |
+
<div style={{ textAlign: "right" }}>
|
| 610 |
+
<div style={{ display: "flex", gap: 6, justifyContent: "flex-end", marginBottom: 6 }}>
|
| 611 |
+
{["★", "◈", "▲"].map((icon, i) => (
|
| 612 |
+
<span key={i} style={{
|
| 613 |
+
fontSize: i === 0 ? 11 : 9, color: "#3a3a3a", fontFamily: "monospace",
|
| 614 |
+
}}>{icon}</span>
|
| 615 |
+
))}
|
| 616 |
+
</div>
|
| 617 |
+
<div style={{
|
| 618 |
+
fontSize: 7, color: "#333", letterSpacing: "0.22em",
|
| 619 |
+
textTransform: "uppercase", lineHeight: 1.7,
|
| 620 |
+
}}>
|
| 621 |
+
ADMIT<br />ONE
|
| 622 |
+
</div>
|
| 623 |
+
</div>
|
| 624 |
+
</div>
|
| 625 |
+
|
| 626 |
+
{/* bottom vignette */}
|
| 627 |
+
<div style={{
|
| 628 |
+
position: "absolute", bottom: 0, left: 0, right: 0, height: 80,
|
| 629 |
+
background: "linear-gradient(to top, rgba(0,0,0,0.6), transparent)",
|
| 630 |
+
pointerEvents: "none", zIndex: 8,
|
| 631 |
+
}} />
|
| 632 |
+
</div>
|
| 633 |
+
</TicketShell>
|
| 634 |
+
|
| 635 |
+
{/* ambient page label */}
|
| 636 |
+
<div style={{
|
| 637 |
+
position: "absolute", bottom: 18, left: "50%", transform: "translateX(-50%)",
|
| 638 |
+
fontSize: 7.5, letterSpacing: "0.3em", color: "#222",
|
| 639 |
+
fontFamily: "'IBM Plex Mono', monospace", textTransform: "uppercase",
|
| 640 |
+
zIndex: 20, whiteSpace: "nowrap",
|
| 641 |
+
}}>
|
| 642 |
+
NEXUS PROTOCOL — DOCUMENT RENDER v9.1 — CLASSIFIED
|
| 643 |
+
</div>
|
| 644 |
+
</div>
|
| 645 |
+
);
|
| 646 |
+
}
|