Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>RIGOR-PRS-Secure++ | Electrochemical Genotyping Interface</title> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&family=Fira+Code:wght@400;600&display=swap" rel="stylesheet"> | |
| <style> | |
| /* Modern CSS Reset & Custom Properties */ | |
| :root { | |
| --primary-color: #0a192f; | |
| --secondary-color: #64ffda; | |
| --accent-color: #00a8cc; | |
| --warning-color: #ff6b6b; | |
| --success-color: #4ecdc4; | |
| --text-primary: #e6f1ff; | |
| --text-secondary: #8892b0; | |
| --bg-primary: #0a192f; | |
| --bg-secondary: #112240; | |
| --bg-tertiary: #172a45; | |
| --border-color: #233554; | |
| --gradient-primary: linear-gradient(135deg, #0a192f 0%, #112240 100%); | |
| --gradient-accent: linear-gradient(135deg, #64ffda 0%, #00a8cc 100%); | |
| --shadow-primary: 0 10px 30px -15px rgba(100, 255, 218, 0.2); | |
| --shadow-heavy: 0 20px 50px -20px rgba(0, 0, 0, 0.5); | |
| --glass-bg: rgba(17, 34, 64, 0.7); | |
| --glass-border: rgba(100, 255, 218, 0.1); | |
| --terminal-bg: #0d1117; | |
| --terminal-text: #58a6ff; | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| html { | |
| scroll-behavior: smooth; | |
| } | |
| body { | |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; | |
| background: var(--bg-primary); | |
| color: var(--text-primary); | |
| line-height: 1.6; | |
| overflow-x: hidden; | |
| } | |
| /* Typography */ | |
| h1, h2, h3, h4 { | |
| font-weight: 700; | |
| line-height: 1.2; | |
| } | |
| h1 { | |
| font-size: clamp(2rem, 5vw, 3.5rem); | |
| background: var(--gradient-accent); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| } | |
| h2 { | |
| font-size: clamp(1.5rem, 3vw, 2.5rem); | |
| color: var(--secondary-color); | |
| } | |
| h3 { | |
| font-size: clamp(1.2rem, 2vw, 1.8rem); | |
| color: var(--text-primary); | |
| } | |
| h4 { | |
| font-size: 1.1rem; | |
| color: var(--text-secondary); | |
| } | |
| p, li { | |
| color: var(--text-secondary); | |
| } | |
| /* Header & Navigation */ | |
| header { | |
| position: fixed; | |
| top: 0; | |
| width: 100%; | |
| background: var(--glass-bg); | |
| backdrop-filter: blur(10px); | |
| -webkit-backdrop-filter: blur(10px); | |
| border-bottom: 1px solid var(--glass-border); | |
| z-index: 1000; | |
| padding: 1rem 0; | |
| transition: all 0.3s ease; | |
| } | |
| .header-container { | |
| max-width: 1400px; | |
| margin: 0 auto; | |
| padding: 0 2rem; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| flex-wrap: wrap; | |
| gap: 1rem; | |
| } | |
| .logo-section { | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| } | |
| .logo { | |
| width: 40px; | |
| height: 40px; | |
| background: var(--gradient-accent); | |
| border-radius: 8px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-weight: bold; | |
| color: var(--primary-color); | |
| } | |
| .brand-text { | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .brand-name { | |
| font-size: 1.2rem; | |
| font-weight: 700; | |
| color: var(--secondary-color); | |
| } | |
| .brand-tagline { | |
| font-size: 0.8rem; | |
| color: var(--text-secondary); | |
| } | |
| .anycoder-link { | |
| background: var(--gradient-accent); | |
| padding: 0.5rem 1rem; | |
| border-radius: 20px; | |
| text-decoration: none; | |
| color: var(--primary-color); | |
| font-weight: 600; | |
| font-size: 0.9rem; | |
| transition: transform 0.3s ease, box-shadow 0.3s ease; | |
| box-shadow: var(--shadow-primary); | |
| } | |
| .anycoder-link:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 15px 35px -15px rgba(100, 255, 218, 0.3); | |
| } | |
| /* Main Container */ | |
| .container { | |
| max-width: 1400px; | |
| margin: 0 auto; | |
| padding: 6rem 2rem 2rem; | |
| } | |
| /* Hero Section */ | |
| .hero { | |
| text-align: center; | |
| padding: 4rem 0; | |
| position: relative; | |
| } | |
| /* Section Cards */ | |
| .section { | |
| background: var(--glass-bg); | |
| backdrop-filter: blur(10px); | |
| -webkit-backdrop-filter: blur(10px); | |
| border: 1px solid var(--glass-border); | |
| border-radius: 16px; | |
| padding: 2rem; | |
| margin: 2rem 0; | |
| box-shadow: var(--shadow-heavy); | |
| transition: transform 0.3s ease, box-shadow 0.3s ease; | |
| } | |
| .section:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 25px 60px -20px rgba(100, 255, 218, 0.15); | |
| } | |
| .section-header { | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| margin-bottom: 1.5rem; | |
| padding-bottom: 1rem; | |
| border-bottom: 1px solid var(--border-color); | |
| } | |
| .section-icon { | |
| width: 50px; | |
| height: 50px; | |
| background: var(--gradient-accent); | |
| border-radius: 12px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1.5rem; | |
| color: var(--primary-color); | |
| } | |
| /* Buttons */ | |
| .btn { | |
| background: var(--gradient-accent); | |
| color: var(--primary-color); | |
| border: none; | |
| padding: 0.75rem 1.5rem; | |
| border-radius: 8px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| text-decoration: none; | |
| font-size: 1rem; | |
| } | |
| .btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 25px -10px rgba(100, 255, 218, 0.4); | |
| } | |
| .btn:disabled { | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| .btn-secondary { | |
| background: var(--bg-tertiary); | |
| color: var(--secondary-color); | |
| border: 1px solid var(--border-color); | |
| } | |
| /* Hardware Interface */ | |
| .hardware-interface { | |
| display: grid; | |
| grid-template-columns: 300px 1fr; | |
| gap: 2rem; | |
| } | |
| @media (max-width: 900px) { | |
| .hardware-interface { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| .device-panel { | |
| background: var(--bg-tertiary); | |
| border-radius: 12px; | |
| padding: 1.5rem; | |
| border: 1px solid var(--border-color); | |
| } | |
| .status-row { | |
| display: flex; | |
| justify-content: space-between; | |
| margin-bottom: 0.8rem; | |
| font-size: 0.9rem; | |
| } | |
| .status-value { | |
| color: var(--secondary-color); | |
| font-family: 'Fira Code', monospace; | |
| } | |
| /* Electrode Grid */ | |
| .electrode-grid-container { | |
| background: #000; | |
| border-radius: 8px; | |
| padding: 1rem; | |
| border: 1px solid var(--border-color); | |
| position: relative; | |
| } | |
| .electrode-grid { | |
| display: grid; | |
| grid-template-columns: repeat(12, 1fr); | |
| gap: 4px; | |
| aspect-ratio: 4/3; | |
| } | |
| .electrode { | |
| background: #1a1a1a; | |
| border-radius: 2px; | |
| transition: all 0.2s ease; | |
| position: relative; | |
| } | |
| .electrode:hover { | |
| border: 1px solid #fff; | |
| z-index: 10; | |
| } | |
| .electrode.active-scan { | |
| background: #f1c40f; | |
| box-shadow: 0 0 10px #f1c40f; | |
| } | |
| .electrode.ref-detected { | |
| background: var(--success-color); | |
| } | |
| .electrode.alt-detected { | |
| background: var(--warning-color); | |
| } | |
| .electrode.qc-pass { | |
| background: var(--accent-color); | |
| } | |
| /* Terminal/Console */ | |
| .terminal-window { | |
| background: var(--terminal-bg); | |
| border-radius: 8px; | |
| border: 1px solid #30363d; | |
| font-family: 'Fira Code', monospace; | |
| margin-top: 2rem; | |
| overflow: hidden; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .terminal-header { | |
| background: #161b22; | |
| padding: 0.5rem 1rem; | |
| border-bottom: 1px solid #30363d; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .terminal-title { | |
| font-size: 0.85rem; | |
| color: #8b949e; | |
| } | |
| .terminal-body { | |
| height: 200px; | |
| overflow-y: auto; | |
| padding: 1rem; | |
| font-size: 0.85rem; | |
| color: var(--text-secondary); | |
| } | |
| .log-entry { | |
| margin-bottom: 0.25rem; | |
| word-break: break-all; | |
| } | |
| .log-tx { color: var(--secondary-color); } | |
| .log-rx { color: var(--accent-color); } | |
| .log-sys { color: var(--warning-color); font-style: italic; } | |
| /* Voltammetry Chart */ | |
| .chart-container { | |
| width: 100%; | |
| height: 200px; | |
| background: var(--bg-tertiary); | |
| border-radius: 8px; | |
| margin-top: 1rem; | |
| position: relative; | |
| border: 1px solid var(--border-color); | |
| } | |
| canvas { | |
| width: 100%; | |
| height: 100%; | |
| } | |
| /* Manufacturing Visualization (New) */ | |
| .manufacturing-line { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| position: relative; | |
| padding: 2rem 0; | |
| overflow-x: auto; | |
| } | |
| .mfg-step { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| z-index: 2; | |
| min-width: 100px; | |
| } | |
| .mfg-icon { | |
| width: 60px; | |
| height: 60px; | |
| background: var(--bg-secondary); | |
| border: 2px solid var(--border-color); | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1.2rem; | |
| color: var(--text-secondary); | |
| margin-bottom: 0.5rem; | |
| transition: all 0.5s ease; | |
| } | |
| .mfg-step.active .mfg-icon { | |
| border-color: var(--secondary-color); | |
| background: var(--gradient-accent); | |
| color: var(--primary-color); | |
| box-shadow: 0 0 15px var(--secondary-color); | |
| } | |
| .mfg-label { | |
| font-size: 0.8rem; | |
| text-align: center; | |
| } | |
| .mfg-track { | |
| position: absolute; | |
| top: 50%; | |
| left: 0; | |
| width: 100%; | |
| height: 4px; | |
| background: var(--bg-tertiary); | |
| transform: translateY(-50%); | |
| z-index: 1; | |
| } | |
| .mfg-progress { | |
| height: 100%; | |
| background: var(--gradient-accent); | |
| width: 0%; | |
| transition: width 0.5s ease; | |
| } | |
| /* Blockchain Section (New) */ | |
| .blockchain-viz { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 1rem; | |
| } | |
| .chain-step { | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| background: var(--bg-secondary); | |
| padding: 1rem; | |
| border-radius: 8px; | |
| border-left: 4px solid var(--border-color); | |
| opacity: 0.5; | |
| transition: all 0.3s ease; | |
| } | |
| .chain-step.active { | |
| opacity: 1; | |
| border-left-color: var(--secondary-color); | |
| background: rgba(100, 255, 218, 0.05); | |
| } | |
| .chain-step i { | |
| font-size: 1.2rem; | |
| width: 30px; | |
| text-align: center; | |
| } | |
| .hash-display { | |
| font-family: 'Fira Code', monospace; | |
| font-size: 0.8rem; | |
| color: var(--accent-color); | |
| word-break: break-all; | |
| } | |
| /* Upload Zone */ | |
| .upload-zone { | |
| border: 2px dashed var(--border-color); | |
| border-radius: 12px; | |
| padding: 3rem; | |
| text-align: center; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| } | |
| .upload-zone:hover { | |
| border-color: var(--secondary-color); | |
| background: rgba(100, 255, 218, 0.02); | |
| } | |
| .file-list { | |
| margin-top: 1rem; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 0.5rem; | |
| } | |
| .file-item { | |
| background: var(--bg-secondary); | |
| padding: 0.75rem; | |
| border-radius: 6px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| } | |
| /* Results & Tables */ | |
| .results-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); | |
| gap: 1.5rem; | |
| margin: 2rem 0; | |
| } | |
| .result-card { | |
| background: var(--bg-secondary); | |
| border-radius: 12px; | |
| padding: 1.5rem; | |
| border-left: 4px solid var(--accent-color); | |
| transition: transform 0.3s ease; | |
| } | |
| .result-value { | |
| font-size: 2rem; | |
| font-weight: 700; | |
| color: var(--secondary-color); | |
| margin: 0.5rem 0; | |
| } | |
| .data-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| margin: 1rem 0; | |
| border-radius: 8px; | |
| overflow: hidden; | |
| } | |
| .data-table th { | |
| background: var(--bg-tertiary); | |
| padding: 1rem; | |
| text-align: left; | |
| font-weight: 600; | |
| color: var(--secondary-color); | |
| border-bottom: 1px solid var(--border-color); | |
| } | |
| .data-table td { | |
| padding: 0.75rem 1rem; | |
| border-bottom: 1px solid var(--border-color); | |
| } | |
| .data-table tr:hover { | |
| background: rgba(100, 255, 218, 0.05); | |
| } | |
| /* Accessibility & Utilities */ | |
| .accessibility-controls { | |
| position: fixed; | |
| bottom: 2rem; | |
| right: 2rem; | |
| background: var(--glass-bg); | |
| backdrop-filter: blur(10px); | |
| border: 1px solid var(--glass-border); | |
| border-radius: 12px; | |
| padding: 1rem; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 0.5rem; | |
| z-index: 999; | |
| } | |
| .accessibility-btn { | |
| width: 40px; | |
| height: 40px; | |
| border: none; | |
| background: var(--bg-secondary); | |
| color: var(--text-primary); | |
| border-radius: 8px; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: all 0.3s ease; | |
| } | |
| .accessibility-btn:hover, .accessibility-btn.active { | |
| background: var(--secondary-color); | |
| color: var(--primary-color); | |
| } | |
| /* Animations */ | |
| @keyframes pulse { | |
| 0% { opacity: 1; } | |
| 50% { opacity: 0.5; } | |
| 100% { opacity: 1; } | |
| } | |
| .spinner { | |
| width: 40px; | |
| height: 40px; | |
| border: 4px solid var(--border-color); | |
| border-top: 4px solid var(--secondary-color); | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| margin: 2rem auto; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .fade-in { | |
| animation: fadeIn 0.5s ease-in; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(20px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| /* Scrollbar */ | |
| ::-webkit-scrollbar { | |
| width: 8px; | |
| height: 8px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: var(--bg-primary); | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: var(--secondary-color); | |
| border-radius: 4px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Header --> | |
| <header role="banner"> | |
| <div class="header-container"> | |
| <div class="logo-section"> | |
| <div class="logo" aria-hidden="true">RIGOR</div> | |
| <div class="brand-text"> | |
| <div class="brand-name">RIGOR-PRS-Secure++</div> | |
| <div class="brand-tagline">Electrochemical Genotyping & PRS Platform</div> | |
| </div> | |
| </div> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link" | |
| aria-label="Built with anycoder - Visit Hugging Face Space"> | |
| <i class="fas fa-rocket" aria-hidden="true"></i> | |
| Built with anycoder | |
| </a> | |
| </div> | |
| </header> | |
| <!-- Main Container --> | |
| <main class="container" role="main"> | |
| <!-- Hero --> | |
| <section class="hero" aria-labelledby="main-title"> | |
| <h1 id="main-title">Hardware-Integrated Genetic Analysis</h1> | |
| <p class="hero-subtitle">Direct USB-C interface to 96-electrode electrochemical cartridges with real-time | |
| voltammetry and post-quantum evidence hashing.</p> | |
| </section> | |
| <!-- SECTION 1: Hardware Interface --> | |
| <section class="section fade-in" aria-labelledby="hardware-title"> | |
| <div class="section-header"> | |
| <div class="section-icon" aria-hidden="true"> | |
| <i class="fas fa-microchip"></i> | |
| </div> | |
| <div> | |
| <h2 id="hardware-title">1. Electrochemical Reader Interface</h2> | |
| <p>Phase 2: USB-C Device Detection & Multiplexer Control</p> | |
| </div> | |
| </div> | |
| <div class="hardware-interface"> | |
| <!-- Left: Device Controls --> | |
| <div class="device-panel"> | |
| <h3>Device Status</h3> | |
| <div style="height: 1rem;"></div> | |
| <div class="status-row"> | |
| <span>Connection:</span> | |
| <span class="status-value" id="hw-conn-status" style="color: var(--warning-color)">Disconnected</span> | |
| </div> | |
| <div class="status-row"> | |
| <span>Port:</span> | |
| <span class="status-value" id="hw-port">--</span> | |
| </div> | |
| <div class="status-row"> | |
| <span>MCU:</span> | |
| <span class="status-value">RP2040</span> | |
| </div> | |
| <div class="status-row"> | |
| <span>Firmware:</span> | |
| <span class="status-value">v1.2.3</span> | |
| </div> | |
| <hr style="border: 0; border-top: 1px solid var(--border-color); margin: 1.5rem 0;"> | |
| <h3>Real-time Telemetry</h3> | |
| <div style="height: 1rem;"></div> | |
| <div class="status-row"> | |
| <span>Supply Voltage:</span> | |
| <span class="status-value" id="hw-voltage">0.00 V</span> | |
| </div> | |
| <div class="status-row"> | |
| <span>Current:</span> | |
| <span class="status-value" id="hw-current">0.00 mA</span> | |
| </div> | |
| <div class="status-row"> | |
| <span>Temp (NTC):</span> | |
| <span class="status-value" id="hw-temp">-- °C</span> | |
| </div> | |
| <div style="margin-top: 2rem;"> | |
| <button class="btn" id="btn-connect" onclick="toggleConnection()" style="width: 100%; justify-content: center;"> | |
| <i class="fas fa-plug"></i> Connect Reader | |
| </button> | |
| <button class="btn btn-secondary" id="btn-start-hw-test" onclick="startHardwareTest()" disabled style="width: 100%; justify-content: center; margin-top: 1rem;"> | |
| <i class="fas fa-play"></i> Start Cartridge Scan | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Right: Grid & Charts --> | |
| <div> | |
| <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem;"> | |
| <h4>96-Electrode Array (ADG731 Mux)</h4> | |
| <span style="font-size: 0.8rem; color: var(--text-secondary);">Spatial Multiplexing Map</span> | |
| </div> | |
| <div class="electrode-grid-container"> | |
| <div class="electrode-grid" id="electrode-grid"> | |
| <!-- Generated by JS --> | |
| </div> | |
| </div> | |
| <div class="chart-container"> | |
| <canvas id="voltammetryChart"></canvas> | |
| <div | |
| style="position: absolute; top: 10px; left: 10px; font-size: 0.8rem; color: var(--text-secondary); background: rgba(10,25,47,0.8); padding: 2px 5px; border-radius: 4px;"> | |
| SWV Signal (Current vs Potential) | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- API Terminal --> | |
| <div class="terminal-window"> | |
| <div class="terminal-header"> | |
| <span class="terminal-title"><i class="fas fa-terminal"></i> API CONSOLE (CDC-ACM)</span> | |
| <i class="fas fa-wifi" style="color: var(--success-color); opacity: 0;"></i> | |
| </div> | |
| <div class="terminal-body" id="terminal-output"> | |
| <div class="log-entry log-sys">System ready. Waiting for USB-C device enumeration...</div> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- SECTION 2: Manufacturing Visualization --> | |
| <section class="section fade-in" aria-labelledby="mfg-title"> | |
| <div class="section-header"> | |
| <div class="section-icon" aria-hidden="true"> | |
| <i class="fas fa-industry"></i> | |
| </div> | |
| <div> | |
| <h2 id="mfg-title">2. Roll-to-Roll Manufacturing (FIG. 9)</h2> | |
| <p>Visualizing the cartridge production line: 15 m/min web speed</p> | |
| </div> | |
| </div> | |
| <div class="manufacturing-line"> | |
| <div class="mfg-track"> | |
| <div class="mfg-progress" id="mfg-progress-bar"></div> | |
| </div> | |
| <div class="mfg-step" id="step-0"> | |
| <div class="mfg-icon"><i class="fas fa-file"></i></div> | |
| <div class="mfg-label">PET Stock</div> | |
| </div> | |
| <div class="mfg-step" id="step-1"> | |
| <div class="mfg-icon"><i class="fas fa-print"></i></div> | |
| <div class="mfg-label">Gravure Carbon</div> | |
| </div> | |
| <div class="mfg-step" id="step-2"> | |
| <div class="mfg-icon"><i class="fas fa-temperature-high"></i></div> | |
| <div class="mfg-label">IR Dry</div> | |
| </div> | |
| <div class="mfg-step" id="step-3"> | |
| <div class="mfg-icon"><i class="fas fa-mask"></i></div> | |
| <div class="mfg-label">Screen Ag/AgCl</div> | |
| </div> | |
| <div class="mfg-step" id="step-4"> | |
| <div class="mfg-icon"><i class="fas fa-sun"></i></div> | |
| <div class="mfg-label">UV Cure</div> | |
| </div> | |
| <div class="mfg-step" id="step-5"> | |
| <div class="mfg-icon"><i class="fas fa-bolt"></i></div> | |
| <div class="mfg-label">Laser Ablate</div> | |
| </div> | |
| <div class="mfg-step" id="step-6"> | |
| <div class="mfg-icon"><i class="fas fa-layer-group"></i></div> | |
| <div class="mfg-label">Laminate</div> | |
| </div> | |
| <div class="mfg-step" id="step-7"> | |
| <div class="mfg-icon"><i class="fas fa-cut"></i></div> | |
| <div class="mfg-label">Die-Cut</div> | |
| </div> | |
| </div> | |
| <div style="margin-top: 1rem; text-align: center;"> | |
| <button class="btn btn-secondary" onclick="runManufacturingSim()"> | |
| <i class="fas fa-play-circle"></i> Simulate Production Batch | |
| </button> | |
| <p id="mfg-status" style="margin-top: 0.5rem; font-size: 0.9rem; font-family: 'Fira Code';">Status: Idle</p> | |
| </div> | |
| </section> | |
| <!-- File Upload Section (Fallback/Software Mode) --> | |
| <section class="section fade-in" aria-labelledby="upload-title"> | |
| <div class="section-header"> | |
| <div class="section-icon" aria-hidden="true"> | |
| <i class="fas fa-upload"></i> | |
| </div> | |
| <div> | |
| <h2 id="upload-title">3. Software Mode: Encrypted Ingestion</h2> | |
| <p>Upload VCF or PLINK files if hardware is unavailable</p> | |
| </div> | |
| </div> | |
| <div class="upload-zone" id="upload-zone" role="button" tabindex="0" | |
| aria-label="Drag and drop files here or click to browse"> | |
| <div class="upload-icon" aria-hidden="true" style="font-size: 3rem; color: var(--secondary-color); margin-bottom: 1rem;"> | |
| <i class="fas fa-dna"></i> | |
| </div> | |
| <h3>Drop Genotype Files Here</h3> | |
| <p>Supports VCF, PLINK binary (.bed/.bim/.fam)</p> | |
| <button class="btn" onclick="document.getElementById('file-input').click()" style="margin-top: 1rem;"> | |
| <i class="fas fa-folder-open"></i> Browse Files | |
| </button> | |
| <input type="file" id="file-input" class="file-input" multiple accept=".vcf,.vcf.gz,.bed,.bim,.fam" style="display: none;"> | |
| </div> | |
| <div class="file-list" id="file-list" aria-live="polite"></div> | |
| <button class="btn" id="process-btn" onclick="startProcessing()" disabled style="margin-top: 1rem; width: 100%; justify-content: center;"> | |
| <i class="fas fa-cogs"></i> Process Software Data | |
| </button> | |
| </section> | |
| <!-- SECTION 3: Blockchain Evidence Anchoring --> | |
| <section class="section fade-in" id="blockchain-section" aria-labelledby="bc-title" style="display: none;"> | |
| <div class="section-header"> | |
| <div class="section-icon" aria-hidden="true"> | |
| <i class="fas fa-link"></i> | |
| </div> | |
| <div> | |
| <h2 id="bc-title">4. Quantum-Resistant Blockchain Anchoring</h2> | |
| <p>Post-Quantum Cryptography (Dilithium) & Hyperledger Fabric</p> | |
| </div> | |
| </div> | |
| <div class="blockchain-viz"> | |
| <!-- Step A --> | |
| <div class="chain-step" id="bc-step-a"> | |
| <i class="fas fa-file-contract"></i> | |
| <div> | |
| <h4>Step A: Prepare Evidence Packet</h4> | |
| <p style="font-size: 0.9rem;">UID + Timestamp + Hash(Genotype Vector)</p> | |
| <div class="hash-display" id="bc-packet-hash">Calculating...</div> | |
| </div> | |
| </div> | |
| <!-- Step B --> | |
| <div class="chain-step" id="bc-step-b"> | |
| <i class="fas fa-key"></i> | |
| <div> | |
| <h4>Step B: Lattice-Based Signing (Dilithium)</h4> | |
| <p style="font-size: 0.9rem;">Signing with Private Key (Post-Quantum Secure)</p> | |
| <div class="spinner" id="bc-spinner" style="width: 20px; height: 20px; margin: 0.5rem 0; border-width: 2px;"></div> | |
| </div> | |
| </div> | |
| <!-- Step C --> | |
| <div class="chain-step" id="bc-step-c"> | |
| <i class="fas fa-network-wired"></i> | |
| <div> | |
| <h4>Step C: Submit to Hyperledger Fabric</h4> | |
| <p style="font-size: 0.9rem;">Broadcasting transaction to permissioned nodes</p> | |
| </div> | |
| </div> | |
| <!-- Result --> | |
| <div class="chain-step active" id="bc-result" style="display: none; border-left-color: var(--success-color);"> | |
| <i class="fas fa-check-circle" style="color: var(--success-color);"></i> | |
| <div> | |
| <h4 style="color: var(--success-color);">Evidence Anchored Successfully</h4> | |
| <p style="font-size: 0.9rem;">TXID: <span id="bc-txid" class="hash-display"></span></p> | |
| <button class="btn btn-secondary" style="margin-top: 0.5rem; padding: 0.5rem;" onclick="downloadCertificate()"> | |
| <i class="fas fa-download"></i> Download JSON-LD Certificate | |
| </button> | |
| </div> | |
| </div> | |
| <button id="btn-start-bc" class="btn" onclick="startBlockchainProcess()" style="margin-top: 1rem; align-self: flex-start;"> | |
| <i class="fas fa-shield-alt"></i> Sign & Anchor Evidence | |
| </button> | |
| </div> | |
| </section> | |
| <!-- Results Section --> | |
| <section class="section fade-in" id="results-section" aria-labelledby="results-title" style="display: none;"> | |
| <div class="section-header"> | |
| <div class="section-icon" aria-hidden="true"> | |
| <i class="fas fa-chart-line"></i> | |
| </div> | |
| <div> | |
| <h2 id="results-title">5. Analysis Results</h2> | |
| <p>Polygenic Risk Scores & Evidence Authentication</p> | |
| </div> | |
| </div> | |
| <div class="results-grid"> | |
| <div class="result-card"> | |
| <div class="result-label">Raw PRS Score</div> | |
| <div class="result-value" id="raw-score">0.00</div> | |
| <small>Sum of weighted risk alleles</small> | |
| </div> | |
| <div class="result-card"> | |
| <div class="result-label">Z-Score</div> | |
| <div class="result-value" id="z-score">0.00</div> | |
| <small>Std dev from mean</small> | |
| </div> | |
| <div class="result-card"> | |
| <div class="result-label">Percentile</div> | |
| <div class="result-value" id="percentile">0th</div> | |
| <small>Population rank</small> | |
| </div> | |
| <div class="result-card"> | |
| <div class="result-label">Blockchain TXID</div> | |
| <div class="result-value" style="font-size: 1rem; word-break: break-all;" id="res-txid">Pending...</div> | |
| <small>Dilithium Signed</small> | |
| </div> | |
| </div> | |
| <h3>Genotype Vector</h3> | |
| <div class="table-container" style="overflow-x: auto;"> | |
| <table class="data-table" id="variant-table"> | |
| <thead> | |
| <tr> | |
| <th scope="col">SNP ID</th> | |
| <th scope="col">Chr</th> | |
| <th scope="col">Genotype</th> | |
| <th scope="col">Signal (µA)</th> | |
| <th scope="col">Confidence</th> | |
| </tr> | |
| </thead> | |
| <tbody id="variant-table-body"> | |
| <!-- Populated by JS --> | |
| </tbody> | |
| </table> | |
| </div> | |
| </section> | |
| </main> | |
| <!-- Accessibility Controls --> | |
| <div class="accessibility-controls" role="group" aria-label="Accessibility controls"> | |
| <button class="accessibility-btn" id="high-contrast-btn" onclick="toggleHighContrast()" title="Toggle high contrast mode" aria-label="Toggle high contrast mode"> | |
| <i class="fas fa-adjust"></i> | |
| </button> | |
| <button class="accessibility-btn" onclick="increaseFontSize()" title="Increase font size" aria-label="Increase font size"> | |
| <i class="fas fa-search-plus"></i> | |
| </button> | |
| <button class="accessibility-btn" onclick="decreaseFontSize()" title="Decrease font size" aria-label="Decrease font size"> | |
| <i class="fas fa-search-minus"></i> | |
| </button> | |
| </div> | |
| <script> | |
| // --- CONSTANTS & STATE --- | |
| const GRID_SIZE = 96; | |
| let isConnected = false; | |
| let isScanning = false; | |
| let scanInterval; | |
| let chartAnimationId; | |
| let genotypeData = []; // Store for blockchain | |
| // --- INITIALIZATION --- | |
| document.addEventListener('DOMContentLoaded', () => { | |
| initGrid(); | |
| initChart(); | |
| initFileUpload(); | |
| logToTerminal('SYS', 'Application initialized. Waiting for device...'); | |
| }); | |
| // --- HARDWARE SIMULATION --- | |
| function toggleConnection() { | |
| const btn = document.getElementById('btn-connect'); | |
| const statusLabel = document.getElementById('hw-conn-status'); | |
| const portLabel = document.getElementById('hw-port'); | |
| const testBtn = document.getElementById('btn-start-hw-test'); | |
| if (!isConnected) { | |
| // Simulate Connection | |
| btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Connecting...'; | |
| btn.disabled = true; | |
| logToTerminal('TX', 'CONNECT {"timeout": 5000}'); | |
| setTimeout(() => { | |
| isConnected = true; | |
| btn.innerHTML = '<i class="fas fa-eject"></i |