From 1406d472e565963b8cf8d3207aa763bec80be44d Mon Sep 17 00:00:00 2001
From: Daniel Morrisey
Date: Wed, 29 Jul 2026 10:48:36 -0400
Subject: [PATCH] alx 911 beta
---
alx911.html | 107 ++++++++++++++++++++
css/alx911.css | 264 +++++++++++++++++++++++++++++++++++++++++++++++++
js/alx911.js | 205 ++++++++++++++++++++++++++++++++++++++
js/core.js | 4 +-
luna.html | 4 +-
5 files changed, 580 insertions(+), 4 deletions(-)
create mode 100644 alx911.html
create mode 100644 css/alx911.css
create mode 100644 js/alx911.js
diff --git a/alx911.html b/alx911.html
new file mode 100644
index 0000000..51f9b30
--- /dev/null
+++ b/alx911.html
@@ -0,0 +1,107 @@
+
+
+
+
+
+ ALX 911 Audio & Video Archive - danielmorrisey.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ danielmorrisey.com/alx911
+ ALX 911 Archive
+
+ A public archive of emergency communications and audio dispatches. Submit new records or listen to community archive files.
+
+
+
+
+
+ Submit Recording
+
+
+
+
+
+
+
+ Recent Archives
+
+
+
+
+
+
+ Load More Calls
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/css/alx911.css b/css/alx911.css
new file mode 100644
index 0000000..0f1807e
--- /dev/null
+++ b/css/alx911.css
@@ -0,0 +1,264 @@
+/* ── Form Layout & Controls ── */
+
+.alx-form {
+ background: var(--bg-card);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 1.5rem;
+ display: flex;
+ flex-direction: column;
+ gap: 1.25rem;
+}
+
+.form-group {
+ display: flex;
+ flex-direction: column;
+ gap: 0.4rem;
+}
+
+.form-group label {
+ font-size: 0.875rem;
+ font-weight: 500;
+ color: var(--text);
+}
+
+.form-group label .required {
+ color: var(--accent);
+}
+
+.form-group input[type="text"],
+.form-group textarea {
+ width: 100%;
+ background: var(--bg-raised);
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ padding: 0.65rem 0.85rem;
+ font-family: var(--font-sans);
+ font-size: 0.9375rem;
+ color: var(--text);
+ transition: border-color var(--transition);
+}
+
+.form-group input[type="text"]:focus,
+.form-group textarea:focus {
+ outline: none;
+ border-color: var(--accent);
+}
+
+.field-hint {
+ font-size: 0.75rem;
+ color: var(--text-dim);
+}
+
+.form-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 1rem;
+}
+
+/* ── Custom File Dropzones ── */
+
+.file-dropzone {
+ position: relative;
+ border: 2px dashed var(--border);
+ border-radius: 6px;
+ background: var(--bg-raised);
+ padding: 1.25rem 1rem;
+ text-align: center;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 0.35rem;
+ cursor: pointer;
+ transition: border-color var(--transition), background var(--transition);
+}
+
+.file-dropzone:hover,
+.file-dropzone.dragover {
+ border-color: var(--accent);
+ background: var(--accent-dim);
+}
+
+.file-dropzone i {
+ font-size: 1.5rem;
+ color: var(--accent);
+}
+
+.file-dropzone .file-label {
+ font-size: 0.84rem;
+ font-weight: 500;
+ color: var(--text);
+}
+
+.file-dropzone .file-subtext {
+ font-size: 0.72rem;
+ color: var(--text-dim);
+}
+
+.file-dropzone input[type="file"] {
+ position: absolute;
+ inset: 0;
+ opacity: 0;
+ cursor: pointer;
+}
+
+/* ── Submit & Buttons ── */
+
+.turnstile-container {
+ display: flex;
+ justify-content: center;
+ margin: 0.5rem 0;
+}
+
+.submit-btn,
+.load-more-btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ background: var(--accent);
+ color: var(--bg-deep);
+ font-family: var(--font-mono);
+ font-size: 0.875rem;
+ font-weight: 600;
+ padding: 0.75rem 1.25rem;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+ transition: background var(--transition), transform var(--transition);
+}
+
+.submit-btn:hover,
+.load-more-btn:hover {
+ background: var(--accent-bright);
+ transform: translateY(-1px);
+}
+
+.submit-btn:disabled,
+.load-more-btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ transform: none;
+}
+
+.load-more-wrapper {
+ display: flex;
+ justify-content: center;
+ margin-top: 1.5rem;
+}
+
+.load-more-btn {
+ background: var(--bg-card);
+ color: var(--text-muted);
+ border: 1px solid var(--border);
+}
+
+.load-more-btn:hover {
+ border-color: var(--accent);
+ color: var(--accent);
+}
+
+/* ── Alerts ── */
+
+.alert {
+ padding: 0.75rem 1rem;
+ border-radius: 6px;
+ font-size: 0.875rem;
+}
+
+.alert-error {
+ background: rgba(224, 86, 86, 0.15);
+ border: 1px solid #e05656;
+ color: #f88;
+}
+
+.alert-success {
+ background: rgba(86, 224, 132, 0.15);
+ border: 1px solid #56e084;
+ color: #8ff8b8;
+}
+
+.hidden {
+ display: none !important;
+}
+
+/* ── Calls List & Feed Cards ── */
+
+.calls-list {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+.call-card {
+ background: var(--bg-card);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 1.25rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+ transition: border-color var(--transition);
+}
+
+.call-card:hover {
+ border-color: var(--border-hover);
+}
+
+.call-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ gap: 1rem;
+}
+
+.call-title {
+ font-family: var(--font-serif);
+ font-size: 1.1rem;
+ color: var(--text);
+ margin: 0;
+}
+
+.call-date {
+ font-family: var(--font-mono);
+ font-size: 0.75rem;
+ color: var(--text-dim);
+ white-space: nowrap;
+}
+
+.call-desc {
+ font-size: 0.875rem;
+ color: var(--text-muted);
+ line-height: 1.6;
+ white-space: pre-wrap;
+}
+
+.call-media-player {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ margin-top: 0.25rem;
+}
+
+.call-media-player audio,
+.call-media-player video {
+ width: 100%;
+ border-radius: 6px;
+ background: var(--bg-deep);
+}
+
+.call-media-player video {
+ max-height: 360px;
+ object-fit: contain;
+}
+
+@media (max-width: 600px) {
+ .form-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .call-header {
+ flex-direction: column;
+ gap: 0.25rem;
+ }
+}
\ No newline at end of file
diff --git a/js/alx911.js b/js/alx911.js
new file mode 100644
index 0000000..b0ba2da
--- /dev/null
+++ b/js/alx911.js
@@ -0,0 +1,205 @@
+(() => {
+ let nextCursor = null;
+
+ // Elements
+ const form = document.getElementById('upload-form');
+ const alertBox = document.getElementById('form-alert');
+ const submitBtn = document.getElementById('submit-btn');
+ const callsContainer = document.getElementById('calls-container');
+ const loadMoreContainer = document.getElementById('load-more-container');
+ const loadMoreBtn = document.getElementById('load-more-btn');
+
+ // UI Utilities
+ const showAlert = (message, type = 'error') => {
+ alertBox.className = `alert alert-${type}`;
+ alertBox.textContent = message;
+ alertBox.classList.remove('hidden');
+ };
+
+ const hideAlert = () => {
+ alertBox.classList.add('hidden');
+ };
+
+ // File Input Visual Feedback
+ const setupDropzone = (dropzoneId, inputId) => {
+ const dropzone = document.getElementById(dropzoneId);
+ const input = document.getElementById(inputId);
+ const label = dropzone.querySelector('.file-label');
+
+ input.addEventListener('change', () => {
+ if (input.files.length > 0) {
+ label.textContent = input.files[0].name;
+ } else {
+ label.textContent = inputId === 'audio'
+ ? 'Choose or drag audio file'
+ : 'Choose or drag video file';
+ }
+ });
+
+ ['dragenter', 'dragover'].forEach(eventName => {
+ dropzone.addEventListener(eventName, (e) => {
+ e.preventDefault();
+ dropzone.classList.add('dragover');
+ }, false);
+ });
+
+ ['dragleave', 'drop'].forEach(eventName => {
+ dropzone.addEventListener(eventName, (e) => {
+ e.preventDefault();
+ dropzone.classList.remove('dragover');
+ }, false);
+ });
+ };
+
+ setupDropzone('audio-dropzone', 'audio');
+ setupDropzone('video-dropzone', 'video');
+
+ // Render HTML for a Single Call Card
+ const createCallCard = (call) => {
+ const card = document.createElement('div');
+ card.className = 'call-card';
+
+ const formattedDate = new Date(call.createdAt).toLocaleString(undefined, {
+ dateStyle: 'medium',
+ timeStyle: 'short'
+ });
+
+ let videoHtml = '';
+ if (call.videoUrl) {
+ videoHtml = `
+
+
+ Your browser does not support video playback.
+
+ `;
+ }
+
+ card.innerHTML = `
+
+ ${call.description ? `${escapeHtml(call.description)}
` : ''}
+
+ `;
+
+ return card;
+ };
+
+ const escapeHtml = (str) => {
+ return str.replace(/[&<>"']/g, (m) => {
+ return {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": '''
+ }[m];
+ });
+ };
+
+ // API: Fetch Calls List
+ const fetchCalls = async (cursor = null) => {
+ try {
+ const url = new URL('/api/calls', window.location.origin);
+ if (cursor) url.searchParams.set('cursor', cursor);
+
+ const res = await fetch(url.toString());
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
+
+ const data = await res.json();
+
+ if (!cursor) {
+ callsContainer.innerHTML = '';
+ }
+
+ if (data.items.length === 0 && !cursor) {
+ callsContainer.innerHTML = 'No call records found.
';
+ loadMoreContainer.classList.add('hidden');
+ return;
+ }
+
+ data.items.forEach(item => {
+ callsContainer.appendChild(createCallCard(item));
+ });
+
+ nextCursor = data.nextCursor;
+ if (nextCursor) {
+ loadMoreContainer.classList.remove('hidden');
+ } else {
+ loadMoreContainer.classList.add('hidden');
+ }
+ } catch (err) {
+ console.error('Failed to load calls:', err);
+ if (!cursor) {
+ callsContainer.innerHTML = 'Failed to load recordings.
';
+ }
+ }
+ };
+
+ // API: Form Submit Handler
+ form.addEventListener('submit', async (e) => {
+ e.preventDefault();
+ hideAlert();
+
+ const formData = new FormData(form);
+
+ // Turnstile Token Check
+ const turnstileToken = formData.get('cf-turnstile-response');
+ if (turnstileToken) {
+ formData.set('turnstileToken', turnstileToken);
+ } else if (!formData.get('turnstileToken')) {
+ showAlert('Please complete the Cloudflare security verification.');
+ return;
+ }
+
+ submitBtn.disabled = true;
+ submitBtn.innerHTML = ' Uploading...';
+
+ try {
+ const res = await fetch('/api/calls', {
+ method: 'POST',
+ body: formData
+ });
+
+ const result = await res.json();
+
+ if (!res.ok) {
+ throw new Error(result.error || 'Failed to submit recording.');
+ }
+
+ showAlert('Record successfully submitted!', 'success');
+ form.reset();
+ document.querySelector('#audio-dropzone .file-label').textContent = 'Choose or drag audio file';
+ document.querySelector('#video-dropzone .file-label').textContent = 'Choose or drag video file';
+
+ if (window.turnstile) {
+ window.turnstile.reset();
+ }
+
+ // Refresh call feed instantly
+ fetchCalls();
+ } catch (err) {
+ showAlert(err.message || 'An error occurred while uploading.');
+ } finally {
+ submitBtn.disabled = false;
+ submitBtn.innerHTML = ' Submit Record';
+ }
+ });
+
+ // Pagination Load More
+ loadMoreBtn.addEventListener('click', () => {
+ if (nextCursor) {
+ fetchCalls(nextCursor);
+ }
+ });
+
+ // Initial Load
+ fetchCalls();
+})();
\ No newline at end of file
diff --git a/js/core.js b/js/core.js
index 8597f47..ca72bc3 100644
--- a/js/core.js
+++ b/js/core.js
@@ -54,7 +54,7 @@
- Open witchsky.app and go to Settings
+ Open witchsky.app and go to Settings
- Navigate to the Runes section
+ Navigate to the Runes section
Click on infrastructure
--
2.51.2