1061 lines
34 KiB
HTML
1061 lines
34 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Cyberpunk Data Explorer - Neural Implant Registry</title>
|
|
<style>
|
|
:root {
|
|
--neon-cyan: #00ffff;
|
|
--neon-pink: #ff00ff;
|
|
--neon-yellow: #ffff00;
|
|
--neon-green: #00ff00;
|
|
--dark-bg: #0a0a0a;
|
|
--darker-bg: #050505;
|
|
--panel-bg: rgba(10, 10, 10, 0.9);
|
|
--text-primary: #ffffff;
|
|
--text-secondary: #a0a0a0;
|
|
--border-color: #303030;
|
|
--hover-bg: rgba(0, 255, 255, 0.1);
|
|
--selection-bg: rgba(255, 0, 255, 0.2);
|
|
--glitch-1: #ff00ff;
|
|
--glitch-2: #00ffff;
|
|
}
|
|
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Courier New', monospace;
|
|
background: var(--dark-bg);
|
|
color: var(--text-primary);
|
|
overflow-x: hidden;
|
|
min-height: 100vh;
|
|
position: relative;
|
|
}
|
|
|
|
/* Animated background grid */
|
|
body::before {
|
|
content: '';
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-image:
|
|
repeating-linear-gradient(0deg, transparent, transparent 50px, rgba(0, 255, 255, 0.03) 50px, rgba(0, 255, 255, 0.03) 51px),
|
|
repeating-linear-gradient(90deg, transparent, transparent 50px, rgba(255, 0, 255, 0.03) 50px, rgba(255, 0, 255, 0.03) 51px);
|
|
pointer-events: none;
|
|
z-index: 1;
|
|
}
|
|
|
|
.container {
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
position: relative;
|
|
z-index: 2;
|
|
}
|
|
|
|
/* Header with glitch effect */
|
|
.header {
|
|
text-align: center;
|
|
margin-bottom: 40px;
|
|
position: relative;
|
|
}
|
|
|
|
.header h1 {
|
|
font-size: 3rem;
|
|
font-weight: bold;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.2em;
|
|
position: relative;
|
|
animation: flicker 2s infinite alternate;
|
|
}
|
|
|
|
.header h1::before,
|
|
.header h1::after {
|
|
content: attr(data-text);
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.header h1::before {
|
|
animation: glitch-1 0.3s infinite;
|
|
color: var(--glitch-1);
|
|
z-index: -1;
|
|
}
|
|
|
|
.header h1::after {
|
|
animation: glitch-2 0.3s infinite;
|
|
color: var(--glitch-2);
|
|
z-index: -2;
|
|
}
|
|
|
|
@keyframes flicker {
|
|
0%, 100% { opacity: 1; }
|
|
50% { opacity: 0.8; }
|
|
}
|
|
|
|
@keyframes glitch-1 {
|
|
0% { transform: translateX(0); }
|
|
20% { transform: translateX(-2px); }
|
|
40% { transform: translateX(2px); }
|
|
60% { transform: translateX(-1px); }
|
|
80% { transform: translateX(1px); }
|
|
100% { transform: translateX(0); }
|
|
}
|
|
|
|
@keyframes glitch-2 {
|
|
0% { transform: translateX(0); }
|
|
20% { transform: translateX(2px); }
|
|
40% { transform: translateX(-2px); }
|
|
60% { transform: translateX(1px); }
|
|
80% { transform: translateX(-1px); }
|
|
100% { transform: translateX(0); }
|
|
}
|
|
|
|
/* Control panel */
|
|
.control-panel {
|
|
background: var(--panel-bg);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 0;
|
|
padding: 20px;
|
|
margin-bottom: 20px;
|
|
box-shadow: 0 0 20px rgba(0, 255, 255, 0.3);
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.control-panel::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: -100%;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: linear-gradient(90deg, transparent, rgba(0, 255, 255, 0.2), transparent);
|
|
animation: scan 3s infinite;
|
|
}
|
|
|
|
@keyframes scan {
|
|
0% { left: -100%; }
|
|
100% { left: 100%; }
|
|
}
|
|
|
|
.controls-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
gap: 20px;
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.control-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
.control-group label {
|
|
color: var(--neon-cyan);
|
|
text-transform: uppercase;
|
|
font-size: 0.8rem;
|
|
letter-spacing: 0.1em;
|
|
}
|
|
|
|
input[type="text"],
|
|
select {
|
|
background: rgba(0, 0, 0, 0.5);
|
|
border: 1px solid var(--border-color);
|
|
color: var(--text-primary);
|
|
padding: 10px;
|
|
font-family: inherit;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
input[type="text"]:focus,
|
|
select:focus {
|
|
outline: none;
|
|
border-color: var(--neon-cyan);
|
|
box-shadow: 0 0 10px rgba(0, 255, 255, 0.5);
|
|
}
|
|
|
|
.button {
|
|
background: transparent;
|
|
border: 1px solid var(--neon-pink);
|
|
color: var(--neon-pink);
|
|
padding: 10px 20px;
|
|
cursor: pointer;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.1em;
|
|
transition: all 0.3s;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.button::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
width: 0;
|
|
height: 0;
|
|
background: var(--neon-pink);
|
|
transform: translate(-50%, -50%);
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.button:hover {
|
|
color: var(--dark-bg);
|
|
box-shadow: 0 0 20px var(--neon-pink);
|
|
}
|
|
|
|
.button:hover::before {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.button span {
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
/* Data table */
|
|
.data-table-container {
|
|
background: var(--panel-bg);
|
|
border: 1px solid var(--border-color);
|
|
overflow: hidden;
|
|
position: relative;
|
|
}
|
|
|
|
.table-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 15px 20px;
|
|
border-bottom: 1px solid var(--border-color);
|
|
background: rgba(0, 255, 255, 0.05);
|
|
}
|
|
|
|
.table-title {
|
|
color: var(--neon-cyan);
|
|
font-size: 1.2rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.1em;
|
|
}
|
|
|
|
.table-actions {
|
|
display: flex;
|
|
gap: 10px;
|
|
}
|
|
|
|
.table-wrapper {
|
|
overflow-x: auto;
|
|
max-height: 600px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
th {
|
|
background: rgba(0, 0, 0, 0.5);
|
|
padding: 15px;
|
|
text-align: left;
|
|
color: var(--neon-cyan);
|
|
text-transform: uppercase;
|
|
font-size: 0.8rem;
|
|
letter-spacing: 0.1em;
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 10;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
}
|
|
|
|
th:hover {
|
|
background: rgba(0, 255, 255, 0.1);
|
|
}
|
|
|
|
th.sorted-asc::after {
|
|
content: ' ▲';
|
|
color: var(--neon-yellow);
|
|
}
|
|
|
|
th.sorted-desc::after {
|
|
content: ' ▼';
|
|
color: var(--neon-yellow);
|
|
}
|
|
|
|
td {
|
|
padding: 15px;
|
|
border-bottom: 1px solid rgba(48, 48, 48, 0.5);
|
|
}
|
|
|
|
tr {
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
tbody tr:hover {
|
|
background: var(--hover-bg);
|
|
transform: translateX(5px);
|
|
}
|
|
|
|
tbody tr.selected {
|
|
background: var(--selection-bg);
|
|
box-shadow: 0 0 10px rgba(255, 0, 255, 0.3);
|
|
}
|
|
|
|
.checkbox-cell {
|
|
width: 50px;
|
|
}
|
|
|
|
.cyber-checkbox {
|
|
width: 20px;
|
|
height: 20px;
|
|
appearance: none;
|
|
border: 2px solid var(--neon-pink);
|
|
background: transparent;
|
|
cursor: pointer;
|
|
position: relative;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.cyber-checkbox:checked {
|
|
background: var(--neon-pink);
|
|
box-shadow: 0 0 10px var(--neon-pink);
|
|
}
|
|
|
|
.cyber-checkbox:checked::after {
|
|
content: '✓';
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
color: var(--dark-bg);
|
|
font-weight: bold;
|
|
}
|
|
|
|
/* Status indicators */
|
|
.status {
|
|
padding: 5px 10px;
|
|
border-radius: 3px;
|
|
font-size: 0.8rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
display: inline-block;
|
|
}
|
|
|
|
.status.active {
|
|
background: rgba(0, 255, 0, 0.2);
|
|
border: 1px solid var(--neon-green);
|
|
color: var(--neon-green);
|
|
box-shadow: 0 0 5px rgba(0, 255, 0, 0.5);
|
|
}
|
|
|
|
.status.inactive {
|
|
background: rgba(255, 0, 0, 0.2);
|
|
border: 1px solid #ff0000;
|
|
color: #ff0000;
|
|
}
|
|
|
|
.status.pending {
|
|
background: rgba(255, 255, 0, 0.2);
|
|
border: 1px solid var(--neon-yellow);
|
|
color: var(--neon-yellow);
|
|
animation: pulse 1s infinite;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% { opacity: 1; }
|
|
50% { opacity: 0.5; }
|
|
}
|
|
|
|
/* Risk level indicators */
|
|
.risk-level {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
}
|
|
|
|
.risk-bar {
|
|
width: 60px;
|
|
height: 10px;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.risk-fill {
|
|
height: 100%;
|
|
transition: width 0.3s;
|
|
}
|
|
|
|
.risk-low { background: var(--neon-green); }
|
|
.risk-medium { background: var(--neon-yellow); }
|
|
.risk-high { background: #ff0000; }
|
|
|
|
/* Pagination */
|
|
.pagination {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 20px;
|
|
border-top: 1px solid var(--border-color);
|
|
}
|
|
|
|
.page-button {
|
|
background: transparent;
|
|
border: 1px solid var(--border-color);
|
|
color: var(--text-secondary);
|
|
padding: 8px 12px;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.page-button:hover:not(:disabled) {
|
|
border-color: var(--neon-cyan);
|
|
color: var(--neon-cyan);
|
|
box-shadow: 0 0 10px rgba(0, 255, 255, 0.3);
|
|
}
|
|
|
|
.page-button:disabled {
|
|
opacity: 0.3;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.page-button.active {
|
|
background: var(--neon-cyan);
|
|
color: var(--dark-bg);
|
|
border-color: var(--neon-cyan);
|
|
box-shadow: 0 0 15px var(--neon-cyan);
|
|
}
|
|
|
|
.page-info {
|
|
color: var(--text-secondary);
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
/* Export modal */
|
|
.modal {
|
|
display: none;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(0, 0, 0, 0.9);
|
|
z-index: 1000;
|
|
backdrop-filter: blur(5px);
|
|
}
|
|
|
|
.modal.active {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
.modal-content {
|
|
background: var(--panel-bg);
|
|
border: 1px solid var(--neon-cyan);
|
|
padding: 30px;
|
|
max-width: 500px;
|
|
width: 90%;
|
|
box-shadow: 0 0 30px rgba(0, 255, 255, 0.5);
|
|
position: relative;
|
|
}
|
|
|
|
.modal-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.modal-title {
|
|
color: var(--neon-cyan);
|
|
font-size: 1.5rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.1em;
|
|
}
|
|
|
|
.close-button {
|
|
background: transparent;
|
|
border: none;
|
|
color: var(--neon-pink);
|
|
font-size: 2rem;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.close-button:hover {
|
|
transform: rotate(90deg);
|
|
text-shadow: 0 0 10px var(--neon-pink);
|
|
}
|
|
|
|
.export-options {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 15px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.export-option {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 15px;
|
|
border: 1px solid var(--border-color);
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.export-option:hover {
|
|
border-color: var(--neon-cyan);
|
|
background: rgba(0, 255, 255, 0.05);
|
|
}
|
|
|
|
.export-option input[type="radio"] {
|
|
appearance: none;
|
|
width: 20px;
|
|
height: 20px;
|
|
border: 2px solid var(--neon-cyan);
|
|
border-radius: 50%;
|
|
position: relative;
|
|
}
|
|
|
|
.export-option input[type="radio"]:checked::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
width: 10px;
|
|
height: 10px;
|
|
background: var(--neon-cyan);
|
|
border-radius: 50%;
|
|
transform: translate(-50%, -50%);
|
|
box-shadow: 0 0 10px var(--neon-cyan);
|
|
}
|
|
|
|
/* Loading animation */
|
|
.loading {
|
|
display: none;
|
|
position: fixed;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
z-index: 2000;
|
|
}
|
|
|
|
.loading.active {
|
|
display: block;
|
|
}
|
|
|
|
.loading-spinner {
|
|
width: 50px;
|
|
height: 50px;
|
|
border: 3px solid transparent;
|
|
border-top-color: var(--neon-cyan);
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
/* Responsive design */
|
|
@media (max-width: 768px) {
|
|
.header h1 {
|
|
font-size: 2rem;
|
|
}
|
|
|
|
.controls-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.table-wrapper {
|
|
max-height: 400px;
|
|
}
|
|
|
|
th, td {
|
|
padding: 10px;
|
|
font-size: 0.9rem;
|
|
}
|
|
}
|
|
|
|
/* Accessibility */
|
|
.sr-only {
|
|
position: absolute;
|
|
width: 1px;
|
|
height: 1px;
|
|
padding: 0;
|
|
margin: -1px;
|
|
overflow: hidden;
|
|
clip: rect(0, 0, 0, 0);
|
|
white-space: nowrap;
|
|
border: 0;
|
|
}
|
|
|
|
/* Focus styles */
|
|
*:focus-visible {
|
|
outline: 2px solid var(--neon-cyan);
|
|
outline-offset: 2px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<header class="header">
|
|
<h1 data-text="NEURAL IMPLANT REGISTRY">NEURAL IMPLANT REGISTRY</h1>
|
|
<p style="color: var(--text-secondary); margin-top: 10px;">CyberCorp Industries - Classified Database Access Terminal</p>
|
|
</header>
|
|
|
|
<div class="control-panel">
|
|
<div class="controls-grid">
|
|
<div class="control-group">
|
|
<label for="search">Search Database</label>
|
|
<input type="text" id="search" placeholder="Enter subject ID, name, or implant model...">
|
|
</div>
|
|
<div class="control-group">
|
|
<label for="statusFilter">Status Filter</label>
|
|
<select id="statusFilter">
|
|
<option value="">All Statuses</option>
|
|
<option value="active">Active</option>
|
|
<option value="inactive">Inactive</option>
|
|
<option value="pending">Pending</option>
|
|
</select>
|
|
</div>
|
|
<div class="control-group">
|
|
<label for="riskFilter">Risk Level</label>
|
|
<select id="riskFilter">
|
|
<option value="">All Risk Levels</option>
|
|
<option value="low">Low</option>
|
|
<option value="medium">Medium</option>
|
|
<option value="high">High</option>
|
|
</select>
|
|
</div>
|
|
<div class="control-group">
|
|
<label for="sortBy">Sort By</label>
|
|
<select id="sortBy">
|
|
<option value="id">Subject ID</option>
|
|
<option value="name">Name</option>
|
|
<option value="date">Installation Date</option>
|
|
<option value="risk">Risk Level</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="data-table-container">
|
|
<div class="table-header">
|
|
<span class="table-title">Registered Subjects</span>
|
|
<div class="table-actions">
|
|
<button class="button" id="selectAllBtn">
|
|
<span>Select All</span>
|
|
</button>
|
|
<button class="button" id="exportBtn">
|
|
<span>Export Data</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="table-wrapper">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th class="checkbox-cell">
|
|
<span class="sr-only">Select</span>
|
|
</th>
|
|
<th data-sort="id">Subject ID</th>
|
|
<th data-sort="name">Name</th>
|
|
<th data-sort="model">Implant Model</th>
|
|
<th data-sort="date">Installation Date</th>
|
|
<th data-sort="status">Status</th>
|
|
<th data-sort="risk">Risk Level</th>
|
|
<th>Neural Sync</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="dataTable">
|
|
<!-- Data will be populated here -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="pagination" id="pagination">
|
|
<!-- Pagination will be populated here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Export Modal -->
|
|
<div class="modal" id="exportModal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h2 class="modal-title">Export Data</h2>
|
|
<button class="close-button" id="closeModal">×</button>
|
|
</div>
|
|
<div class="export-options">
|
|
<label class="export-option">
|
|
<input type="radio" name="exportFormat" value="json" checked>
|
|
<span>JSON - Neural Network Format</span>
|
|
</label>
|
|
<label class="export-option">
|
|
<input type="radio" name="exportFormat" value="csv">
|
|
<span>CSV - Legacy Systems Compatible</span>
|
|
</label>
|
|
<label class="export-option">
|
|
<input type="radio" name="exportFormat" value="xml">
|
|
<span>XML - Corporate Database Format</span>
|
|
</label>
|
|
</div>
|
|
<button class="button" id="confirmExport" style="width: 100%;">
|
|
<span>Initialize Data Transfer</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Loading indicator -->
|
|
<div class="loading" id="loading">
|
|
<div class="loading-spinner"></div>
|
|
</div>
|
|
|
|
<script>
|
|
// Sample data generator
|
|
const generateData = () => {
|
|
const firstNames = ['Kaida', 'Zane', 'Nyx', 'Rex', 'Luna', 'Axel', 'Nova', 'Jax', 'Echo', 'Vega'];
|
|
const lastNames = ['Chen', 'Nakamura', 'Volkov', 'Sterling', 'Cross', 'Black', 'Phoenix', 'Cipher', 'Shadow', 'Zero'];
|
|
const implantModels = ['CX-7000', 'NeuralLink Pro', 'SynapseBoost X', 'CortexPlus 2.0', 'MindBridge Alpha', 'CerebrumMax'];
|
|
const statuses = ['active', 'inactive', 'pending'];
|
|
const riskLevels = ['low', 'medium', 'high'];
|
|
|
|
const data = [];
|
|
for (let i = 1; i <= 150; i++) {
|
|
data.push({
|
|
id: `CYB-${String(i).padStart(6, '0')}`,
|
|
name: `${firstNames[Math.floor(Math.random() * firstNames.length)]} ${lastNames[Math.floor(Math.random() * lastNames.length)]}`,
|
|
model: implantModels[Math.floor(Math.random() * implantModels.length)],
|
|
date: new Date(2020 + Math.floor(Math.random() * 5), Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1).toISOString().split('T')[0],
|
|
status: statuses[Math.floor(Math.random() * statuses.length)],
|
|
risk: riskLevels[Math.floor(Math.random() * riskLevels.length)],
|
|
sync: Math.floor(Math.random() * 100)
|
|
});
|
|
}
|
|
return data;
|
|
};
|
|
|
|
// Application state
|
|
let allData = generateData();
|
|
let filteredData = [...allData];
|
|
let currentPage = 1;
|
|
const itemsPerPage = 10;
|
|
let selectedRows = new Set();
|
|
let sortColumn = 'id';
|
|
let sortDirection = 'asc';
|
|
|
|
// DOM elements
|
|
const searchInput = document.getElementById('search');
|
|
const statusFilter = document.getElementById('statusFilter');
|
|
const riskFilter = document.getElementById('riskFilter');
|
|
const sortBySelect = document.getElementById('sortBy');
|
|
const dataTable = document.getElementById('dataTable');
|
|
const pagination = document.getElementById('pagination');
|
|
const selectAllBtn = document.getElementById('selectAllBtn');
|
|
const exportBtn = document.getElementById('exportBtn');
|
|
const exportModal = document.getElementById('exportModal');
|
|
const closeModal = document.getElementById('closeModal');
|
|
const confirmExport = document.getElementById('confirmExport');
|
|
const loading = document.getElementById('loading');
|
|
|
|
// Filter and search functionality
|
|
const applyFilters = () => {
|
|
const searchTerm = searchInput.value.toLowerCase();
|
|
const statusValue = statusFilter.value;
|
|
const riskValue = riskFilter.value;
|
|
|
|
filteredData = allData.filter(item => {
|
|
const matchesSearch = !searchTerm ||
|
|
item.id.toLowerCase().includes(searchTerm) ||
|
|
item.name.toLowerCase().includes(searchTerm) ||
|
|
item.model.toLowerCase().includes(searchTerm);
|
|
|
|
const matchesStatus = !statusValue || item.status === statusValue;
|
|
const matchesRisk = !riskValue || item.risk === riskValue;
|
|
|
|
return matchesSearch && matchesStatus && matchesRisk;
|
|
});
|
|
|
|
currentPage = 1;
|
|
renderTable();
|
|
};
|
|
|
|
// Sorting functionality
|
|
const sortData = (column) => {
|
|
if (sortColumn === column) {
|
|
sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
|
|
} else {
|
|
sortColumn = column;
|
|
sortDirection = 'asc';
|
|
}
|
|
|
|
filteredData.sort((a, b) => {
|
|
let aVal = a[column];
|
|
let bVal = b[column];
|
|
|
|
if (column === 'risk') {
|
|
const riskOrder = { low: 1, medium: 2, high: 3 };
|
|
aVal = riskOrder[aVal];
|
|
bVal = riskOrder[bVal];
|
|
}
|
|
|
|
if (sortDirection === 'asc') {
|
|
return aVal > bVal ? 1 : -1;
|
|
} else {
|
|
return aVal < bVal ? 1 : -1;
|
|
}
|
|
});
|
|
|
|
renderTable();
|
|
};
|
|
|
|
// Render table
|
|
const renderTable = () => {
|
|
const startIndex = (currentPage - 1) * itemsPerPage;
|
|
const endIndex = startIndex + itemsPerPage;
|
|
const pageData = filteredData.slice(startIndex, endIndex);
|
|
|
|
// Update sort indicators
|
|
document.querySelectorAll('th[data-sort]').forEach(th => {
|
|
th.classList.remove('sorted-asc', 'sorted-desc');
|
|
if (th.dataset.sort === sortColumn) {
|
|
th.classList.add(`sorted-${sortDirection}`);
|
|
}
|
|
});
|
|
|
|
dataTable.innerHTML = pageData.map(item => {
|
|
const isSelected = selectedRows.has(item.id);
|
|
const riskWidth = item.risk === 'low' ? 30 : item.risk === 'medium' ? 60 : 90;
|
|
|
|
return `
|
|
<tr class="${isSelected ? 'selected' : ''}" data-id="${item.id}">
|
|
<td class="checkbox-cell">
|
|
<input type="checkbox" class="cyber-checkbox" ${isSelected ? 'checked' : ''} aria-label="Select ${item.name}">
|
|
</td>
|
|
<td>${item.id}</td>
|
|
<td>${item.name}</td>
|
|
<td>${item.model}</td>
|
|
<td>${item.date}</td>
|
|
<td><span class="status ${item.status}">${item.status}</span></td>
|
|
<td>
|
|
<div class="risk-level">
|
|
<div class="risk-bar">
|
|
<div class="risk-fill risk-${item.risk}" style="width: ${riskWidth}%"></div>
|
|
</div>
|
|
<span>${item.risk.toUpperCase()}</span>
|
|
</div>
|
|
</td>
|
|
<td>${item.sync}%</td>
|
|
</tr>
|
|
`;
|
|
}).join('');
|
|
|
|
renderPagination();
|
|
};
|
|
|
|
// Render pagination
|
|
const renderPagination = () => {
|
|
const totalPages = Math.ceil(filteredData.length / itemsPerPage);
|
|
const pages = [];
|
|
|
|
// Previous button
|
|
pages.push(`
|
|
<button class="page-button" ${currentPage === 1 ? 'disabled' : ''} data-page="${currentPage - 1}">
|
|
<
|
|
</button>
|
|
`);
|
|
|
|
// Page numbers
|
|
let startPage = Math.max(1, currentPage - 2);
|
|
let endPage = Math.min(totalPages, startPage + 4);
|
|
if (endPage - startPage < 4) {
|
|
startPage = Math.max(1, endPage - 4);
|
|
}
|
|
|
|
for (let i = startPage; i <= endPage; i++) {
|
|
pages.push(`
|
|
<button class="page-button ${i === currentPage ? 'active' : ''}" data-page="${i}">
|
|
${i}
|
|
</button>
|
|
`);
|
|
}
|
|
|
|
// Next button
|
|
pages.push(`
|
|
<button class="page-button" ${currentPage === totalPages ? 'disabled' : ''} data-page="${currentPage + 1}">
|
|
>
|
|
</button>
|
|
`);
|
|
|
|
// Page info
|
|
pages.push(`
|
|
<span class="page-info">
|
|
${filteredData.length} records | Page ${currentPage} of ${totalPages}
|
|
</span>
|
|
`);
|
|
|
|
pagination.innerHTML = pages.join('');
|
|
};
|
|
|
|
// Event listeners
|
|
searchInput.addEventListener('input', applyFilters);
|
|
statusFilter.addEventListener('change', applyFilters);
|
|
riskFilter.addEventListener('change', applyFilters);
|
|
sortBySelect.addEventListener('change', (e) => sortData(e.target.value));
|
|
|
|
// Table header sorting
|
|
document.querySelectorAll('th[data-sort]').forEach(th => {
|
|
th.addEventListener('click', () => sortData(th.dataset.sort));
|
|
});
|
|
|
|
// Row selection
|
|
dataTable.addEventListener('click', (e) => {
|
|
if (e.target.classList.contains('cyber-checkbox')) {
|
|
const row = e.target.closest('tr');
|
|
const id = row.dataset.id;
|
|
|
|
if (e.target.checked) {
|
|
selectedRows.add(id);
|
|
row.classList.add('selected');
|
|
} else {
|
|
selectedRows.delete(id);
|
|
row.classList.remove('selected');
|
|
}
|
|
}
|
|
});
|
|
|
|
// Select all functionality
|
|
selectAllBtn.addEventListener('click', () => {
|
|
const checkboxes = dataTable.querySelectorAll('.cyber-checkbox');
|
|
const allChecked = Array.from(checkboxes).every(cb => cb.checked);
|
|
|
|
checkboxes.forEach(cb => {
|
|
const row = cb.closest('tr');
|
|
const id = row.dataset.id;
|
|
|
|
if (allChecked) {
|
|
cb.checked = false;
|
|
selectedRows.delete(id);
|
|
row.classList.remove('selected');
|
|
} else {
|
|
cb.checked = true;
|
|
selectedRows.add(id);
|
|
row.classList.add('selected');
|
|
}
|
|
});
|
|
|
|
selectAllBtn.querySelector('span').textContent = allChecked ? 'Select All' : 'Deselect All';
|
|
});
|
|
|
|
// Pagination clicks
|
|
pagination.addEventListener('click', (e) => {
|
|
if (e.target.classList.contains('page-button') && !e.target.disabled) {
|
|
currentPage = parseInt(e.target.dataset.page);
|
|
renderTable();
|
|
}
|
|
});
|
|
|
|
// Export functionality
|
|
exportBtn.addEventListener('click', () => {
|
|
exportModal.classList.add('active');
|
|
});
|
|
|
|
closeModal.addEventListener('click', () => {
|
|
exportModal.classList.remove('active');
|
|
});
|
|
|
|
exportModal.addEventListener('click', (e) => {
|
|
if (e.target === exportModal) {
|
|
exportModal.classList.remove('active');
|
|
}
|
|
});
|
|
|
|
confirmExport.addEventListener('click', () => {
|
|
const format = document.querySelector('input[name="exportFormat"]:checked').value;
|
|
const dataToExport = selectedRows.size > 0
|
|
? allData.filter(item => selectedRows.has(item.id))
|
|
: filteredData;
|
|
|
|
loading.classList.add('active');
|
|
|
|
setTimeout(() => {
|
|
let content = '';
|
|
let filename = `neural_implant_registry_${new Date().toISOString().split('T')[0]}`;
|
|
|
|
switch (format) {
|
|
case 'json':
|
|
content = JSON.stringify(dataToExport, null, 2);
|
|
filename += '.json';
|
|
break;
|
|
case 'csv':
|
|
const headers = ['Subject ID', 'Name', 'Implant Model', 'Installation Date', 'Status', 'Risk Level', 'Neural Sync'];
|
|
const rows = dataToExport.map(item =>
|
|
[item.id, item.name, item.model, item.date, item.status, item.risk, `${item.sync}%`].join(',')
|
|
);
|
|
content = [headers.join(','), ...rows].join('\n');
|
|
filename += '.csv';
|
|
break;
|
|
case 'xml':
|
|
content = '<?xml version="1.0" encoding="UTF-8"?>\n<neural_implants>\n';
|
|
dataToExport.forEach(item => {
|
|
content += ' <subject>\n';
|
|
content += ` <id>${item.id}</id>\n`;
|
|
content += ` <name>${item.name}</name>\n`;
|
|
content += ` <model>${item.model}</model>\n`;
|
|
content += ` <installation_date>${item.date}</installation_date>\n`;
|
|
content += ` <status>${item.status}</status>\n`;
|
|
content += ` <risk_level>${item.risk}</risk_level>\n`;
|
|
content += ` <neural_sync>${item.sync}</neural_sync>\n`;
|
|
content += ' </subject>\n';
|
|
});
|
|
content += '</neural_implants>';
|
|
filename += '.xml';
|
|
break;
|
|
}
|
|
|
|
// Create download
|
|
const blob = new Blob([content], { type: 'text/plain' });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = filename;
|
|
a.click();
|
|
URL.revokeObjectURL(url);
|
|
|
|
loading.classList.remove('active');
|
|
exportModal.classList.remove('active');
|
|
}, 1000);
|
|
});
|
|
|
|
// Initialize
|
|
renderTable();
|
|
|
|
// Add glitch effect on hover
|
|
document.querySelectorAll('.button').forEach(button => {
|
|
button.addEventListener('mouseenter', () => {
|
|
button.style.animation = 'glitch-1 0.3s infinite';
|
|
});
|
|
button.addEventListener('mouseleave', () => {
|
|
button.style.animation = '';
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |