Spaces:
Running
Running
| <html lang="en" data-theme="dark"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>GitHub Secret Scanner</title> | |
| <script src="https://unpkg.com/lucide@latest"></script> | |
| <style> | |
| /* ===== THEME & RESET ===== */ | |
| :root { | |
| --bg-base: #05050a; | |
| --bg-1: #0b0b12; | |
| --bg-2: #10101a; | |
| --bg-3: #161624; | |
| --bg-4: #1c1c30; | |
| --fg-1: #e2e2f0; | |
| --fg-2: #a0a0bf; | |
| --fg-3: #6b6b8a; | |
| --accent: #00d4aa; | |
| --accent-dim: #00d4aa33; | |
| --accent-glow: #00d4aa22; | |
| --danger: #ff4757; | |
| --danger-dim: #ff475722; | |
| --warn: #feca57; | |
| --info: #54a0ff; | |
| --radius: 14px; | |
| --radius-sm: 8px; | |
| --shadow: 0 8px 32px rgba(0,0,0,0.4); | |
| --transition: 0.25s cubic-bezier(0.4, 0, 0.2, 1); | |
| --font: 'Segoe UI', system-ui, -apple-system, sans-serif; | |
| --mono: 'SF Mono', 'Fira Code', 'Consolas', monospace; | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| html, body { | |
| background: var(--bg-base); | |
| color: var(--fg-1); | |
| font-family: var(--font); | |
| min-height: 100vh; | |
| overflow-x: hidden; | |
| } | |
| /* ===== BACKGROUND EFFECTS ===== */ | |
| body::before { | |
| content: ''; | |
| position: fixed; | |
| inset: 0; | |
| z-index: 0; | |
| pointer-events: none; | |
| background: | |
| radial-gradient(ellipse 80% 50% at 50% -20%, var(--accent-glow), transparent), | |
| radial-gradient(ellipse 60% 40% at 20% 100%, var(--danger-dim), transparent); | |
| opacity: 0.6; | |
| } | |
| body::after { | |
| content: ''; | |
| position: fixed; | |
| inset: 0; | |
| z-index: 0; | |
| pointer-events: none; | |
| background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.03'/%3E%3C/svg%3E"); | |
| opacity: 0.4; | |
| } | |
| /* ===== SCROLLBAR ===== */ | |
| ::-webkit-scrollbar { width: 6px; } | |
| ::-webkit-scrollbar-track { background: transparent; } | |
| ::-webkit-scrollbar-thumb { background: var(--bg-4); border-radius: 3px; } | |
| ::-webkit-scrollbar-thumb:hover { background: var(--fg-3); } | |
| /* ===== ANIMATIONS ===== */ | |
| @keyframes fadeUp { | |
| from { opacity: 0; transform: translateY(20px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; } | |
| to { opacity: 1; } | |
| } | |
| @keyframes scaleIn { | |
| from { opacity: 0; transform: scale(0.95); } | |
| to { opacity: 1; transform: scale(1); } | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; box-shadow: 0 0 0 0 var(--accent-dim); } | |
| 50% { opacity: 0.7; box-shadow: 0 0 0 8px transparent; } | |
| } | |
| @keyframes scan { | |
| 0% { left: -100%; } | |
| 100% { left: 100%; } | |
| } | |
| @keyframes spin { | |
| to { transform: rotate(360deg); } | |
| } | |
| @keyframes typewriter { | |
| from { width: 0; } | |
| to { width: 100%; } | |
| } | |
| @keyframes blink { | |
| 50% { opacity: 0; } | |
| } | |
| @keyframes float { | |
| 0%, 100% { transform: translateY(0px); } | |
| 50% { transform: translateY(-6px); } | |
| } | |
| @keyframes gridMove { | |
| 0% { background-position: 0 0; } | |
| 100% { background-position: 40px 40px; } | |
| } | |
| .anim-fade-up { | |
| animation: fadeUp 0.6s ease-out forwards; | |
| opacity: 0; | |
| } | |
| .anim-scale-in { | |
| animation: scaleIn 0.4s ease-out forwards; | |
| opacity: 0; | |
| } | |
| .anim-float { | |
| animation: float 3s ease-in-out infinite; | |
| } | |
| /* ===== LAYOUT ===== */ | |
| .wrapper { | |
| position: relative; | |
| z-index: 1; | |
| max-width: 1400px; | |
| margin: 0 auto; | |
| padding: 24px; | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| /* ===== NAV ===== */ | |
| .nav { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| padding: 16px 24px; | |
| margin: -24px -24px 32px; | |
| background: var(--bg-1); | |
| border-bottom: 1px solid var(--bg-3); | |
| backdrop-filter: blur(20px); | |
| position: sticky; | |
| top: 0; | |
| z-index: 100; | |
| } | |
| .nav-brand { | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| } | |
| .nav-brand .icon-box { | |
| width: 40px; | |
| height: 40px; | |
| background: var(--accent-dim); | |
| border: 1px solid var(--accent); | |
| border-radius: var(--radius-sm); | |
| display: grid; | |
| place-items: center; | |
| color: var(--accent); | |
| box-shadow: 0 0 20px var(--accent-glow); | |
| } | |
| .nav-brand h1 { | |
| font-size: clamp(1rem, 2.5vw, 1.3rem); | |
| font-weight: 700; | |
| letter-spacing: -0.5px; | |
| background: linear-gradient(135deg, var(--fg-1), var(--accent)); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| .nav-brand h1 span { | |
| color: var(--fg-3); | |
| font-weight: 400; | |
| font-size: 0.8em; | |
| -webkit-text-fill-color: var(--fg-3); | |
| } | |
| .nav-links { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .nav-link { | |
| padding: 8px 14px; | |
| border-radius: var(--radius-sm); | |
| font-size: 0.8rem; | |
| color: var(--fg-2); | |
| text-decoration: none; | |
| transition: var(--transition); | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| } | |
| .nav-link:hover { | |
| color: var(--fg-1); | |
| background: var(--bg-3); | |
| } | |
| .nav-link.accent { | |
| background: var(--accent-dim); | |
| color: var(--accent); | |
| border: 1px solid var(--accent); | |
| } | |
| .nav-link.accent:hover { | |
| background: var(--accent); | |
| color: var(--bg-base); | |
| box-shadow: 0 0 20px var(--accent-glow); | |
| } | |
| /* ===== STATUS HEADER ===== */ | |
| .status-header { | |
| display: grid; | |
| grid-template-columns: 1fr auto; | |
| gap: 20px; | |
| align-items: center; | |
| padding: 20px 24px; | |
| background: var(--bg-1); | |
| border: 1px solid var(--bg-3); | |
| border-radius: var(--radius); | |
| margin-bottom: 24px; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .status-header::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| height: 2px; | |
| background: linear-gradient(90deg, var(--accent), var(--info), var(--accent)); | |
| background-size: 200% 100%; | |
| animation: scan 3s linear infinite; | |
| } | |
| .status-left { | |
| display: flex; | |
| align-items: center; | |
| gap: 16px; | |
| } | |
| .status-dot { | |
| width: 12px; | |
| height: 12px; | |
| border-radius: 50%; | |
| background: var(--accent); | |
| box-shadow: 0 0 12px var(--accent); | |
| position: relative; | |
| } | |
| .status-dot::after { | |
| content: ''; | |
| position: absolute; | |
| inset: -4px; | |
| border-radius: 50%; | |
| border: 1px solid var(--accent); | |
| animation: pulse 2s infinite; | |
| } | |
| .status-dot.idle { | |
| background: var(--fg-3); | |
| box-shadow: none; | |
| } | |
| .status-dot.idle::after { | |
| display: none; | |
| } | |
| .status-dot.danger { | |
| background: var(--danger); | |
| box-shadow: 0 0 12px var(--danger); | |
| } | |
| .status-info h2 { | |
| font-size: 1.1rem; | |
| font-weight: 600; | |
| margin-bottom: 2px; | |
| } | |
| .status-info p { | |
| font-size: 0.85rem; | |
| color: var(--fg-2); | |
| font-family: var(--mono); | |
| } | |
| /* ===== CARDS ===== */ | |
| .card { | |
| background: var(--bg-1); | |
| border: 1px solid var(--bg-3); | |
| border-radius: var(--radius); | |
| overflow: hidden; | |
| transition: var(--transition); | |
| position: relative; | |
| } | |
| .card::before { | |
| content: ''; | |
| position: absolute; | |
| inset: 0; | |
| border-radius: inherit; | |
| padding: 1px; | |
| background: linear-gradient(135deg, transparent 40%, var(--bg-4) 100%); | |
| -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); | |
| mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); | |
| -webkit-mask-composite: xor; | |
| mask-composite: exclude; | |
| pointer-events: none; | |
| } | |
| .card:hover { | |
| border-color: var(--bg-4); | |
| transform: translateY(-2px); | |
| box-shadow: var(--shadow); | |
| } | |
| .card-head { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| padding: 18px 20px 0; | |
| font-size: 0.85rem; | |
| font-weight: 600; | |
| color: var(--fg-2); | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| } | |
| .card-head i { | |
| color: var(--accent); | |
| } | |
| .card-body { | |
| padding: 16px 20px 20px; | |
| } | |
| /* ===== GRID ===== */ | |
| .grid { | |
| display: grid; | |
| gap: 20px; | |
| grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); | |
| } | |
| .grid-2 { | |
| grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); | |
| } | |
| .grid-3 { | |
| grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); | |
| } | |
| /* ===== STAT ===== */ | |
| .stat { | |
| text-align: center; | |
| padding: 20px; | |
| background: var(--bg-2); | |
| border-radius: var(--radius-sm); | |
| border: 1px solid var(--bg-3); | |
| transition: var(--transition); | |
| } | |
| .stat:hover { | |
| border-color: var(--accent); | |
| background: var(--bg-3); | |
| } | |
| .stat-value { | |
| font-size: 2.2rem; | |
| font-weight: 800; | |
| color: var(--accent); | |
| line-height: 1; | |
| margin-bottom: 6px; | |
| font-family: var(--mono); | |
| } | |
| .stat-value.danger { | |
| color: var(--danger); | |
| } | |
| .stat-value.info { | |
| color: var(--info); | |
| } | |
| .stat-label { | |
| font-size: 0.75rem; | |
| color: var(--fg-3); | |
| text-transform: uppercase; | |
| letter-spacing: 1.5px; | |
| } | |
| /* ===== BUTTONS ===== */ | |
| .btn { | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 8px; | |
| padding: 10px 22px; | |
| border-radius: var(--radius-sm); | |
| border: 1px solid transparent; | |
| font-size: 0.85rem; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: var(--transition); | |
| font-family: inherit; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .btn::after { | |
| content: ''; | |
| position: absolute; | |
| inset: 0; | |
| background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent); | |
| transform: translateX(-100%); | |
| transition: var(--transition); | |
| } | |
| .btn:hover::after { | |
| transform: translateX(100%); | |
| } | |
| .btn-accent { | |
| background: var(--accent); | |
| color: var(--bg-base); | |
| border-color: var(--accent); | |
| box-shadow: 0 4px 20px var(--accent-glow); | |
| } | |
| .btn-accent:hover { | |
| box-shadow: 0 6px 30px var(--accent-glow); | |
| transform: translateY(-1px); | |
| } | |
| .btn-accent:disabled { | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| .btn-danger { | |
| background: var(--danger); | |
| color: #fff; | |
| border-color: var(--danger); | |
| box-shadow: 0 4px 20px var(--danger-dim); | |
| } | |
| .btn-danger:hover { | |
| box-shadow: 0 6px 30px var(--danger-dim); | |
| } | |
| .btn-ghost { | |
| background: transparent; | |
| color: var(--fg-2); | |
| border-color: var(--bg-3); | |
| } | |
| .btn-ghost:hover { | |
| border-color: var(--fg-3); | |
| color: var(--fg-1); | |
| background: var(--bg-2); | |
| } | |
| .btn-sm { | |
| padding: 6px 14px; | |
| font-size: 0.8rem; | |
| } | |
| /* ===== FORMS ===== */ | |
| .form-group { | |
| margin-bottom: 16px; | |
| } | |
| .form-label { | |
| display: block; | |
| font-size: 0.8rem; | |
| font-weight: 500; | |
| color: var(--fg-2); | |
| margin-bottom: 6px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| } | |
| .form-input, .form-select { | |
| width: 100%; | |
| padding: 10px 14px; | |
| background: var(--bg-2); | |
| border: 1px solid var(--bg-3); | |
| border-radius: var(--radius-sm); | |
| color: var(--fg-1); | |
| font-size: 0.9rem; | |
| font-family: inherit; | |
| transition: var(--transition); | |
| } | |
| .form-input:focus, .form-select:focus { | |
| outline: none; | |
| border-color: var(--accent); | |
| box-shadow: 0 0 0 3px var(--accent-dim); | |
| } | |
| .form-input::placeholder { | |
| color: var(--fg-3); | |
| } | |
| .input-hint { | |
| font-size: 0.75rem; | |
| color: var(--fg-3); | |
| margin-top: 4px; | |
| } | |
| /* ===== FINDINGS TABLE ===== */ | |
| .findings-wrap { | |
| max-height: 480px; | |
| overflow-y: auto; | |
| border-radius: var(--radius-sm); | |
| } | |
| .finding-item { | |
| display: grid; | |
| grid-template-columns: auto 1fr auto auto; | |
| gap: 12px; | |
| align-items: center; | |
| padding: 14px 16px; | |
| border-bottom: 1px solid var(--bg-3); | |
| transition: var(--transition); | |
| cursor: pointer; | |
| } | |
| .finding-item:last-child { | |
| border-bottom: none; | |
| } | |
| .finding-item:hover { | |
| background: var(--bg-2); | |
| padding-left: 20px; | |
| } | |
| .finding-icon { | |
| width: 36px; | |
| height: 36px; | |
| border-radius: var(--radius-sm); | |
| display: grid; | |
| place-items: center; | |
| flex-shrink: 0; | |
| } | |
| .finding-icon.severity-high { | |
| background: var(--danger-dim); | |
| color: var(--danger); | |
| } | |
| .finding-icon.severity-med { | |
| background: #feca5722; | |
| color: var(--warn); | |
| } | |
| .finding-icon.severity-low { | |
| background: var(--accent-dim); | |
| color: var(--accent); | |
| } | |
| .finding-info h4 { | |
| font-size: 0.9rem; | |
| font-weight: 600; | |
| margin-bottom: 2px; | |
| } | |
| .finding-info a { | |
| color: var(--accent); | |
| text-decoration: none; | |
| font-size: 0.78rem; | |
| font-family: var(--mono); | |
| } | |
| .finding-info a:hover { | |
| text-decoration: underline; | |
| } | |
| .finding-meta { | |
| text-align: right; | |
| } | |
| .finding-count { | |
| font-size: 1.1rem; | |
| font-weight: 700; | |
| color: var(--danger); | |
| font-family: var(--mono); | |
| } | |
| .finding-time { | |
| font-size: 0.7rem; | |
| color: var(--fg-3); | |
| font-family: var(--mono); | |
| } | |
| .finding-badge { | |
| display: inline-block; | |
| padding: 2px 8px; | |
| border-radius: 4px; | |
| font-size: 0.7rem; | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| } | |
| .badge-danger { | |
| background: var(--danger-dim); | |
| color: var(--danger); | |
| } | |
| .badge-warn { | |
| background: #feca5722; | |
| color: var(--warn); | |
| } | |
| /* ===== LOGS ===== */ | |
| .log-entry { | |
| display: flex; | |
| align-items: flex-start; | |
| gap: 10px; | |
| padding: 8px 0; | |
| border-bottom: 1px solid var(--bg-3); | |
| font-family: var(--mono); | |
| font-size: 0.78rem; | |
| line-height: 1.5; | |
| } | |
| .log-entry:last-child { | |
| border-bottom: none; | |
| } | |
| .log-time { | |
| color: var(--fg-3); | |
| flex-shrink: 0; | |
| font-size: 0.7rem; | |
| } | |
| .log-msg { | |
| color: var(--fg-2); | |
| } | |
| .log-msg.success { | |
| color: var(--accent); | |
| } | |
| .log-msg.error { | |
| color: var(--danger); | |
| } | |
| .log-msg.warn { | |
| color: var(--warn); | |
| } | |
| /* ===== TERMINAL ===== */ | |
| .terminal { | |
| background: #07070e; | |
| border: 1px solid var(--bg-3); | |
| border-radius: var(--radius-sm); | |
| padding: 16px; | |
| font-family: var(--mono); | |
| font-size: 0.8rem; | |
| line-height: 1.7; | |
| overflow: hidden; | |
| } | |
| .terminal-line { | |
| display: flex; | |
| gap: 8px; | |
| opacity: 0; | |
| animation: fadeIn 0.3s ease forwards; | |
| } | |
| .terminal-prompt { | |
| color: var(--accent); | |
| flex-shrink: 0; | |
| } | |
| .terminal-cursor { | |
| display: inline-block; | |
| width: 8px; | |
| height: 16px; | |
| background: var(--accent); | |
| animation: blink 1s step-end infinite; | |
| vertical-align: text-bottom; | |
| margin-left: 2px; | |
| } | |
| /* ===== PROGRESS ===== */ | |
| .progress-track { | |
| height: 4px; | |
| background: var(--bg-3); | |
| border-radius: 2px; | |
| overflow: hidden; | |
| margin-top: 12px; | |
| } | |
| .progress-fill { | |
| height: 100%; | |
| background: linear-gradient(90deg, var(--accent), var(--info)); | |
| border-radius: 2px; | |
| transition: width 0.5s ease; | |
| position: relative; | |
| } | |
| .progress-fill::after { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| right: 0; | |
| bottom: 0; | |
| width: 20px; | |
| background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3)); | |
| } | |
| /* ===== SPINNER ===== */ | |
| .spinner { | |
| width: 16px; | |
| height: 16px; | |
| border: 2px solid var(--bg-3); | |
| border-top-color: var(--accent); | |
| border-radius: 50%; | |
| animation: spin 0.8s linear infinite; | |
| } | |
| /* ===== TOAST ===== */ | |
| .toast-container { | |
| position: fixed; | |
| top: 24px; | |
| right: 24px; | |
| z-index: 1000; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| pointer-events: none; | |
| } | |
| .toast { | |
| padding: 12px 18px; | |
| border-radius: var(--radius-sm); | |
| font-size: 0.85rem; | |
| font-weight: 500; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| animation: fadeUp 0.3s ease, fadeIn 0.3s ease reverse forwards 3s; | |
| pointer-events: all; | |
| backdrop-filter: blur(10px); | |
| } | |
| .toast-success { | |
| background: var(--accent-dim); | |
| border: 1px solid var(--accent); | |
| color: var(--accent); | |
| } | |
| .toast-error { | |
| background: var(--danger-dim); | |
| border: 1px solid var(--danger); | |
| color: var(--danger); | |
| } | |
| /* ===== MODAL ===== */ | |
| .modal-overlay { | |
| position: fixed; | |
| inset: 0; | |
| background: rgba(0,0,0,0.7); | |
| backdrop-filter: blur(8px); | |
| z-index: 200; | |
| display: grid; | |
| place-items: center; | |
| opacity: 0; | |
| pointer-events: none; | |
| transition: opacity 0.3s ease; | |
| } | |
| .modal-overlay.active { | |
| opacity: 1; | |
| pointer-events: all; | |
| } | |
| .modal { | |
| background: var(--bg-1); | |
| border: 1px solid var(--bg-3); | |
| border-radius: var(--radius); | |
| padding: 28px; | |
| max-width: 520px; | |
| width: 90%; | |
| transform: scale(0.95); | |
| transition: transform 0.3s ease; | |
| } | |
| .modal-overlay.active .modal { | |
| transform: scale(1); | |
| } | |
| .modal-title { | |
| font-size: 1.2rem; | |
| font-weight: 700; | |
| margin-bottom: 8px; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .modal-title i { | |
| color: var(--accent); | |
| } | |
| .modal-desc { | |
| color: var(--fg-2); | |
| font-size: 0.9rem; | |
| margin-bottom: 20px; | |
| } | |
| /* ===== EMPTY STATE ===== */ | |
| .empty-state { | |
| text-align: center; | |
| padding: 40px 20px; | |
| color: var(--fg-3); | |
| } | |
| .empty-state i { | |
| margin-bottom: 12px; | |
| opacity: 0.5; | |
| } | |
| .empty-state p { | |
| font-size: 0.9rem; | |
| } | |
| /* ===== SCANNER VISUAL ===== */ | |
| .scanner-visual { | |
| position: relative; | |
| height: 120px; | |
| background: var(--bg-base); | |
| border-radius: var(--radius-sm); | |
| overflow: hidden; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| margin-bottom: 16px; | |
| border: 1px solid var(--bg-3); | |
| } | |
| .scanner-grid { | |
| position: absolute; | |
| inset: 0; | |
| background-image: | |
| linear-gradient(var(--bg-3) 1px, transparent 1px), | |
| linear-gradient(90deg, var(--bg-3) 1px, transparent 1px); | |
| background-size: 20px 20px; | |
| opacity: 0.3; | |
| animation: gridMove 20s linear infinite; | |
| } | |
| .scanner-beam { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 2px; | |
| height: 100%; | |
| background: linear-gradient(180deg, transparent, var(--accent), transparent); | |
| animation: scan 2s linear infinite; | |
| opacity: 0; | |
| } | |
| .scanner-beam.active { | |
| opacity: 1; | |
| } | |
| .scanner-pulse { | |
| width: 60px; | |
| height: 60px; | |
| border-radius: 50%; | |
| border: 2px solid var(--accent); | |
| position: relative; | |
| opacity: 0; | |
| transition: opacity 0.3s; | |
| } | |
| .scanner-pulse.active { | |
| opacity: 1; | |
| } | |
| .scanner-pulse::before, .scanner-pulse::after { | |
| content: ''; | |
| position: absolute; | |
| inset: -10px; | |
| border-radius: 50%; | |
| border: 1px solid var(--accent); | |
| animation: pulse 2s infinite; | |
| } | |
| .scanner-pulse::after { | |
| inset: -20px; | |
| animation-delay: 0.5s; | |
| } | |
| .scanner-pulse i { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| color: var(--accent); | |
| } | |
| .scanner-idle { | |
| text-align: center; | |
| color: var(--fg-3); | |
| } | |
| .scanner-idle i { | |
| margin-bottom: 8px; | |
| display: block; | |
| } | |
| /* ===== RESPONSIVE ===== */ | |
| @media (max-width: 768px) { | |
| .nav { | |
| padding: 12px 16px; | |
| flex-wrap: wrap; | |
| gap: 8px; | |
| } | |
| .nav-brand h1 { | |
| font-size: 1rem; | |
| } | |
| .status-header { | |
| grid-template-columns: 1fr; | |
| gap: 12px; | |
| } | |
| .status-right { | |
| justify-self: start; | |
| } | |
| .wrapper { | |
| padding: 16px; | |
| } | |
| .finding-item { | |
| grid-template-columns: 1fr; | |
| gap: 8px; | |
| } | |
| .finding-meta { | |
| text-align: left; | |
| } | |
| .grid { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| @media (max-width: 480px) { | |
| .nav-links { | |
| display: none; | |
| } | |
| .btn { | |
| padding: 8px 16px; | |
| font-size: 0.8rem; | |
| } | |
| } | |
| /* ===== DIVIDER ===== */ | |
| .section-divider { | |
| height: 1px; | |
| background: linear-gradient(90deg, transparent, var(--bg-3), transparent); | |
| margin: 32px 0; | |
| } | |
| /* ===== FOOTER ===== */ | |
| .footer { | |
| text-align: center; | |
| padding: 24px; | |
| margin-top: auto; | |
| color: var(--fg-3); | |
| font-size: 0.8rem; | |
| } | |
| .footer a { | |
| color: var(--accent); | |
| text-decoration: none; | |
| } | |
| .footer a:hover { | |
| text-decoration: underline; | |
| } | |
| /* ===== ANIMATED BORDER ===== */ | |
| .glow-border { | |
| position: relative; | |
| } | |
| .glow-border::before { | |
| content: ''; | |
| position: absolute; | |
| inset: -1px; | |
| border-radius: inherit; | |
| background: linear-gradient(135deg, var(--accent), var(--info), var(--accent)); | |
| z-index: -1; | |
| opacity: 0; | |
| transition: opacity 0.3s; | |
| filter: blur(4px); | |
| } | |
| .glow-border:hover::before { | |
| opacity: 0.3; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="wrapper"> | |
| <!-- ===== NAVIGATION ===== --> | |
| <nav class="nav"> | |
| <div class="nav-brand"> | |
| <div class="icon-box anim-float"> | |
| <i data-lucide="shield-check" style="width:20px;height:20px;"></i> | |
| </div> | |
| <h1>Secret Scanner <span>v2.0</span></h1> | |
| </div> | |
| <div class="nav-links"> | |
| <a href="#" class="nav-link" onclick="showPage('dashboard'); return false;"> | |
| <i data-lucide="layout-dashboard" style="width:14px;height:14px;"></i> | |
| Dashboard | |
| </a> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="nav-link accent" id="anycoder-link"> | |
| <i data-lucide="code-2" style="width:14px;height:14px;"></i> | |
| Built with anycoder | |
| </a> | |
| </div> | |
| </nav> | |
| <!-- ===== STATUS HEADER ===== --> | |
| <div class="status-header anim-fade-up" style="animation-delay: 0.1s;"> | |
| <div class="status-left"> | |
| <div class="status-dot idle" id="statusDot"></div> | |
| <div class="status-info"> | |
| <h2 id="statusTitle">Scanner Idle</h2> | |
| <p id="statusText">Ready to begin scanning...</p> | |
| </div> | |
| </div> | |
| <div class="status-right" id="statusActions"> | |
| <button class="btn btn-accent" id="startBtn" onclick="startScan()"> | |
| <i data-lucide="play" style="width:14px;height:14px;"></i> | |
| Start Scanning | |
| </button> | |
| <button class="btn btn-danger" id="stopBtn" onclick="stopScan()" style="display:none;"> | |
| <i data-lucide="square" style="width:14px;height:14px;"></i> | |
| Stop | |
| </button> | |
| </div> | |
| </div> | |
| <!-- ===== SCANNER VISUAL ===== --> | |
| <div class="card anim-fade-up" style="animation-delay: 0.15s; margin-bottom: 24px;"> | |
| <div class="card-head"> | |
| <i data-lucide="scan-search" style="width:16px;height:16px;"></i> | |
| Live Scanner Visualization | |
| </div> | |
| <div class="card-body"> | |
| <div class="scanner-visual"> | |
| <div class="scanner-grid"></div> | |
| <div class="scanner-beam" id="scannerBeam"></div> | |
| <div class="scanner-pulse" id="scannerPulse"> | |
| <i data-lucide="radar" style="width:24px;height:24px;"></i> | |
| </div> | |
| <div class="scanner-idle" id="scannerIdle"> | |
| <i data-lucide="shield" style="width:32px;height:32px;"></i> | |
| <p>Scanner is idle</p> | |
| </div> | |
| </div> | |
| <div class="progress-track"> | |
| <div class="progress-fill" id="progressFill" style="width: 0%;"></div> | |
| </div> | |
| <p style="text-align: center; margin-top: 8px; font-size: 0.8rem; color: var(--fg-3);" id="scanProgressText">No scan in progress</p> | |
| </div> | |
| </div> | |
| <!-- ===== STATS ROW ===== --> | |
| <div class="grid grid-4" style="margin-bottom: 24px;"> | |
| <div class="card stat anim-fade-up" style="animation-delay: 0.2s;"> | |
| <div class="stat-value" id="statRepos">0</div> | |
| <div class="stat-label">Repos Scanned</div> | |
| </div> | |
| <div class="card stat anim-fade-up" style="animation-delay: 0.25s;"> | |
| <div class="stat-value danger" id="statFindings">0</div> | |
| <div class="stat-label">Secrets Found</div> | |
| </div> | |
| <div class="card stat anim-fade-up" style="animation-delay: 0.3s;"> | |
| <div class="stat-value info" id="statCycles">0</div> | |
| <div class="stat-label">Scan Cycles</div> | |
| </div> | |
| <div class="card stat anim-fade-up" style="animation-delay: 0.35s;"> | |
| <div class="stat-value" id="statUptime">0m</div> | |
| <div class="stat-label">Uptime</div> | |
| </div> | |
| </div> | |
| <!-- ===== MAIN GRID ===== --> | |
| <div class="grid" style="margin-bottom: 24px;"> | |
| <!-- SETTINGS --> | |
| <div class="card anim-fade-up" style="animation-delay: 0.4s;"> | |
| <div class="card-head"> | |
| <i data-lucide="settings-2" style="width:16px;height:16px;"></i> | |
| Configuration | |
| </div> | |
| <div class="card-body"> | |
| <form id="settingsForm" onsubmit="saveSettings(event)"> | |
| <div class="form-group"> | |
| <label class="form-label">GitHub Token</label> | |
| <input type="password" class="form-input" id="githubToken" placeholder="ghp_xxxxxxxxxxxx"> | |
| <p class="input-hint">Required for higher API rate limits</p> | |
| </div> | |
| <div class="form-group"> | |
| <label class="form-label">Search Days</label> | |
| <input type="number" class="form-input" id="days" value="1" min="1" max="30"> | |
| <p class="input-hint">How many days back to search</p> | |
| </div> | |
| <div class="form-group"> | |
| <label class="form-label">Max Repos / Cycle</label> | |
| <input type="number" class="form-input" id="maxRepos" value="12" min="1" max="100"> | |
| <p class="input-hint">Repositories to scan per cycle</p> | |
| </div> | |
| <div class="form-group"> | |
| <label class="form-label">Interval (seconds)</label> | |
| <input type="number" class="form-input" id="interval" value="900" min="60" max="3600"> | |
| <p class="input-hint">Wait time between scan cycles</p> | |
| </div> | |
| <div style="display: flex; gap: 10px;"> | |
| <button type="submit" class="btn btn-accent"> | |
| <i data-lucide="save" style="width:14px;height:14px;"></i> | |
| Save Settings | |
| </button> | |
| <button type="button" class="btn btn-ghost" onclick="resetSettings()"> | |
| <i data-lucide="rotate-ccw" style="width:14px;height:14px;"></i> | |
| Reset | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| <!-- SCANNER LOGS --> | |
| <div class="card anim-fade-up" style="animation-delay: 0.45s;"> | |
| <div class="card-head"> | |
| <i data-lucide="terminal" style="width:16px;height:16px;"></i> | |
| Terminal Output | |
| </div> | |
| <div class="card-body"> | |
| <div class="terminal" id="terminal"> | |
| <div class="terminal-line" style="opacity:1;"> | |
| <span class="terminal-prompt">$</span> | |
| <span>GitHub Secret Scanner v2.0 initialized</span> | |
| </div> | |
| <div class="terminal-line" style="opacity:1;"> | |
| <span class="terminal-prompt">$</span> | |
| <span>Database connected: scanner.db</span> | |
| </div> | |
| <div class="terminal-line" style="opacity:1;"> | |
| <span class="terminal-prompt">$</span> | |
| <span>Loaded 8 secret detection patterns</span> | |
| <span class="terminal-cursor"></span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ===== FINDINGS ===== --> | |
| <div class="card anim-fade-up" style="animation-delay: 0.5s; margin-bottom: 24px;"> | |
| <div class="card-head" style="justify-content: space-between;"> | |
| <div style="display: flex; align-items: center; gap: 10px;"> | |
| <i data-lucide="file-search" style="width:16px;height:16px;"></i> | |
| Recent Findings | |
| </div> | |
| <div style="display: flex; gap: 8px;"> | |
| <button class="btn btn-ghost btn-sm" onclick="exportFindings()"> | |
| <i data-lucide="download" style="width:12px;height:12px;"></i> | |
| Export | |
| </button> | |
| <button class="btn btn-ghost btn-sm" onclick="clearFindings()"> | |
| <i data-lucide="trash-2" style="width:12px;height:12px;"></i> | |
| Clear | |
| </button> | |
| </div> | |
| </div> | |
| <div class="card-body" style="padding: 0;"> | |
| <div class="findings-wrap" id="findingsList"> | |
| <div class="empty-state"> | |
| <i data-lucide="inbox" style="width:40px;height:40px;"></i> | |
| <p>No findings yet. Start scanning to detect secrets.</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ===== ACTIVITY LOGS ===== --> | |
| <div class="card anim-fade-up" style="animation-delay: 0.55s; margin-bottom: 24px;"> | |
| <div class="card-head"> | |
| <i data-lucide="activity" style="width:16px;height:16px;"></i> | |
| Activity Log | |
| </div> | |
| <div class="card-body" style="padding: 8px 20px 16px;"> | |
| <div id="activityLog" style="max-height: 240px; overflow-y: auto;"> | |
| <!-- Logs injected here --> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ===== FOOTER ===== --> | |
| <footer class="footer"> | |
| <p>GitHub Secret Scanner — | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">Built with anycoder</a> | |
| </p> | |
| </footer> | |
| </div> | |
| <!-- ===== TOAST CONTAINER ===== --> | |
| <div class="toast-container" id="toastContainer"></div> | |
| <!-- ===== MODAL ===== --> | |
| <div class="modal-overlay" id="modalOverlay"> | |
| <div class="modal"> | |
| <div class="modal-title" id="modalTitle"> | |
| <i data-lucide="alert-triangle"></i> | |
| Confirmation | |
| </div> | |
| <p class="modal-desc" id="modalDesc">Are you sure?</p> | |
| <div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px;"> | |
| <button class="btn btn-ghost" onclick="closeModal()">Cancel</button> | |
| <button class="btn btn-danger" id="modalConfirm" onclick="closeModal()">Confirm</button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // ===== STATE ===== | |
| let isScanning = false; | |
| let scanInterval = null; | |
| let progressInterval = null; | |
| let scanStartTime = null; | |
| let reposScanned = 0; | |
| let totalFindings = 0; | |
| let scanCycles = 0; | |
| let settings = { | |
| githubToken: '', | |
| days: 1, | |
| maxRepos: 12, | |
| interval: 900 | |
| }; |