| import { useState, useEffect, useRef } from "react"; |
|
|
| const FONT_URL = "https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&display=swap"; |
|
|
| const TERMINAL_LINES = [ |
| "INIT SEQUENCE 0x4F2A...", |
| "AUTH TOKEN: ββββββββββββββ", |
| "SCAN FREQ 847.221 MHz", |
| "SECTOR [CLASSIFIED]", |
| "NODE_ID NX-9912-DELTA", |
| "TIMESTAMP 2025.03.06", |
| "CLEARANCE LEVEL-OMEGA", |
| "STATUS AUTHORIZED", |
| "PAYLOAD ENCRYPTED", |
| "UPLINK ACTIVE", |
| "SIGNAL ββββββββ 61%", |
| "VERIFY SHA256:c0d3f", |
| "MATRIX 7Γ7 LATTICE", |
| "HASH 0xDEADBEEF", |
| "PING 12.4ms OK", |
| "TRACE DISABLED", |
| "VAULT SEALED", |
| "PROTOCOL OMEGA-9", |
| "ENTROPY HIGH", |
| "FRAME #00441", |
| "BUFFER FLUSHED", |
| "CIPHER AES-256", |
| "RELAY PROXIED", |
| "ECHO SILENT", |
| "LOCK ENGAGED", |
| "GRID REF 44.0N 76.2W", |
| "KERNEL 3.14.159", |
| "DAEMON RUNNING", |
| "WATCHDOG ARMED", |
| "ROUTE OBFUSCATED", |
| ]; |
|
|
| |
| function GrainOverlay({ strength = 0.18 }) { |
| const canvasRef = useRef(); |
| const tick = useRef(0); |
|
|
| useEffect(() => { |
| const canvas = canvasRef.current; |
| if (!canvas) return; |
| const ctx = canvas.getContext("2d"); |
| let raf; |
|
|
| const draw = () => { |
| tick.current++; |
| if (tick.current % 2 !== 0) { raf = requestAnimationFrame(draw); return; } |
| const { width, height } = canvas; |
| const img = ctx.createImageData(width, height); |
| const d = img.data; |
| for (let i = 0; i < d.length; i += 4) { |
| const px = i / 4; |
| const x = px % width; |
| const y = Math.floor(px / width); |
| const cx = x / width - 0.5, cy = y / height - 0.5; |
| const dist = Math.sqrt(cx * cx + cy * cy); |
| const vignette = Math.min(1, dist * 2.2); |
| const noise = (Math.random() - 0.5) * 255 * (strength + vignette * 0.18); |
| d[i] = d[i + 1] = d[i + 2] = 128 + noise; |
| d[i + 3] = Math.floor(18 + vignette * 30); |
| } |
| ctx.putImageData(img, 0, 0); |
| raf = requestAnimationFrame(draw); |
| }; |
|
|
| const resize = () => { |
| canvas.width = canvas.offsetWidth; |
| canvas.height = canvas.offsetHeight; |
| }; |
| resize(); |
| draw(); |
| window.addEventListener("resize", resize); |
| return () => { cancelAnimationFrame(raf); window.removeEventListener("resize", resize); }; |
| }, [strength]); |
|
|
| return ( |
| <canvas |
| ref={canvasRef} |
| style={{ |
| position: "absolute", inset: 0, width: "100%", height: "100%", |
| pointerEvents: "none", mixBlendMode: "overlay", zIndex: 10, |
| }} |
| /> |
| ); |
| } |
|
|
| |
| function GeometricOverlay() { |
| return ( |
| <svg |
| viewBox="0 0 480 860" |
| style={{ |
| position: "absolute", inset: 0, width: "100%", height: "100%", |
| pointerEvents: "none", zIndex: 5, opacity: 0.08, |
| }} |
| preserveAspectRatio="none" |
| > |
| <polygon points="0,0 72,0 0,72" fill="none" stroke="white" strokeWidth="0.8" /> |
| <polygon points="480,0 408,0 480,72" fill="none" stroke="white" strokeWidth="0.8" /> |
| <polygon points="0,860 72,860 0,788" fill="none" stroke="white" strokeWidth="0.8" /> |
| <polygon points="480,860 408,860 480,788" fill="none" stroke="white" strokeWidth="0.8" /> |
| {[0.2, 0.4, 0.6, 0.8].map((t) => ( |
| <line key={t} x1={480 * t} y1="0" x2={480 * t} y2="860" stroke="white" strokeWidth="0.4" /> |
| ))} |
| {[0.25, 0.5, 0.75].map((t) => ( |
| <line key={t} x1="0" y1={860 * t} x2="480" y2={860 * t} stroke="white" strokeWidth="0.4" /> |
| ))} |
| <line x1="240" y1="430" x2="0" y2="0" stroke="white" strokeWidth="0.3" /> |
| <line x1="240" y1="430" x2="480" y2="0" stroke="white" strokeWidth="0.3" /> |
| <line x1="240" y1="430" x2="0" y2="860" stroke="white" strokeWidth="0.3" /> |
| <line x1="240" y1="430" x2="480" y2="860" stroke="white" strokeWidth="0.3" /> |
| <circle cx="240" cy="430" r="200" fill="none" stroke="white" strokeWidth="0.5" /> |
| </svg> |
| ); |
| } |
|
|
| |
| function TerminalMask() { |
| const [offset, setOffset] = useState(0); |
| const [glitch, setGlitch] = useState({ row: -1, px: 0 }); |
|
|
| useEffect(() => { |
| const iv = setInterval(() => { |
| setOffset((o) => (o + 1) % TERMINAL_LINES.length); |
| }, 120); |
| const gv = setInterval(() => { |
| setGlitch({ row: Math.floor(Math.random() * 18), px: (Math.random() - 0.5) * 8 }); |
| setTimeout(() => setGlitch({ row: -1, px: 0 }), 80); |
| }, 900); |
| return () => { clearInterval(iv); clearInterval(gv); }; |
| }, []); |
|
|
| const visible = Array.from({ length: 20 }, (_, i) => |
| TERMINAL_LINES[(offset + i) % TERMINAL_LINES.length] |
| ); |
|
|
| return ( |
| <div style={{ |
| width: "100%", height: "100%", |
| background: "#000", |
| borderRadius: "50%", |
| overflow: "hidden", |
| display: "flex", |
| flexDirection: "column", |
| justifyContent: "center", |
| padding: "18px 14px", |
| gap: 0, |
| position: "relative", |
| }}> |
| <div style={{ |
| position: "absolute", inset: 0, borderRadius: "50%", |
| background: "radial-gradient(ellipse at center, transparent 30%, rgba(0,0,0,0.72) 100%)", |
| zIndex: 2, pointerEvents: "none", |
| }} /> |
| {visible.map((line, i) => ( |
| <div key={i} style={{ |
| fontFamily: "'IBM Plex Mono', monospace", |
| fontSize: "9.5px", |
| fontWeight: i === 0 ? 700 : 400, |
| letterSpacing: "0.08em", |
| color: i === 0 ? "#FFFFFF" : i < 3 ? "#D8D8D8" : "#888", |
| lineHeight: "1.55", |
| opacity: i === 0 ? 1 : Math.max(0.18, 1 - i * 0.045), |
| transform: glitch.row === i ? `translateX(${glitch.px}px)` : "none", |
| whiteSpace: "nowrap", |
| overflow: "hidden", |
| textOverflow: "ellipsis", |
| transition: "transform 0.04s", |
| position: "relative", |
| zIndex: 3, |
| }}> |
| {i === 0 ? "βΆ " : " "}{line} |
| </div> |
| ))} |
| </div> |
| ); |
| } |
|
|
| |
| function Barcode() { |
| const bars = Array.from({ length: 60 }, (_, i) => { |
| const w = [1, 1, 2, 1, 3, 1, 2, 1, 1, 2][i % 10]; |
| return { w, gap: [2, 1, 2, 3, 1, 2, 1, 2, 3, 1][i % 10] }; |
| }); |
| return ( |
| <svg width="160" height="36" viewBox="0 0 160 36"> |
| {bars.reduce((acc, bar, i) => { |
| const x = acc.x; |
| acc.elements.push( |
| <rect key={i} x={x} y={2} width={bar.w} height={28} fill="white" /> |
| ); |
| acc.x += bar.w + bar.gap; |
| return acc; |
| }, { x: 2, elements: [] }).elements} |
| </svg> |
| ); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function TicketShell({ children, stubHeight = 120 }) { |
| const W = 460; |
| const H = 820; |
| const R = 14; |
| const PR = 9; |
| const SR = 11; |
| const tearY = H - stubHeight; |
|
|
| |
| const zigzagLR = (x0, x1, y, dir) => { |
| const count = Math.floor((x1 - x0) / (PR * 2.2)); |
| const step = (x1 - x0) / count; |
| let d = ""; |
| for (let i = 0; i < count; i++) { |
| const mx = x0 + i * step + step / 2; |
| const ex = x0 + (i + 1) * step; |
| d += ` L ${mx} ${y + dir * PR} L ${ex} ${y}`; |
| } |
| return d; |
| }; |
|
|
| |
| const zigzagRL = (x0, x1, y, dir) => { |
| const count = Math.floor((x1 - x0) / (PR * 2.2)); |
| const step = (x1 - x0) / count; |
| let d = ""; |
| for (let i = count - 1; i >= 0; i--) { |
| const mx = x0 + i * step + step / 2; |
| const ex = x0 + i * step; |
| d += ` L ${mx} ${y + dir * PR} L ${ex} ${y}`; |
| } |
| return d; |
| }; |
|
|
| const pad = 2; |
| const x0 = pad + R, x1 = W - pad - R; |
| const y0 = pad, y1 = H - pad; |
|
|
| |
| |
| const rightX = W - pad; |
| const leftX = pad; |
|
|
| |
| const path = [ |
| `M ${x0} ${y0}`, |
| |
| zigzagLR(x0, x1, y0, -1), |
| |
| `L ${W - pad - R} ${y0}`, |
| `Q ${W - pad} ${y0} ${W - pad} ${y0 + R}`, |
| |
| `L ${rightX} ${tearY - SR}`, |
| |
| `L ${rightX - SR} ${tearY}`, |
| `L ${rightX} ${tearY + SR}`, |
| |
| `L ${rightX} ${y1 - R}`, |
| |
| `Q ${rightX} ${y1} ${x1} ${y1}`, |
| |
| `L ${x1} ${y1}`, |
| zigzagRL(x0, x1, y1, 1), |
| |
| `L ${x0} ${y1}`, |
| `Q ${leftX} ${y1} ${leftX} ${y1 - R}`, |
| |
| `L ${leftX} ${tearY + SR}`, |
| |
| `L ${leftX + SR} ${tearY}`, |
| `L ${leftX} ${tearY - SR}`, |
| |
| `L ${leftX} ${y0 + R}`, |
| |
| `Q ${leftX} ${y0} ${x0} ${y0}`, |
| `Z`, |
| ].join(" "); |
|
|
| const clipId = "ticket-clip"; |
| const innerId = "ticket-inner"; |
|
|
| return ( |
| <div style={{ position: "relative", width: "100%", maxWidth: 460 }}> |
| {/* SVG defs for clip path */} |
| <svg width="0" height="0" style={{ position: "absolute" }}> |
| <defs> |
| <clipPath id={clipId} clipPathUnits="userSpaceOnUse"> |
| <path d={path} /> |
| </clipPath> |
| </defs> |
| </svg> |
| |
| {/* Outer shadow/glow layer */} |
| <div style={{ |
| position: "absolute", |
| inset: -2, |
| borderRadius: 18, |
| boxShadow: "0 40px 100px rgba(0,0,0,0.95), 0 0 0 1px #1a1a1a", |
| pointerEvents: "none", |
| zIndex: 0, |
| }} /> |
| |
| {/* The actual ticket content clipped to shape */} |
| <div |
| style={{ |
| position: "relative", |
| width: "100%", |
| clipPath: `url(#${clipId})`, |
| // Fallback border for browsers without clipPath |
| }} |
| > |
| {/* SVG border drawn on top of content */} |
| <svg |
| viewBox={`0 0 ${W} ${H}`} |
| width="100%" |
| style={{ |
| position: "absolute", inset: 0, width: "100%", height: "100%", |
| pointerEvents: "none", zIndex: 50, |
| }} |
| preserveAspectRatio="none" |
| > |
| {/* Outer border */} |
| <path |
| d={path} |
| fill="none" |
| stroke="#2e2e2e" |
| strokeWidth="1.2" |
| /> |
| {/* Inner inset border */} |
| <path |
| d={path} |
| fill="none" |
| stroke="#1e1e1e" |
| strokeWidth="3" |
| strokeDasharray="4 3" |
| opacity="0.5" |
| /> |
| |
| {/* Tear-line dashed rule */} |
| <line |
| x1={pad + SR + 4} y1={tearY} |
| x2={W - pad - SR - 4} y2={tearY} |
| stroke="#2a2a2a" |
| strokeWidth="1" |
| strokeDasharray="5 4" |
| /> |
| {/* Tear scissors icon hint */} |
| <text x={W / 2 - 10} y={tearY - 5} fill="#333" fontSize="9" fontFamily="monospace" textAnchor="middle">β TEAR</text> |
| |
| {/* Corner punch holes β diamond arrowhead shapes */} |
| {[ |
| [pad + 28, pad + 28], |
| [W - pad - 28, pad + 28], |
| [pad + 28, H - pad - 28], |
| [W - pad - 28, H - pad - 28], |
| ].map(([cx, cy], i) => { |
| const ro = 9; |
| const ri = 4.5; |
| return ( |
| <g key={i}> |
| <polygon |
| points={`${cx},${cy - ro} ${cx + ro},${cy} ${cx},${cy + ro} ${cx - ro},${cy}`} |
| fill="#0A0A0A" stroke="#2e2e2e" strokeWidth="1" |
| /> |
| <polygon |
| points={`${cx},${cy - ri} ${cx + ri},${cy} ${cx},${cy + ri} ${cx - ri},${cy}`} |
| fill="none" stroke="#252525" strokeWidth="0.8" |
| /> |
| </g> |
| ); |
| })} |
| |
| {/* Perforation diamond dots along tear line */} |
| {Array.from({ length: 18 }, (_, i) => { |
| const spacing = (W - pad * 2 - SR * 2 - 20) / 17; |
| const x = pad + SR + 10 + i * spacing; |
| const r = 2.2; |
| return ( |
| <polygon |
| key={i} |
| points={`${x},${tearY - r} ${x + r},${tearY} ${x},${tearY + r} ${x - r},${tearY}`} |
| fill="#252525" |
| /> |
| ); |
| })} |
| </svg> |
| |
| {children} |
| </div> |
| </div> |
| ); |
| } |
|
|
| |
| export default function CyberTicket() { |
| const [pulse, setPulse] = useState(false); |
|
|
| useEffect(() => { |
| if (!document.getElementById("ibm-plex-mono")) { |
| const link = document.createElement("link"); |
| link.id = "ibm-plex-mono"; |
| link.rel = "stylesheet"; |
| link.href = FONT_URL; |
| document.head.appendChild(link); |
| } |
| const iv = setInterval(() => setPulse((p) => !p), 1400); |
| return () => clearInterval(iv); |
| }, []); |
|
|
| const STUB_H = 130; |
|
|
| return ( |
| <div |
| style={{ |
| minHeight: "100vh", |
| background: "#060606", |
| display: "flex", |
| alignItems: "center", |
| justifyContent: "center", |
| padding: "60px 16px", |
| fontFamily: "'IBM Plex Mono', monospace", |
| position: "relative", |
| overflow: "hidden", |
| }} |
| > |
| {/* ambient radial glow */} |
| <div style={{ |
| position: "absolute", |
| width: 600, height: 600, |
| background: "radial-gradient(circle, rgba(255,255,255,0.015) 0%, transparent 70%)", |
| left: "50%", top: "50%", |
| transform: "translate(-50%,-50%)", |
| pointerEvents: "none", |
| }} /> |
| <GrainOverlay strength={0.13} /> |
| |
| <TicketShell stubHeight={130}> |
| {/* ββ background fill */} |
| <div style={{ |
| background: "#0A0A0A", |
| minHeight: 820, |
| position: "relative", |
| overflow: "hidden", |
| }}> |
| <GrainOverlay strength={0.2} /> |
| <GeometricOverlay /> |
| |
| {/* 1 ββ TOP HEADER BAR */} |
| <div style={{ |
| background: "#0f0f0f", |
| borderBottom: "1px solid #1e1e1e", |
| padding: "16px 28px", |
| display: "flex", |
| alignItems: "center", |
| justifyContent: "space-between", |
| position: "relative", |
| zIndex: 20, |
| marginTop: 14, // clear top scallop |
| }}> |
| <div> |
| <div style={{ |
| fontSize: 9, fontWeight: 700, letterSpacing: "0.32em", |
| color: "#FFF", textTransform: "uppercase", |
| }}>NEXUS PROTOCOL</div> |
| <div style={{ fontSize: 7.5, color: "#555", letterSpacing: "0.18em", marginTop: 1 }}> |
| CLASSIFIED ACCESS DOCUMENT |
| </div> |
| </div> |
| <div style={{ textAlign: "right" }}> |
| <div style={{ fontSize: 9, color: "#555", letterSpacing: "0.1em" }}>Ξ©-09</div> |
| <div style={{ fontSize: 7, color: "#333", marginTop: 1 }}>Β© 2025</div> |
| </div> |
| </div> |
| |
| {/* 2 ββ BRANDING BLOCK */} |
| <div style={{ padding: "22px 32px 14px", position: "relative", zIndex: 20 }}> |
| <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 6 }}> |
| <svg width="28" height="28" viewBox="0 0 28 28" fill="none"> |
| <circle cx="14" cy="14" r="13" stroke="white" strokeWidth="1" /> |
| <path d="M4 14 Q7 8 10 14 Q13 20 16 14 Q19 8 22 14 Q24 18 24 14" |
| stroke="white" strokeWidth="1.2" fill="none" strokeLinecap="round" /> |
| <circle cx="14" cy="14" r="2" fill="white" /> |
| </svg> |
| <div> |
| <div style={{ |
| fontSize: 22, fontWeight: 700, letterSpacing: "0.18em", |
| color: "#FFF", textTransform: "uppercase", lineHeight: 1, |
| }}>NEURAL</div> |
| <div style={{ |
| fontSize: 22, fontWeight: 400, letterSpacing: "0.34em", |
| color: "#666", textTransform: "uppercase", lineHeight: 1, marginTop: 2, |
| }}>ACCESS</div> |
| </div> |
| </div> |
| <div style={{ |
| fontSize: 7.5, color: "#383838", letterSpacing: "0.22em", textTransform: "uppercase", |
| }}> |
| HIGH-CLEARANCE ADMISSION β SERIES 9 β OMEGA TIER |
| </div> |
| </div> |
| |
| {/* 3 ββ TERMINAL CIRCLE */} |
| <div style={{ |
| padding: "0 28px", position: "relative", zIndex: 20, |
| display: "flex", justifyContent: "center", |
| }}> |
| <div style={{ |
| width: 250, height: 250, |
| borderRadius: "50%", |
| border: "1px solid #2a2a2a", |
| overflow: "hidden", |
| position: "relative", |
| boxShadow: "0 0 0 6px #0e0e0e, 0 0 0 7px #1e1e1e", |
| }}> |
| <TerminalMask /> |
| <GrainOverlay strength={0.26} /> |
| </div> |
| </div> |
| |
| {/* scan status pill */} |
| <div style={{ |
| display: "flex", justifyContent: "center", marginTop: 12, |
| position: "relative", zIndex: 20, |
| }}> |
| <div style={{ |
| display: "flex", alignItems: "center", gap: 6, |
| border: "1px solid #222", borderRadius: 999, |
| padding: "4px 14px", |
| background: "#0d0d0d", |
| }}> |
| <div style={{ |
| width: 5, height: 5, borderRadius: "50%", |
| background: pulse ? "#FFF" : "#333", |
| transition: "background 0.4s", |
| }} /> |
| <span style={{ fontSize: 7.5, letterSpacing: "0.2em", color: "#666", textTransform: "uppercase" }}> |
| SIGNAL {pulse ? "ACTIVE" : "PINGING"} |
| </span> |
| </div> |
| </div> |
| |
| {/* 4 ββ INFO GRID */} |
| <div style={{ |
| padding: "24px 32px 0", |
| position: "relative", zIndex: 20, |
| display: "grid", gridTemplateColumns: "1fr 1fr", gap: "16px 12px", |
| }}> |
| {[ |
| { label: "BEARER", value: "AGENT_NX-44" }, |
| { label: "ISSUED", value: "2025.03.06" }, |
| { label: "CLEARANCE", value: "OMEGA / Ξ©" }, |
| { label: "EXPIRES", value: "2025.12.31" }, |
| { label: "SECTOR", value: "DELTA-9" }, |
| { label: "NODE", value: "0xDEADΒ·BF9" }, |
| ].map(({ label, value }) => ( |
| <div key={label}> |
| <div style={{ |
| fontSize: 7, color: "#3a3a3a", letterSpacing: "0.28em", |
| textTransform: "uppercase", marginBottom: 3, |
| }}>{label}</div> |
| <div style={{ |
| fontSize: 11, color: "#CDCDCD", letterSpacing: "0.1em", |
| fontWeight: 600, |
| }}>{value}</div> |
| </div> |
| ))} |
| </div> |
| |
| {/* subtle divider */} |
| <div style={{ padding: "22px 32px 0", position: "relative", zIndex: 20 }}> |
| <div style={{ display: "flex", gap: 6, alignItems: "center" }}> |
| {Array.from({ length: 3 }).map((_, i) => ( |
| <div key={i} style={{ flex: 1, height: "1px", background: i === 1 ? "#222" : "#161616" }} /> |
| ))} |
| <svg width="10" height="10" viewBox="0 0 10 10" fill="none" style={{ opacity: 0.3 }}> |
| <polygon points="5,0 10,10 0,10" stroke="white" strokeWidth="1" fill="none" /> |
| </svg> |
| {Array.from({ length: 3 }).map((_, i) => ( |
| <div key={i} style={{ flex: 1, height: "1px", background: i === 1 ? "#222" : "#161616" }} /> |
| ))} |
| </div> |
| </div> |
| |
| {/* ββ STUB SECTION (below tear line) ββ */} |
| {/* Spacer to push stub below the scalloped tear cutout */} |
| <div style={{ height: 36 }} /> |
| |
| <div style={{ |
| padding: "4px 32px 32px", |
| position: "relative", zIndex: 20, |
| display: "flex", alignItems: "center", justifyContent: "space-between", |
| }}> |
| {/* Barcode */} |
| <div> |
| <Barcode /> |
| <div style={{ fontSize: 7, color: "#2e2e2e", letterSpacing: "0.16em", marginTop: 3 }}> |
| NX-9912-DELTA-44-0x2F |
| </div> |
| </div> |
| {/* Stamp area */} |
| <div style={{ textAlign: "right" }}> |
| <div style={{ display: "flex", gap: 6, justifyContent: "flex-end", marginBottom: 6 }}> |
| {["β
", "β", "β²"].map((icon, i) => ( |
| <span key={i} style={{ |
| fontSize: i === 0 ? 11 : 9, color: "#3a3a3a", fontFamily: "monospace", |
| }}>{icon}</span> |
| ))} |
| </div> |
| <div style={{ |
| fontSize: 7, color: "#333", letterSpacing: "0.22em", |
| textTransform: "uppercase", lineHeight: 1.7, |
| }}> |
| ADMIT<br />ONE |
| </div> |
| </div> |
| </div> |
| |
| {/* bottom vignette */} |
| <div style={{ |
| position: "absolute", bottom: 0, left: 0, right: 0, height: 80, |
| background: "linear-gradient(to top, rgba(0,0,0,0.6), transparent)", |
| pointerEvents: "none", zIndex: 8, |
| }} /> |
| </div> |
| </TicketShell> |
| |
| {/* ambient page label */} |
| <div style={{ |
| position: "absolute", bottom: 18, left: "50%", transform: "translateX(-50%)", |
| fontSize: 7.5, letterSpacing: "0.3em", color: "#222", |
| fontFamily: "'IBM Plex Mono', monospace", textTransform: "uppercase", |
| zIndex: 20, whiteSpace: "nowrap", |
| }}> |
| NEXUS PROTOCOL β DOCUMENT RENDER v9.1 β CLASSIFIED |
| </div> |
| </div> |
| ); |
| } |